Анимация с использованием Cartopy
Я пытаюсь написать программу на python, которая отображает анимацию карты мира, где страны меняют цвет в зависимости от того, насколько они используют возобновляемую энергию. Я пытаюсь, чтобы он отображал цвета для всех стран в 1960 году, затем цвета для всех стран в 1961 году, а затем в 1962 году...
Я использую cartopy для добавления стран к рисунку и основываю их цвет на значениях, которые я перетаскиваю в кадр данных pandas из базы данных SQL. Я смог получить карту, чтобы показать, что я хочу в течение одного года, как это:
Однако я не могу понять, как его оживить. Я пытался использовать FuncAnimate, но я действительно изо всех сил пытаюсь понять, как это работает. Кажется, что во всех примерах есть функции, которые возвращают линии, но я не рисую линии или контуры. Вот что я попробовал:
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
2 ответа
На самом деле есть несколько проблем с тем, как вы настроили run()
, но главная проблема, казалось, на самом деле enumate(countries_map)
, records()
Функция возвращает генератор, который, как только вы однажды пробежали его, кажется, не любит повторяться через него - я попробовал его отдельно от анимации, чтобы убедиться.
Тем не менее, проблему можно полностью избежать, переместив много кода из 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
# 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):
ax.add_geometries(country.geometry, ccrs.PlateCarree(),
facecolor=facecolor, edgecolor=edgecolor)
geom_dict[country.attributes['name_long']] = country.geometry
def run(data):
"""Update the Dist"""
# "data" in this setup is a frame number starting from 0, so it corresponds nicely
# with your years
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)
plt.show()
Основное отличие состоит в том, что я не обращаюсь к shpreader вообще внутри функции запуска. При создании анимации единственной вещью, которая должна быть в функции запуска, являются вещи, которые меняются, вам не нужно перерисовывать все каждый кадр.
Тем не менее, это может быть даже лучше, если вы просто удержите художника от самого первого рисования и просто измените его цвет в функции запуска вместо того, чтобы делать совершенно новый ax.add_geometries
, Для этого вам нужно узнать, как изменить цвет картографического 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 - поскольку он остается прежним, вы можете оставить это в покое.)