
简介覆盖武汉市域范围的陆路路网矢量数据集适合 GIS 初学者、城市规划与交通工程从业者在 ArcGIS 等平台中开展底图叠加、路网密度分析或专题制图。压缩包共 21 个文件核心为道路、武汉市边界、各行政区边界三类 shp 图层每类图层均配套 prj 投影文件、dbf 属性表、shx/sbn/sbx 索引与 xml 元数据整体仅 3.45MB解压后可直接加载也可按道路、市界、区界分别进行符号化与标注。道路数据只包含陆路通达路径未混入地铁、铁路、水路、航空边界数据覆盖市级和区级适用于行政区划统计、交通可达性分析、路网结构可视化、地图服务发布等常见 GIS 工作流并便于后续进行拓扑检查、网络分析等扩展操作。已有 2926 人学习下载作为教学演示、科研底图或项目前期基础数据都很合适二次编辑、裁剪和出图也较为便捷。1. 武汉市路网矢量数据shp一张能跑起来的城市骨架把武汉市路网矢量数据shp拖进 GIS 工具时你看到的不是地图上那种平滑连续的马路而是由成千上万条 LineString 组成的几何网络。每条路都是一个独立要素带着长度、名称、道路等级等属性这份数据才真正具备分析价值。它解决的是一类很具体的问题在没有底图服务也能离线完成的场景里做路网密度、可达性、最短路径、区域规划这些计算。适合 GIS 开发、城市规划、物流调度和数据分析方向的从业者新手可以用它入门空间数据处理熟手能拿来做批量分析和模型输入。很多人拿到这份数据的第一反应是直接用结果翻车集中在几个地方坐标对不上底图、属性字段缺失、交叉路口没有打断。接下来按我的实操顺序讲清楚文件结构怎么看、数据怎么读、坐标和拓扑怎么处理、避坑的重点在哪里。2. 先读懂武汉市路网shp的文件结构与数据选型2.1 路网shp不是“单个文件”图层组织与常见字段一份完整的 Shapefile 至少包含四个文件存储几何的.shp、存储几何索引的.shx、存储属性表的.dbf以及描述坐标系的.prj。很多时候还会有一个.cpg文件用来记录 dbf 的字符编码。武汉市路网矢量数据的核心图层一般是以道路中心线为几何类型的 Polyline 图层要素数量从几千到十几万不等取决于颗粒度。公开渠道能拿到的大多是简化版本属性字段通常比你想的少。我在处理这类数据时一定会先看属性表确定有哪些字段可用。常见的武汉市路网 shp 字段大致如下字段类型说明name字符串道路名称可能为空class / grade整数或字符串道路等级高速、快速路、主干道、次干道、支路length浮点道路长度单位和坐标系相关oneway整数是否单行0/1 或空speed整数设计或限速值经常缺失bridge / tunnel整数0/1 标记桥梁和隧道字段缺失不是不能用而是会在后面的路径规划、路网密度统计里限制分析精度。我接手某城市路网项目时原始数据只有 name 和 geometry道路等级全靠后面从开放数据源补这个过程很耗时但值得。拿到 shp 后先在 GIS 工具里打开属性表扫一眼比直接跑分析靠谱得多。2.2 用 GeoPandas 快速打开武汉市路网shp用 Python 处理这份数据最常见的库是 GeoPandas。它把 pandas 的 DataFrame 和空间几何对象结合读取 shp 只需要一行代码import geopandas as gpd # 读取路网数据中文属性乱码时尝试改成 encodinggbk roads gpd.read_file(wuhan_roads.shp, encodingutf-8) print(要素数量:, len(roads)) print(坐标系:, roads.crs) print(前5行属性:) print(roads[[name, class, length]].head() if name in roads else roads.head()) print(roads.geom_type.unique())这段代码做的事情有四个加载路网、打印要素总数、输出坐标系信息、查看属性和几何类型。encodingutf-8是常见设定但如果文件名或 dbf 里的中文是从旧版软件生成的大概率是 GBK读出来乱码就改成gbk。roads.crs返回的是 EPSG 编号如果输出是None说明 prj 文件缺失后面必须处理和确认坐标系否则数据分析结果全是错的。读取之后的几何检查也建议顺手做掉。roads.geom_type.unique()能快速发现数据里混入 Point 或 Polygon 的情况虽然少见但一旦碰上后续网络建模会直接报错。2.3 为什么选shp而不是GeoJSON或PostGIS武汉市路网数据用 shp 交付不是因为格式新潮而是因为这个格式兼容性最稳。QGIS、ArcGIS、FME 都能直接打开Python 和 Java 的 GIS 库也都支持几乎不会遇到打不开的情况。相比之下 GeoJSON 在 Web 前端很友好但属性表里如果有复杂类型或者中文编码处理起来会多几个坑。PostGIS 功能最强但你需要先装数据库服务一次性分析任务里属于杀鸡用牛刀。三者的实际取舍可以看表维度shpGeoJSONPostGIS打开兼容性全平台全平台需要数据库连接属性字段支持dbf较老但稳定JSON轻量最完整拓扑关系无需自行处理无内置拓扑函数数据量承载单文件 2GB 上限大文件解析慢海量数据无压力适用阶段分析、交付Web 可视化服务端存储与查询我对一份武汉市路网数据的常见处理路径是先用 shp 做清洗和拓扑修复交付给同事时保留 shp需要上 Web 系统时再用gdf.to_file(roads.geojson, driverGeoJSON)转出。这样两头都方便也避免中途格式转换引入几何精度损失。3. 坐标、裁剪和拓扑武汉市路网数据落地的三个硬骨头3.1 坐标系校验不看prj文件就分析等于玄学拿到武汉市路网 shp 的第一个动作不是看路网长什么样而是确认坐标系。.prj文件里面写的是 WKT 形式的坐标系描述但很多简化版数据压根没有这个文件或者坐标值范围明显不对。我见过有人直接拿经纬度图层计算长度出来的 road.length 字段全是零点几跟实际差了几百倍这就是坐标系没搞对。先用 GeoPandas 看一眼当前坐标系print(当前坐标系:, roads.crs)如果输出EPSG:4326说明是经纬度坐标直接做长度和面积计算没有意义。我的习惯是立刻转成投影坐标系最常用的是 Web 墨卡托 EPSG:3857方便和在线底图叠但精度要求高的分析会用 CGCS2000 的投影带roads_proj roads.to_crs(EPSG:3857) print(投影后的坐标系:, roads_proj.crs)to_crs做的是坐标基准转换不是简单单位换算。转换为投影坐标系之后length字段如果要重新计算需要显式更新roads_proj[length_m] roads_proj.geometry.length print(roads_proj[length_m].describe())这一步很关键因为 shp 里自带的 length 字段是原始坐标系下算出来的如果原来是经纬度这个字段基本不能直接用。更新后的length_m单位是米后续路网密度、路径规划都能以它为准。另一点容易忽略的是如果数据本身是国内常用的偏移坐标系 GCJ-02但 prj 写的却是 WGS84直接转 EPSG:3857 仍然会有几十米的偏移。判断方法很简单把路网截取一小段和已知在线底图上的道路对照整体偏移超过几米就要警惕坐标基准不匹配。坐标系特点适用场景EPSG:4326经纬度跨区域通用存储、Web 可视化EPSG:3857全球投影长度有变形和在线底图叠加地方投影坐标系区域形变最小单位米面积、长度、密度计算偏移坐标系与 WGS84 有明显系统差国内地图服务产出的数据3.2 按行政区边界裁剪路网坐标系决定裁剪质量武汉市路网数据覆盖全市范围实际分析时往往只需要其中一部分区域比如某个开发区或者行政区。用边界图层裁剪是高频操作但不少人直接在经纬度坐标系下裁剪结果边界出现锯齿道路被切得支离破碎。原因很简单在 EPSG:4326 下容差和缓冲区都按度计算几米的道路在经度方向可能被误删。推荐的做法是先投影、再裁剪、后过滤roads_proj roads.to_crs(EPSG:3857) boundary_proj boundary.to_crs(EPSG:3857) clipped gpd.clip(roads_proj, boundary_proj) # 过滤裁剪产生的碎片线段长度小于 5 米的基本是切出来的残渣 clipped clipped[clipped.geometry.length 5] print(裁剪前:, len(roads_proj), 裁剪后:, len(clipped))gpd.clip是 GeoPandas 内置的空间裁剪按边界要素的几何范围做交集。两个入参的坐标系必须一致否则结果为空很难排查。长度过滤我一般取 5 米如果分析对象是城市支路这个值可能太小应当根据数据的道路最小长度调整。还有一点裁剪后路网的边缘线会恰好落在边界上后续如果要和相邻区域拼接需要对边界线上的节点做吸附处理否则两片路网之间存在肉眼可见的裂缝。3.3 拓扑修复交叉路口没有节点路径规划直接翻车武汉市路网数据如果来源比较原始交叉路口的状态通常是这样两条路在几何上相交但没有共享端点拓扑上是“跨线”而不是“节点”。这种数据做地图渲染没问题放到路径规划里就成了大麻烦——车辆在这里无法转向寻路算法直接报错或绕远。我用 shapely 的unary_union强制在交点处打断几何from shapely.ops import unary_union, linemerge # 先把所有路网线段合并成几何集合 unioned unary_union(roads_proj.geometry) # 如果是 MultiLineString拆开成单个 LineString linestrings [] for geom in unioned.geoms if hasattr(unioned, geoms) else [unioned]: if geom.geom_type LineString: linestrings.append(geom) elif geom.geom_type MultiLineString: linestrings.extend(list(geom.geoms)) topo_roads gpd.GeoDataFrame( geometrylinestrings, crsroads_proj.crs ) topo_roads[length_m] topo_roads.geometry.length print(拓扑修复前:, len(roads_proj), 修复后:, len(topo_roads))unary_union会把所有相交的线段在交点处切开重复的线段被合并悬挂的短线段保留为单独的 LineString。修复后的要素数量通常会增多因为交叉口被打断成多个线段这是正常现象。如果你后续要用 NetworkX 建模这个步骤是必须的否则图模型里的边是跨越节点的长线带转向约束的分析完全没法做。需要注意unary_union对小数据量友好但十几万要素的全市路网跑起来会比较慢建议在裁剪到目标区域之后再做拓扑修复别一开始就对全量数据操作。其次是修复后原有属性字段会丢需要在执行前把 name、class 等属性按空间位置回填常见做法是用空间连接匹配最近的原始道路线。4. 武汉市路网数据能做什么三个高频落地场景4.1 区域路网密度用缓冲区叠合算覆盖面积路网密度是城市规划里的常见指标但直接用道路总长除以区域面积会低估主干道对周边的服务能力。我常用的做法是对路网做缓冲区叠合再看看缓冲区和区域面积的覆盖率。# 道路中心线向两侧各扩 50 米形成服务覆盖带 buffer_size_m 50 buffered roads_proj.buffer(buffer_size_m) coverage buffered.unary_union area_intersect coverage.intersection(region_proj.geometry.unary_union).area area_region region_proj.geometry.unary_union.area coverage_ratio area_intersect / area_region print(f道路缓冲覆盖面积占比: {coverage_ratio:.2%})buffer参数决定了覆盖带的宽度。50 米适用于城区道路两侧服务范围郊区道路建议改成 100 米或 200 米不然覆盖率会显得过低。这个指标比较适合横向对比不同区域的路网完善程度也能用来排查明显断头路导致的服务空洞。计算前确保区域和路网都在投影坐标系下否则面积数值会偏离真实值很远。4.2 基于路网构建图模型NetworkX 最短路径路网数据变成可计算路径的图需要把 LineString 转化为“节点 边”的结构。每个线段的两个端点作为节点线段长度作为边权重再用 NetworkX 构建有向或无向图。import networkx as nx G nx.Graph() for idx, row in topo_roads.iterrows(): line row.geometry start line.coords[0] end line.coords[-1] length row[length_m] # 一条线段对应一条边权重用投影坐标系的几何长度 G.add_edge(start, end, weightlength) path nx.shortest_path(G, sourcestart_point, targetend_point, weightweight) print(路径节点数:, len(path))这里面最容易被忽略的是节点一致性。不同线段如果端点坐标完全一致NetworkX 会自然把它们合并为同一个节点但如果线段没有经过拓扑修复交叉点处没有共享端点则图里会出现两条边跨越但没有交点的情况。这也是我在上一章反复强调先拓扑修复的原因。做驾车路径时权重可以用实际长度乘以道路等级的折算速度做步行路径可以直接用长度。对武汉市这种大规模路网建议把节点对象映射成整数 ID 代替坐标运行更快。4.3 快速渲染路网用 Matplotlib 做数据体检处理完的路网最好先出图看一遍原文再继续分析很多数据问题在渲染图上比在数值表里暴露得更快。用 GeoPandas 和 Matplotlib 画道路网只要几行代码import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(12, 12)) topo_roads.plot(axax, linewidth0.3, colorgray) # 叠加区域边界 region_proj.plot(axax, facecolornone, edgecolorblack, linewidth1.5) ax.set_title(Topology-fixed road network) plt.axis(off) plt.show()渲染比例尺和线宽根据城区范围调整中心城区路网密集线宽超过 0.5 就会糊成一团。也可以按道路等级给不同颜色比如高速用红色、主干道用橙色、支路用灰色这样一眼能看出图层是否分层准确。如果渲染出来的图边界处有大量悬空短线说明裁剪过滤没做好如果看见两条线交叉但颜色互不连接多半是拓扑问题还没解决。我通常会在拓扑修复前后各出一张图对比着看交叉口的变化比单看数据有说服力得多。5. 武汉市路网数据避坑实录四个翻车场景与解法5.1 图层整体偏移和底图对不上现象把路网和在线地图叠加所有道路朝某个方向偏移了几十米看起来像平行错位局部区域甚至偏移达几百米。原因shp 的 prj 文件丢失数据本身是某种坐标系但 GIS 工具默认按 WGS84 加载或者图层实际是偏移坐标系却被标注成 WGS84。这两种情况表现相似处理方式不同。解决先检查roads.crs如果是None或显示错误找数据的配套说明确认坐标系。我一般会在 QGIS 里尝试几个候选坐标系加载后和底图对照偏移量确认准确后用to_crs转换到目标坐标系。如果已经混入偏移基准不能靠简单的to_crs解决因为偏移量本身不是一个常数这时需要找原始数据源重新导出或者用少量控制点做地理配准抢救。配准属于血泪经验能避免尽量别走到那一步费时且精度不可控。5.2 裁剪后边界出现锯齿和裂缝现象用行政区边界裁剪路网后边界线附近出现大量小于 5 米的碎线段两个相邻区域的路网拼接到一起时中间有缝隙。原因裁剪操作在未投影坐标系下进行空间计算容差按度度量导致边界附近几何精度丢失。另一个原因是路网和行政区边界各自独立本来就没有共享节点裁剪后边界上的道路端点悬空。解决先做投影转换再执行gpd.clip。裁剪后增加长度过滤把 5 米以下的碎片清理掉。要彻底消除裂缝可以把裁剪后的路网在边界处做一次小的buffer(0)融合或者使用snap函数让线段端点吸附到边界线上。这个坑在武汉市这种大范围数据上特别容易遇见因为边界线很长跨区的道路被反复切分。5.3 属性表里找不到道路等级和速度字段现象属性表只有 name、length甚至只有 geometry没有 class、speed、lanes。做路网分析时无法区分高速公路和小区支路路径规划权重没法算。原因公开渠道的路网数据很多经过脱敏和压缩属性字段被裁剪过。有些数据来自 OSM虽然 OSM 有 highway 字段但导出到 shp 时可能会丢掉。这类问题不是数据处理能解决的得在数据源阶段考虑。解决我遇到这种情况时优先用 name 字段结合道路宽度和长度做等级推断。比如名称以“高速”、“大道”、“路”、“街”、“巷”结尾分别对应快速路、主干道、次干道、支路。再不行就从开放数据源里导入属性通过空间连接把缺失属性匹配到现有路网。这里要注意名称推断只能用于初步分析不能用于要求精确的交通仿真误差很大。若做路径规划速度字段缺失时可用默认值比如主干道 40km/h、次干道 30km/h但要写清楚假设。5.4 交叉路口没有共享节点路径规划直接穿过现象两条道路相交在图上看起来像个十字路口但用 NetworkX 寻路时车辆不能在这里转弯路线规划出现绕行或者断头。原因原始数据没有做节点-边拓扑结构线要素在交叉处没有被打断成独立线段。shp 格式本身不强制拓扑关系裁剪、融合、编辑中都可能重新引入这种问题。解决用unary_union统一打断线段再拆分提取 LineString重新生成 GeoDataFrame。修复后的数据里相交节点位置会出现坐标相同或距离足够近的端点。执行后可以用空间连接检查端点匹配数量确保修复真正生效。这个操作会损失非空间属性务必提前保留原始表通过空间位置回填。6. 给路网数据做一次体检六个检查一项脚本搞定我每次拿到新的武汉市路网 shp不管来源声称多标准都会先跑一遍体检脚本。把坐标系、几何类型、要素数量、悬挂端点、重复线段、空值字段一次性查出来免得后面分析到一半才发现问题再回头清理。这里分享一个精简版import geopandas as gpd import numpy as np from shapely.ops import unary_union roads gpd.read_file(wuhan_roads.shp, encodingutf-8) print(1. 要素数量:, len(roads)) print(2. 坐标系:, roads.crs) print(3. 几何类型:, roads.geom_type.unique()) print(4. 空几何数:, roads.geometry.is_empty.sum()) # 重复几何检查按坐标序列去重看数量差 coords_series roads.geometry.apply(lambda g: g.wkb) print(5. 重复要素数:, len(roads) - coords_series.nunique()) # 属性空值统计只打印非全量填充的字段 for col in roads.columns: if roads[col].dtype object or np.issubdtype(roads[col].dtype, np.number): null_count roads[col].isna().sum() if null_count 0: print(f6. 字段 {col} 空值: {null_count})这个脚本不处理数据只输出体检报告。第 1 项确认数据规模第 2 项确认坐标系是否缺失第 3 项发现混入几何的异常第 4 项检查空几何第 5 项用 WKB 序列化坐标来判断几何是否重复第 6 项统计属性空值。跑完之后该不该做拓扑修复、该不该回填属性基本已经有了答案。悬挂线和交叉点数量的检查也可以写在里面但需要遍历所有端点做空间匹配脚本会明显变长有需要时我才会加。我的习惯是把这个体检脚本存成inspect_roads.py每次拿到新数据先运行一遍输出结果贴在项目笔记开头。路网数据是后续所有分析的地基地基没打牢后面跑多少模型都得推倒重来。这些坑都是踩出来的尤其是坐标系和拓扑这两个黑匣子出问题时的表现往往隐蔽。希望这些经验能帮你在拿到武汉市路网数据时少走几步弯路。本文还有配套的精品资源点击获取