简介:面向GIS从业者、城市规划研究者与高校地理信息相关专业学生的武汉市路网矢量数据包,可用于城市交通分析、路网密度统计、行政区划制图及空间建模等场景,帮助读者省去自行采集与数字化道路的工作量。压缩包共21个文件,约3.45MB,以shp、shx、dbf、prj等Shapefile标准组成文件为主,辅以sbn、sbx空间索引与xml元数据,可直接在ArcGIS等软件中加载编辑。数据涵盖武汉市道路矢量图层,以及各行政区和武汉市边界图层,道路部分仅保留陆路数据,不含地铁、铁路、水路与航空要素,边界与路网可配合完成分区统计与专题出图。目前已有2928人学习下载,适合需要快速获取武汉基础路网底图、开展空间分析与制图练习的读者使用。
1. 拿到一份城市路网 shp,先别急着拖进 ArcGIS
上周有个做交通仿真的大四同学找我,说他从网上扒了一份武汉市路网矢量数据 shp,双击打开属性表一看,字段全是FID、Shape、Id这种,道路等级、名称、单双向一个都没有,问我是不是下到了假数据。我让他把图层加载进去按属性符号化,结果主干道、次干道、支路混成一锅粥,连高速和小区内部路都分不出来。这不是数据假,是典型的「原始路网 shp 没做字段清洗」——很多公开渠道拿到的武汉市路网矢量数据,几何是完整的,但属性表是半成品,直接拿去做可达性分析或者路径规划,跑出来的结果自己都不敢信。
这份资源解决的就是「有几何、缺语义」这个断层:它提供的是武汉市域范围的路网矢量数据(shp 格式),核心价值不在文件本身多大,而在于你能不能在 ArcGIS 里把它拆成「高速—主干—次干—支路」四级、补上长度字段、修掉悬挂节点,再喂给后面的网络分析。适合三类人:做城市交通课程设计的学生、需要底图做空间统计的规划从业者、以及拿路网当基础图层做选址或热力分析的数据分析人员。如果你只是想要一张能看的图,随便哪个在线底图都行;但你要做缓冲区统计、OD 成本矩阵、服务区分析,这份 shp 才是能动手改的原材料。
2. 路网 shp 的字段结构与坐标系:先读懂再动手
2.1 shp 三件套到底装了什么
很多人以为.shp就是一个文件,实际上 ArcGIS 和 QGIS 认的是一组同名文件:.shp存几何,.shx存索引,.dbf存属性表,缺一个都打不开或者属性丢失。武汉市路网矢量数据这类线要素图层,几何类型是Polyline,每条记录就是一段道路中心线。你拿到手第一件事不是符号化,是右键图层 → 属性 → 源,看清楚三件事:几何类型是不是 Polyline、坐标系是什么、要素数量级对不对。
常见做法是先用 Python 的geopandas快速体检一遍,比在 ArcGIS 里点来点去快得多:
import geopandas as gpd # 读取 shp,注意 encoding 参数,中文属性常见 GBK 乱码 gdf = gpd.read_file("wuhan_road.shp", encoding="gbk") print("几何类型:", gdf.geom_type.unique()) # 应全为 LineString print("坐标系:", gdf.crs) # 看是否为地理坐标或投影坐标 print("要素数:", len(gdf)) print("字段清单:", list(gdf.columns)) print("前 3 行属性:\n", gdf.head(3))这段代码的逻辑是先确认几何类型统一为LineString,如果混进了MultiLineString,后面做网络数据集会报错;crs决定你量算长度时单位是度还是米,地理坐标系(如 WGS84,单位度)直接算长度会得到荒谬的小数;字段清单则告诉你有没有name、highway、oneway这类语义字段。参数上encoding="gbk"是针对中文属性表的常见设置,如果读出来还是乱码,换成gb18030再试,这是血泪经验——很多 shp 的 dbf 是早期工具导出的,编码标称和实际不一致。
2.2 坐标系选错,长度全废
武汉市路网矢量数据如果给的是 WGS84 地理坐标(EPSG:4326),你在属性表里新建Length字段算出来的单位是「度」,一条主干道可能显示 0.03,毫无意义。正确做法是先投影到适合武汉的平面坐标系。武汉大致位于东经 114° 附近,常用的是 CGCS2000 3 度带第 38 带(中央经线 114°),EPSG 代码 4547 左右,或者直接用 UTM 50N(EPSG:32650)也能凑合,误差在可接受范围。
# 投影到平面坐标系后再算长度,单位才是米 gdf_proj = gdf.to_crs(epsg=4547) gdf_proj["length_m"] = gdf_proj.geometry.length # 统计一下长度分布,判断有没有异常短段或异常长段 print(gdf_proj["length_m"].describe())to_crs是重投影,不是简单改标签,几何坐标会真正换算;geometry.length在投影坐标系下返回米。看describe()的min和max:如果最小长度出现 0.001 米这种,说明有重复点或零长度线段,得先清理;如果最大长度几万米,可能是把整条高速合成了一条 MultiLineString,需要拆分。这一步不做,后面网络分析的距离成本全是错的,属于典型的「玄学结果」源头。
2.3 属性字段的语义映射
原始路网 shp 的属性表往往只有Id和Shape,但做分析至少需要道路等级和单双向。如果数据里带了highway字段(常见于从 OSM 转换来的数据),值可能是motorway、trunk、primary、secondary、residential等;如果没有,就得靠几何特征和长度阈值做粗分类,或者叠加行政区划、用地类型去推断。我一般会先建一个映射字典,把英文等级转成中文四级,方便后续按等级筛选:
# 道路等级映射,没有 highway 字段时先跳过 level_map = { "motorway": "高速", "trunk": "快速路", "primary": "主干道", "secondary": "次干道", "tertiary": "支路", "residential": "支路" } if "highway" in gdf_proj.columns: gdf_proj["road_level"] = gdf_proj["highway"].map(level_map).fillna("其他") print(gdf_proj["road_level"].value_counts())map加fillna("其他")是为了兜住映射表里没有的值,避免产生 NaN 导致后续分组统计丢数据。value_counts()出来之后你要看一眼「其他」占比,如果超过三成,说明映射表覆盖不够,得补规则。这一步的产出是后续所有按等级做符号化、按等级设权重的基础,字段没理顺,网络数据集里的等级字段就是空的。
3. 在 ArcGIS 里把 shp 变成可分析的网络数据集
3.1 加载与拓扑检查:悬挂节点是头号敌人
shp 是「哑」的线要素集合,ArcGIS 的网络数据集要求线在交点处真正连通。原始路网最常见的毛病是:两条路视觉上相交,但端点没咬合,差了几厘米,网络分析时就是不连通,算出来的路径绕一大圈。所以进 ArcGIS 后第一件事是建文件地理数据库(.gdb),把 shp 导进去,然后建拓扑。
操作路径:Catalog 里新建 File Geodatabase → 右键 Import → Feature Class 导入 shp → 新建 Topology → 添加规则「Must Not Have Dangles」(不能有悬挂点)。验证拓扑后,红色标记的就是断头路。处理方式分两种:如果是数据本身缺失,手动补线;如果是端点没咬合,用「Snap」工具按容差吸附,容差设 1~2 米比较稳妥,设太大容易把不该连的连上。
# 用 shapely 预检悬挂点:统计只出现一次的端点坐标 from shapely.geometry import LineString from collections import Counter endpoints = Counter() for line in gdf_proj.geometry: if isinstance(line, LineString): coords = list(line.coords) endpoints[coords[0]] += 1 endpoints[coords[-1]] += 1 dangling = [pt for pt, cnt in endpoints.items() if cnt == 1] print("疑似悬挂端点数量:", len(dangling))这段逻辑是统计所有线段的起点和终点坐标,只出现一次的端点大概率是断头。coords[0]和coords[-1]取首尾点,Counter累计出现次数。注意这是近似判断,环路的端点会出现两次所以不会被误判,但真正的路网末端(比如小区门口)也会被算进来,所以数量只能作参考,具体还得在拓扑里看位置。我一般会把这个数量和总要素数比一下,超过 5% 就说明数据拼接质量一般,得留出清理时间。
3.2 建网络数据集的关键参数
拓扑清理完,就可以建 Network Dataset 了。右键 Feature Dataset → New → Network Dataset,向导里几个参数别乱选:连通性策略选「End Point」(端点连通),因为路网线要素通常在端点相交;如果数据里有立交桥、隧道这种不在地面相交的,需要额外加「Elevation Field」或者用「Any Vertex」策略,但普通城市路网用 End Point 足够。阻抗属性至少加两个:Length(米)和TravelTime(分钟),后者需要你有一个速度字段,没有的话按等级赋默认速度。
| 参数 | 建议值 | 说明 |
|---|---|---|
| 连通性策略 | End Point | 端点相交才连通,适合大多数路网 |
| 阻抗字段 | Length (米) | 最稳妥,不依赖速度假设 |
| 方向属性 | 有 oneway 字段则启用 | 无字段时全部按双向 |
| 等级字段 | road_level | 用于分层显示和权重 |
| 容差 | 1~2 米 | 吸附和连通判断共用 |
建完之后一定要做一次「Build」并检查有没有警告。常见警告是「edge 没有连通到任何 junction」,说明那段线是孤立的,要么删要么补。网络数据集建好之后,Network Analyst 工具条里的「Route」「Service Area」「OD Cost Matrix」才能用,否则你点上去只会提示没有网络。
3.3 用 OD 成本矩阵验证连通性
建完网络别急着做正式分析,先跑一个小规模的 OD 成本矩阵验证。选 5 个分散的起点和 5 个终点,看输出矩阵里有没有大量NULL或者异常大的值。如果某对起终点之间明明直线距离很近,成本却大得离谱,说明中间有断点没连通。
# ArcPy 跑 OD 成本矩阵的骨架,需在 ArcGIS Pro 的 Python 环境执行 import arcpy arcpy.CheckOutExtension("Network") nds = r"C:\data\wuhan.gdb\road_nd" # 网络数据集路径 out = r"C:\data\wuhan.gdb\OD_Result" arcpy.na.MakeODCostMatrixAnalysisLayer(nds, "OD", "Length") arcpy.na.AddLocations("OD", "Origins", r"C:\data\origins.shp", "Name #") arcpy.na.AddLocations("OD", "Destinations", r"C:\data\dests.shp", "Name #") arcpy.na.Solve("OD")MakeODCostMatrixAnalysisLayer的第三个参数是阻抗,必须和网络数据集里定义的阻抗名一致,写错会直接报错;AddLocations的最后一个参数是字段映射,"Name #"表示用 Name 字段做标识,#是占位。Solve之后看OD_Result里的Total_Length,如果出现-1或者空值,就是不可达。这一步是黑匣子打开的关键——不验证就往下做,后面所有结论都建立在「假设连通」上,翻车了都不知道从哪查。
4. 避坑与排查:路网 shp 落地时最容易翻的五个车
4.1 属性表中文乱码,字段名变成问号
现象:用 ArcGIS 打开 dbf,中文字段名显示为???或者乱码,Python 读出来也是乱码。原因:dbf 文件的编码声明和实际存储不一致,早期工具默认用系统本地编码导出,跨环境就崩。解决:先用geopandas以gb18030读取,确认字段内容正常后,重新导出为 UTF-8 的 GeoPackage 或 File Geodatabase,再进 ArcGIS。别在原 shp 上反复改,越改越乱。
4.2 长度字段算出来是小数,单位不对
现象:新建字段算几何长度,主干道显示 0.02。原因:图层是地理坐标系,单位是度。解决:先to_crs投影到 EPSG:4547 或 UTM 50N,再算geometry.length。ArcGIS 里对应的是「Project」工具,投影完再「Calculate Geometry」,单位选米。这一步顺序不能反,先算后投影,字段值不会自动更新。
4.3 网络数据集建完,Route 求解报「无路径」
现象:起终点都在路网附近,但求解结果为空。原因:起终点没有正确「吸附」到网络,或者吸附容差太小。解决:AddLocations时把搜索容差设到 50~100 米,并检查LocationError字段,有错误的点会标出来。另外确认网络数据集的连通性策略不是「None」,None 等于没建连通关系。
4.4 重复线段导致长度翻倍
现象:某段路在属性表里出现两条记录,几何几乎重合,统计总里程偏大。原因:数据拼接时重复导入,或者双向道路被拆成两条同向线。解决:用「Delete Identical」工具按几何去重,或者 Python 里按geometry.wkb哈希去重。去重前先备份,别把真正并行的辅路误删。
4.5 单双向字段缺失,路径规划方向错乱
现象:单行道被当成双向,算出来的路径逆行。原因:原始 shp 没有oneway字段,或者字段值全是空。解决:没有字段就只能按双向处理,并在报告里注明假设;有字段但值不规范(如yes/no、1/0、F/T),先统一映射成Oneway字段的FT/TF/B三值,再建网络。这个字段的清洗规则最好写进脚本,别手动改,几百条以上手动必错。
5. 进阶:把路网 shp 接进 Python 空间分析流水线
ArcGIS 图形界面适合探索和出图,但要做批量统计或者可复现的分析,我习惯把清洗好的路网落到 GeoPackage,然后用geopandas+networkx跑轻量网络分析。这样不依赖 ArcGIS 许可,也方便版本管理。关键技巧是:把线要素转成图结构时,节点用坐标四舍五入到小数点后 5 位做 key,避免浮点误差导致同一交点被当成两个节点。
import geopandas as gpd import networkx as nx gdf = gpd.read_file("wuhan_road_clean.gpkg", layer="road") G = nx.Graph() for _, row in gdf.iterrows(): line = row.geometry coords = list(line.coords) # 首尾点取 5 位小数做节点标识,抵消浮点误差 u = (round(coords[0][0], 5), round(coords[0][1], 5)) v = (round(coords[-1][0], 5), round(coords[-1][1], 5)) G.add_edge(u, v, weight=row["length_m"], level=row.get("road_level", "其他")) # 示例:算某点到其他所有节点的最短路径长度 source = list(G.nodes)[0] lengths = nx.single_source_dijkstra_path_length(G, source, weight="weight") print("可达节点数:", len(lengths), "最远距离(米):", max(lengths.values()))nx.Graph()是无向图,如果数据有单双向就换nx.DiGraph()并按oneway字段决定加边方向;weight="weight"让 Dijkstra 按长度算,换成时间就把weight指向时间字段。round(..., 5)是这套流水线里最不起眼但最救命的细节——不取整的话,相邻线段端点差 1e-9 就会断图,single_source_dijkstra出来的可达节点数会少得离谱。我一般会先打印G.number_of_nodes()和G.number_of_edges(),和原始要素数对一下,节点数明显偏多就是没咬合上。
另一个实用技巧是按等级做子图筛选。做公交可达性时只保留主干道和次干道,把支路滤掉能大幅降低图规模:
# 只保留主干及以上等级,构建子图 main_levels = {"高速", "快速路", "主干道", "次干道"} sub_edges = [(u, v, d) for u, v, d in G.edges(data=True) if d["level"] in main_levels] G_main = G.edge_subgraph(sub_edges).copy() print("子图节点:", G_main.number_of_nodes(), "边:", G_main.number_of_edges())edge_subgraph会保留边两端的节点,但可能产生孤立节点,后续算连通分量时用nx.connected_components检查一下。这套流程跑通之后,武汉市路网矢量数据就不再是一张静态图,而是能反复调参、能出统计口径的分析底座。
从那以后我每次拿到任何城市的 shp 路网,都强制先走一遍「读字段 → 投影 → 算长度 → 查悬挂 → 建图验证」这五步,哪怕数据来源写着「已清洗」也不跳过。因为路网数据的坑从来不在文件本身,而在你以为它已经干净了。希望这份拆解能帮到你,少走几段弯路。
本文还有配套的精品资源,点击获取