简介:全国乡镇级行政区划shp数据是一份面向GIS从业者、规划研究人员及数据分析师的高价值基础地理资源,覆盖全国所有乡镇与街道级行政边界,可用于区域人口统计、经济差异分析、专题地图制作、公共服务设施布局、灾害应急响应等多元场景。压缩包内含7个文件,包括.shp几何数据、.dbf属性表、.shx空间索引,以及.prj坐标系统、.xml元数据、.sbn和.sbx辅助索引等,完整保留边界几何、乡镇名称、行政代码、所属省份等属性信息,包体共94.37MB,可直接导入ArcGIS、QGIS等主流软件,无需额外转换。该资源已吸引13299人学习下载,具备良好的实用口碑。使用者可获得一套规范齐整、字段完整的乡镇级行政边界数据集,省去自行采集和整理坐标的繁琐过程,同时借助坐标系统定义与空间索引提升调用效率,适应从基础制图到深度空间分析的多种工作流。
1. 全国乡镇级行政区划shp数据:先搞清楚它能不能直接进你的项目
全国乡镇级行政区划 shp 矢量数据这类资源,是地图可视化和空间统计绕不开的底图。做业务大屏、选址评估、网格治理或者 GIS 课程设计的时候,手里只有省界和县界,乡镇边界没有,很多需求根本落不了地。这份数据覆盖全国乡镇(街道)级行政单元,属性表里带着行政区划代码、名称、级别字段,能直接读进 QGIS、ArcGIS 和 Python 环境里的 geopandas。适合谁:需要按乡镇做聚合统计、画分级设色图、做多级下钻地图的开发者,以及想拿真实边界数据练空间分析的新手。下文先把数据结构和坐标系讲清楚,再给一套从读取到接入 Web 地图的可复现流程,最后把最容易翻车的五个坑逐个拆开。
2. 拆开 Shapefile 四个文件:坐标系、字段与几何怎么协同
2.1 Shapefile 不是一个文件:主文件 / 索引 / 属性表各管什么
拿到这类数据包,第一件事不是急着往地图上拖,而是看清这一坨同名文件到底谁是谁。很多初学者只把 .shp 拖进软件,报错了才回头问为什么少文件。Shapefile 不是单个文件,而是一组同名不同后缀的文件组合,缺一个都会影响读取结果。
在工作目录里,你会看到类似下面的文件组:
| 文件后缀 | 作用 | 缺失后果 |
|---|---|---|
| .shp | 存储几何坐标,乡镇边界的每个折点都在这里 | 完全无法读取 |
| .shx | 几何索引文件,记录 .shp 里每条要素的位置偏移 | 读取变慢,部分软件直接报错 |
| .dbf | 属性表,存行政区划代码、名称、级别等字段 | 地图只剩图形,查不了名字和代码 |
| .prj | 坐标系定义,描述数据用的是哪种投影或地理坐标 | 软件靠猜测,位置可能偏到海里 |
| .cpg | 字符编码声明,常见 UTF-8 或 GBK | 中文属性名或名称字段乱码 |
实际项目中我一般会先用 Python 读一遍,确认整套文件完整。文件缺失时 geopandas 会抛异常,比如缺 .shx 会提示打开失败,缺 .prj 则通常不报错但坐标系是空的——这种沉默的错误最坑,因为后面所有图层叠加都会偏移,你还找不到原因。
所以拿到下载包后,先看是否同时包含 .shp、.shx、.dbf、.prj 四个核心文件。.cpg 没有的话也能用,只是中文乱码的风险会上升。市面上的发布版通常还会附带 .dbf 的说明文档或字段字典,那个不要删,字段含义全靠它。
2.2 字段长什么样:行政区划代码、名称与级别怎么对应
乡镇级行政区划数据的属性表里,最关键的三个字段是行政区划代码、名称、级别。版本不同,字段名差异很大,常见的有 PAC、ADCODE、XZQH_CODE,对应中文名一般是“区划代码”;NAME、TOWN_NAME、MC 对应“名称”;LEVEL、CLASS、JB 对应“级别”。
行政区划代码的编码规则是分层的:前两位是省级,中间两位是地级市,再两位是区县,后面是乡镇和村。以乡镇级数据为例,常见发布版会把这个代码补到 12 位,其中第 1 到 6 位指明所属区县,第 7 到 9 位是乡镇(街道)本身,最后 3 位通常补零。也有部分版本只给到 9 位,遇到这种数据做聚合时先看一下最大长度,别硬按 12 位去截取。
实际操作里,最容易被忽略的是“级别”字段。乡镇级数据里常常混着街道办事处、乡、镇、民族乡,甚至个别版本会把开发区的虚拟管理单元也放进来。如果你要画的图例是“乡 / 镇 / 街道办”三类,就应该用级别字段过滤,不能只按名称猜。还有一个细节:同样的乡镇名称,在不同地市可能重名,比如“城关镇”这种高频名称。所以任何操作都要以行政区划代码为主键,不要用名称做关联。
2.3 坐标系选型:CGCS2000 与 WGS84 在这个场景下的取舍
乡镇级边界数据的坐标系,市面上主流是 CGCS2000 经纬度,不少平台下载的版本标注的是 WGS84,还有一部分是高斯投影或 Web 墨卡托。搞清楚数据本身属于哪种,比后面所有操作都重要。
CGCS2000 和 WGS84 在地图上的差异不大,国内多数地区平面偏差在几米以内,对乡镇级别这种尺度来说,画图和聚合统计都感知不到。真正危险的是“地理坐标”和“投影坐标”混用。判断方法很直接:字段里的数值范围如果是经度 73 到 135、纬度 3 到 54 之间的十进制度数,那就是经纬度坐标;如果 X 是几十万、几百万的米制数字,通常是高斯投影或 Web 墨卡托,这时候直接拿去做空间计算,面积和距离都会因为单位混淆而得出离谱结果。
我一般拿到手先打印 crs 和 geometry 的坐标范围,做一次“看两边是否对得上”的确认。如果 .prj 文件缺失,就按坐标范围反推:度坐标直接当 CGCS2000 或 WGS84 用,米制坐标再进一步看是带号的高斯投影还是 Web 墨卡托。这个动作成本极低,但能避免后面所有图层的整体偏移。
3. 从下载到项目落地:读取、过滤与 Web 底图接入
3.1 用 geopandas 把 shp 读进来:先验证再使用
我一般会在虚拟环境里先装好 geopandas,然后用一段很短的代码把数据读进来,这一步主要不是为了出图,而是验证文件是否完整、字段名是否符合预期。
import geopandas as gpd # 读取乡镇级 shp,encoding 按下载包说明调整,utf-8 或 gbk gdf = gpd.read_file("township.shp", encoding="utf-8") # 数据规模与坐标系 print("要素总数:", len(gdf)) print("坐标系:", gdf.crs) # 属性字段预览 print("字段列表:", gdf.columns.tolist()) print(gdf.head(3))这段代码的核心作用有三个。read_file会把 .shp 的几何和 .dbf 的属性合并成一个 GeoDataFrame;crs属性直接暴露坐标系定义,缺失时会显示 None;columns让你一眼看到这一版数据的字段名,方便后续把 PAC、NAME 这些实际字段名替换进脚本。
参数上要注意encoding。如果你的下载包版本是 GBK 编码而这里写了 utf-8,读出来中文名称直接是乱码,而且不会报错。我一般会先看一眼 .cpg 文件内容,写着 UTF-8 就用 utf-8,写着 GBK 就用 gbk;没有 .cpg 且出现乱码时,两种编码各试一次。
3.2 按行政区划代码切出目标区域:前缀匹配的正确用法
手上有了全国数据,但大多数项目只需要某一个市或某一个县的乡镇边界。这时不要用几何裁剪,直接按行政区划代码前缀过滤,速度快得多。
# 假设要提取 A 市(代码前四位为 9901)下辖全部乡镇 code_col = "PAC" # 字段名以实际版本为准 target_prefix = "9901" towns = gdf[gdf[code_col].astype(str).str.startswith(target_prefix)] print("A 市乡镇数:", len(towns)) # 导出为独立的 shp towns.to_file("A市_乡镇.shp", encoding="utf-8")行政区划代码的先天优势就是层级包含关系。994201是 A 市 B 县的前六位,B 县下辖乡镇的代码全部以这个六位数字开头,所以用startswith就能精确切出某个区县的所有乡镇;同理,用前四位可以切出整个地级市,用前六位切出单区县。这比用边界做空间裁剪要稳定得多,不受几何缝隙影响。
这里有一个易错点:数据里代码可能是纯数字,pandas 读取时大概率变成 int 类型,前面的 0 会被丢掉,直接startswith就会报错或匹配不上。所以我习惯先astype(str)强制转字符串,再做前缀匹配。导出时encoding="utf-8"是常规操作,如果后面要用 ArcGIS 打开再考虑 GBK,但现代 GIS 都支持 UTF-8 了,没必要迁就老版本。
3.3 接入 Web 地图:坐标转换与几何简化
做可视化大屏或 Leaflet 地图时,前端通常要 GeoJSON,而且底图是 Web 墨卡托投影。直接丢原始 shp 给浏览器,数据量大、坐标不准,页面直接卡死。这里给一个标准处理链路。
# 1. 转到 Web 墨卡托投影,单位变成米 towns_proj = towns.to_crs(3857) # 2. 按 100 米容差简化几何,去掉冗余折点 towns_proj["geometry"] = towns_proj.geometry.simplify( 100, preserve_topology=True ) # 3. 转回经纬度并导出 GeoJSON towns_web = towns_proj.to_crs(4326) towns_web.to_file("A市_乡镇_web.geojson", driver="GeoJSON")第一步to_crs(3857)是把经纬度投到 Web 墨卡托,投影后坐标单位变成米,这样后面简化容差才有实际意义。第二步simplify的容差参数很关键:我常用的值是 100,也就是在 Web 墨卡托坐标下丢掉 100 米以内的弯曲细节,对乡镇边界这种尺度,肉眼几乎看不出变化,但文件体积能压缩一半以上。容差越大图形越粗糙,500 米以上就明显失真了。第三步转回 WGS84 经纬度输出 GeoJSON,是因为 Leaflet 这类前端库默认用 4326,不需要在浏览器里做投影转换。
这里有一个必须记住的顺序:先投影、再简化、最后转回经纬度。如果在经纬度坐标下直接simplify,容差 100 的意思是 100 度,一个乡镇边界直接变成几根直线。
3.4 拿到手先核对文件清单:四样缺一不可
下载包解压后,我建议按这个顺序自查文件完整性。第一,确认 .shp、.shx、.dbf、.prj 四个核心文件都在,且文件名完全一致,前缀名不同的文件会被识别成另一套数据。第二,用鼠标点开 .dbf 看一眼,中文名称是否正常,这条能快速发现编码问题。第三,用文本编辑器打开 .prj,里面能直接看到字符串,如果写着 GEOGCS 和 CGCS2000,说明是地理坐标;如果写的是 PROJECTION 加 Web Mercator,就是投影坐标。第四,把 shp 拖进 QGIS 或 ArcGIS,鼠标悬停在一个乡镇上,看属性表里是否有行政区划代码和名称。
这一步做完,数据的“底”就摸清了,后面所有操作都不会因为基础信息错乱而翻车。
4. 乡镇级 shp 数据避坑:五条实测踩坑记录与解决路径
4.1 属性表中文乱码:编码声明不一致
现象:用 QGIS 打开数据正常,用 Python 读出来后,乡镇名称变成“绂忓窞”或“??”。用 Excel 直接打开 .dbf 更是惨不忍睹。
原因:.dbf 内部实际是 GBK 编码,但 .cpg 文件缺失或声明为 UTF-8,各软件按自己的默认编码去解析,结果各不相同。QGIS 有自动编码探测,Excel 则默认 ANSI,Python 的 geopandas 默认按系统区域设置,三者对同一份文件的理解都不一样。
解决:读取时显式指定编码,不要依赖自动探测。gpd.read_file("township.shp", encoding="gbk")读出来如果是乱码,换成encoding="utf-8"再试。确定正确编码后,导出时统一用 UTF-8,并在导出后删掉可能误导下次读取的 .cpg 文件,或者重新写一个声明为 UTF-8 的 .cpg。从那以后我凡是遇到中文属性表的 shp,第一步永远先看编码。
4.2 乡镇边界有缝隙或重叠:数据来源不同导致拓扑不干净
现象:按乡镇聚合 POI 点的时候,落点在两个乡镇边界的缝隙里统计不到;用面积汇总时,重叠区被重复计算。
原因:乡镇边界数据往往由多个来源拼接,相邻乡镇的公共边在几何上并没有完全共点。表面看着两张图严丝合缝,放大到折点级别,缝隙和重叠可能达到几十米甚至上百米。叠加分析对这种误差极其敏感。
解决:在空间计算之前先统一清理几何。我一般先用gdf.geometry.is_valid找出非法几何,再用geometry.buffer(0)修复自相交问题;针对微小缝隙,可以把面整体做一次极小缓冲再取交集。更彻底的做法是在 QGIS 里跑 GRASS 的v.clean,把相邻面的公共边吸附到一起。这个清理动作对空间统计类任务不是可选项,而是必修课。
4.3 乡镇代码过期:行政区划调整后的旧代码匹配不上
现象:拿 2023 年业务系统的乡镇代码去 join 一份 2019 年的 shp,能匹配上的不到七成,其余记录全部悬空。
原因:这几年乡镇撤并、改街道办的调整很频繁,被合并的乡镇代码已经从官方区划代码表里移除。地理数据是按当年行政区划绘制的,代码表又是按当前行政区划发布的,时间差就是匹配裂痕。
解决:不要直接拿业务代码去 join,先建一张“新旧代码映射表”。把旧代码对应的乡镇名称、所属区县、是否已撤销、并入哪一级单位都列出来,再做关联。如果你不确定当前年份的官方区划代码,就去查最新的区划代码发布包,以它为准。代码过期这个问题,表现是 join 不上,根子是你对数据版本的时间标签没有建立意识,所以拿到 shp 第一件事先确认它是什么年份的区划。
4.4 底图整体偏移:坐标系被当成另一个坐标系使用
现象:把乡镇边界叠加到在线地图上,整体朝东北或西南方向偏移了几百米到几公里,乡镇形状完全正确就是位置不对。
原因:最常见的两种,一是数据本身是 CGCS2000 经纬度,但加载时被指定成了 WGS84,两者的差异虽然小,但乡镇级放大后偏移量也可见;二是数据其实是高斯投影坐标,被当成经纬度加载,这种偏移直接就是几十公里级别。
解决:第一步先看坐标范围,经纬度范围在 73 到 135 和 3 到 54 之间,米制坐标则是几百万的大数。第二步对比 .prj 文件里的描述,看数据到底声明的是什么。第三步在代码里显式指定正确坐标系,再按目标做转换。gdf.to_crs(4326)只能做标准转换,如果源坐标本身就错了,转出来的结果还是错的。坐标系这种问题最像玄学,但它不是黑匣子,范围判断加 .prj 核对永远能定位。
4.5 整包加载卡顿:全国数据直接丢进 Web 端
现象:把全国乡镇级数据导出成一个 GeoJSON,几 MB 到几十 MB,浏览器地图一加载就白屏或旋转几秒无响应。
原因:全国乡镇级单元数量约三点八万个,每个面都有大量折点,完整精度下 GeoJSON 体积轻松超过一百兆。前端渲染大量矢量面本身就是高消耗操作,再加上非投影坐标实时投影,性能直接崩掉。
解决:按实际业务范围先过滤出局部数据再可视化,只保留目标区域的乡镇。其次,simplify对几何做减点处理,容差按投影后米制单位取 50 到 200 之间,边界的毛刺细节适当丢弃。最后,如果必须展示全国,就把数据转成矢量瓦片,分层加载,而不是一次性推给前端。桌面端 GIS 对这问题不敏感,Web 端强烈建议先简化再发布。
5. 进阶:用一份乡镇数据拆出多级底图与自检脚本
5.1 按代码前缀 dissolve:省、市、县三级边界都能生成
许多项目是多级下钻的,省点进去是市,市点进去是县。后端只需要存一份乡镇数据,动态聚合出上级边界即可,不用维护省、市、县三套文件。
# 新增县、市、省三级代码字段 gdf["county_code"] = gdf["PAC"].astype(str).str[:6] gdf["city_code"] = gdf["PAC"].astype(str).str[:4] gdf["province_code"] = gdf["PAC"].astype(str).str[:2] # 按县级代码融合出县级边界 county_map = gdf.dissolve(by="county_code", aggfunc="first")dissolve的作用是按指定字段合并几何,把同一县的乡镇边界拼接成一个完整县界。aggfunc="first"表示属性字段取第一条记录的值,代码字段天然一致所以没问题。这样做出来的县界,在多数地区与官方发布的县级边界基本吻合,但在省市交界处可能因为乡镇界线精度问题出现细微出入,正式发布前还是要和权威边界做一次叠加比较。
5.2 计算面积与中心点:统计场景的标准操作
按乡镇统计面积是最常见的需求。直接对经纬度坐标求面积,结果单位是平方度,不是平方米。先投影到 Web 墨卡托或适合当地的高斯投影,再用投影坐标计算,才是正确姿势。
towns_proj = gdf.to_crs(3857) gdf["area_km2"] = towns_proj.geometry.area / 1e6 # 质心用于标注和点定位 gdf["centroid_lon"] = towns_proj.geometry.centroid.x gdf["centroid_lat"] = towns_proj.geometry.centroid.y # 质心落在面外的凹形乡镇,取内部代表点 gdf["label_lon"] = gdf.geometry.representative_point().x gdf["label_lat"] = gdf.geometry.representative_point().ygeometry.area在投影坐标系下返回平方米,除以一百万得到平方公里。centroid返回的是几何质心,对 L 形或凹形乡镇来说可能落到行政边界外,这时候标注文本会漂到邻镇地盘上。representative_point能帮你拿到一个必然落在多边形内部的点,专门用于放标注。这两个点的取舍,做地图的人基本都踩过。
5.3 数据自检脚本:每次拿到新数据强制跑一遍
下载数据、修改字段、导出成果,这一串流程重复几次之后,我养成了一个习惯:写一个固定的自检函数,每次拿到乡镇级 shp 先跑一遍,确认不是坏数据再进入业务操作。
def check_township_shp(path): import geopandas as gpd gdf = gpd.read_file(path, encoding="utf-8") assert gdf.crs is not None, "缺少坐标系定义" assert len(gdf) > 0, "要素为空" code_len = set(gdf["PAC"].astype(str).str.len()) assert 9 in code_len or 12 in code_len, f"区划代码长度异常: {code_len}" assert gdf["PAC"].is_unique, "存在重复行政区划代码" assert gdf.geometry.is_valid.all(), "存在非法几何" overlap = gdf.geometry.geometry.buffer(0).area.sum() print("文件检查通过,要素数:", len(gdf))这段脚本把前面所有坑的检查集中在一起:坐标系缺失、代码长度异常、重复代码、非法几何、自相交。is_unique判断有没有同一乡镇出现两次,这种情况常见于多源数据合并时没有去重;is_valid.all()是捕捉几何自相交的标准手段。
乡镇级行政区划数据是地理信息项目里最容易“看着能用、用起来到处是坑”的资源。从编码到坐标系,从拓扑到代码时效,每一层都可能出问题。也正是这种项目做多了,我才把检查步骤彻底脚本化——从那以后每次拿到新的行政区划数据,都强制先跑一遍上面的自检,确认坐标、编码、字段和拓扑四项全部正常,才敢往下一个环节走。这套流程同样适用于省界、县界甚至村级边界,只是字段名和代码长度各有差异。希望帮到你。
本文还有配套的精品资源,点击获取