使用 Cartopy 制作动画
Animation Using Cartopy
我正在尝试编写一个 python 程序来显示世界地图的动画,其中各国根据可再生能源的使用量改变颜色。我想让它显示 1960 年所有国家/地区的颜色,然后是 1961 年所有国家/地区的颜色,然后是 1962 年...
我正在使用 cartopy 将国家/地区添加到图中,并将它们的颜色基于我从 SQL 数据库中提取到 pandas 数据框中的值。我能够像这样获得地图来显示我想要的一年:
但是,我不知道如何制作动画。我尝试使用 FuncAnimate,但我真的很难理解它是如何工作的。所有示例似乎都具有 return 行的功能,但我没有绘制线条或等高线。这是我尝试过的:
import sqlite3
import pandas as pd
import os
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation as animation
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
from math import log
from math import exp
from matplotlib import colors
path = 'H:/USER/DVanLunen/indicator_data/world-development-indicators/'
os.chdir(path)
con = sqlite3.connect('database.sqlite')
# Grab :
# % of electricity from renewable sources EG.ELC.RNWX.ZS
# 1960 - 2013
Indicator_df = pd.read_sql('SELECT * '
'FROM Indicators '
'WHERE IndicatorCode in('
'"EG.ELC.RNWX.ZS"'
')'
, con)
# setup colorbar stuff and shape files
norm = mpl.colors.Normalize(vmin=0, vmax=30)
colors_in_map = []
for i in range(30):
val = log(i + 1, logbase) / log(31, logbase)
colors_in_map.append((1 - val, val, 0))
cmap = colors.ListedColormap(colors_in_map)
shpfilename = shpreader.natural_earth(resolution='110m',
category='cultural',
name='admin_0_countries')
reader = shpreader.Reader(shpfilename)
countries_map = reader.records()
logbase = exp(1)
fig, ax = plt.subplots(figsize=(12, 6),
subplot_kw={'projection': ccrs.PlateCarree()})
def run(data):
"""Update the Dist"""
year = 1960 + data % 54
logbase = exp(1)
for n, country in enumerate(countries_map):
facecolor = 'gray'
edgecolor = 'black'
indval = Indicator_df.loc[(Indicator_df['CountryName'] ==
country.attributes['name_long']) &
(Indicator_df['Year'] == year), 'Value']
if indval.any():
greenamount = (log(float(indval) + 1, logbase) /
log(31, logbase))
facecolor = 1 - greenamount, greenamount, 0
ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
ax.set_title('Percent of Electricity from Renewable Sources ' +
str(year))
ax.figure.canvas.draw()
cax = fig.add_axes([0.92, 0.2, 0.02, 0.6])
cb = mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm,
spacing='proportional')
cb.set_label('%')
ani = animation.FuncAnimation(fig, run, interval=200, blit=False)
plt.show()
如有任何帮助,我们将不胜感激。谢谢!
Indicator_df 的一些示例数据(不是真实的):
CountryName Year Value
United States 1960 5
United States 1961 10
United States 1962 20
United States 1963 30
您设置 run()
的方式实际上存在几个问题,但主要问题似乎实际上是 enumate(countries_map)
。 records()
函数 returns 一个生成器,一旦你 运行 通过它一次,它似乎不喜欢再次 运行 通过 - 我尝试将它与动画分开确定一下。
也就是说,可以通过将大量代码移出 run()
来完全避免该问题。目前,即使它有效,你也在每一帧重新绘制每个国家,而不仅仅是那些有颜色的国家。它既密集又不必要 - 你不需要多次绘制任何灰色的。
我对你的代码进行了一些重组,使用我为美国和阿根廷输入的虚假数据,它对我来说工作正常。
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation as animation
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
from math import log
from math import exp
from matplotlib import colors
from shapely.geometry.multipolygon import MultiPolygon
# Grab :
# % of electricity from renewable sources EG.ELC.RNWX.ZS
# 1960 - 2013
# Make fake data
Indicator_df = pd.DataFrame({
'CountryName': ['United States'] * 4 + ['Argentina'] * 4,
'Year': [1960, 1961, 1962, 1963] * 2,
'Value': [5, 10, 20, 30] * 2
})
# setup colorbar stuff and shape files
norm = mpl.colors.Normalize(vmin=0, vmax=30)
colors_in_map = []
logbase = exp(1)
for i in range(30):
val = log(i + 1, logbase) / log(31, logbase)
colors_in_map.append((1 - val, val, 0))
cmap = colors.ListedColormap(colors_in_map)
shpfilename = shpreader.natural_earth(resolution='110m',
category='cultural',
name='admin_0_countries')
reader = shpreader.Reader(shpfilename)
countries_map = reader.records()
# These don't need to constantly be redefined, especially edgecolor
facecolor = 'gray'
edgecolor = 'black'
fig, ax = plt.subplots(figsize=(12, 6),
subplot_kw={'projection': ccrs.PlateCarree()})
# Draw all the gray countries just once in an init function
# I also make a dictionary for easy lookup of the geometries by country name later
geom_dict = {}
def init_run():
for n, country in enumerate(countries_map):
if country.geometry.type == "Polygon":
geom = MultiPolygon([country.geometry])
else:
geom = country.geometry
ax.add_geometries(geom,
ccrs.PlateCarree(),
facecolor=facecolor,
edgecolor=edgecolor)
geom_dict[country.attributes['NAME_LONG']] = geom
def run(data):
"""Update the Dist"""
# "data" in this setup is a frame number starting from 0, so it corresponds nicely
# with your years
# data = 0
year = 1960 + data
# get a subset of the df for the current year
year_df = Indicator_df[Indicator_df['Year'] == year]
for i, row in year_df.iterrows():
# This loops over countries, gets the value and geometry and adds
# the new-colored shape
geom = geom_dict[row['CountryName']]
value = row['Value']
greenamount = (log(float(value) + 1, logbase) / log(31, logbase))
facecolor = 1 - greenamount, greenamount, 0
ax.add_geometries(geom,
ccrs.PlateCarree(),
facecolor=facecolor,
edgecolor=edgecolor)
# I decreased the indent of this, you only need to do it once per call to run()
ax.set_title('Percent of Electricity from Renewable Sources ' + str(year))
cax = fig.add_axes([0.92, 0.2, 0.02, 0.6])
cb = mpl.colorbar.ColorbarBase(cax,
cmap=cmap,
norm=norm,
spacing='proportional')
cb.set_label('%')
ani = animation.FuncAnimation(fig,
run,
init_func=init_run,
frames=4,
interval=500,
blit=False)
ani.save(filename="test.gif")
主要区别在于我根本没有在 运行 函数中访问 shpreader。制作动画时,运行函数中唯一应该做的就是改变的东西,不需要每一帧都重新绘制。
就是说,如果您只保留第一次绘制时的艺术家并更改颜色,这可能会甚至更好它在 运行 函数中,而不是做一个全新的 ax.add_geometries
。为此,您必须研究如何更改 cartopy FeatureArtist 的颜色。
只是为了解决关于不必再次绘制整个形状的第二点:
不存储形状信息,而是存储特征艺术家,即:
feature_artist = ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
geom_dict[country.attributes['name_long']] = feature_artist
然后,在更新循环中,不要再次调用 ax.add_geometries,而是调用以下内容:
geom._feature._kwargs['facecolor'] = facecolor
这将更新面部颜色。 (您也可以更改 adgecolor - 因为它保持不变,所以您可以将其保留。)
我正在尝试编写一个 python 程序来显示世界地图的动画,其中各国根据可再生能源的使用量改变颜色。我想让它显示 1960 年所有国家/地区的颜色,然后是 1961 年所有国家/地区的颜色,然后是 1962 年...
我正在使用 cartopy 将国家/地区添加到图中,并将它们的颜色基于我从 SQL 数据库中提取到 pandas 数据框中的值。我能够像这样获得地图来显示我想要的一年:
但是,我不知道如何制作动画。我尝试使用 FuncAnimate,但我真的很难理解它是如何工作的。所有示例似乎都具有 return 行的功能,但我没有绘制线条或等高线。这是我尝试过的:
import sqlite3
import pandas as pd
import os
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation as animation
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
from math import log
from math import exp
from matplotlib import colors
path = 'H:/USER/DVanLunen/indicator_data/world-development-indicators/'
os.chdir(path)
con = sqlite3.connect('database.sqlite')
# Grab :
# % of electricity from renewable sources EG.ELC.RNWX.ZS
# 1960 - 2013
Indicator_df = pd.read_sql('SELECT * '
'FROM Indicators '
'WHERE IndicatorCode in('
'"EG.ELC.RNWX.ZS"'
')'
, con)
# setup colorbar stuff and shape files
norm = mpl.colors.Normalize(vmin=0, vmax=30)
colors_in_map = []
for i in range(30):
val = log(i + 1, logbase) / log(31, logbase)
colors_in_map.append((1 - val, val, 0))
cmap = colors.ListedColormap(colors_in_map)
shpfilename = shpreader.natural_earth(resolution='110m',
category='cultural',
name='admin_0_countries')
reader = shpreader.Reader(shpfilename)
countries_map = reader.records()
logbase = exp(1)
fig, ax = plt.subplots(figsize=(12, 6),
subplot_kw={'projection': ccrs.PlateCarree()})
def run(data):
"""Update the Dist"""
year = 1960 + data % 54
logbase = exp(1)
for n, country in enumerate(countries_map):
facecolor = 'gray'
edgecolor = 'black'
indval = Indicator_df.loc[(Indicator_df['CountryName'] ==
country.attributes['name_long']) &
(Indicator_df['Year'] == year), 'Value']
if indval.any():
greenamount = (log(float(indval) + 1, logbase) /
log(31, logbase))
facecolor = 1 - greenamount, greenamount, 0
ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
ax.set_title('Percent of Electricity from Renewable Sources ' +
str(year))
ax.figure.canvas.draw()
cax = fig.add_axes([0.92, 0.2, 0.02, 0.6])
cb = mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm,
spacing='proportional')
cb.set_label('%')
ani = animation.FuncAnimation(fig, run, interval=200, blit=False)
plt.show()
如有任何帮助,我们将不胜感激。谢谢!
Indicator_df 的一些示例数据(不是真实的):
CountryName Year Value
United States 1960 5
United States 1961 10
United States 1962 20
United States 1963 30
您设置 run()
的方式实际上存在几个问题,但主要问题似乎实际上是 enumate(countries_map)
。 records()
函数 returns 一个生成器,一旦你 运行 通过它一次,它似乎不喜欢再次 运行 通过 - 我尝试将它与动画分开确定一下。
也就是说,可以通过将大量代码移出 run()
来完全避免该问题。目前,即使它有效,你也在每一帧重新绘制每个国家,而不仅仅是那些有颜色的国家。它既密集又不必要 - 你不需要多次绘制任何灰色的。
我对你的代码进行了一些重组,使用我为美国和阿根廷输入的虚假数据,它对我来说工作正常。
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation as animation
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
from math import log
from math import exp
from matplotlib import colors
from shapely.geometry.multipolygon import MultiPolygon
# Grab :
# % of electricity from renewable sources EG.ELC.RNWX.ZS
# 1960 - 2013
# Make fake data
Indicator_df = pd.DataFrame({
'CountryName': ['United States'] * 4 + ['Argentina'] * 4,
'Year': [1960, 1961, 1962, 1963] * 2,
'Value': [5, 10, 20, 30] * 2
})
# setup colorbar stuff and shape files
norm = mpl.colors.Normalize(vmin=0, vmax=30)
colors_in_map = []
logbase = exp(1)
for i in range(30):
val = log(i + 1, logbase) / log(31, logbase)
colors_in_map.append((1 - val, val, 0))
cmap = colors.ListedColormap(colors_in_map)
shpfilename = shpreader.natural_earth(resolution='110m',
category='cultural',
name='admin_0_countries')
reader = shpreader.Reader(shpfilename)
countries_map = reader.records()
# These don't need to constantly be redefined, especially edgecolor
facecolor = 'gray'
edgecolor = 'black'
fig, ax = plt.subplots(figsize=(12, 6),
subplot_kw={'projection': ccrs.PlateCarree()})
# Draw all the gray countries just once in an init function
# I also make a dictionary for easy lookup of the geometries by country name later
geom_dict = {}
def init_run():
for n, country in enumerate(countries_map):
if country.geometry.type == "Polygon":
geom = MultiPolygon([country.geometry])
else:
geom = country.geometry
ax.add_geometries(geom,
ccrs.PlateCarree(),
facecolor=facecolor,
edgecolor=edgecolor)
geom_dict[country.attributes['NAME_LONG']] = geom
def run(data):
"""Update the Dist"""
# "data" in this setup is a frame number starting from 0, so it corresponds nicely
# with your years
# data = 0
year = 1960 + data
# get a subset of the df for the current year
year_df = Indicator_df[Indicator_df['Year'] == year]
for i, row in year_df.iterrows():
# This loops over countries, gets the value and geometry and adds
# the new-colored shape
geom = geom_dict[row['CountryName']]
value = row['Value']
greenamount = (log(float(value) + 1, logbase) / log(31, logbase))
facecolor = 1 - greenamount, greenamount, 0
ax.add_geometries(geom,
ccrs.PlateCarree(),
facecolor=facecolor,
edgecolor=edgecolor)
# I decreased the indent of this, you only need to do it once per call to run()
ax.set_title('Percent of Electricity from Renewable Sources ' + str(year))
cax = fig.add_axes([0.92, 0.2, 0.02, 0.6])
cb = mpl.colorbar.ColorbarBase(cax,
cmap=cmap,
norm=norm,
spacing='proportional')
cb.set_label('%')
ani = animation.FuncAnimation(fig,
run,
init_func=init_run,
frames=4,
interval=500,
blit=False)
ani.save(filename="test.gif")
主要区别在于我根本没有在 运行 函数中访问 shpreader。制作动画时,运行函数中唯一应该做的就是改变的东西,不需要每一帧都重新绘制。
就是说,如果您只保留第一次绘制时的艺术家并更改颜色,这可能会甚至更好它在 运行 函数中,而不是做一个全新的 ax.add_geometries
。为此,您必须研究如何更改 cartopy FeatureArtist 的颜色。
只是为了解决关于不必再次绘制整个形状的第二点:
不存储形状信息,而是存储特征艺术家,即:
feature_artist = ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
geom_dict[country.attributes['name_long']] = feature_artist
然后,在更新循环中,不要再次调用 ax.add_geometries,而是调用以下内容:
geom._feature._kwargs['facecolor'] = facecolor
这将更新面部颜色。 (您也可以更改 adgecolor - 因为它保持不变,所以您可以将其保留。)