做 GIS 和地图这块十来年,被问得最多的一句话就是"openstreetmap 的地图怎么下"。问的人里有做物流路径规划的、有做城市研究的、有搞室内外一体化导航的,也有只是想给自己项目画一张好看底图的前端同学。大家的共同困惑是:OpenStreetMap 听起来是个"地图网站",可打开一看,数据是能编辑的、页面是能看的,但就是找不到一个显眼的"下载"按钮。于是有人去截屏拼图,有人去扒瓦片,还有人以为这东西只能在线看。
先给一个结论性的定位:OpenStreetMap(下面简称 OSM)本质上是一个全球性的开放地理数据库,它同时对外提供三种东西——原始矢量数据、渲染好的栅格瓦片、以及各种衍生产品。你要下载的到底是哪一种,直接决定了后面该走哪条路。这跟去菜市场买菜是一个道理:你想买毛坯食材、半成品净菜,还是直接点一份外卖,三种需求对应三个完全不同的摊位。选错了摊位,不是买不到,而是买回来没法用。
这篇文章我把这些年用过的下载路径从头捋一遍,从几十平方公里的点位导出,到几百 GB 的全球全量数据,中间还有 Overpass 查询、区域切块、命令行裁剪、格式转换这些绕不开的环节。每一种方法我都会说清楚它适合什么场景、坑在哪里、参数怎么定。新手可以照着抄命令,有经验的同学可以直接跳到第 4、5 章看工具链和瓦片部分的细节。
1. 先分清你要的到底是哪种地图数据
很多人一上来就问"下载地图",这个问题本身是模糊的。OSM 对外输出的数据形态至少有三大类,用法完全不同,混着理解会一直卡住。
1.1 三种数据形态:原始矢量、矢量瓦片、栅格瓦片
第一类是原始矢量数据,通常以.osm.pbf、.osm、.osm.xml这些后缀出现。里面存的是赤裸裸的几何 + 标签,比如一条道路就是一条折线加一堆键值对:highway=residential、name=某某路、oneway=yes、maxspeed=30。这个形态信息量最大,能拿去做空间分析、路网计算、专题制图。缺点是它只存几何和属性,不存样式,你打开它看到的是线条和点,不是一张"图"。
第二类是矢量瓦片,常见后缀.mvt、.pbf(注意和上面的.osm.pbf不是一回事,只是扩展名撞车了)。它把矢量数据按瓦片编号切好、按图层组织好,浏览器端配合样式表渲染,能做出可缩放、可旋转、可换肤的动态地图。它是"给前端用的",不是"给分析用的",因为跨瓦片查询和属性检索很不方便。
第三类是栅格瓦片,就是我们熟悉的z/x/y.png这种图片金字塔。它直接就是图,谁都能用,但它不可查询、不可编辑、放大到一定级别就糊。离线地图包、PPT 里的插图、CAD 底图,多数用的是这一类。
提示:如果你要做的是"算出两点之间的最短路径""统计某个片区的学校分布",选第一类;如果是"给自己的 App 做一张能换主题的地图",选第二类;如果只是"要一张截图当底图",第三类最省事。
1.2 下载前必须锁定的四个参数
不管走哪种方式,动手前先把下面四件事定下来,能省掉大量返工。
- 范围:是经纬度矩形框(bbox),还是行政边界多边形(relation),还是某个地名匹配出来的区域。这三者的数据量和复杂度差一个数量级。矩形框最好算,多边形最灵活,地名匹配最容易出意外(重名)。
- 要素类型:全要素、只要路网、只要建筑轮廓、只要 POI。只要你明确了这一点,后面用标签过滤可以从几百 GB 直接缩到几十 MB。
- 格式:PBF、GeoJSON、Shapefile、GPKG、CSV。格式决定了你后面用什么工具打开,也决定了坐标系和字段长度这些隐形限制。
- 时间版本:OSM 是持续编辑的,今天下载的路网和下个月下载的会不一样。做对比分析的时候,一定要把版本日期记下来,否则半年后你根本说不清两版数据的差异是"城市真变了"还是"有人改错了"。
我见过最典型的翻车案例:一个团队做交通流量模型,两个人在不同时间各下了一份路网,结果合并的时候发现同一条路一边有、一边没有,排查了一整天才意识到是数据版本差了两周。这种坑,只要在文件名里加个日期就能避免。
2. 小范围取数:官方导出面板与 Overpass 查询
范围在几十到几百平方公里这个量级,完全不需要碰全量数据。两条路:图形化点选,或者写查询语句。
2.1 官方导出面板:点选式下载,适合几十平方公里
OSM 主站上有一个导出入口,你在页面里把矩形框拖到目标区域,点导出,就能拿到一个.osm文件。它的优势是零门槛,浏览器打开就能用;劣势也很明确:有面积上限,超了会提示缩小范围。而且不同时间点的限制不完全一致,通常会根据当前服务压力动态调整。
这个面板适合的场景很具体:临时看一下某个片区的数据长什么样、给一个小项目做一次性的底图、教学演示。如果你要的是"一个地级市全市的路网",这条路走不通。另外它的输出格式是.osm(XML),体积比 PBF 大好几倍,几十平方公里就可能上百 MB,加载慢是正常的。
2.2 Overpass API:按标签精确提取
Overpass 是我个人用得最多的一类方案。它不是一个"下载按钮",而是一套查询语言加一个查询端点。你写一段查询语句,描述"我要哪个区域里满足什么标签的要素",它把结果回给你。返回格式可以是 XML、JSON 或者 CSV。
它的价值在于精确过滤。比如你只要充电桩位置,写一个node["amenity"="charging_station"]就完事了,不需要把整个城市的建筑轮廓都下下来再删。数据量能降两三个数量级。
用法上有两种:一是网页版查询界面,左边写语句右边看结果,确认无误后导出;二是直接用 HTTP 请求打端点,配合脚本批量跑。后一种在需要重复取数(比如每月更新一次)的时候非常划算。
2.3 Overpass 查询语句的写法与超时控制
Overpass QL 的语法看着有点怪,但结构其实很固定。一个典型的路网查询长这样:
[out:xml][timeout:180][maxsize:1073741824]; area["name"="杭州市"]["admin_level"="5"]->.a; ( way["highway"](area.a); ); (._;>;); out body;逐行解释一下,这几行是最容易写错的地方:
- 第一行是全局设置。
timeout是服务端最长执行时间,秒为单位;maxsize限制返回数据量上限。这两个值给太小会直接超时,给太大又可能被服务端拒绝,一般从小往大试。 - 第二行用
area锁定行政区域,admin_level的取值因地区而异,写错就匹配不到。省事的做法是先用简单查询把 area 的 id 查出来,直接写area(3600000000)这种数字 id,稳定性最高。 - 第三行是要素选择,
way["highway"]表示所有带highway标签的线要素。想只要主干路可以加值过滤,比如way["highway"~"motorway|trunk|primary"]。 - 第四行
(._;>;)是递归向下取完整几何,把构成这些线的节点也一起拉出来。少了这一行,你拿到的线是断的,导进 GIS 会变成一堆不闭合的碎线。 - 第五行
out body控制输出内容,out skel qt只出骨架不带标签,速度更快。
注意:Overpass 是公共资源,官方明确希望大家别做高频批量请求。需要大规模反复取数的时候,自己搭一个实例,或者改用区域切块文件,别硬怼公共端点。
3. 中等到大范围:按区域切块下载
范围上升到省、国家甚至洲的级别,前两种方式就不合适了。这时候要用"预切好的区域文件"。
3.1 区域切分逻辑:大洲到国家的层级
目前最常用的区域切块服务,是按"大洲 → 国家 → 省级子区域"这样的层级把全量数据切好,每个区域一个.osm.pbf文件,并且提供每日或每几小时更新一次的版本。它的便利之处在于,你只需要知道自己要的国家或省份,直接下对应的文件就行,不用自己动手切。
拿中国区域来说,通常对应一个单独的文件,体积在数百 MB 到 1 GB 出头这个量级,具体随数据增长逐年变化。再往下细分,部分省份也会有独立文件。如果你的目标就是"某一个省的路网",这种切块文件的效率远比自己去切全量高。
需要留意的是,切块文件的边界是按行政区域划分的,如果你要的范围横跨两个省的接壤地带,需要下两份再合并。另外,行政区域的映射关系(relation)在 OSM 里是一个嵌套结构,取子区域之前最好先确认一下 relation 的 id,不要凭名字猜。
3.2 自定义边界导出工具
区域切块服务的局限是"只能按它切好的粒度拿"。如果你要的是一个非行政形状的范围,比如流域、某个经济圈、或者你自己画的多边形,就得用支持自定义边界的导出工具。
这类工具的工作方式是:你上传一个多边形文件(GeoJSON、KML、WKT 都行),选好要的要素类型,它后台去拉数据、裁剪、打包,完成后给你一个下载链接。整个过程通常几分钟到几十分钟,取决于范围和要素量。
它的实现原理其实不神秘:本质上就是"调用大数据集的子集提取接口"。所以你在用的时候要注意两点。第一,它给的成果是异步的,任务量大时排队很正常,别以为是卡住了。第二,选要素类型的时候尽量收敛,全要素导出会显著变慢,而且很多用不上的字段会让后续处理变重。
3.3 全量数据文件与硬件门槛
再往上就是全球全量文件,一般被称为 Planet 文件。压缩后的体积在几十 GB 这个量级,解压后翻好几倍。这个量级的东西,下载不是难点,处理才是。
先算一笔账。假设压缩包 70 GB,磁盘上占 70 GB,解压成 XML 可能到 800 GB 以上,再转成可查询的数据库又要一份空间。也就是说,你要留出 1 TB 以上的可用磁盘才比较从容。内存方面,做全球范围的空间索引至少得 64 GB 起步,128 GB 比较舒服。CPU 核心数决定了提取和转换的耗时,全球级别的处理动辄是以"天"为单位计时的。
我的建议很直接:除非你确实需要全球范围的完整数据,否则不要碰 Planet。绝大多数项目需要的是某个国家、某个省、甚至某个城市。省下来的时间用来调参和验证,价值高得多。
| 数据源类型 | 典型适用范围 | 获取方式 | 主要限制 |
|---|---|---|---|
| 官方导出面板 | 几十平方公里 | 网页拖框 | 有面积上限,格式为 XML |
| Overpass 查询 | 市县级,按标签 | 写查询语句 / HTTP | 公共端点有频率限制 |
| 区域切块文件 | 国家、省级 | 直接下载.osm.pbf | 粒度固定,跨区需合并 |
| 自定义边界导出 | 任意多边形 | 上传边界异步生成 | 需排队,全要素较慢 |
| 全球全量文件 | 全球 | 下载大文件 | 磁盘、内存、时间成本高 |
4. 拿到 PBF 之后的裁剪与格式转换
下载只是第一步。真正让很多人卡住的是"文件下来了,怎么变成我能用的东西"。
4.1 命令行工具链的安装与常用命令
处理.osm.pbf最顺手的是一套命令行工具,核心是三个命令:提取、过滤、导出。安装方式在主流系统上都很简单,包管理器基本都有。
# 按矩形范围裁剪 osmium extract -b 120.10,30.20,120.35,30.40 china-latest.osm.pbf -o hangzhou.osm.pbf # 按边界多边形裁剪 osmium extract -p boundary.geojson china-latest.osm.pbf -o area.osm.pbf # 只保留路网要素 osmium tags-filter hangzhou.osm.pbf w/highway -o hangzhou-roads.osm.pbf # 转成 GeoJSON osmium export hangzhou-roads.osm.pbf -o hangzhou-roads.geojson # 查看文件基本信息 osmium fileinfo hangzhou.osm.pbf这里面的关键点是先裁剪、再过滤、最后转换。顺序反了会非常痛苦。原因很简单:裁剪是纯几何操作,不涉及属性解析,速度最快;过滤要解析全部标签,稍慢;转换到 GeoJSON 要重建几何、处理坐标系、序列化成文本,最慢而且最吃内存。先把数据量砍下来,后面的每一步都省力。
-b后面的参数顺序是"最小经度,最小纬度,最大经度,最大纬度",注意是经度在前。这个顺序错一次,你会得到一个在太平洋中间的空文件,而且不会报错。我第一年做这行的时候在这个点上栽过不止一次。
4.2 格式转换时的字段与坐标系处理
导出成 Shapefile 或者 GeoJSON 的时候,有几个隐形的坑要提前知道。
Shapefile 的字段名长度上限是 10 个字符。OSM 里很多标签名超过这个长度,转换工具会做截断或者重命名,导致你后面按字段名匹配的时候对不上。解决办法是改用 GPKG 格式,它是基于 SQLite 的,字段名长度基本没限制,而且单文件、支持索引,现在是我的默认选择。
Shapefile 的单文件体积上限是 2 GB。数据量大了会静默截断,这种错误极难排查,因为文件看起来是完整的。同样,用 GPKG 可以绕开。
坐标系默认是 WGS84(EPSG:4326),经纬度单位。如果你要做面积计算或者距离统计,直接在这个坐标系下算出来的数值是"度",没有物理意义。要么在计算前投影到合适的平面坐标系,要么用测地线算法。
# 用 ogr2ogr 转成投影坐标系再做分析 ogr2ogr -f GPKG hangzhou-roads-proj.gpkg hangzhou-roads.geojson \ -t_srs EPSG:32651EPSG:32651 是 UTM 51 带,覆盖华东一带。选带号的时候看经度:每 6 度一个带,中央经线从 0 度开始,带号大致是"经度除以 6 再加 31"。这个换算过程看起来麻烦,但只用算一次,写进脚本就行。
4.3 加载到 GIS 软件时的性能取舍
直接在 GIS 软件里打开.osm.pbf是支持的,因为底层驱动可以解析它。但性能不一定好,尤其是全要素加载的时候。原因在于 OSM 的数据模型是多层嵌套的:点、线、面(关系)、以及它们之间的引用关系。驱动解析时要把这些引用关系重新拼装,CPU 开销很大。
我的做法是:先在命令行把范围裁小、把要素过滤干净,再丢进 GIS。比如一个省的路网 PBF 可能有 500 MB,裁到市区、过滤掉非道路要素之后可能只剩 20 MB,加载速度差几十倍。
另外,驱动本身有一个配置文件,可以控制哪些标签被解析成字段。默认配置会解析一大堆你用不上的键,导致属性表宽到没法看。按需精简这个配置,能让属性表瘦身一半以上,也让后续的字段筛选轻松很多。
5. 瓦片与离线底图的获取方式
前面讲的都是矢量数据。如果你的目标是"一张能在没网环境下显示的地图",那你要的是瓦片,思路完全不同。
5.1 瓦片编号规则与范围估算
瓦片金字塔的编号规则是固定的。给定缩放级别z和经纬度,瓦片编号可以用下面这个公式算出来:
import math def lonlat_to_tile(lon, lat, z): n = 2 ** z x = int((lon + 180.0) / 360.0 * n) lat_rad = math.radians(lat) y = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n) return x, y # 示例:杭州附近,z=14 print(lonlat_to_tile(120.15, 30.27, 14))知道这个公式的意义在于估算工作量。一个矩形范围在z级别下需要的瓦片数量是:
数量 = (x_max - x_min + 1) × (y_max - y_min + 1)每往下一级,瓦片数翻四倍。z=14 下覆盖一个市区的瓦片可能是几百张,z=18 下同样的范围就变成几万张。很多人做离线包的时候只算了几百张,做到一半发现要下几万张,时间预算直接崩掉。先把数量算出来再决定要不要降级,是必要的功课。
5.2 自建瓦片服务的思路
公共瓦片服务的资源是有限的,做大规模离线包的时候不适合直接对公共端点做批量抓取。可行的替代方案是自建渲染服务:拿前面下载的 PBF 数据,用渲染引擎自己出图。
整个链路大致是:PBF 数据导入数据库 → 按瓦片按需查询 → 用样式文件渲染成矢量瓦片 → 前端或离线包消费。这条路的前期投入明显更大,要装数据库、导入数据、配样式,但跑通之后你就有了一套完全自主的出图能力,想改配色改配色,想加图层加图层,还不用担心数据量的问题。
导入数据库这一步是耗时大头。以省级数据为例,导入可能需要几个小时,全球数据要以天计。索引建好之后,查询和渲染就很快了。
5.3 瓦片抓取的礼貌原则
如果你确实只需要一小块区域的少量瓦片,直接抓也不是不行,但有几条底线要守住。
第一,遵守服务方的使用条款。公共瓦片服务通常会在文档里写明允许的使用量级和禁止的行为,批量下载整座城市这种做法基本都在禁止之列。第二,设置合理的请求间隔,不要并发几十个线程去轰。第三,填一个能识别身份的请求头,让对方能联系到你,这既是礼貌也是出事时的自保。第四,能缓存就缓存,同一张瓦片不要反复请求。
对绝大多数项目来说,更稳妥的路径是:矢量数据用前面几章讲的方法拿到本地,瓦片在自己机器上渲染,一次投入,长期省心。抓取只是在"只要几张图"这种极小需求下才合理。
6. 常见问题与排查速查
前面几章讲的是正常流程。实际做下来,出问题的概率远大于一次成功。下面这些是我踩过的、也算比较有代表性的几个坑。
6.1 下载与文件层面的坑
下载中断导致文件损坏是最常见的。大文件下载到一半断了,文件大小看起来差不多,但解压报错或者解析到一半崩掉。用支持断点续传的命令行工具比网页下载靠谱得多:
wget -c https://example.org/region-latest.osm.pbf-c参数是续传的关键。断了重新执行同一条命令就行,不用从头开始。
文件其实很小但加载很慢,十有八九是把.osm(XML)当成了.osm.pbf。前者是纯文本,体积能大到后者的五到十倍。用文件头或者文件大小快速判断一下,能省很多排查时间。另外还有一种情况是,文件其实是空的,原因就是前面说的 bbox 参数顺序写反了,地理范围落在了没有数据的地方。
磁盘空间不够导致处理中途失败。转换格式的时候,中间文件、临时文件、输出文件会同时占用空间,峰值可能是最终文件的好几倍。动手前先用df -h看一眼剩余空间,留出三倍余量比较稳。
6.2 数据内容层面的坑
行政区划匹配不到。用名称去匹配区域的时候,同名地名非常常见。稳妥的做法是先查 id 再按 id 取数,别用名字硬匹配。OSM 里每个区域都有一个稳定的数字 id,一旦查到就可以长期复用。
关系(relation)要素导出异常。面要素在 OSM 里是用关系表达的,包含"外环"和"内环"(洞)。有些转换工具处理内环的时候会丢洞,导致本该中间空的区域被填满。遇到这种情况,要么换工具,要么先检查一下原始数据里内环的成员角色是否正确。
中文名称拿不到。OSM 的名称标签分主标签和语言子标签。主标签name存的是当地最常见的写法,中文名称通常在name:zh里。做中文专题图的时候要显式指定用哪个字段,否则可能出现一半中文一半外语的情况。城市边界、道路名称都存在这个问题。
6.3 性能与渲染层面的坑
属性表宽到无法操作。前面提过,默认解析会带出一大堆用不上的标签列。处理办法是精简驱动配置文件,或者转换的时候只用tags-filter保留你关心的键。
空间索引缺失导致查询极慢。GeoJSON 和 Shapefile 加载时通常会自动建索引,但如果你是自己拼装的数据或者从 CSV 转过来的,一定要手动建索引。GPKG 格式可以显式创建空间索引,建不建索引在筛选操作上的速度差几十倍。
文字标注不显示或乱码。这一般是字体问题,跟数据无关。渲染时要用支持目标语言的字体,中文字体缺失是最常见的原因。
| 现象 | 高概率原因 | 快速验证方式 |
|---|---|---|
| 解析报错、文件损坏 | 下载中断 | -c续传后重试 |
| 加载极慢、文件超大 | 误用 XML 格式 | 看文件大小和头部 |
| 导出结果为空 | bbox 参数顺序写反 | 检查经纬度顺序 |
| 面要素中间被填满 | 内环丢失 | 检查 relation 成员角色 |
| 属性表几十列 | 未精简驱动配置 | 看配置文件解析列表 |
| 查询筛选慢 | 缺空间索引 | GPKG 里建索引后重测 |
| 中文标注不显示 | 字体缺失 | 换含中文字体的渲染配置 |
最后分享一个我自己用了很多年的小习惯:每次下完数据,在同目录下顺手建一个文本文件,记下三行信息——下载日期、数据源地址、范围参数。听起来很幼稚,但当你半年后回头看一堆名字叫data、data2、data_new的文件夹时,这三行字能救你的命。