市级行政区域shp实战:从字段结构到城市匹配的避坑指南
简介:这份资源面向地理信息系统从业者、城市规划研究者及高校师生,提供中国全部地级市行政区域的空间边界数据,可用于城市分布研究、人口统计、行政边界划分与专题制图等场景,帮助使用者在ArcGIS中完成裁剪、合并等空间分析操作。压缩包共11个文件,约1.91MB,以Shapefile标准文件组为主:shp存储行政区域几何边界,dbf保存城市名称、代码、人口、面积等属性,prj定义坐标系统,shx、sbn、sbx承担索引与快速检索功能,另有xml元数据及若干lock临时文件。数据年代为2004年,字段与边界信息完整,可直接导入GIS软件进行可视化与统计分析。目前已有2079人学习下载,适合需要基础市级行政区划底图、开展空间分析练习或搭建研究数据框架的读者参考使用。
1. 市级行政区域 shp 到底解决什么问题:从一张对不上的地图说起
做数据可视化的人大概都遇到过这种场面:业务方甩来一份门店清单,字段里只有“城市名”,你兴冲冲地往地图上一贴,结果发现同一个“市”在底图上要么找不到,要么落到了隔壁省。问题往往不在数据,而在底图——你手里那份市级行政区域 shp 的行政区划口径,和业务数据用的口径根本不是一套。市级行政区域 shp 格式,说白了就是把全国地级行政单元的边界,以 shapefile 这种 GIS 通用矢量格式存下来,每个面要素带一个城市名和行政代码。它能解决的核心问题就一个:让“城市”这个模糊的文字概念,变成地图上可计算、可叠加、可做空间统计的多边形。适合谁?做区域业务分析、门店选址、物流分区、大屏可视化的从业者,以及需要把业务表按城市聚合再落到地图上的数据工程同学。这一章先把“它是什么、为什么是 shp、口径为什么是最大的坑”讲透,后面再动手。
2. 市级行政区域 shp 的字段结构与选型:为什么不是随便下一份就能用
2.1 shapefile 不是单个文件,四个兄弟缺一不可
很多人第一次拿到 shp 会懵:解压出来一堆同名不同后缀的文件,到底哪个是数据?shapefile 是 ESRI 定义的一种矢量数据交换格式,它本质是一个文件集合,而不是一个文件。真正干活的是下面这几个:
| 后缀 | 作用 | 缺失后果 |
|---|---|---|
| .shp | 存几何图形(点线面坐标) | 没有几何,直接打不开 |
| .shx | 几何索引,加速定位 | 部分软件能重建,但读取会变慢甚至报错 |
| .dbf | 属性表,存城市名、行政代码等字段 | 只剩空壳面,没有名字 |
| .prj | 坐标系定义(WGS84、CGCS2000 等) | 坐标对不上,叠加时整体偏移 |
血泪经验是:只拷贝 .shp 发给同事,对方打开是一片空白或者属性全空,八成就是漏了 .dbf 和 .prj。所以传输时永远打包整个同名文件组,别挑单个文件。另外 .cpg 文件管的是 dbf 的字符编码,中文城市名乱码时先看它。
2.2 市级口径的三种来源,选错等于白干
“市级”这两个字在不同数据源里含义不一样,这是最容易翻车的地方。常见的有三类口径:
- 地级行政区口径:包含地级市、自治州、地区、盟,这是统计年鉴和大多数业务分析默认的口径,要素数量在三百多个量级。
- 地级市口径:只保留“市”,把自治州、地区排除,数量会少一截,做“城市排名”类分析时常用。
- 市辖区口径:把地级市拆成区,粒度更细,适合做城市内部的分区分析,但和“按城市汇总”的业务表对不上。
选型原则很简单:先看你的业务表里“城市”字段是怎么来的。如果是从订单地址里抽的“XX市”,那大概率对应地级市口径;如果是统计报表里的“XX地区”,就得用地级行政区口径。口径不统一,后面做空间连接时会出现大量匹配失败,或者一个城市被拆成好几块。我一般会先拿业务表里的城市名去和 shp 的属性表做一次交集统计,匹配率低于九成就要回头查口径,而不是硬着头皮往下做。
2.3 坐标系:WGS84 和 CGCS2000 别混着用
市级行政区域 shp 常见的坐标系有两种:WGS84(EPSG:4326,经纬度)和 CGCS2000(国家大地坐标系,EPSG:4490)。两者在多数业务场景下差异很小,但如果你要和其他图层(比如 GPS 点位、卫星影像)叠加,坐标系必须一致,否则会出现几十米到几百米的偏移。判断方法:看 .prj 文件内容,或者用 GIS 软件打开后看图层属性里的 CRS。做 Web 地图展示时,通常还要再投影到 Web 墨卡托(EPSG:3857),这一步在代码里做,不要提前改源文件,保留原始经纬度版本作为底稿。
3. 用 Python 把市级 shp 读进来并做一次城市匹配
3.1 环境准备与最小读取
先装依赖。geopandas 是读 shp 最顺手的库,底层依赖 fiona 和 pyproj,用 conda 或 pip 都能装。下面是最小可运行代码:
import geopandas as gpd # 读取市级行政区域 shp,注意路径指向 .shp 文件即可 gdf = gpd.read_file("./data/city_boundary.shp") # 看前几行属性,确认字段名 print(gdf.columns.tolist()) print(gdf.head()) # 看坐标系 print(gdf.crs) # 看要素数量和几何类型 print(len(gdf), gdf.geom_type.unique())逻辑说明:read_file会自动关联同名的 .dbf、.shx、.prj,所以只要路径对,四个文件会被一起读进来。gdf.columns打印出来的是属性字段,常见的有name、adcode、city之类,字段名因数据源而异,必须先看清楚再写后续代码。gdf.crs如果打印出None,说明 .prj 缺失或没被识别,这时候要么补 .prj,要么手动指定gdf.set_crs("EPSG:4326")。参数上,read_file默认按文件编码读 dbf,中文乱码时可以加encoding="utf-8"或encoding="gbk"试。
3.2 城市名匹配:先清洗再连接
业务表里的城市名往往带“市”“地区”“自治州”后缀,还可能有多余空格。直接做字符串相等匹配,匹配率会很难看。稳妥做法是先做一轮标准化:
import pandas as pd # 假设业务表 biz 有 city 列 biz = pd.read_csv("./data/store.csv") def normalize_city(name): if pd.isna(name): return None s = str(name).strip() # 去掉常见后缀,统一成核心名 for suffix in ["市", "地区", "自治州", "盟", "特别行政区"]: if s.endswith(suffix): s = s[: -len(suffix)] break return s biz["city_key"] = biz["city"].apply(normalize_city) gdf["city_key"] = gdf["name"].apply(normalize_city) # 先看匹配率,再决定是否继续 matched = biz["city_key"].isin(gdf["city_key"]).mean() print(f"匹配率: {matched:.2%}")逻辑说明:normalize_city只去掉一个后缀,避免把“XX市市”这种脏数据越洗越乱。先算匹配率是关键一步,匹配率低说明口径或命名规则有问题,这时候应该回去核对数据源,而不是继续往下做空间连接。参数上,后缀列表要按你实际数据里出现的类型补全,比如有些数据源用“自治县”,那就得加进去。这一步做完,把没匹配上的城市名单独导出来人工看一眼,往往能发现是简称、别名或者历史名称的问题。
3.3 空间连接:把点落到面上
如果业务数据带经纬度,可以直接做空间连接,比按名字匹配更可靠:
from shapely.geometry import Point # 业务表有 lng、lat 两列 biz["geometry"] = biz.apply(lambda r: Point(r["lng"], r["lat"]), axis=1) biz_gdf = gpd.GeoDataFrame(biz, geometry="geometry", crs="EPSG:4326") # 确保两边坐标系一致 if gdf.crs != biz_gdf.crs: biz_gdf = biz_gdf.to_crs(gdf.crs) # 空间连接,点在面内 joined = gpd.sjoin(biz_gdf, gdf[["name", "geometry"]], how="left", predicate="within") print(joined["name"].isna().sum(), "个点未落入任何城市")逻辑说明:predicate="within"表示点落在面内部才算匹配,边界上的点可能匹配不上,这是正常现象。how="left"保证业务表所有行都保留,没匹配上的name为 NaN,方便后续排查。参数上,如果点位在边界附近频繁丢失,可以改用predicate="intersects",但要注意相邻城市共享边界时可能产生重复匹配,需要去重。坐标系不一致时先to_crs对齐,这一步不做,空间连接结果会整体错位。
4. 市级 shp 的常见避坑:那些让你加班到深夜的细节
4.1 现象:地图整体偏移几百米,点位全跑到海里
原因:源 shp 是 CGCS2000,业务点位是 WGS84,两者没对齐就直接叠加。解决:统一用to_crs转到同一坐标系再操作,展示层再转 Web 墨卡托。别在源文件上反复改,保留一份原始底稿。
4.2 现象:城市名匹配率只有六成,大量“XX市”对不上
原因:口径不一致,业务表用的是地级市,shp 用的是地级行政区,自治州和地区被排除在外。解决:换用地级行政区口径的 shp,或者在匹配前把业务表里的“地区”“自治州”也纳入标准化规则,做一次映射表兜底。
4.3 现象:dbf 里的中文城市名全是乱码
原因:dbf 的字符编码和读取时指定的编码不一致,常见是源文件用 GBK,代码默认按 UTF-8 读。解决:读取时显式指定encoding="gbk",或者用 .cpg 文件确认编码。乱码不影响几何,但会让所有按名字的匹配失效。
4.4 现象:空间连接后一个城市出现多条记录
原因:用了intersects且点位落在共享边界上,或者 shp 本身存在重叠面。解决:连接后按业务主键去重,保留第一条;同时检查 shp 是否有几何重叠,用gdf.overlaps排查异常面。
4.5 现象:shp 打开报错“无法读取几何”
原因:文件组不完整,或者 .shp 在传输中损坏。解决:重新打包完整文件组,用gpd.read_file前先确认 .shp、.shx、.dbf 三者大小正常,损坏的文件只能重新获取。
5. 进阶:把市级 shp 做成可复用的匹配底表
做到这一步,单次分析已经能跑通了。但如果每周都要处理新一批业务数据,每次都重新读 shp、重新清洗城市名,效率很低。我的习惯是把市级行政区域 shp 预处理成一张“城市名—行政代码—几何中心”的匹配底表,存成 parquet 或 gpkg,后续直接查表。具体做法:读入 shp 后,用gdf.geometry.representative_point()取每个面的内部代表点,再提取x、y作为中心坐标,和城市名、行政代码一起落表。这样既保留了面数据用于地图渲染,又有一张轻量的点表用于快速匹配。
验证方法也简单:拿一批已知城市的经纬度做回归测试,看匹配结果是否和人工判断一致,匹配率稳定在九成五以上再投入使用。参数上,代表点用representative_point而不是centroid,因为后者可能落在凹多边形的外部,尤其是形状不规则的城市。另外,底表要记录数据来源和口径版本,行政区划是会调整的,隔一段时间要核对一次,别拿几年前的底表套新数据。我自己就吃过这个亏,一份旧底表用了大半年,某次业务方反馈“这个城市怎么没了”,一查才发现区划调整过,白忙一晚上。希望帮到你。
本文还有配套的精品资源,点击获取