ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

高速国道省道路网zip处理:合并、坐标系统一与避坑指南

高速国道省道路网zip处理:合并、坐标系统一与避坑指南 简介这是一份覆盖全国范围的道路交通网络矢量数据集涵盖高速公路、国道、省道、铁路、县道、乡道六大类道路构成完整路网底图适合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最常见需要整套拷贝GeoPackageroads.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 下推荐用 unzipWindows 下用资源管理器解压即可但要注意中文文件名在部分命令行下会乱码最好在 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_indexTrue) 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, layerlayer_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(epsg4326) unified[length_m] unified.geometry.to_crs(epsg3857).length unified.to_file(unified_roads.gpkg, layerroads, driverGPKG)这段代码有三处关键点。第一to_crs(epsg4326) 会把所有要素统一到经纬度坐标如果原始数据是 GCJ-02这一步只会改变数值表示不会消除偏移偏移问题要放到避坑章节处理。第二length_m 用 EPSG:3857 计算长度单位是米EPSG:3857 在高纬度地区长度变形明显但如果只是做相对比较和筛选精度够用。第三保存成 GeoPackage 而不是 Shapefile避免再次出现“文件缺一个扩展名就打不开”的问题。保存后再读一遍确认import geopandas as gpd check gpd.read_file(unified_roads.gpkg, layerroads) 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, layerroads) if (gdf.geometry.type MultiLineString).any(): gdf gdf.explode(index_partsFalse).reset_index(dropTrue)explode 会把一条多线拆成多条单线index_partsFalse 表示不保留原来的部件序号。拆完以后要检查线的端点一条线段的端点应该和相邻线段共享同一个坐标。常见做法是把所有端点抽出来做 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/hmotorway100trunk80primary60secondary40tertiary30rail0铁路不参与公路路径计算直接把 rail 过滤掉。然后按线段的 length_m 和速度计算通行时间作为边权重import geopandas as gpd import networkx as nx gdf gpd.read_file(unified_roads.gpkg, layerroads) 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, weightrow[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有的叫 NAMEconcat 之后 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 的那一刻起少走几步弯路。本文还有配套的精品资源点击获取
返回列表