简介:涵盖安徽省、市、县、乡镇、村五级行政边界的shp数据集,基于WGS1984坐标系,村级名称完整无缺失,可直接用于GIS制图、空间查询与区域分析。面向GIS专业人员、政府规划部门、物流企业和科研机构,为土地管理、乡村振兴、设施规划及应急响应提供精确的地理参照框架。资源压缩包共33个文件,包括5个shp主文件及配套的shx索引、dbf属性表、prj投影文件等,另有sbn/sbx空间索引文件辅助加速空间运算,整体约217MB。数据按省、市、县、乡、村五个层级分别组织,结构清晰,便于按需调取任意行政层级的边界与属性信息。已有689人学习下载,可直接在ArcGIS、QGIS等软件中加载使用,适合需要精细行政边界数据的研究与业务场景。这份数据可作为各类空间分析、资源分配和政策制定工作的基础底图,帮助用户快速完成区域划分与可视化表达。
1. 2024年安徽省五级行政区划shp数据集:村级名称无缺失,意味着你不用返工
做村庄规划、乡村振兴评估或者按村出图的人,大概都有过这种经历:从公开渠道找到一份安徽省的行政区划shp,省、市、县三层都好好的,到乡级就开始丢字段,到村级直接变成一堆空值或者"××村"的乱码。想按村名挂业务数据,第一步就被卡住。而这份2024年安徽省五级行政区划shp数据集,从省、市、县、乡一路落到村,五级全齐,最关键的卖点是村级名称无缺失。对GIS工程师、规划院的技术人员、做Web地图产品和数据分析的从业者来说,这意味着少一轮字段清洗、少一轮人工比对,拿到手可以直接进入业务层。下面按我平时处理这类数据的流程,把字段结构、坐标系、业务挂接、格式转换和踩坑点一次讲透。
2. 数据认识工作流:五级shp的物理结构、坐标系与版本口径
2.1 先分清shapefile的配套文件:五级名称其实存在.dbf里
很多人拿到shp文件,以为只有一个文件,拷贝的时候只拷了.shp,结果打开发现图层只有图形没有属性。实际上shapefile是一组文件的集合,核心是三个:.shp存几何坐标,.dbf存属性字段,.shx是几何与属性的索引。另外两个配套文件也最好一起带上:.prj记录坐标系信息,.cpg记录字段编码格式。
常用字段名一般是:
| 字段示例 | 含义 |
|---|---|
| NAME_PROV / NAME_省 | 省级名称,如"安徽省" |
| NAME_CITY / NAME_市 | 市级名称,如"合肥市" |
| NAME_COUNTY / NAME_县 | 县级名称,如"肥东县" |
| NAME_TOWN / NAME_乡 | 乡级名称,如"店埠镇" |
| NAME_VILLAGE / NAME_村 | 村级名称,如"某某村" |
| ADCODE / PAC | 行政区划代码,12位数字,从省级到村级逐级截取 |
看到这组文件时,我的习惯是先用QGIS或ArcGIS打开.dbf看一眼字段内容,而不是急着加载整张地图。重点确认两件事:村级名称字段里到底有没有值,以及行政区划代码字段是不是12位。12位代码的前两位是省、中间四位是市、六位是县、九位是乡、后三位是村,这个结构后面做字段连接和唯一标识时非常重要。
2.2 坐标系选择:CGCS2000与WGS84怎么选,.prj文件说得很明白
坐标系是shp数据集最容易翻车的地方。拿到数据后不要凭经验猜,直接打开.prj文件看。常见的坐标系有两种:WGS84地理坐标系,经纬度单位,适合Web地图展示;CGCS2000高斯投影,单位为米,适合面积和距离计算。国内国土、规划、测绘口径的成果数据,大概率是CGCS2000,而GPS设备和互联网底图多用WGS84。
用Python读.prj文件判断坐标系:
from osgeo import osr # 读取prj文件,注意与shp同名同目录 prj_path = "anhui_2024.shp.prj" with open(prj_path, "r", encoding="utf-8") as f: prj_text = f.read() # 解析坐标系 sr = osr.SpatialReference() sr.ImportFromWkt(prj_text) print(sr.GetName()) # 例如: CGCS2000 / 3-degree Gauss-Kruger zone... print(sr.IsGeographic()) # True为经纬度,False为投影坐标这段代码的逻辑是先读取.prj里的WKT描述,再用osr库解析出坐标系名称和类型。IsGeographic()返回True表示是经纬度坐标系,返回False表示是投影坐标系。后续做面积统计时,我一般会先按需转换:如果要做亩数、平方米等精确长度面积统计,必须用CGCS2000投影;如果只是出Web地图或叠加在线底图,统一转到WGS84经纬度即可,避免地图上出现几百米的整体偏移。
2.3 版本口径:2024年数据不等于所有边界都符合业务口径
行政区划是动态的,2024年的乡和村跟2023年相比可能已经发生过合并、拆分、村改居。这份数据集标注2024年,意味着它的边界和名称对应的是2024年的行政区划现状。使用时要注意你的业务数据是什么口径:如果业务数据是2024年的统计年鉴,可以直接挂接;如果是2020年人口普查数据或者某一年的三调成果,那就必须先做名称和代码比对,甚至要处理村拆并前后的边界差异。
另一个常见误区是把村级名称当成唯一标识来用。安徽省内同名村很多,尤其"中心村""新庄村""新河村"这类名称在不同县甚至同一县的不同乡镇都会出现。真正适合做主键的是12位行政区划代码,名称只用来展示和辅助核对。拿到数据的第一件事,建议先把村级字段里有值但代码为空的记录查出来,这类记录后续一定会出问题。
3. 上图与业务挂接:把五级名称变成能用的业务字段
3.1 在QGIS中按层级加载与渲染,从全省到村级逐级切换
拿到shp后在QGIS里直接拖入即可显示。如果是多级数据放在一个图层里,通常靠字段筛选来分级显示。比如想看全省轮廓,用省名称字段做分类即可;想看某个市的乡级边界,就选中对应市的记录导出或单独显示。注册一个分层符号:右击图层,选择"符号体系",按"规则"设置五条规则,每条规则匹配一级,缩放级别高时显示村级,缩放级别低时只显示市县级。
规则写法类似:
省级别显示: "NAME_PROV" IS NOT NULL AND "NAME_CITY" IS NULL 市级别显示: "NAME_CITY" IS NOT NULL AND "NAME_COUNTY" IS NULL 县级别显示: "NAME_COUNTY" IS NOT NULL AND "NAME_TOWN" IS NULL 乡级别显示: "NAME_TOWN" IS NOT NULL AND "NAME_VILLAGE" IS NULL 村级别显示: "NAME_VILLAGE" IS NOT NULL这里的逻辑是逐级判断字段是否为空:省级记录只有省名没有市名,市级记录有市名但没有县名,依次类推。这样同一份shp可以按缩放层级自动切换显示粒度,做Web地图瓦片预渲染时也能直接输出不同级别的GeoJSON。
3.2 业务数据落村:字段连接(JOIN)与空间连接的区别
要把人口、面积、项目投资这类业务表挂到村边界上,最稳妥的方式是字段连接,不是空间连接。字段连接的匹配键是行政区划代码,两边都有12位代码就能一一对应。在QGIS中右击村级图层,选择"属性连接",连接目标表,字段选"ADCODE"对应业务表的"ADCODE"即可。这样挂接的前提是两边代码口径一致,如果业务表用的是简码,需要先补零或截断处理。
空间连接则用于另一种场景:手上有一批POI点数据或项目坐标点位,需要统计落在哪个村。QGIS处理方式是在处理工具箱里搜索"按位置连接属性"或"空间连接",以村边界图层为目标层,点图层为连接层,相交条件统计每个村的点数。注意空间连接会受坐标系精度影响,使用前两者的坐标系要一致,否则点位距离边界几米就可能误判落到隔壁村。
import geopandas as gpd # 读取村级边界和业务表 villages = gpd.read_file("anhui_2024_village.shp", encoding="utf-8") business = gpd.read_file("project_points.shp", encoding="utf-8") # 按位置连接:计算每个村范围内的项目点数 joined = gpd.sjoin(business, villages, how="left", predicate="within") counts = joined.groupby("ADCODE").size().reset_index(name="project_count") merged = villages.merge(counts, on="ADCODE", how="left").fillna({"project_count": 0})这段代码用geopandas完成空间连接和聚合。先读入村边界和项目点,sjoin以点图层为左表,村边界为右表,within表示点必须在面内才算命中。随后按ADCODE分组计数,再把计数表合并回村边界图层。参数说明:how="left"保留所有项目点,predicate="within"是点在面内条件,如果点恰好落在边界线上,推荐改用intersects并在后续用投影坐标做距离容差判定。
3.3 组装五级全名字段,避免每次都用多级JOIN
实际项目里前端做树形选择器或报表导出时,经常需要"安徽省/合肥市/肥东县/店埠镇/某某村"这种完整路径字符串。每次都去JOIN省、市、县、乡四张表太啰嗦,建议在数据预处理阶段就拼好一个字段存进去。
import geopandas as gpd gdf = gpd.read_file("anhui_2024_five.shp", encoding="utf-8") # 用fillna替代有缺失的层,保证路径连续 gdf["full_path"] = ( gdf["NAME_PROV"].fillna("") + "/" + gdf["NAME_CITY"].fillna("") + "/" + gdf["NAME_COUNTY"].fillna("") + "/" + gdf["NAME_TOWN"].fillna("") + "/" + gdf["NAME_VILLAGE"].fillna("") ) # 去掉可能出现的双斜杠 gdf["full_path"] = gdf["full_path"].str.replace(r"/{2,}", "/", regex=True) gdf.to_file("anhui_2024_with_path.shp", encoding="utf-8")这段代码的核心逻辑有两点:一是用fillna填空字符串,确保从省到村每一级至少有一个占位,不会因为中间某级缺失导致路径断掉;二是用正则把连续斜杠合并成单斜杠,避免出现"合肥市//店埠镇"这类脏数据。注意保存输出shp时指定encoding="utf-8",否则中文路径在其他系统上又会变成乱码。这里生成的full_path字段在后续做行政区划树下拉框时可以直接复用,不需要重复拼接。
4. 热格式转换落地:shp转GeoJSON、3dtiles与数据导出
4.1 ogr2ogr把shp转GeoJSON:Web前端直接可用的轻量格式
把安徽省五级shp转成GeoJSON是给Web地图和可视化项目用的最常见动作。GeoJSON是JSON结构,浏览器原生支持,配合Leaflet、Mapbox、ECharts都能直接渲染。推荐用GDAL自带的ogr2ogr命令行工具,转换精度高,还支持字段裁剪和坐标裁剪。
ogr2ogr -f GeoJSON anhui_villages.geojson anhui_2024_village.shp \ -t_srs EPSG:4326 \ -lco COORDINATE_PRECISION=6 \ -lco RFC7946=YES参数说明:-t_srs EPSG:4326固定输出为WGS84经纬度坐标系,避免前端底图对不上;COORDINATE_PRECISION=6把坐标小数位限制到6位,大约0.1米的精度,对村级边界图足够,同时又能把文件体积瘦身一半以上;RFC7946=YES是GeoJSON标准坐标顺序约束(先经度后纬度)。如果源shp已经是4326,可以不加-t_srs,但加了更保险。
转出来的GeoJSON里每个feature的properties会保留省、市、县、乡、村名称和12位代码,前端直接用properties做弹窗和筛选即可。如果文件太大,超过几MB,我会顺手用mapshaper或turf做一次简化抽稀,容差设在0.0001左右。
4.2 shp转3dtiles:让村级边界进入三维场景
做城市级三维展示、规划管控系统时,经常需要把行政区划面数据叠加到三维地球上。这时候shp转3dtiles就是必经路径。业界通用做法是用CesiumLab或py3dtiles把shp转成3dtiles分片数据。流程分三步:先把shp转成带高程的GeoJSON,再切分成四叉树瓦片,最后打包成tileset.json加b3dm或pnts。
常见命令行方式是:
py3dtiles convert anhui_2024_village.shp -o output_3dtiles --srs_in 4326py3dtiles会自动按空间范围切块,生成瓦片金字塔。要注意的是,村级边界面数量大,动辄上万面,直接全量转会导致瓦片文件巨大。我的做法是先按市分一层,每市单独转换输出,前端按需动态加载对应的3dtiles目录。坐标偏移也是个坑:三维场景里如果底图是WGS84,而shp实际是CGCS2000,转出来的建筑或边界会看起来在底图上"漂移",所以转换前务必确认源坐标系。
4.3 从dwg到shp,以及shp转txt:CAD数据与空间数据库的交叉路径
做规划的人手边经常有村庄的dwg地形图,需要和行政区划shp一起用。dwg转shp常用做法是先打开CAD软件把dwg另存为dxf,再用QGIS的DXF导入插件,最后导出为shp。导入时坐标系往往会丢失,需要在QGIS里手动指定坐标参考;如果dwg里的图是用任意坐标画的,还要先做配准,否则转换出的shp和行政区划shp叠不上。一个替代路径是直接用FME或GDAL的dxf驱动批量处理,但无论如何,坐标系指定这一步都不能省。
shp转txt则是把属性或坐标导出为文本,用于非GIS系统交接。最常用的文本格式是WKT,PostGIS空间数据库可以直接用WKT插入几何字段。
import geopandas as gpd gdf = gpd.read_file("anhui_2024_village.shp", encoding="utf-8") gdf["wkt"] = gdf.geometry.to_wkt() # 只保留名称和WKT,输出制表符分隔txt out = gdf[["NAME_PROV", "NAME_CITY", "NAME_COUNTY", "NAME_TOWN", "NAME_VILLAGE", "ADCODE", "wkt"]] out.to_csv("anhui_villages_wkt.txt", sep="\t", index=False, encoding="utf-8")这段代码用geopandas把几何对象转成WKT字符串,输出为制表符分隔的文本文件。WKT的格式是POLYGON((x1 y1, x2 y2...)),可以直接被PostGIS的ST_GeomFromText函数接收。参数说明:sep="\t"是为了方便Excel和数据库工具粘贴,encoding="utf-8"则避免中文在文本编辑器里乱码。如果txt要交给老的Oracle或SQL Server环境,可能需要把utf-8换成gbk,这一点要看接收方的字符集配置。
4.4 渔网分割shp:把村级边界切成规则格网做统计
有些业务场景需要把村级边界按固定大小格网切开,比如按1km×1km或500m×500m出统计热力。QGIS的"创建渔网"工具可以做这件事。操作路径:处理工具箱搜索"创建渔网",设置网格范围勾选"从图层计算",输入村级shp范围,设置行数和列数,或者直接设网格宽度和高度。生成渔网后,用"相交"工具把渔网和村级边界叠加,得到带村代码的格网切片。
这个操作的坑在于格网边界处容易产生碎面。渔网切过村界时,落在边界上的多边形可能会被切成几十条极窄的小面片。解决办法是生成渔网后先做一步"消除"或合并,合并条件按格网ID分组,只保留面积大于阈值的小面,或者干脆在统计时按村代码+格网ID分组聚合。
5. 常见问题与避坑清单:村级名称的乱码、漂移、重叠与重名
5.1 中文乱码:GBK字段被UTF-8程序读出全问号
现象:在某台电脑上打开村级shp,省市区乡村全是乱码,或者直接显示"?"。在另一台电脑上打开正常。
原因:这份数据的字段编码是GBK或GB2312(国内早期数据常见),属性编码存在.cpg文件里。而很多开源库和Web程序默认按UTF-8读属性表,编码不匹配自然乱码。
解决:读数据时显式指定编码。Python里用geopandas读取时加encoding="gbk"参数。QGIS里则在图层属性中设置"数据源编码"为GBK。转出给Web端用之前,先统一转成UTF-8再输出GeoJSON。
5.2 坐标漂移:CGCS2000数据叠加WGS84底图差了上百米
现象:村级边界shp叠加到在线卫星底图上,整体偏移一两百米,而且不同区域偏移方向不一致。
原因:源数据坐标系是CGCS2000投影坐标,单位是米,直接当成经纬度或WGS84来用,必然产生大尺度错位。
解决:先读.prj确认坐标系。如果是CGCS2000且要配合Web底图,使用ogr2ogr加-t_srs EPSG:4326转一次。需要强调一点:CGCS2000和WGS84的相同坐标差异虽然只有厘米级,但对边界线的接边和面积统计来说,仍然应该用标准的七参数或格网改正做转换,不要手动加减常数。
5.3 相邻村级边界重叠或有缝隙:面积统计出现重复或漏算
现象:用村级shp统计各类用地面积时,所有村面积加起来比县级面积大;或者反过来,村落边界之间存在裂缝导致有块地不属于任何村。
原因:这是原始数据生产时的拓扑问题,相邻多边形没有严格公共边。尤其是从不同来源拼出来的村级边界,最容易出现重叠和缝隙。
解决:在QGIS中用"拓扑检查"插件检查重叠,然后用v.clean工具处理。处理方式:先对图层建立拓扑,选择清理工具中"break"和"rmline",破开相交处后删除重复线,最后再用"snap"设置容差0.001度或1米做吸附。对必须精确统计面积的场景,建议用干净顶点的县级上图边界做减法来反推村级统计口径。
5.4 同名村太多:"中心村""新庄村"在安徽省内到处都是
现象:直接按村名做数据挂接,匹配率看着很高,但一抽查发现挂错了村。
原因:村级名称重复率极高,仅凭村名无法唯一确定一个村。
解决:必须以12位行政区划代码或村级唯一编码作为主键。代码不存在时退而求其次用"县代码+乡代码+村名"拼接,这样就能把同名村区分开。具体做法是写一段Python,按县和乡分组检查村名重复,重复的记录下来人工处理。
5.5 行政区划代码和业务代码不一致:合并村、改居村更严重
现象:业务数据里的村代码还在用旧版,挂接后很多村对应不上。比如某个村已经在2023年并入邻村,但业务表里还保留旧村代码。
原因:区划代码随着乡镇合并、村组调整在动态更新,业务系统没有同步。
解决:拿到2024年代码后,先做旧代码映射。映射方法是在省级民政或统计发布的历史区划代码表里,找到旧代码对应的新代码。这份数据集虽然保证村级名称无缺失,但名称和代码之间如果存在一对多的历史关系,直接按名称匹配出来的结果仍然要人工复核。我处理这一类问题的习惯是:先挂代码,再挂名称,最后对挂接不上的记录导出人工比对,不盲目相信自动匹配的结果。
6. 校验村级名称无缺失的脚本与一个实操习惯
6.1 一行代码跑完村级名称校验:空值、空白字符与组合重复
数据写明了村级名称无缺失,但我拿到任何行政区划shp后都会跑一遍独立校验,用代码验证比信承诺靠谱得多。
import geopandas as gpd gdf = gpd.read_file("anhui_2024_village.shp", encoding="utf-8") # 1. 村级名称空值检查 name_null = gdf["NAME_VILLAGE"].isna().sum() name_blank = (gdf["NAME_VILLAGE"].astype(str).str.strip() == "").sum() print(f"村级名称空值: {name_null}, 空白值: {name_blank}") # 2. 组合重复检查:县+乡+村名完全相同的记录 dup = gdf.duplicated(subset=["NAME_COUNTY", "NAME_TOWN", "NAME_VILLAGE"]).sum() print(f"组合名称重复记录数: {dup}") # 3. 代码唯一性检查 code_dup = gdf.duplicated(subset=["ADCODE"]).sum() print(f"行政区划代码重复数: {code_dup}")这段脚本做了三重校验:第一重查村级名称字段的空值和纯空白,第二重用县级、乡级、村级名称三字段组合查完全重复,第三重查ADCODE是否唯一。第三重是最容易被忽略的——哪怕名称齐全,代码一旦重复,挂接业务数据必出问题。根据项目需要,这个脚本还可以继续扩展,比如检查space记录数是否等于全要素数、每个县面积是否有异常值等。
6.2 一张村级边界索引图的检查价值
代码校验只是第一步,人工抽检不能省。我的做法是把村级shp按乡镇分组出索引图:每个乡镇单独画一张图,显示所有村界和村名注记,导出PDF后快速翻看。这一步能发现很多代码发现不了的问题:边界锯齿是否异常、是否有村被环岛式包围、注记是否溢出到邻村范围。这个步骤在QGIS里用"按字段分组合并打印"功能即可实现,模板里加一个按乡名排序的标签,自动为每个乡镇生成一张图。
我第一次用这类五级数据做项目时,就是只看属性没做抽检,结果有一个乡镇的边界在修拓扑时被误删了整整一排村,面积统计出来全县少了一块。后来我养成了"先跑脚本、再抽图、后挂业务"的固定顺序,现在每次拿到shp数据集都这么过一遍,省下的沟通时间远比这两步操作时间多。希望这个流程对你也有用。
本文还有配套的精品资源,点击获取