简介:这份资源是2022年5月整理的新版全国行政区划矢量数据包,面向GIS分析、地图可视化、城乡规划及数据开发等场景的从业者与学习者,可解决省、市、县三级边界底图缺失或版本陈旧的问题。包内共33个文件,以shp、shx、dbf、prj等Shapefile核心格式为主,辅以sbn、sbx空间索引、cpg编码说明与xml元数据,另附一张预览图与readme说明,压缩包约83.11MB。数据覆盖34个省级、333个地级、2843个县级行政单位,并依据国家行政区划查询平台逐项核对修正,同时包含九段线要素,可直接在ArcGIS等软件中加载使用。目前已有3721人学习下载。读者可获得一套层级完整、属性规范的矢量底图,用于专题制图、空间统计、区域划分与教学演示,省去自行采集与纠偏的时间成本。
1. 行政区划 SHP 文件到底装了什么:从一次数据对不上的排查说起
做数据可视化或空间分析的人,大概率都遇到过这种场景:项目需要按省、市、县三级做统计出图,手头只有一份 Excel 的行政区划代码表,没有边界几何,于是去找「全国省/市/县级行政区划及其他SHP文件.zip」这类打包资源。下载解压后一看,里面往往不止一个 shapefile,还有道路、水系、居民点等「其他」图层,文件命名五花八门,坐标系也不统一。真正开始用的时候才发现,省界和县界对不上、属性表里没有行政区划代码、投影后面积算出来差了好几个百分点。
这个标题指向的核心,是一套覆盖全国三级行政区划边界的矢量数据集合,通常以 shapefile 格式分发,附带若干基础地理要素图层。它能解决的是「我需要一份能直接落到 GIS 软件或代码里的行政边界底图」这个问题,适合做专题制图、空间统计、区域规划、选址分析、数据大屏的从业者。但「能用」和「用对」之间隔着坐标系、属性字段、拓扑质量三道坎,这篇就把这三道坎拆开讲清楚。
2. 拿到压缩包先别急着加载:目录结构与图层识别
2.1 shapefile 不是单个文件,解压后先认全这套「五件套」
很多人第一次接触 shapefile 会以为它就是一个.shp文件,实际上它是一个多文件组合格式。一个完整的 shapefile 至少包含.shp(几何)、.shx(索引)、.dbf(属性表)三个必需文件,常见还有.prj(投影定义)和.cpg(字符编码)。少任何一个,GIS 软件都可能报错或读出一堆乱码。
拿到「全国省/市/县级行政区划及其他SHP文件.zip」这类包,解压后典型目录长这样:
全国行政区划_SHP/ ├── 省级/ │ ├── province.shp │ ├── province.shx │ ├── province.dbf │ ├── province.prj │ └── province.cpg ├── 市级/ │ ├── city.shp │ └── ... ├── 县级/ │ ├── county.shp │ └── ... └── 其他/ ├── road.shp ├── river.shp └── ...先别打开属性表,先用命令行把每个图层的元信息扫一遍,比在 GUI 里一个个点开快得多。用 GDAL 的ogrinfo是最省事的做法:
# 查看单个 shapefile 的摘要信息:几何类型、要素数、坐标系、字段列表 ogrinfo -so -al province.shp # 批量查看当前目录下所有 shp 的摘要,只保留关键行 for f in *.shp; do echo "===== $f =====" ogrinfo -so -al "$f" | grep -E "Geometry|Feature Count|Layer name|PROJCRS|GEOGCRS" done-so表示 summary only,不输出每个要素;-al表示所有图层。输出里重点看三行:Geometry告诉你这是面还是线,Feature Count告诉你要素数量,PROJCRS或GEOGCRS告诉你坐标系。如果Feature Count是 34 左右,那基本是省级;如果是 300 多,是市级;2800 以上,是县级。这个数量级判断能帮你快速确认图层级别,避免把市级当省级用。
2.2 属性表里有没有行政区划代码,决定了后续能不能关联
行政区划数据的价值一半在几何,一半在属性。属性表里最关键的是行政区划代码字段,通常叫adcode、code、GBK、xzqhdm之类。没有这个字段,你就没法把统计数据按代码关联到边界上,只能靠名称匹配,而名称匹配在「市辖区」「省直辖县级行政区划」这类名称上几乎必然翻车。
用ogrinfo看字段列表:
# 只看字段定义,不输出要素 ogrinfo -so province.shp province | grep -A 50 "Fields"如果字段名是拼音缩写或英文,先确认哪个是代码字段。常见对应关系:
| 常见字段名 | 含义 | 典型值示例 |
|---|---|---|
| adcode / code | 行政区划代码 | 110000 |
| name / NAME | 行政区名称 | 北京市 |
| level / LEVEL | 层级 | province / city / county |
| parent / PARENT | 上级代码 | 110000 |
| gb / GB | 国标代码 | 110000 |
如果包里没有代码字段,只有名称,那就要做好名称清洗的准备。常见做法是先用名称做一次关联,把匹配不上的单独拎出来人工核对,而不是硬写一个模糊匹配规则了事。
2.3 坐标系不统一是常态,先判断是地理坐标还是投影坐标
同一个包里,省级可能是EPSG:4326(WGS84 经纬度),市级可能是EPSG:4490(CGCS2000 经纬度),县级可能是某个高斯克吕格投影带。坐标系不统一直接叠加,轻则位置偏移,重则整个图层跑到地球另一边。
判断方法很简单,看.prj文件内容或ogrinfo输出:
# 直接看 prj 文件 cat province.prj # 用 ogrinfo 看坐标系标识 ogrinfo -so province.shp province | grep -E "PROJCRS|GEOGCRS|ID\["如果输出里有GEOGCRS和degree,说明是地理坐标系,单位是度;如果有PROJCRS和metre,说明是投影坐标系,单位是米。做面积统计必须用投影坐标系,做叠加显示可以用地理坐标系但要注意性能。我一般会统一转成EPSG:4490做存储,需要算面积时再按区域转对应的投影带。
3. 用 Python 把三级边界跑通:读取、筛选、投影、关联
3.1 用 geopandas 读取并检查几何有效性
geopandas 是目前 Python 里处理 shapefile 最顺手的库,底层依赖 GDAL 和 Fiona。先装环境:
# 建议用 conda 装,geopandas 的依赖链在 pip 下容易出问题 conda install -c conda-forge geopandas shapely pyproj fiona读取和检查:
import geopandas as gpd # 读取省级边界 prov = gpd.read_file("省级/province.shp") # 看基本信息 print(prov.crs) # 坐标系 print(prov.shape) # 行列数 print(prov.columns.tolist()) # 字段列表 print(prov.geom_type.unique()) # 几何类型 # 检查几何有效性,无效几何会导致后续空间操作报错 invalid = prov[~prov.is_valid] print(f"无效几何数量: {len(invalid)}") # 如果有无效几何,尝试修复 if len(invalid) > 0: prov["geometry"] = prov["geometry"].buffer(0) print(f"修复后无效几何数量: {len(prov[~prov.is_valid])}")buffer(0)是修复自相交多边形的常用技巧,但它会改变几何形状,对面积统计有细微影响。如果数据质量要求高,应该用shapely.make_valid或explain_validity定位具体问题再针对性处理。
3.2 按行政区划代码筛选目标区域
假设你要做某个省的分析,先按代码前缀筛选:
# 假设 adcode 字段是行政区划代码,筛选 33 开头的省 target = prov[prov["adcode"].astype(str).str.startswith("33")] # 如果要做市级,读取市级数据后按 parent 字段筛选 city = gpd.read_file("市级/city.shp") city_target = city[city["parent"].astype(str).str.startswith("33")] # 县级同理 county = gpd.read_file("县级/county.shp") county_target = county[county["parent"].astype(str).str.startswith("33")]这里有个坑:adcode读进来可能是整数,也可能是字符串,还可能是带小数点的浮点数。统一转成字符串再比较,能避免330000和"330000"不相等的问题。如果代码字段有前导零(比如某些县级代码),必须确保读进来就是字符串,否则前导零会丢失。
3.3 投影转换与面积计算:别在经纬度上直接算面积
经纬度坐标系下算出来的面积单位是「平方度」,没有物理意义。必须转成投影坐标系:
# 先转成适合目标区域的投影坐标系 # 以 33 开头的省为例,常用 UTM 50N 或 CGCS2000 高斯投影 target_proj = target.to_crs("EPSG:32650") # UTM zone 50N # 算面积,单位是平方米 target_proj["area_km2"] = target_proj.geometry.area / 1e6 # 对比一下经纬度下直接算的结果,感受差异 target["area_deg2"] = target.geometry.area print(target[["name", "area_deg2"]].head()) print(target_proj[["name", "area_km2"]].head())投影带的选择有讲究。跨带的区域如果强行用一个带,边缘变形会很大。常见做法是:省级用 Albers 等积投影,市级用对应 UTM 带,县级用高斯克吕格。如果只是做相对比较,统一用一个带也能接受,但绝对面积值会有偏差。
3.4 把统计数据关联到边界上
有了代码字段,关联就是一次 merge:
import pandas as pd # 假设有一份统计数据,包含 adcode 和指标值 stats = pd.read_csv("stats.csv", dtype={"adcode": str}) # 确保两边代码字段类型一致 target["adcode"] = target["adcode"].astype(str) # 左连接,保留所有边界 merged = target.merge(stats, on="adcode", how="left") # 检查匹配率 match_rate = merged["指标值"].notna().mean() print(f"匹配率: {match_rate:.2%}") # 匹配不上的单独看 unmatched = merged[merged["指标值"].isna()] print(unmatched[["adcode", "name"]])匹配率低于 95% 就要查原因:是代码版本不一致,还是名称有差异,还是统计数据本身缺项。我一般会把匹配不上的导出成 CSV,人工核对一遍再决定是修代码还是修名称。
4. 避坑与排查:行政区划 SHP 最常见的五个翻车点
4.1 省界和县界套不上,缝隙能塞下一整个县
现象:把省级和县级图层叠加,发现县界边缘和省级边界有明显偏移,有的县甚至一半在省外。
原因:两个图层来自不同数据源,采集精度和简化程度不同。省级可能做过抽稀,县级保留更多细节,边界自然对不上。
解决:不要试图用县级去拼省级。如果要做嵌套统计,以县级为基准,用dissolve按省代码合并出省级边界,保证拓扑一致:
# 用县级数据合并出省级,保证边界完全吻合 prov_from_county = county.dissolve(by="parent", aggfunc="first")4.2 属性表中文乱码,名称全变成问号
现象:用某些 GIS 软件打开,属性表里的中文名称显示为乱码或问号。
原因:shapefile 的.dbf文件默认编码是系统编码,Windows 下常是 GBK,而很多工具默认按 UTF-8 读。
解决:先确认.cpg文件内容,如果没有.cpg,用ogrinfo指定编码读:
# 指定 GBK 编码读取 ogrinfo -so -al province.shp --config SHAPE_ENCODING GBKPython 里 geopandas 读取时可以传encoding参数:
prov = gpd.read_file("province.shp", encoding="gbk")如果还是乱码,用dbfread单独读 dbf 文件排查编码。
4.3 行政区划代码对不上,因为版本年份不同
现象:统计数据用的是 2023 年代码,边界数据是 2019 年的,合并后匹配率只有 80%。
原因:行政区划代码每年都有调整,撤县设区、合并、更名都会导致代码变化。
解决:找一份代码变更对照表,或者以边界数据的代码为准,把统计数据往边界上靠。如果变更太大,考虑用名称做辅助匹配,但一定要人工核对变更项。
4.4 几何无效导致空间分析报错
现象:做intersect或union时抛出TopologyException。
原因:多边形自相交、重复点、环方向错误等几何问题。
解决:先检查再修复:
from shapely.validation import explain_validity # 定位无效几何 for idx, geom in prov.geometry.items(): if not geom.is_valid: print(idx, explain_validity(geom))修复用buffer(0)或make_valid,修复后重新检查。
4.5 文件太大加载慢,内存直接爆掉
现象:县级数据几万个多边形,read_file卡住或内存溢出。
原因:shapefile 没有空间索引,全量读入内存。
解决:用pyogrio引擎或分块读:
# 用 pyogrio 引擎,速度更快 prov = gpd.read_file("county.shp", engine="pyogrio") # 或者先用 ogr2ogr 裁剪出目标区域再读# 用 ogr2ogr 按范围裁剪,减少数据量 ogr2ogr -f "ESRI Shapefile" county_clip.shp county.shp -clipsrc 116 39 117 405. 从能用到好用:一套可复用的行政区划数据处理流程
5.1 把重复操作封装成一个处理脚本
每次拿到新包都手动跑一遍检查太累,我一般会写一个prepare_admin.py,把读取、检查、投影、导出串起来:
import geopandas as gpd import pandas as pd from pathlib import Path def load_and_check(path, target_crs="EPSG:4490"): """读取 shapefile,检查几何,统一坐标系""" gdf = gpd.read_file(path) # 几何有效性检查 invalid_count = (~gdf.is_valid).sum() if invalid_count > 0: print(f"[警告] {path} 有 {invalid_count} 个无效几何,尝试修复") gdf["geometry"] = gdf["geometry"].buffer(0) # 统一坐标系 if gdf.crs is None: print(f"[警告] {path} 没有坐标系定义,跳过转换") elif gdf.crs.to_string() != target_crs: gdf = gdf.to_crs(target_crs) return gdf def export_geojson(gdf, out_path): """导出为 GeoJSON,方便前端使用""" gdf.to_file(out_path, driver="GeoJSON", encoding="utf-8") print(f"[完成] 导出 {len(gdf)} 个要素到 {out_path}") if __name__ == "__main__": base = Path("全国行政区划_SHP") prov = load_and_check(base / "省级" / "province.shp") city = load_and_check(base / "市级" / "city.shp") county = load_and_check(base / "县级" / "county.shp") export_geojson(prov, "output/province.geojson") export_geojson(city, "output/city.geojson") export_geojson(county, "output/county.geojson")这个脚本的核心逻辑是:先检查再转换,转换前确认坐标系存在。很多翻车都是因为.prj文件丢失导致坐标系为空,直接to_crs会报错或产生错误结果。
5.2 用简化几何换加载速度,但别简过头
前端展示或大屏场景,几万个多边形的 GeoJSON 可能几十兆,加载卡顿。用simplify抽稀:
# tolerance 单位与坐标系一致,EPSG:4490 下是度 # 0.01 度约 1 公里,根据展示精度调整 county_simplified = county.copy() county_simplified["geometry"] = county_simplified.geometry.simplify(0.01, preserve_topology=True) # 对比文件大小 county.to_file("county_full.geojson", driver="GeoJSON") county_simplified.to_file("county_simplified.geojson", driver="GeoJSON")preserve_topology=True能避免简化后多边形自相交,但速度慢一些。如果只是做底图展示,0.01到0.05的容差通常够用;如果要做面积统计,简化后的面积会有偏差,不建议在分析环节用简化数据。
5.3 验证数据质量的三个快速检查
在把数据交给下游之前,我习惯跑三个检查:
| 检查项 | 方法 | 合格标准 |
|---|---|---|
| 要素数量 | len(gdf) | 省级约 34,市级约 340,县级约 2800 |
| 代码唯一性 | gdf["adcode"].duplicated().sum() | 0 |
| 几何有效性 | (~gdf.is_valid).sum() | 0 |
如果要素数量明显偏少,可能是数据不完整;代码有重复,可能是合并了某些区域;几何无效,必须修复后再用。这三个检查花不了两分钟,但能避免后面几个小时的排查。
5.4 一个容易忽略的细节:名称字段的空白字符
行政区名称里经常混入空格、全角空格、换行符,导致名称匹配失败。清洗一下:
# 去除首尾空白和不可见字符 county["name_clean"] = county["name"].str.strip().str.replace(r"\s+", "", regex=True) # 对比清洗前后 diff = county[county["name"] != county["name_clean"]] print(f"需要清洗的名称数量: {len(diff)}")这个操作看起来不起眼,但在做名称关联时能救回不少匹配。我遇到过名称里带\u3000(全角空格)的情况,肉眼看不出,程序匹配就是失败,血泪经验。
5.5 最后一步:把处理好的数据存成 Parquet 或 GeoPackage
shapefile 有字段名长度限制、编码问题、2GB 大小限制,处理完的数据建议转成 GeoPackage 或 Parquet:
# 存成 GeoPackage,单文件,支持长字段名和 UTF-8 prov.to_file("admin.gpkg", layer="province", driver="GPKG") city.to_file("admin.gpkg", layer="city", driver="GPKG") county.to_file("admin.gpkg", layer="county", driver="GPKG") # 如果只需要属性表,存 Parquet 更快 county.drop(columns="geometry").to_parquet("county_attr.parquet")GeoPackage 是 OGC 标准,QGIS、ArcGIS、geopandas 都能直接读,比 shapefile 省心得多。我现在的习惯是:原始 shapefile 只读不改,所有处理结果都存 GeoPackage,下次直接用,不再碰原始文件。希望帮到你。
本文还有配套的精品资源,点击获取