1. 为什么选择cartopy绘制中国地图第一次接触地理数据可视化时我尝试过Basemap、GeoPandas等多种工具最终发现cartopy在绘制中国地图时有着不可替代的优势。这个由英国气象局开发的库不仅继承了Basemap的易用性还解决了投影变换时的诸多痛点。特别是在处理中国版图这种包含特殊区域的场景时cartopy的灵活性让我印象深刻。记得去年做气象数据可视化项目时我需要同时展示大陆、台湾地区、南海诸岛和十段线的完整版图。当时试了三个不同的shp文件不是缺少藏南地区就是台湾地区的边界不完整。后来在GMT中文社区找到的CN-border-La.dat文件配合Meteo-Python群的shp资源才终于解决了这个问题。这也让我意识到选择正确的数据源和工具组合有多么重要。cartopy的核心优势在于它原生支持多种地图投影并且能够智能处理坐标转换。比如PlateCarree投影虽然简单但在展示跨越180度经线的区域时容易出错。这时只需要设置central_longitude参数就能完美解决经线跳跃的问题。我在实际项目中测试过从70°E到140°E的中国全图范围用PlateCarree(central_longitude180)投影效果最稳定。2. 数据准备与常见陷阱规避2.1 获取可靠的边界数据绘制准确的中国地图第一步就是找到完整可靠的边界数据。经过多次踩坑我总结出几个可信的数据源GMT中文社区提供的CN-border-La.dat文件包含完整的陆地边界和十段线Meteo-Python技术交流群分享的china_country.shp文件Natural Earth Data的1:50m行政边界数据需要额外验证完整性这里有个重要提示千万不要随便下载来路不明的shp文件。我曾经用一个国外网站下载的China_Boundary.shp结果发现缺少了钓鱼岛等重要区域。后来对比发现有些数据源为了所谓的简化会刻意省略敏感区域这在专业项目中是绝对不可接受的。2.2 处理dat格式的边界数据CN-border-La.dat文件虽然完整但直接读取会遇到两个问题文件包含注释行以#开头边界段用符号分隔这是我处理这类文件的代码模板with open(CN-border-La.dat) as src: # 过滤注释行 context .join([line for line in src if not line.startswith(#)]) # 按分割边界段 blocks [cnt for cnt in context.split() if len(cnt) 0] # 转换为numpy数组 borders [np.fromstring(block, dtypefloat, sep ) for block in blocks]2.3 加载shp文件的正确姿势cartopy支持两种加载shp文件的方式经过实测都可用方法一使用ShapelyFeatureshape_feature cfeature.ShapelyFeature( shpfile.geometries(), ccrs.PlateCarree(), facecolorteal) ax.add_feature(shape_feature)方法二遍历记录逐个添加for rec in shpfile.records(): ax.add_geometries( [rec.geometry], crsccrs.PlateCarree(), facecolorred)特别注意shp文件会覆盖cartopy自带的河流湖泊等要素所以一定要先添加基础地理要素最后再加载shp文件。这个顺序问题曾经让我调试了整整一个下午。3. 完整绘图流程详解3.1 初始化地图画布创建画布时需要特别注意两点设置足够大的figsize建议至少8×6选择合适的投影方式fig plt.figure(figsize[8, 5.5]) ax plt.axes(projectionccrs.PlateCarree(central_longitude180))3.2 添加基础地理要素按顺序添加这些要素可以避免图层覆盖问题海洋和陆地背景国界线和海岸线河流和湖泊自定义shp文件十段线等特殊边界ax.add_feature(cfeature.OCEAN.with_scale(50m)) ax.add_feature(cfeature.LAND.with_scale(50m)) ax.add_feature(cfeature.BORDERS.with_scale(50m), lw0.5) ax.add_feature(cfeature.COASTLINE.with_scale(50m), lw0.5)3.3 绘制十段线和特殊边界使用之前处理好的borders数据绘制完整边界for line in borders: ax.plot(line[0::2], line[1::2], -, lw1, colork, transformccrs.Geodetic())这里有个关键细节transform参数必须设置为ccrs.Geodetic()这样才能确保线段在不同投影下正确显示。3.4 配置经纬度网格网格线的配置相对复杂但掌握这几个参数就能应对大多数场景gl ax.gridlines( crsccrs.PlateCarree(), draw_labelsFalse, linewidth1.2, colork, alpha0.5, linestyle--) # 设置标签显示位置 gl.xlabels_top False gl.ylabels_right False # 配置格式化器 gl.xformatter LONGITUDE_FORMATTER gl.yformatter LATITUDE_FORMATTER # 设置网格间隔 gl.xlocator mticker.FixedLocator(np.arange(70, 14010, 10)) gl.ylocator mticker.FixedLocator(np.arange(0, 5510, 10))4. 常见问题解决方案4.1 跨越180度经线的显示问题中国地图的经度范围70°E-140°E正好跨越了180度经线附近这会导致地图在显示时出现断裂。解决方案是设置central_longitude参数# 错误的做法 - 会出现地图断裂 ax plt.axes(projectionccrs.PlateCarree()) # 正确的做法 ax plt.axes(projectionccrs.PlateCarree(central_longitude180))4.2 台湾地区显示不完整遇到台湾地区显示不全的情况通常有两个原因使用的shp文件本身数据不全地图范围设置不当确保set_extent包含完整范围ax.set_extent([70, 140, 0, 55], crsccrs.PlateCarree())4.3 南海诸岛比例失调南海诸岛在小比例尺地图上常常显示过小可以通过以下方式优化使用inset_axes添加放大的南海小图调整主图范围给南海留出更多空间使用不同的投影方式如AlbersEqualArea4.4 输出图像分辨率不足在保存图像时建议设置dpi≥300使用矢量格式如PDF、SVG调整figsize确保细节清晰plt.savefig(china_map.png, dpi300, bbox_inchestight)5. 进阶技巧与优化建议5.1 添加省界和城市标记在完成国界绘制后可以进一步添加省界和主要城市# 加载省界shp文件 province_shp shapereader.Reader(china_province.shp) for rec in province_shp.records(): ax.add_geometries( [rec.geometry], crsccrs.PlateCarree(), edgecolorgray, facecolornone)5.2 自定义样式美化通过调整这些参数可以让地图更专业海岸线线宽lw参数陆地填充色facecolor边界线样式linestyle透明度alphaax.add_feature(cfeature.LAND.with_scale(50m), facecolor#f0f0f0, edgecolornone)5.3 添加比例尺和指北针虽然cartopy没有内置的比例尺工具但可以通过matplotlib实现# 添加比例尺 scale_bar(ax, 1000) # 需要自定义scale_bar函数 # 添加指北针 ax.annotate(N, xy(0.9, 0.9), xycoordsaxes fraction, fontsize20, hacenter)5.4 性能优化技巧当处理高精度数据时可以尝试使用simplify属性降低shp文件复杂度设置合适的map_extent减少渲染范围使用rasterize处理密集点数据from matplotlib.path import Path from matplotlib.patches import PathPatch for rec in shpfile.records(): geom rec.geometry.simplify(0.01) # 简化几何图形 ax.add_geometries( [geom], crsccrs.PlateCarree(), facecolorred)6. 完整代码示例与效果展示将上述所有步骤整合这里给出一个完整的绘图示例# coding:utf-8 import numpy as np import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER import cartopy.io.shapereader as shapereader import matplotlib.ticker as mticker # 加载dat格式边界数据 with open(CN-border-La.dat) as src: context .join([line for line in src if not line.startswith(#)]) blocks [cnt for cnt in context.split() if len(cnt) 0] borders [np.fromstring(block, dtypefloat, sep ) for block in blocks] # 加载shp文件 shpfile shapereader.Reader(china_country.shp) # 创建画布 fig plt.figure(figsize[10, 7]) ax plt.axes(projectionccrs.PlateCarree(central_longitude180)) # 添加基础地理要素 ax.add_feature(cfeature.OCEAN.with_scale(50m)) ax.add_feature(cfeature.LAND.with_scale(50m)) ax.add_feature(cfeature.BORDERS.with_scale(50m), lw0.5) ax.add_feature(cfeature.COASTLINE.with_scale(50m), lw0.5) # 绘制shp文件 for rec in shpfile.records(): ax.add_geometries([rec.geometry], crsccrs.PlateCarree(), facecolor#f5deb3, edgecolorgray) # 绘制十段线等边界 for line in borders: ax.plot(line[0::2], line[1::2], -, lw1, colork, transformccrs.Geodetic()) # 添加河流湖泊必须在shp之后 ax.add_feature(cfeature.RIVERS.with_scale(50m)) ax.add_feature(cfeature.LAKES.with_scale(50m)) # 配置网格 gl ax.gridlines(crsccrs.PlateCarree(), draw_labelsTrue, linewidth1, colorgray, alpha0.5, linestyle--) gl.xformatter LONGITUDE_FORMATTER gl.yformatter LATITUDE_FORMATTER gl.xlocator mticker.FixedLocator(np.arange(70, 14010, 10)) gl.ylocator mticker.FixedLocator(np.arange(0, 5510, 10)) # 设置显示范围 ax.set_extent([70, 140, 0, 55], crsccrs.PlateCarree()) plt.title(中国全图含南海诸岛和十段线) plt.savefig(china_full_map.png, dpi300, bbox_inchestight) plt.show()最终效果图中你可以看到完整的中国陆地边界清晰的台湾地区轮廓南海诸岛和十段线的准确位置协调的色彩搭配和专业的标注在实际项目中这套代码框架已经成功应用于气象数据可视化、地理信息系统开发等多个领域。根据具体需求你可以灵活调整颜色方案、添加数据叠加层或者集成到更复杂的可视化流程中。