☰
2024江苏省四级行政区划矢量图层:GIS空间分析与下钻统计实战
2026/10/20 11:43:02 网站建设 项目流程

简介:这份2024年江苏省行政区划矢量图层数据集,面向GIS开发、地图可视化、空间分析与城乡规划等方向的从业者及学生,可解决底图数据缺失、行政边界不统一的问题。资源包共32个文件,约5.09MB,以shp、shx、dbf、prj等Shapefile核心文件为主,辅以sbn、sbx空间索引、cpg编码说明及xml元数据,另附一个shp2json.py转换脚本,方便将矢量数据转为GeoJSON供前端或Web地图调用。数据覆盖省、市、县、乡镇四级行政区划,采用WGS84坐标系,层级完整、结构清晰,可直接用于专题制图、区域统计、空间叠加分析或作为项目底图。目前已有217人学习下载,适合需要快速获取江苏行政边界、搭建GIS实验环境或进行数据格式转换的读者参考使用。

1. 拿到一份四级行政区划矢量图层,先搞清楚它能干什么

做 GIS 项目的人大概都有过这种经历:底图配好了、坐标也对上了,结果一叠加业务数据,发现行政区划边界缺了乡镇一级,或者省市县三级的编码对不上,整个空间连接直接翻车。这次拆的是一份 2024 年江苏省行政区划矢量图层,覆盖省、市、县、乡镇四个层级,属于典型的行政边界基础数据。它的核心价值不在于"好看",而在于给空间分析提供一个可对齐、可关联、可下钻的骨架——比如做人口热力按乡镇聚合、做物流网点覆盖分析、做选址评估时按行政单元统计指标,都绕不开这套边界。

适合谁用?做区域分析的数据分析师、做政务或商业选址的 GIS 工程师、需要行政单元做聚合统计的后端开发,以及拿它当底图做可视化的前端。不适合谁?如果你只是想要一张"能看"的地图截图,用在线底图就够了,没必要折腾矢量数据。这份资源的门槛在于:你得知道坐标系怎么选、属性表怎么读、四级之间怎么关联,否则拿到手也只是一堆打不开的 shp 文件。

2. 四级行政区划数据的结构与选型逻辑

2.1 省、市、县、乡镇四级的层级关系

行政区划矢量数据的本质是"面要素 + 属性表"。每个面代表一个行政单元,属性表里存着名称、编码、层级、上级编码等字段。四级数据的组织方式通常有两种:一种是四个独立的图层文件,省一个、市一个、县一个、乡镇一个;另一种是一个图层里用字段区分层级。前者更常见,因为不同层级的几何精度和要素数量差异很大——江苏省乡镇级要素大概在 1200 到 1300 个左右,而省级只有 13 个地级市(含省直辖),混在一起管理反而麻烦。

层级之间的关联靠的是行政区划代码。国家标准是 6 位码:前两位省、中间两位市、后两位县。乡镇一级是 9 位码,在 6 位基础上加 3 位。这份数据如果编码规范,你就能用前 2 位筛出江苏(32),用前 4 位筛出某个地级市,用前 6 位筛出某个县,再往下钻到乡镇。这是做下钻分析的基础。

提示:拿到数据第一件事不是打开看,而是先确认编码字段是否完整、是否统一为字符串类型。数值类型会把前导零吃掉,320000 变成 320000 没问题,但 320102 里的 0 如果被当数值处理就可能出问题。

2.2 坐标系选择:地理坐标还是投影坐标

这是最容易踩坑的地方。矢量数据常见的坐标系有两类:地理坐标系(如 WGS84、CGCS2000)单位是度,投影坐标系(如 UTM、高斯-克吕格)单位是米。如果你要做面积计算、距离测量,必须用投影坐标;如果只是做叠加显示,地理坐标也能用,但会有变形。

江苏省跨了 UTM 的 50 带和 51 带,常见做法是用 CGCS2000 的高斯-克吕格投影,中央经线选 120°E(3 度带第 40 带)或 117°E。我一般会先看数据自带的 .prj 文件,确认原始坐标系,再决定要不要重投影。如果原始数据是 WGS84 地理坐标,而你要算乡镇面积,直接算出来的单位是"平方度",毫无意义,必须先投影。

# 用 ogrinfo 查看数据的坐标系和字段结构 ogrinfo -so -al jiangsu_township.shp # 输出里重点看两行: # Geometry: Polygon # Feature Count: 1268 # 以及 Coordinate System 部分,确认是 GEOGCS 还是 PROJCS

这段命令的作用是"只看结构不读几何",-so是 summary only,-al是 all layers。输出里会列出所有字段名和类型,你重点确认三件事:编码字段叫什么、名称字段叫什么、坐标系是什么。如果 Coordinate System 显示GEOGCS["China Geodetic Coordinate System 2000"...],说明是地理坐标,要投影;如果显示PROJCS[...],说明已经是投影坐标,可以直接算面积。

2.3 属性表字段的读取与关联

属性表是这份数据的灵魂。常见的字段包括:NAME(名称)、CODE(行政区划代码)、LEVEL(层级)、PARENT_CODE(上级代码)。但不同来源的数据字段名可能不一样,有的用中文拼音缩写,有的用英文全称。我一般会先用 Python 把字段列出来,再决定怎么处理。

import geopandas as gpd # 读取乡镇级图层 gdf = gpd.read_file("jiangsu_township.shp") # 查看字段名和类型 print(gdf.dtypes) print(gdf.columns.tolist()) # 查看前几行属性 print(gdf[['NAME', 'CODE', 'PARENT_CODE']].head(10)) # 检查编码长度分布,确认是否统一为 9 位 print(gdf['CODE'].astype(str).str.len().value_counts())

逻辑说明:geopandas读取 shp 后返回的是 GeoDataFrame,dtypes能看出编码字段是 object(字符串)还是 int64(数值)。如果是数值,后面做字符串匹配时会出问题,需要先astype(str)。str.len().value_counts()是检查编码长度是否统一——如果出现 6 位和 9 位混杂,说明数据里可能混了县级要素,需要按长度拆分或过滤。参数上,PARENT_CODE是关联上级的关键,如果这个字段缺失,你就只能靠编码前缀来推断层级关系。

3. 把数据用起来:从加载到空间分析的完整流程

3.1 加载与可视化:先确认数据能正常显示

拿到数据后别急着做分析,先画出来看一眼。这一步能发现很多问题:几何是否完整、有没有飞地、边界是否闭合、坐标系是否错位。用 Python 的 matplotlib 或 folium 都行,我习惯先用静态图快速检查。

import geopandas as gpd import matplotlib.pyplot as plt # 分别加载四级数据 province = gpd.read_file("jiangsu_province.shp") city = gpd.read_file("jiangsu_city.shp") county = gpd.read_file("jiangsu_county.shp") township = gpd.read_file("jiangsu_township.shp") # 快速可视化,检查几何是否正常 fig, axes = plt.subplots(2, 2, figsize=(14, 12)) for ax, gdf, title in zip(axes.flat, [province, city, county, township], ['Province', 'City', 'County', 'Township']): gdf.plot(ax=ax, edgecolor='black', facecolor='none', linewidth=0.5) ax.set_title(f"{title} - {len(gdf)} features") ax.set_axis_off() plt.tight_layout() plt.savefig("check_layers.png", dpi=150)

这段代码的关键在facecolor='none',只画边界不填充,这样能清楚看到各级边界的嵌套关系。如果乡镇级图层画出来是一团乱麻或者有明显错位,说明坐标系可能不一致。len(gdf)输出要素数量,江苏省乡镇级正常在 1200 以上,如果只有几百个,可能数据不完整。保存成 png 而不是直接 show,是为了在服务器环境也能跑。

3.2 按行政层级筛选与下钻

实际项目里很少一次性用全部四级数据,更多是按需下钻。比如先定位到某个市,再筛出它下辖的县,再往下到乡镇。用编码前缀筛选是最稳的方式。

# 假设要看南京市(3201)下辖的所有乡镇 city_code = "3201" # 从乡镇级数据里筛出属于南京的 nanjing_towns = township[township['CODE'].astype(str).str.startswith(city_code)] print(f"南京下辖乡镇数量: {len(nanjing_towns)}") print(nanjing_towns[['NAME', 'CODE']].head(20)) # 如果要按县级下钻,比如江宁区(320115) county_code = "320115" jiangning_towns = township[township['CODE'].astype(str).str.startswith(county_code)] print(f"江宁区下辖乡镇: {len(jiangning_towns)}")

逻辑说明:str.startswith()是按前缀匹配,比精确匹配更灵活。乡镇级 9 位码的前 4 位是市,前 6 位是县,所以用3201能筛出南京所有乡镇,用320115能筛出江宁区所有乡镇。参数上要注意astype(str)不能省,否则数值类型的编码做字符串操作会报错。如果数据里编码字段有空格或不可见字符,先用.str.strip()清洗一遍。

3.3 空间连接:把业务数据挂到行政区划上

这是最常见的需求:你有一批带经纬度的点位数据(门店、基站、传感器),想统计每个乡镇有多少个点。做法是先把点位转成 GeoDataFrame,再做空间连接(spatial join)。

import pandas as pd from shapely.geometry import Point # 模拟一批点位数据 points_df = pd.DataFrame({ 'name': ['P1', 'P2', 'P3', 'P4'], 'lon': [118.78, 118.95, 119.12, 118.62], 'lat': [32.05, 32.12, 31.98, 32.18] }) # 转成 GeoDataFrame,注意坐标系要和行政区划一致 geometry = [Point(xy) for xy in zip(points_df['lon'], points_df['lat'])] points_gdf = gpd.GeoDataFrame(points_df, geometry=geometry, crs="EPSG:4326") # 如果行政区划是投影坐标,先统一 township_proj = township.to_crs(points_gdf.crs) # 空间连接:每个点落在哪个乡镇 joined = gpd.sjoin(points_gdf, township_proj, how='left', predicate='within') # 按乡镇统计点位数量 count_by_town = joined.groupby('NAME').size().reset_index(name='point_count') print(count_by_town.sort_values('point_count', ascending=False).head(10))

逻辑说明:gpd.sjoin的predicate='within'表示点完全落在面内,how='left'保证即使点不在任何乡镇内也保留(值为 NaN)。参数上,crs必须两边一致,否则 sjoin 会报错或结果错乱。如果点位数据是 WGS84 经纬度,而行政区划是投影坐标,用to_crs统一到同一坐标系。统计时用groupby('NAME').size()比count()更稳,因为count()会忽略 NaN。

注意:空间连接前一定要检查两边坐标系是否一致。我见过太多案例是点位用 WGS84、边界用 CGCS2000,两者差了几十米,导致点落在错误的乡镇里,统计结果全错。

3.4 面积计算与指标聚合

乡镇级数据做面积计算时,必须用投影坐标。如果原始数据是地理坐标,先投影再算。

# 投影到 CGCS2000 高斯-克吕格,中央经线 120E township_proj = township.to_crs("EPSG:4547") # CGCS2000 / 3-degree Gauss-Kruger CM 120E # 计算每个乡镇的面积(平方公里) township_proj['area_km2'] = township_proj.geometry.area / 1e6 # 按市聚合面积 city_area = township_proj.dissolve(by='CITY_NAME', aggfunc={'area_km2': 'sum'}) print(city_area[['area_km2']].sort_values('area_km2', ascending=False))

逻辑说明:EPSG:4547是 CGCS2000 的 3 度带投影,单位是米,算出来的面积除以 1e6 就是平方公里。dissolve是按字段合并几何,这里按市名合并乡镇,得到市级总面积。参数上,aggfunc指定面积字段求和,如果还有其他指标(如人口),可以一起聚合。注意dissolve会丢失原始几何的细节,如果只是要统计值,用groupby更轻量。

4. 避坑与排查:四级数据最容易翻车的五个地方

4.1 编码前导零丢失导致筛选失败

现象:用startswith('3201')筛南京的乡镇,结果为空或数量不对。原因:编码字段被读成数值类型,320102变成320102没问题,但320100可能变成320100,而3201开头的 9 位码如果被截断就匹配不上。解决:读取时强制指定dtype={'CODE': str},或读完后astype(str).str.zfill(9)补齐位数。

4.2 坐标系不一致导致空间连接错位

现象:点位明明在南京,空间连接后却落到了隔壁市。原因:点位是 WGS84 经纬度,行政区划是 CGCS2000 投影坐标,两者差了几十到上百米。解决:用to_crs统一到同一坐标系,优先统一到投影坐标再做连接。检查方法是先画一张叠加图,看点位是否落在预期区域内。

4.3 乡镇级边界存在缝隙或重叠

现象:相邻乡镇之间有一条细缝,点位落在缝里导致within判断为 False。原因:数据生产时边界未严格闭合,或不同来源的数据拼接时产生缝隙。解决:用buffer(0)修复几何,或改用intersects代替within。如果缝隙较大,考虑用unary_union合并后再重新划分。

4.4 属性表字段名不统一

现象:代码里写NAME,但数据里实际是name或MC(名称拼音缩写)。原因:不同来源的数据字段命名规范不同。解决:先print(gdf.columns.tolist())确认字段名,再写后续代码。如果要做通用脚本,用字段名映射表做兼容。

4.5 面积计算单位错误

现象:算出来的乡镇面积是几百,实际应该是几十平方公里。原因:用了地理坐标直接算面积,单位是平方度。解决:先to_crs投影到米制坐标,再算面积。江苏省常用的投影是 EPSG:4547 或 EPSG:4526,具体看中央经线。

5. 进阶技巧:用四级数据做下钻统计与边界校验

5.1 构建层级索引实现快速下钻

如果频繁做"省→市→县→乡镇"的下钻查询,每次都startswith效率不高。更好的做法是预先构建层级索引,用字典或 MultiIndex 加速。

# 构建层级字典:市码 -> 县码列表 -> 乡镇列表 from collections import defaultdict hierarchy = defaultdict(lambda: defaultdict(list)) for _, row in township.iterrows(): code = str(row['CODE']).zfill(9) city_code = code[:4] county_code = code[:6] hierarchy[city_code][county_code].append(row['NAME']) # 查询南京(3201)下所有县和乡镇 for county_code, towns in hierarchy['3201'].items(): print(f"县码 {county_code}: {len(towns)} 个乡镇")

逻辑说明:defaultdict嵌套实现两级索引,zfill(9)保证编码长度统一。这样查询时直接hierarchy['3201']就能拿到所有县和乡镇,不用每次遍历整个 GeoDataFrame。参数上,code[:4]是市码,code[:6]是县码,如果数据编码规范,这个索引是准确的。

5.2 用上级边界校验下级边界的完整性

四级数据之间应该满足"下级完全被上级包含"的关系。如果某个乡镇超出了它所属县的边界,说明数据有问题。校验方法是用within做空间关系判断。

# 校验:每个乡镇是否完全落在其所属县内 county_dict = {str(row['CODE']).zfill(6): row['geometry'] for _, row in county.iterrows()} errors = [] for _, town in township.iterrows(): town_code = str(town['CODE']).zfill(9) county_code = town_code[:6] if county_code in county_dict: if not town['geometry'].within(county_dict[county_code]): errors.append(town['NAME']) print(f"越界乡镇数量: {len(errors)}") print(errors[:10])

逻辑说明:先构建县码到几何的字典,再遍历乡镇,用within判断是否被包含。within比intersects更严格,要求完全包含。如果出现越界,可能是数据生产时的拓扑错误,也可能是编码关联错了。参数上,zfill(6)和zfill(9)保证编码长度一致,避免因前导零丢失导致匹配失败。

5.3 导出为 GeoJSON 供前端使用

如果要把数据给前端做可视化,shp 不方便,通常转成 GeoJSON。但乡镇级数据量大,直接转会导致文件过大,需要简化几何。

# 简化几何后导出 GeoJSON township_simplified = township.copy() township_simplified['geometry'] = township_simplified['geometry'].simplify(0.001) # 只保留必要字段 township_simplified = township_simplified[['NAME', 'CODE', 'geometry']] # 导出 township_simplified.to_file("jiangsu_township_simplified.geojson", driver='GeoJSON')

逻辑说明:simplify(0.001)的容差单位取决于坐标系,如果是地理坐标,0.001 度约 100 米,能大幅减少顶点数。导出时只保留必要字段,减小文件体积。参数上,容差太大会导致边界变形,太小则简化效果不明显,一般从 0.001 开始试。导出后建议用浏览器打开检查是否正常显示。

从那以后我每次拿到新的行政区划数据,都强制走一遍"看坐标系→查编码长度→画叠加图→校验层级包含"这四步,宁可多花十分钟,也不想到最后统计结果全错再回头排查。希望帮到你。

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

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

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

立即咨询