如果你手里只有一台电脑,也想画出一张完全属于自己的世界地图,不依赖在线地图服务、不交订阅费,那这篇文章就是给你准备的。我用了几个晚上,把基于Python的矢量地图绘制流程完整跑通了一遍,从环境搭建到出图美化,每一步都踩过坑,也把关键细节都记了下来。整条路线用到的核心库是geopandas、matplotlib和cartopy,它们能让你用代码“画”出世界地图——没错,不是去地图网站截图,而是用几千行坐标数据,自己在画布上渲染出陆地边界、海岸线、经纬网格,甚至你自己标定的城市点位。这个方案的适用人群很广:想在地图上展示统计数据的分析师、做GIS课设的学生、想在朋友圈晒一张风格化地图的Python爱好者,都能用上这套方法。而“只属于你自己”这件事,恰恰是这套方案最有魅力的地方——从颜色、投影方式到标注信息,所有细节都由代码说了算,而不是被地图服务商的默认样式牵着走。
1. 整体设计与方案拆解:为什么选geopandas + matplotlib这套组合
1.1 核心思路:把地图拆成“数据 + 绘制”两层来看
在动手写代码之前,我先花了一点时间想清楚一个根本问题:一张世界地图,本质上到底是什么?答案是数据。地球上的陆地、海洋、河流、国界,在地理信息领域都被抽象成一组组由经纬度坐标组成的几何对象——多边形(polygon)、线(linestring)和点(point)。世界地图的绘制过程,其实就是把这堆坐标数据读进来,再用绘图库把它们渲染到画布上。想明白了这一点,方案选型就清晰了:需要一个能处理地理空间数据的库,也就是geopandas;还需要一个能把数据画成图片的库,也就是matplotlib;如果对投影方式有更高要求,再加一个cartopy。
我见过不少初学者一上来就想到用pyecharts或者folium这类交互式地图库。它们确实方便,内置了在线底图和交互组件,但对“专属感”这件事帮助不大——底图样式、瓦片来源、缩放行为全都受制于第三方服务。离线矢量绘制则刚好相反,所有视觉细节都在你掌控之中,甚至地图上每个像素是怎么来的,你都一清二楚。考虑到这篇文章的核心是“只属于你自己的世界地图”,我最终确定了geopandas + matplotlib这条主线。
1.2 方案对比:离线绘制和在线地图服务的取舍
市面上用Python画世界地图的方案大概可以分成三类:第一类是folium/pyecharts这类在线交互地图,优点是零基础友好、能缩放拖拽,缺点是底图来自第三方瓦片服务,样式定制能力有限,而且部分场景下需要稳定的网络环境;第二类是基于plotly的交互式矢量地图,优点是内置世界地图数据,写几行代码就能出图,但默认数据和样式都比较“大众脸”,做深度定制时绕不开它的底层数据结构;第三类就是我采用的方案——用geopandas读入自然地球(Natural Earth)开源数据,再交给matplotlib渲染,必要时用cartopy处理投影变换。
这套组合最大的优势是每一层都解耦。数据层、投影层、渲染层各自独立,你随时可以把其中一个环节换成自己更顺手的工具。比如数据源可以从Natural Earth换成OSM的导出数据,渲染层也可以从matplotlib换成plotly做交互式输出。我在实际使用中体会很深的另一点是:geopandas的数据结构是建立在pandas之上的,这意味着你可以像处理普通表格一样,对地图数据进行筛选、排序、聚合操作。想只画亚洲?一条query语句就搞定。想把人口大于1亿的国家高亮出来?这也是普通的DataFrame条件筛选而已。这种灵活性,在在线地图方案里很难找到。
2. 环境准备与依赖安装:手把手把绘图环境搭起来
2.1 Python环境与核心依赖库的安装细节
在动手安装之前,我建议你先把Python环境准备好。如果你已经装了Python 3.9及以上版本,可以跳过这一步;如果你是从零开始,建议直接去Python官网下载最新稳定版,安装时务必勾选“Add Python to PATH”,否则后续在命令行里敲python会提示找不到命令。我自己用的是Python 3.11,实测下来geopandas和cartopy对3.11的支持已经很完善了。
依赖安装这一步,不同系统的体验差异比较大。在Windows上,我强烈建议不要直接用pip install geopandas,因为geopandas的底层依赖里包括GDAL、Fiona、pyproj这些C扩展库,pip在Windows上往往需要本地编译,经常报错。我自己最开始就是踩了这个坑,后来换成conda环境一次就装好了。如果你不想用conda,还有一个折中方案:去Christoph Gohlke维护的第三方whl仓库,把GDAL、Fiona、pyproj、Shapely、geopandas这几个包手动下载到本地,再用pip按顺序安装。macOS用户相对省心一些,只要系统里有Xcode Command Line Tools,pip install geopandas一般能顺利跑完。Linux用户如果遇到依赖问题,可以用apt先装libgdal-dev和libgeos-dev。为了让不同系统的读者都能顺利复现,我这里给出一个conda环境的完整安装命令:
conda create -n mapenv python=3.11 conda activate mapenv conda install -c conda-forge geopandas matplotlib cartopy这几条命令执行完之后,可以顺手装一个jupyter notebook或者直接用VS Code写脚本。我个人习惯用VS Code的交互式窗口,因为地图绘制的代码往往需要反复调整样式参数,交互式窗口里每块代码都能独立运行,调试效率比完整脚本要高不少。需要提醒的是,geopandas和cartopy的版本更新比较频繁,为了保证代码运行结果一致,建议留意一下conda输出的版本号,并记录在自己的项目文档里。
2.2 世界地图数据从哪里来:Natural Earth数据下载指南
有了库还不够,我们还需要地图的“原材料”——地理空间数据。我首选Natural Earth这个开源数据项目,它提供公共领域的地图数据,可以免费用于任何用途,而且数据质量非常高。我们需要的文件是“Admin 0 – Countries”,也就是国家边界的矢量数据,里面包含每个国家的几何形状和名称、ISO编码等属性信息。在Natural Earth官网下载页面选择“Countries”分类,找到1:110m(110百万分之一)比例尺的版本即可。这个比例尺的数据精度对于画整张世界地图已经足够了,文件只有几百KB,加载速度很快。
如果你想画更精细的版本,也有1:50m和1:10m两个比例尺可选,但文件体积会大不少。需要指出的是,我只是把一个通用开源数据集推荐给你,数据里呈现的具体国界形状由该数据集的制图规则决定,你在复现时根据自己的用途判断数据是否满足需求即可。下载完成后,会得到一个.shp后缀的压缩包,解压后里面至少有.shp、.shx、.dbf、.prj四个文件,这四个文件必须放在同一个目录下,一个都不能少。.shp保存几何坐标、.shx是空间索引、.dbf保存属性信息、.prj保存坐标系定义,任何一个缺失,geopandas都无法正确读取。我从官网下载之后,全部放在项目的data目录下,路径比较清爽,后面写代码时引用也方便。
3. 从零开始:绘制你的第一张基础世界地图
3.1 加载数据与初识GeoDataFrame结构
环境准备好之后,激动人心的时刻到了——写代码画世界地图。我先把完整的入门代码贴出来,然后逐段解释。:
import geopandas as gpd import matplotlib.pyplot as plt # 读取Natural Earth国家边界数据 world = gpd.read_file('data/ne_110m_admin_0_countries.shp') # 创建一个画布 fig, ax = plt.subplots(figsize=(16, 9)) # 绘制世界地图 world.plot(ax=ax, color='#f2efe9', edgecolor='#666666', linewidth=0.3) # 去掉坐标轴边框 ax.set_axis_off() # 保存图片 plt.savefig('world_map_basic.png', dpi=200, bbox_inches='tight') plt.show()这段代码的执行过程并不复杂,但每一行背后都有值得展开的原理。gpd.read_file读取.shp文件时,geopandas会把文件里的几何数据转换成GeoSeries,并和属性数据一起组装成一个GeoDataFrame。你可以用world.head()查看数据内容,用world.plot()直接绘图——这是geopandas封装好的快捷绘图方法,本质上就是调用了matplotlib的plot函数。我在第一次运行代码时,看到整张世界地图以米色底和灰色边的形式出现在画布上,那种“从数据到图形”的成就感确实很强烈。
3.2 掌握基础绘图参数:颜色、边界线宽和画布尺寸
基础版本的代码虽然能出图,但离“只属于你自己”还有很大差距。我们先分析一下绘图参数怎么调,才能让地图既专业又有个人风格。
首先是color参数,它控制的是陆地填充色。这里用了暖色调#f2efe9,如果你想要更冷峻的科技感,可以换成深色背景+亮色陆地的组合,比如背景设为#1a1a2e,陆地填充设为#e8d5b7。其次是edgecolor,它控制国家边界的颜色,默认的黑色往往显得生硬,换成低饱和度的灰色之后,整张图的气质会立刻变得素雅。linewidth则控制边界线的粗细,0.3是个比较合适的值——太粗会让地图显得笨重,太细在小尺寸屏幕上又看不清。画布尺寸用figsize=(16,9)控制,这个比例接近常见显示器的宽高比,视觉上比较舒服。最后,ax.set_axis_off()把坐标轴边框和经纬度刻度全部去掉,这一步是必须的——不然图片周围会多出一个难看的白边和刻度标签。保存图片时dpi=200能把清晰度拉高,用于打印或者发朋友圈都很够用。
4. 个性化定制:让你的世界地图真正“属于你”
4.1 自定义配色方案:从默认样式到风格化视觉
基础地图跑通之后,真正好玩的个性化定制才刚开始。主题色方案是区分“随手画的”和“精心设计的”最直观的分界线。我调试过几套配色方案,这里挑三套风格差异比较大的分享给你们。
第一套是简洁米色系,适合做报告配图。背景用海洋的浅蓝色#cfe3f0,陆地用米白色#f5f0e6,边界线用淡灰色#9a9a9a。这套的视觉特点是安静、百搭,不抢正文的风头。第二套是黑金风格,适合做海报或封面。海洋部分我通常用一个彩色地图的辅助色带,或者干脆用黑色#0d0d0d,陆地用金色#d4af37,边界线用暗金色#8a6d1c。这套方案对比度强,视觉冲击力拉满。第三套是马卡龙风格,适合做儿童教育类内容或者轻松主题的分享。海洋用奶油蓝#cfe8ef,陆地用淡粉#f9d5e5,边界线直接用白色,看起来就像一块甜点。实现的方式很简单,就是在world.plot()函数里替换color和edgecolor的值:
world.plot(ax=ax, color='#f9d5e5', edgecolor='white', linewidth=0.4)不过这里要提醒一下,配色的核心原则是“海洋颜色要比陆地颜色更安静”,因为人的视觉注意力天然会落在面积更小、对比度更高的区域上。如果海洋颜色太跳,陆地的存在感就会被削弱,整张地图看起来会非常奇怪。我试过把海洋调成明黄色,结果地图看起来像一块硫磺皂,完全没法用。
4.2 添加自定义城市点位与文字标注
纯粹的世界地图其实信息量很有限,真正让地图“属于你”的,是你自己叠加在上面的数据点。比如我想在地图上标出我去过的城市,并用不同颜色区分旅行地和计划中的目的地。实现思路是在matplotlib的同一张画布上,用scatter函数叠加散点,再用annotate函数添加标注。我这里先列坐标数据,注意经纬度顺序是经度在前、纬度在后:
cities = { '北京': [116.40, 39.90], '上海': [121.47, 31.23], '伦敦': [-0.13, 51.51], '巴黎': [2.35, 48.86], '东京': [139.69, 35.69], '纽约': [-74.01, 40.71], '悉尼': [151.21, -33.87], } for name, (lon, lat) in cities.items(): ax.scatter(lon, lat, s=30, color='#d32f2f', zorder=5) ax.annotate(name, (lon, lat), textcoords="offset points", xytext=(8, 8), fontsize=9)这里的zorder参数值得特别留意。matplotlib的绘图顺序按照zorder的值从小到大叠放,值越大的图层在越上层。默认情况下,地图多边形的zorder是1,如果散点图不设zorder,点可能会被陆地边界覆盖住,导致看不见。我把zorder设成5,确保数据点永远显示在地图图层上方。另一个容易踩坑的点是annotate中的textcoords参数,用offset points表示标注文本相对于点的偏移量,xytext=(8,8)意思是向右上方各偏移8个点。如果不加这个偏移,文本会直接压在点上,文字和数据点挤成一团,非常难看。
4.3 用GeoDataFrame筛选高亮特定国家或地区
想要让地图更聚焦,除了全局展示,还有一种很常见的玩法:把特定国家或区域高亮出来。因为GeoDataFrame本质上是pandas DataFrame的扩展,所以你可以直接使用pandas的条件筛选功能。比如我想把中国、俄罗斯、加拿大这三个面积大国用不同的颜色填充,其余国家用统一底色,代码可以这么写:
highlight_names = ['China', 'Russia', 'Canada'] highlight_colors = ['#e74c3c', '#2980b9', '#27ae60'] for name, color in zip(highlight_names, highlight_colors): country = world[world['NAME'] == name] country.plot(ax=ax, color=color, edgecolor='white', linewidth=0.4, zorder=2)这段代码的关键在于,先通过world['NAME'] == name筛出单个国家的子集,再对这个子集调用plot方法。每一个高亮国家的图层zorder设为2,大于默认的1,所以能覆盖在其他国家之上。需要说明的是,不同版本Natural Earth数据的属性字段名可能不同,有的是NAME,有的是NAME_EN,你可以用world.columns查看当前数据的字段列表,别想当然地写属性名。另外,这里只是举一个技术操作的例子,国家名称和颜色都只是演示用途,任何超出绘图本身的政治解读都不是这篇文章讨论的范围。
5. 进阶玩法:分层绘制、投影变换与区域聚焦
5.1 什么是分层图绘制:把视觉元素拆成独立图层
分层图绘制是一个很实用但容易被忽略的概念,它的核心思想是把一张地图拆成多个独立的视觉层,每一层只负责一个信息维度。这样的好处有三个:一是每层可以单独修改和调试,不用为了改一个点而重画整张图;二是可以灵活控制图层顺序,利用zorder实现不同层级之间的遮盖关系;三是方便复用——同一份底图数据可以用在十张不同主题的图中,只需替换上面的数据层。
真实场景中,一套典型的分层结构可以是这样的:第一层是海洋底图,用整个axes的背景色填充;第二层是陆地多边形,包含所有国家的边界;第三层是高亮图层,只显示你重点关注的国家或区域;第四层是数据图层,可以是散点、热力图或者统计图表;最后一层是标注图层,放置文本说明、指北针、比例尺。我在做的“足迹地图”就是按这个思路搭建的,改配色只需要动第一层和第三层的代码,加新的旅行地点只需要操作第四层的数据,整体灵活度比把所有元素写在一段代码里高得多。
5.2 用Cartopy更换投影方式:从“平面世界”到“球面视角”
如果只是用geopandas的默认绘图方式,纬度线是直线,经度线也是直线,这种投影叫等距圆柱投影,它最大的问题是高纬度地区的面积会被严重拉伸,格陵兰岛看起来和非洲差不多大。如果想打破这种视觉定式,让地图呈现出更科学或者更有艺术感的形态,就需要引入cartopy来更换投影方式。cartopy是专门处理地图投影的Python库,它支持几十种投影方式,我推荐三个比较有代表性的:
第一个是等距圆柱投影(PlateCarree),适合数据分析和科研绘图,因为经纬度就是线性坐标,不会引入变形误差,这也是很多专业论文的默认选择。第二个是罗宾逊投影(Robinson),视觉上非常平衡,国家形状变形小,适合做展示图和封面图。第三个是北极正射投影(NorthPolarStereo),适合极地主题的内容,能展现高纬度的空间关系。代码实现方式是以cartopy的GeoAxes作为画布,再把geopandas的数据用to_crs做坐标转换:
import cartopy.crs as ccrs fig = plt.figure(figsize=(14, 8)) ax = plt.axes(projection=ccrs.Robinson(central_longitude=0)) # 将geopandas数据转换为罗宾逊投影 world_robinson = world.to_crs(ccrs.Robinson(central_longitude=0).proj4_init) world_robinson.plot(ax=ax, color='#f2efe9', edgecolor='#666666', linewidth=0.3) ax.stock_img() # 这一行会导致底图冲突,实际使用时不建议 ax.set_global()需要特别提醒的是,cartopy和geopandas在版本更新中,交互方式有一些变化。在cartopy 0.21及以上版本,to_crs方法接受一个CRS对象,代码写法略有不同。如果遇到报错,检查一下两个库的版本,再去官方文档确认对应写法即可。另外,cartopy本身自带地形、海岸线等数据,你可以用ax.add_feature(cfeature.LAND)直接添加,但这种方式和geopandas绘制的多边形可能会在边缘处有细微缝隙,混用时需要仔细检查效果。
5.3 聚焦特定区域:从世界地图到区域裁剪
很多时候我们需要的不一定是整个世界,而是某个大洲或者某个区域。geopandas做区域裁剪有两种常见思路。第一种是直接筛选,用bbox坐标范围把地图数据裁出来,比如我只想画东亚,就选经度在100到150之间、纬度在20到50之间的国家。第二种是更优雅的做法,用一个矩形几何对象做空间裁剪,利用geopandas的clip函数实现。这里的原理是:先构造一个目标区域的边界框(bounding box),然后用clip函数把所有与这个边界框相交的几何对象裁切出来,最后只绘制落在框内的部分。
我实际用得比较多的是第一种思路,因为简单直接:
east_asia = world.cx[100:150, 20:50] east_asia.plot(ax=ax, color='#f2efe9', edgecolor='#666666', linewidth=0.3)cx属性是GeoDataFrame的一个便利索引器,它允许你按坐标范围直接筛选数据。第一次用的时候我被这个用法惊艳到了——一行代码就把世界地图裁剪成了东亚地图。需要注意,cx的参数顺序是经度区间在前,纬度区间在后,和坐标习惯的(x, y)对应。如果发现筛选出来的区域和自己想的不一样,优先检查是不是把经纬度顺序写反了。
6. 常见问题与避坑指南:我踩过的那些坑
6.1 中文标注和负号显示乱码问题
用matplotlib绘制地图时,中文乱码问题几乎人人都会遇到。具体表现是:图上的中文城市名显示为一个个方框。原因是matplotlib默认字体不包含中文字形。解决方法是把绘图字体设置为系统里的中文字体。Windows系统通常有微软雅黑或宋体,macOS有苹方,Linux则要看你装了什么中文字体。我还发现一个细节点:坐标轴上的负号也会因为字体设置而变成方块,所以用rcParams做全局设置时,要把axes.unicode_minus也设为False,才能让负号正常显示。:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'PingFang SC'] plt.rcParams['axes.unicode_minus'] = False这段设置放在脚本开头,一次设置,全局生效。不过要注意,如果系统里没有列表中的这些字体,matplotlib还是会回退到默认字体,依然显示方框。此时需要用font_manager手动注册字体文件路径,这也是一个比较常见的进阶需求。
6.2 输出图片只有地图没有海洋背景
有朋友照着教程跑完代码之后发现,生成的图片周围是一片空白而不是预期的海洋蓝色。其实这个现象很正常,因为matplotlib的画布默认就是白色,它并不会自动把“海洋”渲染成一种颜色。所谓海洋颜色,要么是通过fig.patch.set_facecolor设置整个画布的背景,要么是绘制一个覆盖全图的矩形并指定颜色。我个人的经验是用fig.patch.set_facecolor,因为它只管画布背景,不影响地图图层的渲染逻辑:
fig = plt.figure(figsize=(16, 9), facecolor='#cfe3f0')当然这样设置之后,海洋和陆地的接缝处可能会有细小的白色间隙,因为陆地多边形和画布边缘之间是分离的。如果想要更精细的效果,可以用cartopy的海洋要素数据,或者自己绘制一个全图范围的海洋多边形,这里就不展开讲了。
6.3 geopandas读取文件报错或性能卡顿怎么办
读取.shp文件时,最常见的报错是“No features found”和“CPLE_OpenFailedError”。前者通常是因为文件路径不对,我建议尽量用绝对路径或者提前用os.chdir切换到数据目录,避免相对路径在不同环境下解析出错。后者往往是因为缺失了.prj等辅文件,回到2.2节确认文件完整性。性能方面,如果地图数据用1:10m的比例尺,绘制时图层数量多,渲染耗时会明显上升。此时的处理方式是把数据做个简化:用world.simplify(tolerance=0.01)在损失可控的范围内减少多边形顶点数,绘图速度可以提升好几倍。我在绘制全国地市级别数据时尝试过,效果非常明显。
6.4 快速故障排查参考表
为了让你在遇到问题时能快速定位,我把最容易出现的几类问题整理成表格,方便对照排查:
| 症状 | 可能原因 | 解决思路 |
|---|---|---|
| 中文标题/标注显示方框 | matplotlib缺少中文字体 | 设置rcParams字体参数或在环境中安装中文字体 |
| 图片整体没有海洋颜色 | 未设置画布背景色 | 用fig.patch.set_facecolor指定海洋色 |
| 数据点被地图盖住 | zorder未设置或值太小 | 把散点图的zorder设为明显高于地图的层级 |
| 读取.shp时报错 | 文件路径错误或缺少辅文件 | 检查路径和.shp同目录下的四个文件 |
| 绘制范围与预期不符 | cx索引经纬度顺序写反 | 确认经度在前、纬度在后 |
| 罗宾逊投影to_crs报错 | geopandas/cartopy版本不匹配 | 查看版本并根据官方文档调整写法 |
| 图形卡顿严重 | 数据精度太高 | 用simplify减少多边形顶点数 |
7. 项目扩展与灵感参考:让地图应用场景更丰富
7.1 在地图上叠加热力图:展示统计数据的分布
世界地图做出来之后,自然想叠加真实的数据信息,让地图“说话”。一个很常见的应用是热力图,比如我想展示某品牌全球各门店的数量分布,可以用每个国家作为统计单元,把门店数量映射到颜色深浅上。实现方式是在GeoDataFrame里新增一列数据,然后在plot函数中用column参数指定这一列,用cmap参数指定颜色映射。:
# 假设world数据里新增一列store_count表示每个国家的门店数量 world.plot(ax=ax, column='store_count', cmap='OrRd', edgecolor='white', linewidth=0.3, legend=True)这段代码跑通之后,你会得到一个带图例的填色地图,门店数量越多的国家颜色越深。这种图在业务分析、市场报告里非常实用,而且因为数据列完全由你控制,今天可以放门店数量,明天可以换成销量、人口密度、森林覆盖率,一套代码通吃。需要留意的一点是,当数据有缺失值时,geopandas默认会把对应区域画成灰色,这是正常现象,也可以手动指定missing_kwds参数来控制缺失值的表现样式。
7.2 从静态到动态:用Matplotlib生成GIF动画地图
静态地图之外,很多读者会期待地图“动起来”。比如展示一家公司五年间门店数量变化的动态地图,或者展示候鸟在不同月份迁移路线的动画。matplotlib本身不支持直接输出GIF,但我们可以配合pillow库,把一帧一帧的PNG图片合成动画。基本思路是:写一个循环,每次更新要展示的年份数据,重新绘制当前年份的地图并保存为PNG,循环结束后用pillow把所有PNG读进来,保存为GIF。:
from PIL import Image import glob frames = [] for img_path in sorted(glob.glob('frames/frame_*.png')): frames.append(Image.open(img_path)) frames[0].save('world_map_animation.gif', save_all=True, append_images=frames[1:], duration=500, loop=0)这段代码我实测下来能稳定工作,生成的GIF大约是每500毫秒切换一帧,视觉上类似于一个简易的新闻数据播报。需要提醒的是,帧率设置要合理,太快看不清信息,太慢又显得拖沓。还有一点,生成动画之前所有帧的图片尺寸必须一致,所以绘图时figsize和dpi参数要全程保持一致。比如每年的人口数据变化,或者每季度的气候数据变化,都可以延用这个方法方案。
7.3 添加比例尺与指北针:让地图看起来更像专业作品
一张地图如果只有陆地多边形和城市标注,严格来说还不够完整。专业的制图规范里,比例尺和指北针是基础的地理信息要素。虽然matplotlib没有内置这两个组件,但我们可以用简单的坐标轴操作手动加上去。比例尺的常规做法是用ax.plot画一段表示实际距离的横线,再用ax.text标注长度单位,比如“2000 km”。指北针则是画一条带箭头的竖线,上方标注“N”。:
# 在左下角添加比例尺 ax.plot([-170, -120], [-55, -55], color='black', linewidth=2) ax.text(-145, -58, '3000 km', ha='center', fontsize=10) # 在右下角添加指北针 ax.annotate('N', xy=(160, -40), xytext=(160, -55), arrowprops=dict(arrowstyle='->'), fontsize=14, ha='center')比例尺的长度需要根据实际纬度来估算,因为在等距圆柱投影下,1度经度的实际地面距离约等于111公里乘以纬度的余弦。我在-55度纬度线上画的这段60度经度跨度,乘以111再乘以cos(-55度),大约是3800公里。上面的示例代码是我随手取的数值,实际使用时需要根据你关注区域所在纬度重新计算,以免比例尺失真。想做好这一步,其实也是理解地图投影原理的一次很好的练习。
7.4 自定义数据来源:用本国或本地数据替换Natural Earth
当你掌握这套流程后,大概率不会满足于只有Natural Earth这一种数据源。一个自然的延伸是使用更高精度的本国或本地行政区划数据。这类数据通常由测绘部门或开放数据平台发布,常见的格式还包括GeoJSON和TopoJSON。geopandas同样支持直接读取GeoJSON格式,一条gpd.read_file就能搞定。拿到本地数据之后的处理流程和上面完全一致:读取、筛选、投影、绘制、叠加数据。我自己的经验是,处理本地数据时精度问题会变得更加敏感——乡镇级别的边界数据文件动辄几十MB,一定要先做simplify简化多边形顶点,否则绘图时的内存占用和渲染时间会非常难看。另外,本地数据的属性字段经常是中文字段名,比如“省”、“市”、“区”,在代码里写筛选条件时小心别用错字段名。
8. 写在最后的经验之谈
整套流程走下来,我最想跟读者分享的一句话是:画世界地图,真正难的不是库的用法,而是你想清楚“这张地图画出来要传达什么”。如果只是照着教程跑一遍代码,那它只是一张数据可视化作品;如果带着“把我去过的城市标出来”“把产品销量分布展示给团队看”这样的明确目标,那地图才真正变成了“只属于你自己的世界地图”。我在调试配色和投影方式的时候,反复调整了几十次,有时候只是为了把某一处标注挪开几毫米,但这种“非标准答案”的尝试也正是手工绘制的乐趣所在。希望这个项目也能成为你探索地理可视化的起点——下次当你想展示一组有地理属性的数据时,不妨想想能不能用这套方案画出一张属于你自己的地图。