简介:这是一份覆盖全国范围的道路交通网络矢量数据集,涵盖高速公路、国道、省道、铁路、县道、乡道六大类道路,构成完整路网底图,适合GIS开发、城乡规划、交通地理分析人员及相关专业学生,用于地图制图、空间查询、路网展示和项目演示。压缩包共38个文件,大小451.25MB,每一类道路均按标准Shapefile格式组织,包含shp几何、dbf属性、prj投影、shx索引、sbn与sbx空间索引以及xml元数据,结构完整,可直接批量加载至ArcGIS、QGIS等平台。数据按照道路等级分层存储,便于分开展示、配色渲染和局部简化;prj投影信息可保证与全国范围其他空间数据准确套合,dbf属性表支持按道路类别、里程或名称进行筛选统计。已有131人学习下载,适合需要现成全国路网底图进行区域交通分析、制图输出或数据合并的用户,省去自行采集、配准和整理环节,快速进入分析流程。
1. 高速国道省道铁路县道乡道.zip:先搞清这份路网压缩包里到底装了什么
拿到“高速国道省道铁路县道乡道.zip”,大多数人第一反应是直接解压、拖进地图软件里看。实际做的时候却经常卡住:有的文件打不开,有的能打开但线和线叠成一团,有的叠加在线底图上后整个路网平移了几百米。这个 zip 不是单一文件,而是一套按道路等级切好的路网矢量包,里面通常包含高速、国道、省道、铁路、县道、乡道这几类线图层,用来做区域路网分布、分级渲染、路线规划前清洗,或者直接当交通分析项目的底图素材。适合做测绘 GIS、地图可视化前端、交通数据分析的人。它的价值不在压缩包本身,而在于能不能快速读成一张字段统一、坐标系一致的路网表;这一步理顺了,后面所有分析都不容易翻车。
2. 打开压缩包之前:路网数据为什么按高速国道省道县道乡道分开存
2.1 压缩包里常见的三种文件形态
先别急着解压,先看 zip 内部结构。常见做法是解压后得到一堆同名不同扩展名的文件,比如高速.shp、高速.shx、高速.dbf、高速.prj,这是最传统的 Shapefile 形态。一个 Shapefile 必须由多个文件共同组成,单独拷走 .shp 是不完整的,拷文件时最容易漏掉 .shx 和 .dbf。
另一种形态是单个 .gpkg 文件,即 GeoPackage。它把六类道路统一放在一个 SQLite 数据库里,内部按图层区分,好处是只有一个文件,复制、共享、做版本管理都方便。还有少数 zip 里直接放 .geojson,这种体积通常更大,因为 GeoJSON 是纯文本,但读取最简单,浏览器和 Python 都能直接解析。
做选择前可以先列一张文件清单确认:
| 形态 | 典型内容 | 使用建议 |
|---|---|---|
| Shapefile 集合 | 每个等级一个文件夹,内含 .shp/.shx/.dbf/.prj | 最常见,需要整套拷贝 |
| GeoPackage | roads.gpkg 或 national_roads.gpkg | 一个文件包含多个图层,推荐优先转换 |
| GeoJSON | 每等级一个 .geojson 或一个大文件 | 适合前端预览,不适合大批量分析 |
拿到 zip 后,我一般先看压缩包里的命名方式。如果文件名里直接带“高速”“国道”“省道”等关键词,说明数据商已经按等级切好,可以直接映射成 level 字段。如果文件名叫 roads.shp 或 road_net.geojson,则说明等级藏在属性表里,加载后要看 type、class、kind 这类字段。这个判断决定了后面合并脚本怎么写,所以开头别省这一步。
2.2 坐标系和属性字段:决定你能否直接用的两个硬指标
路网数据拿到手,最重要的不是看图形漂不漂亮,而是看坐标系。国内常用的路网数据有两种底子:一种是 WGS84 经纬度坐标,GPS 采集和 OSM 数据大多是这个;另一种是火星坐标系 GCJ-02,很多国产地图商数据会偏移,直接叠加在 WGS84 底图上会差几十到几百米。Shapefile 的坐标系写在 .prj 文件里,GeoPackage 的坐标系写在元数据里。可惜不少二手压缩包把 .prj 丢了或者乱写,这时只能靠图形范围和已知控制点去判断。
属性字段是第二个硬指标。一份能直接用的路网表,至少要包含道路名称和等级信息。理想情况下有 name、type、length,甚至有车道数车道宽度。但实际拿到的 zip 质量参差不齐,常见情况是只有 OBJECTID 和 geometry,其他字段全是空的。这种数据画地图没问题,做路径规划就很吃力,因为缺少拓扑代价的计算依据。
所以在解压之后、写业务代码之前,先做一次“家底盘点”。用 ogrinfo 或者 Python 读一下每个图层的字段列表,把有等级字段、有名称字段的图层挑出来,把字段缺失的图层单独标记,后面合并时给它们补默认值。这比把整个 zip 一股脑接进数据库再报错要省事得多。
2.3 拿到数据后先做的三个前置检查
第一个检查是几何类型。路网必须是 LineString 或 MultiLineString,但偶尔会遇到 MultiPolygon 混进来,尤其是铁路数据里容易混进站房面。几何类型不对,后面所有网络分析都会失败,必须先过滤或者报错处理。
第二个检查是坐标范围。用 ogrinfo 或 GeoPandas 看一眼 minx、miny、maxx、maxy。如果范围在 73 到 135、18 到 53 附近,基本是经纬度坐标;如果数值变成几百万,说明是投影坐标,需要先判断坐标系再考虑转回经纬度。如果数值在 120 到 121、30 到 31 附近但却是 GCJ-02,图形看起来正常,但潜在偏移问题仍在。
第三个检查是重复道路。高速和国道在枢纽路段有时被画成两条重叠线,或者县道和乡道在城乡接合部完全重合,这是多源数据拼接常出现的问题。直接合并后做长度统计会被翻倍计算,做路径规划会让权重失真。建议先用属性字段去重,再按空间距离做一次缓冲去重;这一步放在预处理阶段,不要在出图阶段才处理。
把这三个检查写成脚本固化下来,每次拿到新 zip 都跑一遍,比肉眼扫图靠谱。压缩包里到底装的是什么、坐标系有没有统一、字段能不能支撑分析,这三件事弄明白了,这份数据才真正落到你手里。
3. 解压到出图的完整链路:读图层、统一坐标系、合并六类道路
3.1 解压后用 ogrinfo 检查六类图层是否都在
先创建一个目录,把压缩包解开。Linux 和 macOS 下推荐用 unzip,Windows 下用资源管理器解压即可,但要注意中文文件名在部分命令行下会乱码,最好在 Python 脚本中处理。
mkdir -p roads_extracted unzip -q 高速国道省道铁路县道乡道.zip -d roads_extracted ls -la roads_extracted/如果没有 unzip,可以用 Python 的 zipfile 模块解压,这样对中文文件名更友好:
from zipfile import ZipFile with ZipFile("高速国道省道铁路县道乡道.zip", "r") as z: z.extractall("roads_extracted") for name in z.namelist(): print(name)解压后先看有没有 .prj 文件。如果每个 Shapefile 旁边都有 .prj,说明坐标系信息齐全;如果只有 .shp/.shx/.dbf,后面叠加底图时要格外小心。接着用 ogrinfo 看图层信息:
ogrinfo -ro -so roads_extracted/高速.shp ogrinfo -ro -so roads_extracted/国道.shpogrinfo 是 GDAL 自带的命令行工具,输出里会显示图层名、几何类型、要素数量、字段列表。如果机器上没装 GDAL,直接用 Python 也可以:
import geopandas as gpd gdf = gpd.read_file("roads_extracted/高速.shp") print(gdf.crs) print(gdf.geom_type.unique()) print(gdf.head())这段代码输出坐标系、几何类型、前几行数据,相当于把整个压缩包的家底摸了一遍。注意,如果 gdf.crs 是空值,说明数据没有坐标系信息,这时不要继续做合并,先把投影问题解决掉,否则后续所有长度计算和路径分析都会失真。
3.2 用 GeoPandas 读取并以 level 字段合并
确认完数据可用后,下一步是把六类道路合并成一张表,同时加上 level 字段。level 字段一般用字符串标记,高速对应 motorway,国道对应 trunk,省道对应 primary,铁路对应 rail,县道对应 secondary,乡道对应 tertiary。这套命名和 OSM 的公路分级很像,方便后续直接套用成熟样式。
合并脚本的关键在于文件名匹配。如果压缩包里每个文件都带中文关键词,可以按关键词映射,但要注意“高速铁路”和“高速”两个词容易打架。高速铁路属于铁路,不能因为文件名里包含“高速”就先被人为分到高速。解决方法是把更长的关键词排在前面:
import geopandas as gpd from pathlib import Path roads_dir = Path("roads_extracted") classifier = [ ("高速铁路", "rail"), ("铁路", "rail"), ("高速", "motorway"), ("国道", "trunk"), ("省道", "primary"), ("县道", "secondary"), ("乡道", "tertiary"), ] frames = [] for shp in roads_dir.glob("*.shp"): name = shp.stem matched = None for keyword, level in classifier: if keyword in name: matched = level break if matched is None: continue gdf = gpd.read_file(shp) gdf["level"] = matched frames.append(gdf) unified = gpd.pd.concat(frames, ignore_index=True) print(unified["level"].value_counts())这段代码的逻辑是:遍历目录下所有 .shp,按关键词优先级匹配等级,读到 GeoDataFrame 后追加 level 列,最后纵向合并。这里的参数要说明清楚:classifier 列表的顺序不是随便写的,“高速铁路”必须排在“高速”前面,否则会多出几条 rail 被错误标记成 motorway。如果你拿到的是单文件 GeoPackage,就不需要遍历 shp,只要用 gpd.read_file 加 layer 参数逐个图层读取即可:
import geopandas as gpd layers = { "highway": "motorway", "national": "trunk", } all_frames = [] for layer_name, level in layers.items(): gdf = gpd.read_file("roads_extracted/roads.gpkg", layer=layer_name) gdf["level"] = level all_frames.append(gdf)合并时容易遇到字段不一致。有的图层叫 name,有的叫 NAME,有的根本没有名称字段。concat 之后这些列会变成多列,或者全部是空值。建议在合并前先把字段统一:保留 geometry、name、level 三个字段,其他字段按需求选留。字段名不统一是压缩包数据最常见的“坑”,脚本里做一次 rename 最稳妥。
3.3 统一坐标系并补全长度字段
六类道路如果原始坐标系不同,合并后一张表里会出现不同坐标系的要素,这是后续分析最隐蔽的翻车点。合并后要统一指定一个工作坐标系。做全国路网可视化,我一般统一到 EPSG:4326,也就是 WGS84 经纬度,方便在线底图叠加;做距离计算和路径规划,则先转 EPSG:3857 或当地投影坐标系计算长度,再转回经纬度。
unified = unified.to_crs(epsg=4326) unified["length_m"] = unified.geometry.to_crs(epsg=3857).length unified.to_file("unified_roads.gpkg", layer="roads", driver="GPKG")这段代码有三处关键点。第一,to_crs(epsg=4326) 会把所有要素统一到经纬度坐标,如果原始数据是 GCJ-02,这一步只会改变数值表示,不会消除偏移,偏移问题要放到避坑章节处理。第二,length_m 用 EPSG:3857 计算长度,单位是米;EPSG:3857 在高纬度地区长度变形明显,但如果只是做相对比较和筛选,精度够用。第三,保存成 GeoPackage 而不是 Shapefile,避免再次出现“文件缺一个扩展名就打不开”的问题。
保存后,再读一遍确认:
import geopandas as gpd check = gpd.read_file("unified_roads.gpkg", layer="roads") print(check["level"].value_counts()) print(check.crs)看到六类数量都正常、坐标系是 EPSG:4326,这一步才算真正完成。从 zip 到统一路网表,整个链路并不复杂,核心就是关键词映射、字段统一、坐标系统一这三个动作。把这个脚本存成一个prepare_roads.py,以后拿到新的路网 zip 只需要改路径,其他逻辑可以复用。
4. 把分级数据用起来:分等级样式、连通性修复和最短路径
4.1 按等级做线宽和颜色样式
合并出 unified_roads.gpkg 之后,最容易出效果的第一步是分级渲染。高速、国道、省道、县道、乡道在视觉上要一眼能区分,我一般按“高等级路宽、低等级路窄、铁路用虚线”的规则配置。
如果是在 QGIS 里出图,用基于 level 字段的规则式样式。点击图层属性,选择“基于规则”的渲染,按 level 分别配置。这里给一套常见配色和线宽:
const roadStyle = { motorway: { color: "#e31a1c", width: 3.0 }, trunk: { color: "#ff7f00", width: 2.4 }, primary: { color: "#fdb863", width: 2.0 }, secondary:{ color: "#ffff99", width: 1.4 }, tertiary: { color: "#c2e699", width: 1.0 }, rail: { color: "#878787", width: 0.8, dash: true } };这套参数不是死规矩,主要看底图比例尺。全国视角下高速线宽 3.0、国道 2.4 比较合适;缩小到市域视角时,线宽可以整体下调,避免高速像一条粗血管一样盖住其他等级。铁路单独用灰色虚线,是为了和公路在视觉上区分开,因为铁路不能走汽车路径,后续做路由时要过滤掉。
如果直接做 Web 地图,GeoJSON 加载后可以在前端按 level 映射 stroke-width。注意 GeoJSON 属性字段名不要用中文,否则部分地图引擎对中文 key 支持不好;这就是为什么一开始统一成 level 而不是保留“道路等级”这种中文字段名。
4.2 修复线层之间的拓扑断点
合并后的路网看起来完整,但真正做路径规划时经常发现路“走不通”。原因很典型:高速出入口匝道和国道不完全相交,县道和乡道在交叉口处线段端点不重合,两条线在视觉上交叉,但几何上根本没有共享节点。这属于路网拓扑问题,不修复就做的路径规划,结果往往是绕远路甚至无路可走。
先把 MultiLineString 拆成 LineString,再把非相交的断点处理好。最简单的一步是用 GeoPandas 的 explode 拆开多线要素:
import geopandas as gpd gdf = gpd.read_file("unified_roads.gpkg", layer="roads") if (gdf.geometry.type == "MultiLineString").any(): gdf = gdf.explode(index_parts=False).reset_index(drop=True)explode 会把一条多线拆成多条单线,index_parts=False 表示不保留原来的部件序号。拆完以后要检查线的端点:一条线段的端点应该和相邻线段共享同一个坐标。常见做法是把所有端点抽出来,做 0.5 米到 1 米的缓冲区,再把落在缓冲区内的点标记为同一节点,这一步在 GIS 里称为“打断相交线”。
如果不想自己写拓扑算法,可以用 PostGIS 的ST_Touches和ST_Node做线线打断,或者用 pgrouting 的pgr_nodeNetwork。对于数据量在十万级以下的路网,也可以写一个简单脚本:
from shapely.ops import unary_union, linemerge merged = unary_union(gdf.geometry) merged_lines = linemerge(merged)unary_union 会把所有线段求并集,在相交位置自动生成节点;linemerge 再把连接的线段合并成更长的线。这样处理后的路网,拓扑连通性会好很多。但要注意,这一步会把属性信息丢掉,合并后的要素找不到原来的 level。所以一般做法是:先用 unary_union 生成新几何,再用空间连接把 level 属性匹配回去。如果只是做路径规划的底图,这步不能省。
4.3 level 字段在最短路径分析中的加权用法
路径规划的核心不只是“能不能走通”,还要考虑不同等级道路的速度差异。省道和高速明明几何上是连通的,但走高速和走乡道的代价完全不同。把 level 字段换算成时间成本或阻抗成本,是最实用的做法。
用 NetworkX 做简单演示前,先把每个等级设置一个平均速度。单位是 km/h,仅作示例:
| 等级 | 速度 km/h |
|---|---|
| motorway | 100 |
| trunk | 80 |
| primary | 60 |
| secondary | 40 |
| tertiary | 30 |
| rail | 0 |
铁路不参与公路路径计算,直接把 rail 过滤掉。然后按线段的 length_m 和速度计算通行时间,作为边权重:
import geopandas as gpd import networkx as nx gdf = gpd.read_file("unified_roads.gpkg", layer="roads") gdf = gdf[gdf["level"] != "rail"] speed_map = { "motorway": 100, "trunk": 80, "primary": 60, "secondary": 40, "tertiary": 30, } gdf["speed"] = gdf["level"].map(speed_map).fillna(30) gdf["time_min"] = gdf["length_m"] / (gdf["speed"] * 1000 / 60) G = nx.Graph() for _, row in gdf.iterrows(): geom = row.geometry coords = list(geom.coords) for i in range(len(coords) - 1): u = (round(coords[i][0], 6), round(coords[i][1], 6)) v = (round(coords[i + 1][0], 6), round(coords[i + 1][1], 6)) G.add_edge(u, v, weight=row["time_min"] / max(len(coords) - 1, 1))这段代码的时间权重是把整条线的 time_min 平均分配到每一段子线段上,这样不同等级道路在短路计算中才会体现差异。如果不用速度权重,只按几何长度算,县道也会被选成“最短路径”,这显然不符合开车习惯。还要注意,NetworkX 只认精确相同的节点坐标,所以两端坐标要保留足够多的小数位,否则两条线明明在同一点相交,却会因为坐标精度不够而连不上。这种坐标精度问题,属于路网分析里的典型“玄学”,排查时不要只怀疑数据,先从节点匹配查起。
5. 避坑:六类路网一起用时最常翻车的五个现场
5.1 坐标偏移:叠加在线底图上整条路网平移几百米
现象是高速、国道、县道加载到 QGIS 后,和在线影像图叠加时明显错位,乡道偏离更明显。有些路段看起来像把地图平移了 200 到 500 米,在城区尤其夸张。
原因是原始数据可能是 GCJ-02 坐标系,而在线底图用的是 WGS84。GCJ-02 是国内出版地图使用的一种加偏坐标,区别于 GPS 直采的 WGS84,直接叠加必然偏移,并且偏移量不是固定值,东西南北各方向都有差异。
解决方法是先确认坐标系的真实来源。最简单的方式是取一个城区明显路口,把数据里的坐标和 GPS 实测坐标对比,如果差一点,大概率是 GCJ-02。再拿到专用转换接口把坐标从 GCJ-02 转回 WGS84,或者换用支持 GCJ-02 的底图服务。注意,不要用“肉眼看着差不多”来判断,城区道路偏移影响小,山区公路偏移会被放大得很厉害。
5.2 高速铁路被错误合并到高速等级
现象是合并之后 level 字段统计里 motorway 数量暴增,打开地图一看,很多铁路线也被标成了高速,导致样式上出现一条条红线盖住铁路。
原因是文件名匹配顺序不对。“高速铁路”包含了“高速”两个字,脚本先匹配“高速”就把铁路并进高速了。这类长词匹配问题在压缩包按中文关键词命名时非常常见,遇到“国道”“高速铁路”“省道”这类重叠词不能单纯用 if 判断。
解决方法是把更长的关键词放在匹配列表最前面,或者直接按完整文件名做精确映射。凡是共享词根的道路等级,都要先匹配长词,再匹配短词。这个坑只在我第一次写合并脚本时踩过一次,之后就再也没出现过,但也提醒我:永远不要信任文件名,读数据后要看要素内容。
5.3 合并后字段全空,名称信息全部丢失
现象是六类道路合并后,name 字段大量为空,画出的地图标签全是“未命名道路”。原以为压缩包里的数据完整,合并后用才发现每个图层的字段名不一样,有的叫 name,有的叫 road_name,有的叫 NAME,concat 之后 pandas 把它们当成不同列,于是 name 和 road_name 各保留一部分。
原因是不同来源的 shapefile 字段结构不同,直接合并不会自动合并字段。解决是在合并前先做字段统一:
for gdf in frames: if "NAME" in gdf.columns: gdf["name"] = gdf["NAME"] elif "road_name" in gdf.columns: gdf["name"] = gdf["road_name"] else: gdf["name"] = None写完这段后,所有图层都生成一个统一的 name 字段,再 concat 就不会拆成多列。这种方式也适用于长度字段、道路编号字段,统一字段越早做,后面少填很多坑。
5.4 拓扑不连通:路径规划出现“断头路”
现象是最短路径规划跑出来一条极其离谱的路线,明明两条路在交叉口相交,算法却认为不能转向,或者直接报错找不到路径。
原因是形状上相交,几何上没有共享节点。shapefile 中的线在交叉点处可能一条线穿过另一条线,交点处没有打断,所以 graph 构建时各自端点不重合,网络分析自然不连通。
解决方法是先做拓扑修复,再构建图。用 unary_union 打断所有相交线,或者用 PostGIS 的 ST_Node 统一处理。如果项目只用 NetworkX,还可以导入 shapely 的 intersection 来手动加交点。修复后重新抽端点,能明显减少“看似连接实则断”的状况。
5.5 里程统计重复计算:高速和国道共线段被算两次
现象是统计各等级总里程,发现高速里程数字比该省公开数据高出两成。查看数据发现同一段路在高速图层和国道图层都出现,并且几何几乎重合。
原因是多源数据拼接时,一个路段被归类到两个等级。这种共线在枢纽路段、城市快速路和國道重合段特别常见。解决方法是先做重叠检测:用缓冲区和空间连接判断两条线是否高度重合。对于重叠度超过 90% 的线,保留高等级图层,删掉低等级图层。处理完以后,再做一次里程汇总,确保每条物理道路只属于一个等级。里程统计在交通规划里直接影响汇报数字,不能马虎。
6. 进阶:把 zip 路网整理成能定期更新的统一分级图层
如果只是偶尔画一张路网图,前面几步已经够用。但实际项目里,路网数据往往要按月更新,不能每次拿到新 zip 都重新走一遍手工流程。整理成统一分级图层,是往后所有工作的基础。
我的做法是把字段格式固定在 level、name、length_m、speed 这四列上,每次新数据到货,只做映射转换。旧数据里的“highway”字段是 OSM 的标签,和新 zip 里的“道路等级”字段是同一个东西,都要映射到同一套 level 枚举值里。比如 OSM 的 motorway 和 zip 里的“高速”都写成 motorway,省的每次画图前再改样式规则。
另外,我会用脚本自动检查更新后的图层数量,少于六个等级就报错。图层数量是路网数据质量的简单信号,少了等级说明下载或解压时文件缺失,不能继续往后走。检查通过后再跑一遍长度字段计算和拓扑修复,确保新数据和旧数据在分析口径上是一致的。
这里提醒一句,不要只把制图样式当成工作成果。真正要沉淀的是预处理脚本、字段映射表和避坑清单。下次再拿到任何一个“某某交通路网.zip”,直接套用这套流程,半小时内就能从压缩包变成可分析的标准化路网。
我吃过一次亏:早期做离线路网底图时,图省事直接把 shp 丢进 QGIS,结果坐标系没检查,整条高速偏到农田里,还拿这张图给同事做了演示。从那以后,每次拿到 zip,第一件事永远是看坐标系和字段结构,而不是急着看图形。先把标准和流程定住,后面翻车的概率会小很多。希望这篇笔记能帮你从下载 zip 的那一刻起,少走几步弯路。
本文还有配套的精品资源,点击获取