简介面向GIS空间分析与江苏区域研究场景这份数据包提供省、市、县三级行政区划边界以及道路网公路网和铁路网矢量图层可用于底图制作、空间统计、交通可达性分析与规划研究适合地理信息、城乡规划及区域经济方向的学习者与从业者使用。包内共33个文件以shp、dbf、shx、prj、xml等Shapefile核心与辅助文件为主并配有sbn、sbx空间索引文件压缩包约41.72MB投影信息与元数据相对完整便于在ArcGIS、QGIS等软件中直接加载与叠加分析。已有1972人学习下载具备一定的使用参考价值。读者可据此搭建江苏行政区划与交通网络的基础数据集完成省市县多级边界提取、路网分级展示与空间关系计算也可为专题制图、区位分析和相关课程实验提供现成数据支撑。1. 拿到一套 2022.07 的江苏 SHP省、市、县三级加两套路网能干什么做过一个网点选址的活儿方案改到第三版才发现问题不在模型在底图——行政边界是很多年前的老数据路网是另一批人给的两套数据一个用北京 54、一个用 CGCS2000叠上去差了小两百米怎么调权重都白搭。所以后来我拿到任何一套「江苏行政区划省、市、县 道路网 公路网」的 SHP 文件第一件事不是打开软件看颜色而是先验三样东西坐标系、字段口径、图层之间的层级关系。这套 2022.07 的底图价值不在于「边界画得多准」而在于边界和两套路网处在同一个时点、同一套空间参考下可以直接做空间连接、密度统计和连通性计算省掉整个流程里最耗时间的对齐环节。适合做区域可达性、路网密度评价、选址预筛、规划辅助和教学演示的人也适合手上攒了一堆零散图层、想找一套能自洽的底图把流程跑通的人。2. 拆开这五个图层看SHP 里到底装了什么、字段怎么读Shapefile 不是「一个文件」是一组同名、同目录、扩展名不同的文件捆在一起用。很多人拷数据只拷了.shp打开发现没属性、没坐标系就开始怀疑数据有问题其实是文件没带全。先把这套东西的物理构成摆清楚后面所有坑都跟它有关。文件是否必须装的是什么丢了会怎样.shp必须几何坐标直接打不开.shx必须几何索引打不开或读取极慢.dbf必须属性表有图形没字段等于只剩一张画.prj强烈建议坐标系定义WKT软件不知道数据在哪只能当无坐标处理.cpg建议.dbf的字符编码声明中文属性名、地名大面积乱码.sbn/.sbx可选空间索引只影响查询速度不影响正确性这套数据一般拆成五个图层省界、市界、县界、道路网、公路网。省、市、县三层在几何上高度重复——市界就是若干县界的外包络省界又是若干市界的外包络。这种冗余不是坏事它给了你一个天然的质检手段三层面积互相校验对不上就说明中间有一层被动过手脚。2.1 省、市、县三级边界的层级关系与编码字段三级边界最关键的不是几何是编码字段。行政区划代码是有层级规律的县级 6 位市级取前 4 位省级取前 2 位同级之间不重复。只要这三层数据都带完整代码你就能用代码前缀做聚合而不必依赖名称字段——名称是最不可靠的东西同一个地名在不同年份的写法可能差一个字代码不会。我一般先做三件事打印每个图层的字段清单和要素数用县级数据按市级代码做 dissolve看能不能还原出市界图层把县级面积求和跟市界、省界的面积对一遍。import geopandas as gpd from pathlib import Path root Path(./js_2022_07) # 逐个读入五个图层文件名当 key生产环境建议用显式映射表避免文件名改了就崩 layers {} for shp in sorted(root.glob(*.shp)): gdf gpd.read_file(shp) layers[shp.stem] gdf print(f{shp.stem:10s} 要素{len(gdf):5d} CRS{gdf.crs}) print( 字段:, , .join(gdf.columns))这段的作用是把「黑匣子」打开。len(gdf)告诉你要素数县界正常是几十个量级如果打印出来几百上千八成是几何被打碎成了碎面需要先做融合。gdf.crs是坐标系的直接证据如果输出None说明.prj丢了或者压根没写后面的所有距离、面积计算都不能信。字段清单重点看三样有没有代码字段、有没有名称字段、有没有等级或类型字段。county layers[county].to_crs(projtmerc lat_00 lon_0120 k1 x_0500000 y_00 ellpsGRS80 unitsm no_defs) city layers[city].to_crs(county.crs) prov layers[province].to_crs(county.crs) # 县级按市级代码融合看能否还原市界数量 city_check county.dissolve(bycity_code) print(市界要素数:, len(city), 由县融合得到:, len(city_check)) # 面积三层互校单位平方公里 a_county county.geometry.area.sum() / 1e6 a_city city.geometry.area.sum() / 1e6 a_prov prov.geometry.area.sum() / 1e6 print(f县汇总{a_county:.1f} 市汇总{a_city:.1f} 省{a_prov:.1f}) print(f相对偏差{abs(a_county - a_prov) / a_prov:.3%})to_crs里的投影参数是横轴墨卡托中央经线取 120°Eunitsm保证输出单位是米ellpsGRS80与 CGCS2000 的椭球一致。选 120°E 是因为全省经度跨度大约从 116° 多到 121° 多120° 居中边缘变形可以接受如果你只做某一个片区把lon_0换到片区中心经线长度精度还能再提一档。dissolve(bycity_code)是按属性字段做几何融合同名同代码的县会被合并成一个面。如果融合得到的要素数跟市界图层的要素数不一致只有两种可能代码字段有缺失值或者市界图层本身就是从别的来源拼的。面积互校的相对偏差我的经验阈值是千分之一量级——正常的数据源三层是同源派生能对到 0.1% 以内超过 1%基本可以判定其中一层被单独编辑过后面做统计就得留个心眼。注意名称字段不要拿来做连接键。地名有简称、全称、带「市」「区」和不带两种写法跨图层匹配必然漏。2.2 道路网和公路网不是一回事属性字段决定能做什么分析这两套线数据经常被当成一回事实际上服务的是两类完全不同的分析。公路网是区域尺度的覆盖高速、国道、省道、县道、乡道这一套等级体系几何上是一条条长线段跨市跨县一路贯通中间很少有断点。它适合干的活是区域连通性、货运通道识别、城镇之间通行时间估算、 highway 缓冲区覆盖分析。道路网是城市尺度的覆盖主干路、次干路、支路、街巷几何碎得多一个路口就是一个节点密集区里几十米一条线段很常见。它适合干的活是城市内部可达性、街区尺度的路网密度、慢行网络连通性、最后一公里配送路径。把两者混着用会出很典型的错拿公路网去算城区路网密度结果城区里只有几条国省道穿过密度算出来低得离谱跟直觉完全相反反过来拿城市道路网去算市到市的通行时间中间大片农村没有路图直接不连通。所以拿到数据后第一件事是把两套线的属性字段摸清楚尤其是等级字段。字段名各家不一样常见的是class、type、grade、fclass这一类值可能是中文「高速公路/国道/省道」也可能是数字编码还可能是英文缩写。先看值分布再决定怎么映射。import pandas as pd for key in [road, highway]: gdf layers[key] print( * 40, key, len(gdf)) # 逐字段看唯一值数量唯一值少的字段大概率是分类字段 for col in gdf.columns: if col geometry: continue n gdf[col].nunique(dropnaTrue) if n 20: print(f {col}: {n} 类 -, gdf[col].value_counts().head(20).to_dict())nunique()是筛分类字段的快捷手段字符串字段里唯一值只有个位数的基本就是等级、类型、车道数这类枚举字段唯一值上千的多半是名称或编号不适合做分组维度。筛出等级字段后建一张映射表统一口径这是后面所有统计的前提原始值示例形态归并后等级是否参与机动车分析说明高速 / expressway高速是封闭道路出入口有限不能随意接入国道 / national干线是跨区域主通道省道 / provincial干线是区域次通道县道 / county集散是连接乡镇乡道 / rural支线是通村道路路面宽度差异大主干路 / 次干路城市主次干是仅存在于道路网图层支路 / 街巷 / 步行城市支路视需求做车行分析时应剔除import re # 用正则把中英文、简写统一归到四个等级避免逐值 if-else 写几十行 def normalize(v): if not isinstance(v, str): return 未知 s v.strip().lower() if re.search(r高速|express, s): return 高速 if re.search(r国道|national, s): return 干线 if re.search(r省道|provincial, s): return 干线 if re.search(r县道|county, s): return 集散 if re.search(r乡道|rural|village, s): return 支线 if re.search(r主干|次干|arterial, s): return 城市主次干 return 其他 for key in [road, highway]: gdf layers[key] col class # 换成你实际数据里的等级字段名 gdf[grade_std] gdf[col].map(normalize) print(key, gdf[grade_std].value_counts().to_dict())map(normalize)会把每个原始值过一遍函数输出标准等级。正则里用re.search而不是是因为原始值经常带后缀比如「国道G104」「省道(S223)」精确匹配会大面积漏。跑完之后看「其他」和「未知」的占比超过 5% 就说明映射规则没覆盖全要把这些原始值单独列出来人工归类别硬着头皮往下走——分类错一次后面所有密度、可达性数字全是错的。还有一个容易被忽略的字段单双向和车道数。做路径分析时单行道如果按双向处理规划出来的路线会穿墙公路网里的高速如果按普通道路处理短途路径会全跑到高速上去。有这两个字段就留着没有就退化成「全部双向、不限等级」并在结论里注明这个假设。3. 坐标系与投影先把「对不齐」这件事解决掉所有「数据看起来没问题但结果不对」的事故里坐标系问题占一半以上。判断方法很简单把边界和路网叠在一起找一个你熟悉的交叉口看路线是不是压在路上。差几十米是投影差异差几百米基本就是基准面不同差几度就是地理坐标被当成投影坐标在用。3.1 WGS84 / CGCS2000 / 地方投影带的判断与统一常见的三种状态第一种是地理坐标系单位是度.prj里能看到GCS_WGS_1984或CGCS2000这类字样。这种数据能直接显示但不能直接算长度和面积算出来单位是「度」一个度在江苏大约对应纬度方向 111 公里、经度方向 95 公里左右不换算的话密度值会错得离谱。第二种是投影坐标系单位是米.prj里带 Gauss_Kruger、Transverse_Mercator 之类的投影名和中央经线。这种可以直接算但要确认中央经线是不是覆盖你的研究区——用错带号边缘区域长度会有千分之几的系统偏差做长距离统计时不可忽略。第三种是没有.prj软件按默认值猜猜成什么全看软件心情。这是最危险的状态因为图形看着是正常的只是位置偏了。统一策略我一般这样定分析用投影坐标展示用地理坐标中间不来回转。投影坐标统一到一个自定义的横轴墨卡托中央经线按研究区选地理坐标统一到 CGCS2000 经纬度。不要一会儿转 Web 墨卡托一会儿转回经纬度每转一次都是一次重采样累积误差最后会体现在面积统计上。用途推荐坐标系单位理由长度、面积、密度统计横轴墨卡托中央经线 120°E米变形小单位直观路网拓扑与连通性同上米节点容差用米更好设空间连接与叠加同上米避免跨界判断的浮点误差出图、切瓦片CGCS2000 经纬度或 Web 墨卡托度 / 米底图匹配3.2 用 Python 批量重投影与面积长度核算不要一个图层一个图层手改写个循环一次做完同时把核算结果打出来。手动改最容易出的错是漏掉某一层——五个图层里漏一个叠加时就会得到一堆空结果而你还以为是数据没覆盖。import geopandas as gpd from pathlib import Path TARGET (projtmerc lat_00 lon_0120 k1 x_0500000 y_00 ellpsGRS80 unitsm no_defs) root Path(./js_2022_07) out Path(./js_2022_07_proj) out.mkdir(exist_okTrue) for shp in sorted(root.glob(*.shp)): gdf gpd.read_file(shp) # 没有 CRS 的数据不能直接转先按下一条规则处理别让它静默变成错的位置 if gdf.crs is None: print(f[跳过] {shp.stem} 缺少坐标系定义) continue gdf_p gdf.to_crs(TARGET) # 长度按几何类型分别统计避免 area 对线返回 0 造成误判 if gdf_p.geom_type.isin([LineString, MultiLineString]).any(): total_km gdf_p.geometry.length.sum() / 1000 print(f{shp.stem:10s} 线总长{total_km:10.1f} km) else: total_km2 gdf_p.geometry.area.sum() / 1e6 print(f{shp.stem:10s} 面总面积{total_km2:10.1f} km²) gdf_p.to_file(out / f{shp.stem}.shp, encodingUTF-8)to_crs是纯坐标变换不改变几何形状的拓扑关系但它要求源数据有正确的 CRS 定义。所以代码里对crs is None做了拦截——这一步很关键很多事故就是把无坐标数据硬转转完图形看着正常实际整体偏了几百公里。length.sum() / 1000把米换成公里area.sum() / 1e6把平方米换成平方公里这两个除法是最容易写错的地方写成/1e3数量级直接差三个零。to_file里的encodingUTF-8会同时写出.cpg文件后面在别的软件里打开就不会乱码。核算怎么做才有意义找一个已知量对一遍。比如省界总面积可以跟公开的行政区面积数据比偏差在千分之几以内算正常超过 1% 就要回头查是不是把某个飞地算重了、是不是中央经线选得太偏、是不是有自相交的多边形导致面积虚高。线数据同理高速公路总里程可以跟公开统计量做个量级对比差一个数量级肯定是单位或者投影出了问题。提示重投影后的数据另存一个目录不要覆盖原始文件。原始文件是唯一的后悔药一旦覆盖坐标系错了就再也回不去了。4. 把边界和路网接起来行政区划承载路网的最小流程数据和坐标系都验完之后真正有价值的活才开始让每一段路知道自己属于哪个市、哪个县让整张路网知道自己通不通。这两件事做不好后面的密度、可达性、覆盖率全是空中楼阁。4.1 用空间叠加把路段归到市县很多人第一反应是空间连接spatial join按「路段落在哪个面里」打标签。这个做法有个隐蔽的坑一条跨市的高速几何上是一个整体空间连接只会给它一个市的名字——通常是它的起点所在市或者是面积重叠最大的那个市。结果就是跨市路段的长度全部算给了某一个市相邻市的里程偏低边界附近的密度统计全歪。正确做法是叠加求交intersection把跨界的路段在边界处切断切成两段分别归属。import geopandas as gpd from pathlib import Path root Path(./js_2022_07_proj) roads gpd.read_file(root / highway.shp) counties gpd.read_file(root / county.shp)[[county_code, county_name, geometry]] # 叠加求交跨界的线会被边界切断每段只落在一个县内 cut gpd.overlay(roads, counties, howintersection, keep_geom_typeTrue) # 切断后按县汇总里程单位公里 cut[len_km] cut.geometry.length / 1000 stat cut.groupby([county_code, county_name], as_indexFalse)[len_km].sum() stat stat.sort_values(len_km, ascendingFalse) print(stat.head(10))howintersection表示只保留两边几何相交的部分keep_geom_typeTrue会丢弃叠加产生的点、面等非目标类型——不加这个参数边界相切处可能冒出零散的点要素把线统计搞乱。overlay的开销比sjoin大不少全省几十万条路段叠加几十个县跑几分钟很正常别以为卡死了。groupby(...).sum()得到的就是每县的公路里程。这一步之后你会发现一个有意思的现象面积大的县里程不一定长山区县路网稀疏、平原县路网密集密度才是可比的指标绝对里程只能看规模。有一个细节要处理叠加会把一条路的属性完整复制到每个切片上包括等级。所以按等级分组统计要在叠加之后做不能在叠加之前先汇总否则跨界的路段等级信息会在切片时丢失一致性。# 按县 等级双维度统计用于后面算分等级路网密度 by_grade (cut.groupby([county_code, grade_std], as_indexFalse)[len_km] .sum() .pivot(indexcounty_code, columnsgrade_std, valueslen_km) .fillna(0)) print(by_grade.head())pivot把长表转成宽表每行一个县、每列一个等级缺失填 0。这个表后面直接可以拼到县级面图层上做专题图也可以喂给聚类模型做区域分类。4.2 路网拓扑打断、连通、断头路排查Shapefile 里的线几何上是一段一段独立存储的。两条线在图上看着相交实际上只是坐标碰巧重合属性上没有任何关系。所有基于图论的分析——最短路径、连通分量、服务区——都必须先把线转成「节点 边」的图结构。第一步是节点打断noding把所有线放在一起在交点处切开保证每条边只连接两个端点。import shapely from shapely.ops import unary_union lines list(roads.geometry) merged unary_union(lines) # 关键一步在交点处自动打断 parts list(shapely.get_parts(merged)) print(打断后线段数:, len(parts))unary_union对线集合做的是「合并 打断」输出里的每一段都在交点处断开这是建图的前置条件。直接拿原始线段建图相交但不相连的两条路会被当成两个孤立网络最短路径永远绕不过去。第二步建图。节点用坐标元组做 key为了避开浮点误差坐标要四舍五入到厘米级。import networkx as nx from shapely.geometry import LineString G nx.Graph() # 无向图有单行道字段就换成 nx.DiGraph for seg in parts: coords [(round(x, 2), round(y, 2)) for x, y in seg.coords] for a, b in zip(coords[:-1], coords[1:]): if a b: continue w LineString([a, b]).length # 同一对节点之间保留最短的一条避免重叠路段把权重叠加 if G.has_edge(a, b): G[a][b][weight] min(G[a][b][weight], w) else: G.add_edge(a, b, weightw) print(节点数:, G.number_of_nodes(), 边数:, G.number_of_edges()) # 连通分量正常情况下主体应该是一个巨大的连通块外加若干岛屿 comps sorted(nx.connected_components(G), keylen, reverseTrue) print(最大连通块占比: {:.2%}.format(len(comps[0]) / G.number_of_nodes())) # 度为 1 的节点就是断头路端点按坐标打出来做人工核查 dead_ends [n for n, d in G.degree() if d 1] print(断头节点数:, len(dead_ends))round(x, 2)是必须的坐标保留两位小数相当于 1 厘米容差能吃掉大部分浮点误差不 round 的话差值 1e-9 米的两个点会被当成两个节点图会碎成几万个小块连通分量分析彻底失效。nx.Graph换成nx.DiGraph的前提是数据里有可靠的单向字段。没有单向信息时用无向图代价是路径结果偏乐观规划出来的路线可能逆行这一点要在结果说明里写清楚。「最大连通块占比」是我判断路网质量最常用的一个指标。城市道路网正常在 90% 以上公路网因为存在大量尽头路通往村庄、厂区70% 到 85% 都算合理。如果掉到 50% 以下八成不是数据本身的问题而是打断不彻底或者容差设小了需要回头调round的位数。断头路的处置分两类一类是真实的尽头路保留一类是数据缺失导致的断口——两条本该相接的路差了十几米中间没有连接段。后者要人工补线或者用容差吸附直接忽略会让可达性结果严重低估。我的做法是把断头节点导成点图层跟影像或者更高精度的路网叠一下肉眼扫一遍成片出现的断头区基本就是数据缺失区。5. 避坑与排查这套 SHP 数据最常见的 5 个翻车点下面五条按「踩到的概率」排序每一条我都实际遇到过附上现象、原因和处置方式。5.1 属性表中文全是问号或方块现象几何正常打开属性表地名、等级字段全是???或者乱码字符。原因.dbf是老格式字符编码没有自描述能力全靠.cpg文件声明。数据生产端用了 GBK 写你这边按 UTF-8 读或者反过来就会乱。有些数据包干脆没带.cpg软件只能猜。解决先看有没有.cpg没有就按 GBK 试读读进来之后立刻用 UTF-8 重新落盘把.cpg补上。import geopandas as gpd for enc in [UTF-8, GBK, GB18030]: try: gdf gpd.read_file(county.shp, encodingenc) print(enc, 读取成功样例行:, gdf.iloc[0].to_dict()) break except Exception as e: print(enc, 失败:, e) # 统一落盘为 UTF-8会同时生成 .cpg gdf.to_file(county_utf8.shp, encodingUTF-8)GB18030是 GBK 的超集优先试它覆盖面更广。判断读对了没有不要只看有没有报错——乱码是不报错的要看具体值是不是正常汉字。5.2 字段名被截断成 10 个字符现象county_name变成了county_namroad_grade变成了road_grade刚好 10 位没变稍长一点的直接截断两个不同字段截断后还可能重名后一个把前一个覆盖掉。原因Shapefile 的.dbf规范限制字段名最长 10 个字符这是格式本身的天花板不是软件 bug。解决两条路。短期在读取时改名长期转成 GeoPackage 或 GeoJSON 再干活。import geopandas as gpd rename_map { county_nam: county_name, road_grade: grade_std, city_code: city_code, } gdf gpd.read_file(roads.shp).rename(columnsrename_map) # 长期方案转 GPKG字段名长度不再受限也没有 2GB 单文件限制 gdf.to_file(roads.gpkg, layerroads, driverGPKG)rename要基于实际截断结果写别凭想象。先print(gdf.columns)看清楚再改。转 GPKG 之后后面的叠加、分组都在这份数据上做能省掉很多重复的脏活。5.3 省界和县界对不上出现缝隙和重叠现象县级数据融合出来的省界跟省界图层叠在一起边界处能看到一条几米到几十米的细缝或者反过来有一小块重叠。原因三层边界不是同源派生而是各自独立采集或者在不同年份更新过。缝隙和重叠的宽度通常跟采集精度同一量级。解决以最细的县级为准向上聚合而不是拿三层互相修补。county gpd.read_file(county.shp).to_crs(TARGET) prov_fix county.dissolve(byprov_code, as_indexFalse) prov_fix[area_km2] prov_fix.geometry.area / 1e6 print(prov_fix[[prov_code, area_km2]])dissolve按省代码把所有县合成一个面缝隙和重叠在融合过程中被吞掉得到的省界跟县界严丝合缝。代价是它会丢掉原省界图层里可能带的额外属性需要的话用aggfunc指定保留哪些字段。做任何跨层级统计之前先做这一步能避免大量「面积总和对不上」的返工。5.4 路网等级口径不一致同一等级出现四五种写法现象按等级分组出来十几个类别「高速」「高速公路」「G 字头」「expressway」各占一行每一行的里程都不大。原因两套路网来自不同生产端或者同一套数据在不同年份拼接编码体系没统一。解决回到 2.2 的映射表把所有原始值先value_counts()打出来逐条归类别指望正则一次覆盖全。归完之后做一次交叉验证高速里程除以总面积算出高速密度跟常识比一下量级差太多说明还有类别没归进去。5.5 坐标系看着有实际是错的现象.prj存在图层能正常加载但跟其他数据叠在一起偏几百米且偏移方向一致。原因数据被错误地赋予了一个坐标系。常见于从别的系统导出时默认填了 WGS84实际是地方坐标或者做过偏移处理但.prj没跟着改。解决用已知地物反查。找一个图上能明确识别的交叉口或者行政中心对比它在其他可靠数据里的位置量出偏移量。如果偏移是固定值可以在加载时做平移纠正如果偏移随位置变化说明是投影参数错了得重新定义坐标系而不是平移。注意纠偏之前先备份原始文件。偏移纠正一旦做错方向数据会偏得更远而原始文件是唯一能回退的版本。6. 从「有一份数据」到「能出结论」按县算路网密度并做一次自检前面所有步骤的终点是能算出一个敢写进报告的数字。最常用也最容易被质疑的指标是路网密度单位面积上的道路里程km/km²。它比绝对里程公平因为把面积差异归一化了。算之前先明确三件事用哪套路网、算哪些等级、分母用行政区面积还是建设用地面积。这三个选择不同结果能差好几倍。我的默认配置是公路网算干线及以下剔除高速因为高速主要服务跨区域通过性不代表本地可达性分母用行政区总面积得到的是「区域路网密度」如果要做城市内部评价换成道路网、剔除步行街巷、分母改建成区面积。import geopandas as gpd TARGET (projtmerc lat_00 lon_0120 k1 x_0500000 y_00 ellpsGRS80 unitsm no_defs) county gpd.read_file(county.shp).to_crs(TARGET) roads gpd.read_file(highway.shp).to_crs(TARGET) # 剔除高速只保留干线与集散道路 roads roads[roads[grade_std].isin([干线, 集散, 支线])] cut gpd.overlay(roads, county[[county_code, geometry]], howintersection) cut[len_km] cut.geometry.length / 1000 road_len cut.groupby(county_code, as_indexFalse)[len_km].sum() county[area_km2] county.geometry.area / 1e6 res county.merge(road_len, oncounty_code, howleft).fillna({len_km: 0}) res[density] res[len_km] / res[area_km2] # 自检密度分布是否合理极值是否来自数据问题而非真实差异 print(res[density].describe()) print(res.nlargest(5, density)[[county_code, density, area_km2]]) print(res.nsmallest(5, density)[[county_code, density, area_km2]])merge(howleft)保证没有路过的县也留在结果里fillna(0)把它们的里程补零——不做这一步密度表会少几个县做专题图时出现空白很容易被当成渲染问题排查半天。describe()和极值检查是这套流程里我最看重的一步。密度最高和最低的五个县如果面积特别小或者特别大先怀疑是面积统计口径的问题如果面积正常但密度异常回去看这几个县的叠加结果是不是有重复路段。踩过一次坑某段路在两个图层里各存了一份叠加后里程翻倍密度直接冲到全省第一查了两天才发现是数据重复而不是真的路多。去重的方法是按几何长度和起终点坐标做一次判重# 起终点各保留 2 位小数做键长度相同的视为重复路段 key (cut.geometry.apply(lambda g: (round(g.bounds[0], 2), round(g.bounds[1], 2), round(g.bounds[2], 2), round(g.bounds[3], 2), round(g.length, 1)))) dup key.duplicated(keepfirst) print(疑似重复路段:, dup.sum()) cut_dedup cut[~dup]bounds返回 (minx, miny, maxx, maxy)加上长度一共五个数做联合键重复概率极低偶尔会把两条完全平行的短路段误判成重复但量级很小可以接受。最后说个习惯任何一版密度结果出来我都会挑三个县把它们的路网单独导出来跟底图影像叠一眼。数字可以靠代码算但「这段路是不是真的存在」只能靠眼睛。图上看着路网密布、数字却很低八成是等级映射漏了类别被过滤掉了图上稀稀拉拉、数字却很高多半是重复或者边界切分出了问题。这个动作花五分钟能挡掉大部分返工。这套数据真正值得投入的地方不是它有多全而是它把行政边界和两套路网放在了同一个时空基准上让「路属于谁、密度多少、通不通」这三个问题可以在一个流程里连着答完。我的建议是先拿一个市的数据把这条链路跑通从读字段、统一坐标、叠加切分到密度输出一整套走完再往上堆全省的量别一上来就全省跑出了错根本定位不到是哪一层的问题。希望帮到你。本文还有配套的精品资源点击获取