☰
中国省市县三级行政区划边界数据:shp与GeoJSON双格式实战指南
2026/10/14 10:26:21 网站建设 项目流程

简介:这份资源提供中国省、市、县三级行政区划边界数据,同时包含南海十段线,面向从事GIS制图、空间分析与地图可视化的开发者与研究人员。数据于2020年7月20日从阿里平台采集,采用国内标准领土主张,规避了部分境外地图在争议区域标注不一致的问题,适用于ArcGIS等工具下的行政边界提取、区域统计与专题图制作。压缩包共18个文件,约16.72MB,包含shp、shx、prj、dbf、cpg等Shapefile配套文件及json格式的GeoJSON数据,分别覆盖省级、市级、县级三个层级,可直接导入常见GIS软件使用。目前已有1776人学习下载。资源同时提供Shapefile与GeoJSON两种格式,便于在桌面端与Web端灵活切换,省去自行爬取与坐标整理的环节,适合需要快速获取最新国内行政区划底图的中高级用户。

1. 拿到 shp 和 GeoJSON 双格式的省市县三级区划数据,先别急着往 ArcGIS 里拖

做行政区划底图这件事,最磨人的从来不是画图,而是找一份能直接用的边界数据。网上流传的版本要么只有国界省界、要么县界缺胳膊少腿、要么坐标系是加密偏移的 GCJ-02 导致套不上业务数据。这次拆的这份资源,核心价值就一句话:同时提供 shp 与 GeoJSON 两种格式的中国省市县三级行政区划边界,并且带十段线。省、市、县三层是分开的独立图层,不是揉在一个文件里,这意味着你可以按需加载,做全国专题图时不用把几万个县级面全拖进内存。适合谁?做 ArcGIS 出图的分析师、用 QGIS 做规划底图的从业者、以及在后端用 GeoJSON 直接喂给地图库的前端工程师。十段线单独成层,这一点在正式制图里很关键,后面会专门讲怎么处理。

2. 三级图层拆开看:shp 与 GeoJSON 的字段结构差异

2.1 为什么省、市、县要分成三个独立文件

很多人第一次拿到这类数据会疑惑:为什么不给一个包含所有层级的文件?原因在于行政区划本身是树状结构,但制图需求是扁平的。做全国省级 choropleth 时你只需要 34 个面,做市级热力图时需要 300 多个面,做县级点位聚合时需要 2800 多个面。如果全塞进一个 shp,属性表里得靠一个 level 字段区分,每次筛选都要全表扫描,ArcGIS 里拖动一次就是几秒卡顿。

分成三个文件后,每个文件的属性表可以针对该层级优化。省级文件通常只保留省名、省代码、简称;市级文件保留市名、市代码、所属省代码;县级文件保留县名、县代码、所属市代码。这种设计让关联查询变得直接——县级数据通过市代码挂到市级,市级通过省代码挂到省级,不需要递归解析。

常见做法是:省级用省代码做主键,市级用市代码,县级用县代码,代码遵循国标 GB/T 2260 的六位数字编码。前两位是省,中间两位是市,后两位是县。这个编码规则本身就是层级关系,比任何自建 ID 都可靠。

2.2 shp 与 GeoJSON 的坐标系与字段对照

shp 格式的坐标系需要重点确认。国内公开的行政区划 shp 常见三种坐标系:WGS84(EPSG:4326)、CGCS2000(EPSG:4490)、以及某些平台导出的 GCJ-02 偏移坐标。这份资源如果标注为 WGS84,那它可以直接和 GPS 点位、遥感影像套合;如果是 CGCS2000,在国内制图标准里更规范,但和某些在线地图底图会有微小偏移。

GeoJSON 按 RFC 7946 标准规定使用 WGS84 经纬度,但实际拿到的文件不一定遵守。用文本编辑器打开 GeoJSON,看 coordinates 数组里的数值范围:经度在 73 到 135 之间、纬度在 3 到 54 之间,基本就是 WGS84 或 CGCS2000 的经纬度。如果经度出现 73 到 135 之外的大数,那可能是投影坐标,需要先转换。

字段对照可以用下面这张表快速核对:

层级shp 字段示例GeoJSON 属性示例用途
省NAME, CODE, ABBRname, code, abbr省级填色图、标签
市NAME, CODE, PCODEname, code, pcode市级聚合、下钻
县NAME, CODE, PCODEname, code, pcode县级点位、精细边界
十段线TYPE, NAMEtype, name单独符号化,不参与填色

注意:不同来源的字段名可能用拼音、英文或中文,拿到后先用ogrinfo或 ArcGIS 的字段视图确认一遍,别默认字段名就是上面这些。

2.3 用 ogrinfo 快速验证数据完整性

在动手做任何处理之前,先用 GDAL 的命令行工具把数据摸一遍。这一步能省掉后面大量返工。

# 查看 shp 文件的坐标系和字段结构 ogrinfo -so -al china_province.shp # 输出示例解读: # Layer name: china_province # Geometry: Polygon # Feature Count: 34 # Extent: (73.499000, 3.837000) - (135.095000, 53.558000) # Layer SRS WKT: GEOGCS["WGS 84", ...] # NAME: String (50.0) # CODE: String (6.0)

-so表示只输出摘要不输出几何,-al表示所有图层。重点看三处:Feature Count 是否符合预期(省级 34、市级 300 多、县级 2800 多),Extent 是否覆盖中国全境,Layer SRS WKT 里的坐标系是不是 WGS84 或 CGCS2000。

如果 Feature Count 明显偏少,比如省级只有 31,那可能缺了港澳台;如果 Extent 的纬度下限低于 3 度,可能混入了南海诸岛以外的区域。这些都要在正式制图前确认。

# 检查 GeoJSON 的要素数量和属性 ogrinfo -so -al china_county.geojson # 用 jq 快速看 GeoJSON 的属性键 jq '.features[0].properties' china_county.geojson

GeoJSON 用 jq 看属性键比 ogrinfo 更直观,尤其是当属性名是中文时。如果 jq 输出乱码,说明文件编码不是 UTF-8,需要先转码再用。

3. 在 ArcGIS 里把三级边界跑通:从加载到符号化

3.1 加载 shp 并检查十段线的几何类型

ArcGIS Pro 或 ArcMap 加载 shp 是基本操作,但十段线这个图层有坑。十段线在几何上不是连续的面,而是多段线(Polyline),如果误当成面来符号化,会得到一堆细长的闭合区域,视觉上完全不对。

加载后右键图层打开属性表,看 Shape 字段的类型。如果是 Polyline,那就用线符号;如果是 Polygon,需要确认它是不是被错误地闭合了。常见做法是:十段线单独放一个图层,用红色或黑色实线,线宽 1.5 到 2 磅,不参与任何填色渲染。

# 用 arcpy 检查十段线图层的几何类型 import arcpy fc = r"C:\data\china_ten_dash_line.shp" desc = arcpy.Describe(fc) print("Shape Type:", desc.shapeType) print("Feature Count:", arcpy.GetCount_management(fc)) # 如果 shapeType 是 "Polyline",用以下方式设置符号 # 在 ArcGIS Pro 中通过 CIM 或图层属性手动设置线符号

arcpy.Describe返回的shapeType是判断几何类型的可靠方式。如果输出Polyline,后续所有符号化都按线来处理。如果输出Polygon,需要检查是不是数据源本身就把十段线做成了面,这种情况在正式制图中要改回线,否则填色会覆盖掉。

3.2 用 Python 脚本批量把 shp 转成 GeoJSON

虽然资源里已经带了 GeoJSON,但实际项目中经常需要自己转,比如裁剪后重新导出,或者把 ArcGIS 里处理过的 shp 转成 Web 友好的格式。用 GDAL 的ogr2ogr是最稳的方式。

# 把县级 shp 转成 GeoJSON,指定坐标系和编码 ogr2ogr -f GeoJSON \ -t_srs EPSG:4326 \ -lco ENCODING=UTF-8 \ china_county.geojson \ china_county.shp # 参数说明: # -f GeoJSON 输出格式 # -t_srs EPSG:4326 目标坐标系为 WGS84 # -lco ENCODING=UTF-8 输出文件编码,避免中文乱码

如果不想用命令行,Python 的geopandas也能做,而且更适合批量处理。

import geopandas as gpd import os # 批量转换三个层级的 shp 到 GeoJSON layers = ["province", "city", "county"] input_dir = r"C:\data\shp" output_dir = r"C:\data\geojson" for layer in layers: shp_path = os.path.join(input_dir, f"china_{layer}.shp") geojson_path = os.path.join(output_dir, f"china_{layer}.geojson") # 读取时指定编码,避免中文属性乱码 gdf = gpd.read_file(shp_path, encoding="utf-8") # 确认坐标系,如果不是 WGS84 就转换 if gdf.crs and gdf.crs.to_epsg() != 4326: gdf = gdf.to_crs(epsg=4326) # 导出 GeoJSON,drop_id 避免生成冗余的 id 字段 gdf.to_file(geojson_path, driver="GeoJSON", encoding="utf-8") print(f"{layer}: {len(gdf)} features -> {geojson_path}")

geopandas.read_file的encoding参数很关键,中文 Windows 环境下 shp 的 .dbf 文件常用 GBK 编码,不指定会读出乱码。to_crs(epsg=4326)确保输出统一到 WGS84。to_file的driver="GeoJSON"会生成标准 GeoJSON,但注意 geopandas 默认可能保留索引字段,必要时用drop去掉。

3.3 属性表关联:用国标代码把三级串起来

三级数据独立加载后,如果要实现「点击省高亮所有市」或者「按省统计县的数量」,就需要做属性关联。关联键就是国标代码的前缀。

import geopandas as gpd province = gpd.read_file("china_province.geojson") city = gpd.read_file("china_city.geojson") county = gpd.read_file("china_county.geojson") # 给市级数据添加省代码字段(市代码前两位) city["prov_code"] = city["code"].str[:2] # 给县级数据添加市代码字段(县代码前四位) county["city_code"] = county["code"].str[:4] # 统计每个省下有多少个县 county["prov_code"] = county["code"].str[:2] county_count = county.groupby("prov_code").size().reset_index(name="county_num") # 把统计结果挂回省级 province = province.merge(county_count, left_on="code", right_on="prov_code", how="left") print(province[["name", "county_num"]].head())

这段代码的核心逻辑是利用国标代码的层级前缀。str[:2]取省代码,str[:4]取市代码。groupby加size统计数量,merge挂回省级。注意how="left"保证省级数据不丢,没有县的省(如直辖市)统计结果为 NaN,需要填充为 0。

提示:如果代码字段不是字符串而是数字,str[:2]会报错。先用astype(str)转成字符串,并且注意前导零是否丢失——国标代码里北京是 110000,如果存成数字会变成 110000 没问题,但有些县代码如 110101 存成数字也是 110101,前导零只在省级以下某些编码中出现,稳妥做法是统一按字符串处理。

4. 避坑与排查:坐标系、拓扑和十段线的五个血泪经验

4.1 现象:点位数据套上区划后整体偏移几百米

原因:区划数据是 GCJ-02 偏移坐标,而点位是 WGS84。国内很多在线地图导出的边界都带偏移,但文件里不会标注。判断方法是找一个已知地标,比如某城市政府大楼的经纬度,对比区划边界上的对应位置。

解决:如果确认是 GCJ-02,需要用坐标转换库转回 WGS84。Python 里可以用coord-convert或自己实现转换算法。但更稳妥的做法是直接换一份明确标注 WGS84 的数据,因为 GCJ-02 转 WGS84 是有损的,来回转几次精度会下降。

4.2 现象:县级面之间出现缝隙或重叠

原因:不同来源的边界数据在拼接时没有做拓扑检查。相邻县共用一条边界,但如果两个文件分别数字化,边界点不可能完全一致,就会出现细缝或压盖。

解决:在 ArcGIS 里用「拓扑检查」工具,规则选「不能有缝隙」和「不能有重叠」。修复时用「对齐边」工具把相邻边界吸附到一起。如果数据量不大,也可以在 QGIS 里用「v.clean」工具处理。但要注意,拓扑修复会轻微改变边界形状,如果做面积统计,修复前后会有差异。

4.3 现象:十段线在 Web 地图上显示不全或被裁掉

原因:Web 地图库(如 Leaflet、Mapbox)默认按 Web 墨卡托投影渲染,十段线最南端接近北纬 3 度,在墨卡托投影下会被拉伸到很大范围,如果地图初始视野没包含这个区域,就看不到。

解决:设置地图的maxBounds包含十段线范围,或者在初始化时用fitBounds把整个中国范围(包括南海)纳入视野。另外,十段线在低缩放级别下可能因为线太细而看不见,需要设置minZoom或加大线宽。

4.4 现象:GeoJSON 文件加载到前端后中文属性乱码

原因:GeoJSON 标准要求 UTF-8 编码,但某些转换工具默认输出 GBK,或者 shp 的 .dbf 文件本身就是 GBK,转换时没指定编码。

解决:用ogr2ogr时加-lco ENCODING=UTF-8,用 geopandas 时加encoding="utf-8"。如果已经生成了乱码文件,用iconv转码:iconv -f GBK -t UTF-8 input.geojson > output.geojson。前端加载时确保 HTTP 响应头Content-Type: application/json; charset=utf-8。

4.5 现象:ArcGIS 里面积计算和实际不符

原因:shp 文件如果是地理坐标系(经纬度),ArcGIS 计算面积时默认用椭球体公式,结果单位是平方度,不是平方米。很多人直接看属性表里的面积字段,发现数值很小,以为是数据错了。

解决:计算面积前先把数据投影到合适的投影坐标系,比如 Albers 等面积投影(中国常用Asia_North_Albers_Equal_Area_Conic)。在 ArcGIS 里用「投影」工具转换后再计算几何。或者直接用 Python 的geopandas配合pyproj做投影后计算。

import geopandas as gpd gdf = gpd.read_file("china_county.shp") # 投影到 Albers 等面积投影 gdf_albers = gdf.to_crs("+proj=aea +lat_1=25 +lat_2=47 +lat_0=0 +lon_0=105 +datum=WGS84 +units=m") gdf_albers["area_km2"] = gdf_albers.geometry.area / 1e6 print(gdf_albers[["name", "area_km2"]].head())

+proj=aea是 Albers 等面积投影,lat_1和lat_2是标准纬线,中国常用 25 度和 47 度。units=m保证输出单位是米,除以 1e6 得到平方公里。这样算出来的面积才和实际统计年鉴对得上。

5. 进阶技巧:用 GeoPandas 做裁剪与按省导出,以及一个验证习惯

数据拿到手,最终往往要落到具体业务上。比如只做某个省的专题图,或者把全国数据按省拆成单独文件分发给不同团队。用 GeoPandas 做裁剪和批量导出,比在 ArcGIS 里手动选要素再导出快得多。

import geopandas as gpd import os # 读取全国县级数据和省级数据 county = gpd.read_file("china_county.geojson") province = gpd.read_file("china_province.geojson") # 按省裁剪县级数据并导出 output_dir = "province_county_split" os.makedirs(output_dir, exist_ok=True) for idx, prov in province.iterrows(): prov_name = prov["name"] prov_code = prov["code"] # 用省级边界裁剪县级数据 county_in_prov = county[county["code"].str[:2] == prov_code[:2]] if len(county_in_prov) == 0: print(f"{prov_name}: 无县级数据,跳过") continue # 导出为 GeoJSON out_path = os.path.join(output_dir, f"{prov_name}_county.geojson") county_in_prov.to_file(out_path, driver="GeoJSON", encoding="utf-8") print(f"{prov_name}: {len(county_in_prov)} 个县 -> {out_path}")

这段代码的逻辑是:遍历省级数据,用省代码前两位匹配县级数据,匹配到的导出为单独文件。county["code"].str[:2]取县代码的省前缀,和prov_code[:2]比较。注意这里用的是属性匹配而不是空间裁剪,因为国标代码本身就能保证归属关系,比空间相交更可靠,也不会因为边界缝隙导致漏掉县。

如果确实需要空间裁剪,比如用自定义的流域边界去裁行政区划,那就用gpd.overlay:

# 用自定义边界裁剪 custom_boundary = gpd.read_file("my_region.geojson") clipped = gpd.overlay(county, custom_boundary, how="intersection") clipped.to_file("clipped_county.geojson", driver="GeoJSON")

how="intersection"保留相交部分,how="difference"保留不相交部分。注意overlay要求两个图层坐标系一致,不一致先用to_crs统一。

最后说一个我每次拿到新边界数据都强制走一遍的验证习惯:随机抽三个县,用在线地图查它们的政府驻地经纬度,然后在 QGIS 里叠加卫星影像底图,看边界是否和影像上的实际行政边界吻合。这一步能同时验证坐标系是否正确、边界是否偏移、十段线是否完整。有一次就是靠这个习惯发现某份数据的县级边界整体向东偏移了约 500 米,原因是数据源用了不同的椭球体参数。从那以后,不管数据来源多权威,我都先抽三个县叠影像看一眼,确认没问题再往下做。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询