做GIS项目的人应该都有过这种经历:需要一份杭州市的边界shp,结果网上下载的要么是几年前的旧区划,要么边界粗糙得只能看个大概轮廓,要么坐标系乱到叠不上任何底图。后来我换了个思路,用百度地图开放平台的行政区划查询接口把边界坐标一条条拉出来,再通过ArcGIS整理成标准shp文件,整套流程跑通之后发现这件事完全可以标准化、自动化。这篇就把完整做法拆开讲:怎么拿接口、怎么解析坐标、怎么在ArcGIS里成型,以及我踩过的坐标系和拓扑的坑。
1. 为什么放着现成的边界不用,偏要用API改造
做地理数据的人都清楚,行政区划shp是“高频消耗品”,但想在公开渠道找一份能直接用的杭州市边界,通常要碰运气。很多分享站点的数据下载下来,属性表里连行政代码都不全,边界轮廓也明显是矢量化描出来的,放到大比例尺下一看就露馅。所谓“现成”的文件,往往意味着你还要花大量时间检查精度、清理拓扑、补齐字段,成本一点都不低。
1.1 三种常见获取方式的实际体验
我常用的获取路径大概可以分成三类,各有各的优缺点:
| 获取方式 | 优点 | 缺点 |
|---|---|---|
| 公开数据网站直接下载 | 方便快捷,市面流行格式通常都有 | 数据时效性差,行政区划调整后容易旧;坐标系混乱;来源不明,难以商用 |
| 从API实时拉取边界坐标 | 数据新,结构统一,支持批量获取,可自动化更新 | 需要处理坐标字符串转要素,要写代码;坐标系需要额外处理 |
| 手动矢量化和配准 | 完全可控,精度有保障 | 工作量极大,杭州市边界这么复杂的形状,手工画基本不现实 |
拿杭州市来说,这些年部分县市撤市设区、街道重新划分,很多旧文件里的基层边界早就对不上现状了。API方式的核心价值不是“能省事”,而是“能拿新”,这也是我最后倒向接口路线的原因。
1.2 边界shp的“隐形要求”别忽略
很多人以为边界shp就是一条闭合的线框,导出就能用,其实项目里真正要落地的shp还包含不少隐性要求:
- 必须是要素面,而不是单纯的线,这样后续做裁剪、叠加分析、统计面积才不会出岔子;
- 属性表里要有关键字段,比如行政区划名称、行政代码,方便跟业务表做关联;
- 坐标系要写清楚,否则换一台电脑打开就变成“未知来源”,叠加底图时位置完全乱掉;
- 如果做专题图,还要保证边界没有缝隙、没有重叠,市级和区县级边界要能严丝合缝地套在一起。
百度地图API能解决数据“新鲜不新鲜、完整不完整”的问题,而ArcGIS能解决要素“规范不规范、能不能用”的问题。两件事拼在一起,才算把边界shp这件事情做完。
2. 先把“数据原料”搞清楚:行政区划接口能返回什么
有人可能会问,百度地图API不是做地图展示和路径规划的吗?怎么还能拉行政边界?实际上百度地图开放平台提供了一个专门的“行政区划查询服务”,只要传入一个行政区名称或编码,就能返回该行政区的中心点、行政代码、边界坐标串等基础信息。这个接口的本意是给开发者做区域圈选、行政区域高亮用的,但稍微加工一下,就能变成我们做GIS数据的好原料。
2.1 创建开发者应用,拿到AK
整个流程里最简单也最容易被卡住的就是这一步。你需要先在百度地图开放平台注册成开发者,然后创建“浏览器端”或“服务端”应用,系统会分配一个AK(API Key)。
接口请求必须在服务端发起,所以创建应用时建议选择“服务端”类型,否则容易出现跨域和Referer白名单的问题。我自己第一次就是随手建了个浏览器端应用,结果在Python脚本里直接请求,返回的是校验失败,换成语义未匹配的“ak无效”提示,排查了半天才发现是应用类型选错了。
创建完成之后,把AK复制出来,先存到环境变量或者配置文件里。千万不要硬编码在代码中并传到公开仓库,这个key就是你的凭证,泄漏出去等于别人可以拿你的配额刷请求。
2.2 最快的验证方式:直接在浏览器里请求一次
拿到AK后,不用急着写代码,先在浏览器地址栏里拼一个请求地址,确认接口是否通、返回结构长什么样。
https://api.map.baidu.com/api_region_search/v1/?ak=你的AK®ion=杭州市&sub_admin=0&extensions=1各个参数的含义很简单:
region:要查询的行政区名称,传“杭州市”或者“330100”这样的行政代码都可以;sub_admin:是否返回下级行政区边界,0表示只返回杭州市本级,1表示连区县一起返回;extensions:是否返回边界坐标,1表示返回,不传则只返回中心点和名称。
不同时期的接口版本返回字段略有差异,但通常都会包含一个类似boundary的字段,里面是一长串坐标点。这就是我们需要的原始边界数据,格式通常是经纬度成对出现,点与点之间用分号分隔,多个独立面之间可能还会用竖线分隔。
2.3 针对杭州市的返回字段拆解
以杭州市为例,正常返回的数据结构大致是这样:
{ "status": 0, "data": [ { "name": "杭州市", "adcode": 330100, "center": { "lng": 120.153576, "lat": 30.287459 }, "boundary": "119.956510,30.285282;119.956810,30.285482;..." } ] }这个boundary字符串就是杭州市的边界轮廓,看起来像一堆乱码,实际上是一个多边形坐标序列。后面的所有工作,都是围绕如何把它转成可用的面要素展开的。
这里有个值得注意的点:接口返回的坐标值有小幅偏移,这是国内网络地图服务普遍采用的坐标规范,不是数据错误。后面专门用一章讲坐标系的问题,这步先记住“坐标是偏移过的”就够。
3. 坐标系这道绕不过去的坎,到底怎么处理
如果直接把API返回的坐标丢进ArcGIS,再用常见的互联网底图叠一下,你会发现杭州市边界整体位移了大概几百米,胳膊腿都对不上。这不是操作失误,而是坐标系体系不同导致的。
3.1 先认清BD-09、GCJ-02、WGS-84这三兄弟
做国内地图数据,绕不开三种坐标系:
- WGS-84:GPS设备直接输出的原始经纬度,也是绝大多数国际开源底图采用的坐标系统;
- GCJ-02:国内网络地图的应用坐标,高德、腾讯等都在用,也叫“火星坐标”;
- BD-09:百度地图在GCJ-02基础上二次加密后的坐标系统。
百度地图开放平台接口返回的坐标,默认就是BD-09。如果把BD-09的坐标直接当成WGS-84的经纬度用,必然会产生肉眼可见的偏移。很多新手做国内边界数据时会困惑“为什么叠加底图永远差一截”,多半就是这个原因。
3.2 实际偏移有多大,肉眼可见
不同纬度的偏移量并不均匀,杭州这个位置,BD-09相对WGS-84的偏差通常是经度方向几十到几百米,纬度方向几十米到一百多米。对出图来说,这个误差直接导致边界挂在市区外面;对空间分析来说,更是无法接受。
所以在用ArcGIS导入前,必须先想清楚:你最终要把这份shp叠到什么数据上?
3.3 我建议的三种落地处理方式
根据项目用途不同,坐标处理方式也分三种,我按优先级排一下:
方式A:只用于百度相关场景如果这份shp只用来叠加百度地图的影像图或底图,那干脆不要转换。保持BD-09原坐标,在ArcGIS里把坐标系标记成自定义的经纬度坐标,叠百度底图时位置是完全吻合的。
方式B:叠国内其他地图平台如果最终需要叠高德或腾讯的底图,可以把BD-09转成GCJ-02。百度开放平台提供了坐标转换服务,可以批量处理坐标串,具体from和to的参数值以官方文档为准。
方式C:叠标准天地图或GPS数据如果要做严格的地理分析、跟GPS采集数据叠加,或者出规范图纸,就需要转成WGS-84。百度坐标转换服务同样支持这类转换,转换完成后在ArcGIS中把坐标系定义为GCS_WGS_1984即可。
3.4 ArcGIS里定义坐标参考的实操细节
坐标转换完成、数据导入ArcGIS后,还有一个不起眼但很关键的步骤:给图层声明正确的坐标参考。
如果直接从一个没有空间参考的文本导入,ArcGIS会把它当成未知坐标系。后续任何涉及投影、量算、叠加的操作都会报错或乱套。正确的做法是:
- 在图层属性里查看“源”选项卡,确认坐标系是否已经识别;
- 如果显示“未知”,手动指定为GCS_WGS_1984或目标坐标系;
- 如果是已有边界的Geometry坐标已经是WGS-84,但图层属性里写错了其他坐标系,使用“定义投影”工具修正,而不是“投影”工具。
这个细节花不了两分钟,但能救回后面好几个小时的排查时间。
4. 从API返回字符串到ArcGIS图层的完整链路
坐标拿到之后,接下来的核心工作就是把字符串解析成标准GeoJSON,再交给ArcGIS完成最后一步转换。整个过程我习惯用Python处理,代码量不大,可复用性很强。
4.1 用Python把boundary拆成坐标点串
首先用requests发起请求,然后解析返回的boundary字段:
import requests import json ak = "你的AK" region = "杭州市" url = "https://api.map.baidu.com/api_region_search/v1/" params = { "ak": ak, "region": region, "sub_admin": 0, "extensions": 1 } resp = requests.get(url, params=params) data = resp.json() boundary = data["data"][0]["boundary"] print(boundary[:200])boundary字符串的分隔符在不同接口版本里可能略有差异,稳妥的做法是同时兼容分号和竖线:
def parse_boundary(boundary_str): polygons = [] parts = boundary_str.split("|") for part in parts: coords = [] for point in part.split(";"): lng, lat = point.split(",") coords.append([float(lng), float(lat)]) if coords: polygons.append(coords) return polygons polygons = parse_boundary(boundary) print("多边形数量:", len(polygons))杭州市的边界如果只看市本级,基本上是一个多边形,但真实的行政区边界可能存在飞地或岛屿,这时就会出现多面要素。用竖线分隔的思路可以把它们全部保留下来,避免丢失。
4.2 组装GeoJSON时的关键字段
GeoJSON是ArcGIS原生支持的交换格式之一,把解析好的坐标写入一个GeoJSON文件,后续导入就很顺:
geojson = { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": data["data"][0]["name"], "adcode": data["data"][0]["adcode"] }, "geometry": { "type": "MultiPolygon", "coordinates": [ [[coord for coord in polygon]] for polygon in polygons ] } } ] } with open("hangzhou.geojson", "w", encoding="utf-8") as f: json.dump(geojson, f, ensure_ascii=False)这里统一用MultiPolygon而不是Polygon,是为了兼容多面要素。即使当前只有一个多边形,MultiPolygon也能正常显示和计算,后续如果合并区县边界,可以少改很多代码。
4.3 在ArcGIS中导入GeoJSON并转成shp
我用的是ArcGIS Pro,直接把刚才生成的hangzhou.geojson拖进地图窗口就能预览。ArcMap用户可以在ArcToolbox里找到“JSON转要素”工具,选择对应的GeoJSON文件即可。
导入成功后,右键图层,选择“数据”中的“导出要素”,格式选shp,设置好输出路径和坐标系,一份独立的杭州市边界shp就生成了。
这里有个细节:如果之前做的是BD-09到WGS-84的坐标转换,那么生成shp时坐标系建议显式指定为GCS_WGS_1984。如果shp后续还要投影到某个投影坐标系,可以在导出时直接选择投影,让ArcGIS帮你完成从地理坐标到投影坐标的换算。
4.4 多部件要素:别把飞地弄丢了
杭州市边界在API返回中虽然大体连续,但杭州市行政区划内并不是一个绝对连续的封闭面,部分区域可能存在飞地或跨江的岛屿型地块。解析时如果只取第一个多边形,后续做面积统计就会少一块。
我的做法是保留MultiPolygon结构,导入ArcGIS后再用“多部件转单部件”工具检查一下要素数量。如果你发现面数明显不匹配,就要回头检查boundary字符串是否被截断,或者请求时extensions参数是否生效。
5. 成型之后,边界shp还得经历一轮“深度清洁”
拿到一份能显示的shp,只能算完成了一半。真正进入生产环境前,还要把边界做一轮清洁,否则叠加分析时很容易出现莫名其妙的误差。
5.1 市级边界和区县边界的合并
如果需求是杭州市市级边界,而API返回时带了13个区县的多边形,直接把多个要素融合成一个整体即可。ArcGIS里的“融合”工具(Dissolve)可以一键处理:字段选择不需要保留的字段,或者干脆不留字段,系统会把所有面合并成“杭州市”的完整轮廓。
融合之后要用“要素转面”工具重建拓扑关系,避免融合过程产生多余的悬挂线或微小缝隙。我第一次做的时候偷懒,融合完直接导出,结果边界放大到一比一万时能看到裂缝,后续做裁剪分析时漏了很多小斑块。
5.2 拓扑检查:缝隙和重叠一个不留
边界数据最怕两个问题:面与面之间有缝隙,或者相邻面相互重叠。缝隙会导致统计时“查不到”某些区域,重叠会导致重复统计。
在ArcGIS里建一个要素数据集,把边界图层放进去,然后新建拓扑,规则选“不能有缝隙”和“不能重叠”,系统会自动标记所有问题位置。修复缝隙通常用“延伸”或“更新”工具,重叠则用“擦除”把多余部分去掉。
这个环节看起来繁琐,但对后续要做空间连接、面积统计、叠加分析的场景来说,省掉这步就是给自己埋雷。
5.3 属性表设计:从够用到底层规范
一份能上生产环境的边界shp,属性表最好包含以下字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| name | Text | 行政区名称,如“杭州市” |
| adcode | Long | 行政区划代码,如330100 |
| level | Text | 级别,如“province/city/district” |
| source | Text | 数据来源标记,标注百度地图开放平台 |
| update_time | Date | 数据获取时间,方便日后核查时效 |
尤其是update_time字段,别嫌多此一举。行政区划会调整,你不能保证半年后的自己还记得这份数据是什么时候拉的。有没有这个字段,直接决定后续更新时能不能快速判断“旧数据要不要淘汰”。
5.4 不同比例尺下的显示精修
边界数据在不同比例尺下显示的观感差异很大。市级边界在1:10万比例尺下可能只需要平滑的轮廓,但放大到1:1万就会看到很多细节毛刺,甚至出现自相交。
ArcGIS的“简化面”工具可以处理这些毛刺,简化容差建议根据最终出图比例尺来定。容差设大了,小湾小角会被磨平;容差设小了,毛刺还在。我一般先做一个200米左右的简化测试,对比看效果再决定是否调整。
6. 这趟流程里最容易翻车的几个细节
最后把实际踩过的坑集中聊一聊。这些事情单独看都是小问题,但凑在一起就是整整半天的排查工作量。
6.1 返回的“杭州市”到底包不包括区县
行政区划查询接口有个容易误会的点:查询“杭州市”返回的boundary,是杭州市级行政区的整体轮廓,还是下辖各区县的组合?
不同参数和不同接口版本表现不一致。如果sub_admin设为1,返回的是下级行政区列表;如果设为0,一般返回的是市本级整体边界。问题是有些接口版本即使sub_admin=0,也可能把代管县市的边界一起揉进轮廓。最稳妥的办法是:拿到boundary后用ArcGIS打开,数一下多部件数量,再跟实际区划数量对照。
6.2 行政区划调整会让历史数据失效
杭州市近年经历过撤市设区等区划调整,如果你从网上找到一个三年前的shp,边界大概率已经过时。这也是为什么我强烈建议用API现拉数据,而不是长期存一个固定文件。行政区划变更后,只要重新跑一遍脚本,新数据就到手了。
6.3 别把接口精度当成万能数据源
这个接口适合做宏观分析、示意图、出图底图,但不适合做土地确权、不动产登记这类需要高精度权籍边界的工作。API返回的边界精度通常是行政区划示意级别的,跟测绘部门发布的标准行政界线存在差异。涉及法律和权属问题时,必须以官方权威数据为准。
6.4 合规使用与地图审核的底线
使用百度地图开放平台的数据,需要遵守开放平台的开发者服务条款。拉取数据供内部项目分析一般没有问题,但若要把边界文件对外发布、嵌入商业产品,或者制作公开地图,必须确认授权范围,并按规定在地图上标注来源信息。地图发布在国内有严格的管理要求,做任何公开传播前都要先过合规这一关。
最后分享一个小习惯:我把AK和区域名称做成一个简单的CSV清单,每次项目开工前跑一遍脚本,杭州市十几个区县的边界数据十几分钟就能全部更新完。遇到需要出纸质图的场景,再在ArcGIS里手动微调一圈。做数据这种事,最怕临时抱佛脚,平时把工具链备好,用的时候真的能省下半天时间。