如何缓存或复用Cartopy的GSHHSFeature以优化多子图绘图效率?
复用Cartopy GSHHS海岸线底图优化多子图绘制
问题背景
我使用Cartopy在多个Matplotlib子图中展示GSHHSFeature海岸线,并叠加不同数据。当前代码每次循环都会重新创建海岸线特征,使用full尺度时耗时特别明显。由于所有子图的底图完全一致,希望找到仅创建一次底图、循环复用的方法,比如实现类似ax[i][j].plot_my_basemap(map_ax)或复制轴的逻辑,但不想使用Basemap(已被Cartopy替代,且与Matplotlib 3.8存在兼容性问题)。
当前实现代码
import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import numpy as np xss = np.linspace(-21, 36, 36) extent = [-21, 36, 33, 64] # west,east,south,north rows=3 cols=2 fig, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(13, 15), subplot_kw={'projection': ccrs.PlateCarree()}) for i in range(rows): for j in range(cols): ax[i][j].set_extent(extent) ax[i][j].add_feature(cfeature.GSHHSFeature(scale="intermediate", edgecolor="black", facecolor="#bca89f")) # 叠加子图专属数据 ax[i][j].plot(xss, np.random.uniform(low=33, high=64, size=(len(xss),)), color='cyan', zorder=20, transform=ccrs.PlateCarree(), linewidth=2) # 设置子图标题 ax[i][j].set_title("Row:"+str(i)+", Col:"+str(j)) plt.tight_layout() plt.show()
尝试过的无效方法
- 预创建底图轴复用(FeatureArtist无法像瓦片图直接复用):
# 预创建底图轴 map_ax = plt.axes(projection=ccrs.PlateCarree()) map_ax.set_extent(extent) map_ax.add_feature(cfeature.GSHHSFeature(scale="intermediate", edgecolor="black", facecolor="#bca89f")) fig, ax = plt.subplots(nrows=3, ncols=2, figsize=(13, 15), subplot_kw={'projection': ccrs.PlateCarree()}) for i in range(rows): for j in range(cols): ax[i][j].plot_my_basemap(map_ax) # 此方法不存在,无法生效 # 叠加子图专属数据 ax[i][j].plot(xss, np.random.uniform(low=33, high=64, size=(len(xss),)), color='cyan', zorder=20, transform=ccrs.PlateCarree(), linewidth=2) ax[i][j].set_title("Row:"+str(i)+", Col:"+str(j)) plt.tight_layout() plt.show()
- 直接修改轴属性复用(Matplotlib不支持该逻辑):
for i in range(rows): for j in range(cols): props = ax[i][j].properties() props = dict((k, props[k]) for k in ["position", "subplotspec"]) ax[i][j] = map_ax.update(props) # 无法直接替换轴对象 # 叠加子图专属数据 ax[i][j].plot(xss, np.random.uniform(low=33, high=64, size=(len(xss),)), color='cyan', zorder=20, transform=ccrs.PlateCarree(), linewidth=2) ax[i][j].set_title("Row:"+str(i)+", Col:"+str(j))
可行解决方案
方案1:预渲染底图为图像复用
将底图一次性渲染为图像,后续子图直接调用该图像,避免重复处理地理数据:
import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import numpy as np xss = np.linspace(-21, 36, 36) extent = [-21, 36, 33, 64] rows = 3 cols = 2 # 1. 预渲染底图为图像 fig_blank, ax_blank = plt.subplots(figsize=(13/cols, 15/rows), # 匹配单个子图尺寸 subplot_kw={'projection': ccrs.PlateCarree()}) ax_blank.set_extent(extent) ax_blank.add_feature(cfeature.GSHHSFeature(scale="intermediate", edgecolor="black", facecolor="#bca89f")) ax_blank.set_axis_off() # 隐藏轴元素,仅保留底图 # 渲染并转为numpy数组 fig_blank.canvas.draw() width, height = fig_blank.canvas.get_width_height() basemap_img = np.frombuffer(fig_blank.canvas.tostring_rgb(), dtype=np.uint8).reshape(height, width, 3) plt.close(fig_blank) # 关闭临时画布 # 2. 创建子图并复用底图图像 fig, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(13, 15), subplot_kw={'projection': ccrs.PlateCarree()}) for i in range(rows): for j in range(cols): current_ax = ax[i][j] if rows>1 and cols>1 else ax[j] if cols>1 else ax[i] current_ax.set_extent(extent) # 加载预渲染的底图 current_ax.imshow(basemap_img, origin='upper', extent=extent, transform=ccrs.PlateCarree(), zorder=0) # 叠加子图专属数据 current_ax.plot(xss, np.random.uniform(low=33, high=64, size=(len(xss),)), color='cyan', zorder=20, transform=ccrs.PlateCarree(), linewidth=2) current_ax.set_title(f"Row:{i}, Col:{j}") plt.tight_layout() plt.show()
方案2:缓存GSHHSFeature几何数据
直接提取并缓存GSHHS的几何集合,避免重复读取和处理地理数据:
import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import numpy as np xss = np.linspace(-21, 36, 36) extent = [-21, 36, 33, 64] rows = 3 cols = 2 # 1. 缓存GSHHS几何数据 gshhs_feature = cfeature.GSHHSFeature(scale="intermediate", edgecolor="black", facecolor="#bca89f") geometries = list(gshhs_feature.geometries()) # 一次性读取所有几何对象 cached_feature = cfeature.ShapelyFeature(geometries, ccrs.PlateCarree(), edgecolor="black", facecolor="#bca89f") # 2. 创建子图并复用缓存特征 fig, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(13, 15), subplot_kw={'projection': ccrs.PlateCarree()}) for i in range(rows): for j in range(cols): current_ax = ax[i][j] if rows>1 and cols>1 else ax[j] if cols>1 else ax[i] current_ax.set_extent(extent) # 添加缓存好的海岸线特征 current_ax.add_feature(cached_feature) # 叠加子图专属数据 current_ax.plot(xss, np.random.uniform(low=33, high=64, size=(len(xss),)), color='cyan', zorder=20, transform=ccrs.PlateCarree(), linewidth=2) current_ax.set_title(f"Row:{i}, Col:{j}") plt.tight_layout() plt.show()
方案对比
- 图像复用方案:速度最快,适合底图样式完全固定的场景;缺点是后续无法修改底图样式,需重新渲染。
- 几何数据缓存方案:保留特征的可编辑性,可随时修改海岸线颜色、线宽等属性,同时避免重复读取地理数据,性能提升明显。
内容的提问来源于stack exchange,提问作者darf
相关产品推荐
相关产品推荐

