如何基于多期GeoPandas绘图生成动画/动态GIF展示人口时序演变
GeoDataFrame时间序列人口分布动态GIF实现方案
你之前的代码报错、动画无法渲染,核心是4个问题:
- 初始化GeoDataFrame时
mhg.set_index('month')没有重新赋值或加inplace=True,月份索引实际未生效 - 帧更新函数中取了全量所有月份的几何数据,且传入绘图函数时用了未定义的
population变量,没有使用当月筛选出的人口数据 FuncAnimation生成的动画对象如果没有被全局变量持有,会被Python自动垃圾回收,直接触发animation was deleted without rendering anything警告,渲染中断- 没有固定全时段色阶范围,每帧单独映射颜色会导致不同月份的颜色深浅无法横向对比,看不出人口变化趋势
下面给两种可直接运行的实现方案,适配不同数据量场景:
方案一:基于Matplotlib FuncAnimation实时渲染
适合月份数量在50以内、数据量不大的场景,代码简洁,可直接预览动画效果。
import geopandas as gpd import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation, PillowWriter # --------------- 数据初始化部分 --------------- # 样例数据构造,真实使用时替换成你自己的mhg即可 data = [['2019-01-01', 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 1000], ['2019-01-01', 'POLYGON((1 0, 2 0, 2 1, 1 1, 1 0))', 1500], ['2019-01-01', 'POLYGON((2 0, 3 0, 3 1, 2 1, 2 0))', 1400], ['2019-02-01', 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 1100], ['2019-02-01', 'POLYGON((1 0, 2 0, 2 1, 1 1, 1 0))', 1600], ['2019-02-01', 'POLYGON((2 0, 3 0, 3 1, 2 1, 2 0))', 1300]] mhg = gpd.GeoDataFrame(data, columns=['month','geometry', 'population'], crs="EPSG:4326") mhg = mhg.set_index('month') # 必须重新赋值,否则索引不生效 # 样例数据中geometry是字符串,需要转成几何对象;真实数据如果已经是几何类型可删掉下一行 mhg['geometry'] = gpd.GeoSeries.from_wkt(mhg['geometry']) # --------------- 动画配置部分 --------------- # 取排序后的所有月份 months = np.sort(mhg.index.unique()) # 固定全时段人口最值,保证所有帧色条范围一致,颜色具备可比性 vmin = mhg['population'].min() vmax = mhg['population'].max() # 初始化画布和第一帧 fig, ax = plt.subplots(figsize=(8, 4)) first_plot = mhg.loc[months[0]].plot(column='population', cmap='jet', vmin=vmin, vmax=vmax, ax=ax) fig.colorbar(first_plot.get_children()[0], ax=ax, label='人口数') def update(frame_month): ax.clear() # 绘制当前月份数据 current_data = mhg.loc[frame_month] current_plot = current_data.plot(column='population', cmap='jet', vmin=vmin, vmax=vmax, ax=ax) ax.set_title(f'月份: {frame_month}') return current_plot, # 生成动画,注意anim变量必须全局持有,不能被回收 anim = FuncAnimation( fig, update, frames=months, interval=1000, # 每帧停留1000毫秒 repeat_delay=3000 # 循环结束后停留3秒再重播 ) # 本地预览 plt.show() # 导出为GIF writer = PillowWriter(fps=1) anim.save('population_evolution.gif', writer=writer, dpi=100)
方案二:逐帧绘图后合并GIF
适合月份过百、单月几何数据量大的场景,逻辑和你最开始写的伪代码完全一致,内存占用稳定,排查问题方便。
import os import tempfile from PIL import Image import geopandas as gpd import numpy as np import matplotlib.pyplot as plt # 数据初始化部分和方案一一致,此处省略,直接使用已经处理好的mhg、months、vmin、vmax变量 # 新建临时目录存储单帧图片 temp_dir = tempfile.mkdtemp() frame_paths = [] # 逐月份生成单帧图片 for idx, month in enumerate(months): fig, ax = plt.subplots(figsize=(8,4)) current_data = mhg.loc[month] plot = current_data.plot(column='population', cmap='jet', vmin=vmin, vmax=vmax, ax=ax) ax.set_title(f'月份: {month}') plt.colorbar(plot.get_children()[0], ax=ax, label='人口数') # 保存单帧 frame_path = os.path.join(temp_dir, f'frame_{idx}.png') plt.savefig(frame_path, dpi=100, bbox_inches='tight') plt.close() # 关闭画布释放内存 frame_paths.append(frame_path) # 合并所有帧为GIF frames = [Image.open(f) for f in frame_paths] frames[0].save( 'population_evolution.gif', save_all=True, append_images=frames[1:], duration=1000, # 每帧停留1000毫秒 loop=0, # 无限循环 optimize=True ) # 清理临时文件 for f in frame_paths: os.remove(f) os.rmdir(temp_dir)
内容的提问来源于stack exchange,提问作者ElTitoFranki
相关产品推荐
相关产品推荐

