news 2026/9/15 14:05:40

辽宁省道路数据处理:shp文件配对、坐标统一与断头路修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
辽宁省道路数据处理:shp文件配对、坐标统一与断头路修复

简介:一份面向地理信息系统开发、城乡规划与交通研究者的辽宁省道路矢量数据集,分级精细到乡道,可直接用于地图制图、空间分析与路网建模。压缩包内含97个文件,以12套Shapefile核心数据为主,配套属性表、投影文件、空间索引及元数据,另附1张预览图,总大小约67.77MB。数据覆盖城市一级至四级道路,以及高速、国道、省道、县道、乡道等行政等级道路;同时整合OSM来源的铁路/地铁/轻轨与各级道路图层,既能用于宏观路网展示,也能支撑微观道路分类研究。目前已有206人浏览/学习,适合需要现成矢量底图进行制图、叠加分析或规划辅助的从业者与学生;借助属性表与投影文件可快速拼接、裁剪和符号化,提升前期数据处理效率。

1. 为什么这套辽宁道路数据比单层 shp 更值得拆

拿到辽宁省道路数据压缩包的第一反应是“文件又多又乱”,但仔细捋一遍会发现它其实做了三层分类:第一层是行政等级道路,包含高速、国道、省道、县道、乡道;第二层是城市内部道路,按一至四级划分;第三层是 OSM 来源的道路和铁路地铁轻轨数据。一共 16 种道路类型,文件后缀涵盖 shp、dbf、prj、cpg、sbn、sbx、shx 和 xml,这意味着一份 shp 实际上由至少 5 个配套文件组成。如果只拖拽 .shp 进软件,很容易因为缺少 .prj 或 .dbf 导致坐标系丢失或属性表为空。

这套数据的价值在于“分级精确到乡道”,对做乡镇级路网规划、物流路径分析和国土空间规划底图的人来说,比只给高速国道省道的通用路网要细得多。但反过来,分级过细也带来两个问题:一是多个图层之间边界重叠、属性字段口径不一致,不能直接合并;二是城市道路和行政等级道路之间存在重复,比如一条省道穿城而过时,既出现在省道图层里,也出现在城市一级道路图层里。适合用它的人,是手里已经有行政区划边界、想做路网密度统计或通达性分析的从业者,而不是只要一条全国粗线缆的普通用户。

2. 解开压缩包后的文件配对逻辑与坐标系核对

2.1 shp 不是一个文件而是一组文件,缺一不可

Road 数据包里的每个图层,例如乡道_辽宁省.shp,实际配套了至少 6 个文件:.shp存几何要素,.dbf存属性表,.shx是几何索引,.prj记录坐标系,.cpg指定字符编码,.sbn.sbx是 ESRI 的空间索引。很多人把.rar解压后只复制.shp到工作目录,结果属性表字段全乱码或软件提示“无法打开”。这里最容易被忽略的是.cpg,它决定了 dbf 里的中文字段是用 UTF-8 还是 GBK 读取。

# 解压后建议先用 ogrinfo 检查完整性,直接读取一个图层的元信息 ogrinfo -so 乡道_辽宁省/乡道_辽宁省.shp 乡道_辽宁省 # 输出示例 # INFO: Open of `乡道_辽宁省.shp' using driver `ESRI Shapefile' successful. # Layer name: 乡道_辽宁省 # Geometry: Line String # Feature Count: 15234 # Extent: (119.526575, 38.694588) - (125.788512, 43.481344) # Layer SRS: # WGS 84

ogrinfo -so只读概要不遍历全部要素,适合解压后快速验证文件是否完整。如果输出里出现Feature Count: 0或直接报错,说明.shp.shx配对出了问题,重新解压即可。注意这里Layer SRS显示的是WGS 84,说明数据是经纬度坐标系,没有做投影,后续算长度和面积前必须先转投影坐标系。

2.2 用一个脚本批量核对所有图层的坐标系和要素数量

这套数据里 16 个图层分散在不同目录,手动一个一个看很容易漏。更稳妥的方式是用 Python 的geopandas读取目录下所有 shp,输出一个对照表,重点看三项:要素数量、几何类型、坐标系是否统一。行政等级道路和城市道路理论上都来自同一套基础数据,但 OSM 来源的图层则不同,OSM 原始数据多为 WGS84 地理坐标系,而国内编辑过的数据可能已经被转换为 CGCS2000,两者混用会导致叠加时偏移几百米。

import geopandas as gpd import glob, os shp_files = glob.glob("辽宁省道路数据/**/*.shp", recursive=True) rows = [] for shp in shp_files: try: gdf = gpd.read_file(shp, encoding="utf-8") rows.append({ "file": os.path.basename(shp), "count": len(gdf), "geom": gdf.geom_type.unique().tolist(), "crs": str(gdf.crs), "columns": list(gdf.columns[:8]) }) except Exception as e: rows.append({"file": os.path.basename(shp), "error": str(e)}) overview = gpd.pd.DataFrame(rows) print(overview.to_string())

这段代码递归读取辽宁省道路数据目录下的所有 shp,并把每个图层的要素数量、几何类型、坐标系、前 8 个字段名汇集给截图做比对。逻辑上,glob.glob负责递归找文件,gpd.read_file读取时显式指定encoding="utf-8",确保 dbf 中文属性不乱码。如果某些shp 的 cpg 声明为 GBK,则需要把 encoding 改成"gbk",否则会出现属性字段名为乱码。.cpg文件的存在并不意味着每个图形文件都一定用它,geopandas默认优先读取.cpg里写的编码,但遇到空.cpg时会回退到系统默认编码,这就是为什么同一套数据里有的图层读取正常、有的乱码。

2.3 坐标系统一:CGCS2000 与 WGS84 混用时的偏移处理

核对完会发现,铁路地铁轻轨OSM_辽宁省.shp大概率投影信息不完整或字段结构与其他图层不同。OSM 来源数据的属性表字段名通常是namehighwayrailwaylanes,而行政等级道路的字段名则是道路编码道路名称行政等级,直接合并图层会字段对不齐。常见做法是先统一坐标系,再按需提取字段。

# 用 ogr2ogr 将 WGS84 的 OSM 图层转换为 CGCS2000 / 3-degree Gauss-Kruger CM 123E ogr2ogr -t_srs EPSG:4548 -overwrite \ 铁路地铁轻轨OSM_辽宁_4548.shp \ 铁路地铁轻轨OSM_辽宁省.shp

EPSG:4548 是 CGCS2000 三度带、中央经线 123E 的投影坐标系,辽宁大部分区域用这个带能保证长度变形控制在 0.07 米以内。转换时注意-t_srs是目标坐标系,不是源坐标系,源坐标系不写默认从 prj 文件读取。如果原始 shp 的 prj 丢失,ogr2ogr会默认按 WGS84 解释,导致转换出的坐标偏差。所以解压后第一件事永远是检查 prj 是否存在,而不是急着转投影。

3. 按行政等级与城市道路分类做分层提取与拓扑检查

3.1 字段口径差异:行政等级道路和城市道路不能直接 union

高速_辽宁省.shp城市一级道路_辽宁省.shp直接叠加时,会发现城市一级道路里包含了部分穿城高速的线段,但属性表完全不一样。高速层的字段是道路编号(G15)技术等级,城市道路层则是道路名称(青年大街)道路等级(主干道),两者没有共同主键,无法按字段关联合并。

所以处理顺序应该是:先按道路功能分层,再跨层去重。常见做法是给每个图层加一个来源标记字段,然后统一字段结构,再按空间位置做去重或融合。

import geopandas as gpd # 读取两个图层并统一字段结构 admin = gpd.read_file("省道_辽宁省.shp", encoding="utf-8") city = gpd.read_file("城市一级道路_辽宁省.shp", encoding="utf-8") admin = admin[["geometry"]].copy() city = city[["geometry"]].copy() admin["source"] = "省道" city["source"] = "城市一级" combined = gpd.GeoDataFrame(gpd.pd.concat([admin, city], ignore_index=True), crs=admin.crs) combined = combined.explode() # 拆掉 MultiPart,避免一条线包含多个独立的 LineString combined = combined.reset_index(drop=True) print(combined.head())

explode()是关键一步。shp 里一条要素既可以是单根 LineString,也可以由多个不相连的线段组成 MultiLineString,直接做后续的求交、打断、合并会把多段线当一条线处理,导致长度计算错误。这里crs=admin.crs保证了合并后的 GeoDataFrame 继承源坐标系,而后explode()会把 MultiLineString 拆分,并丢弃原来的属性关联。如果要保留属性,可以先用assign把 source 传到拆分后的每一段。

3.2 用空间关系做重复线段剔除:避免同一条路算两遍

高速在某一段和国道共线是常见情况,行政等级重叠导致统计路网里程时会把同一条路重复计两次。排除重复需要做三步:先统一坐标系,再用overlay做线线求交,最后用长度阈值判断是否为同一段。

# 假设 high 和 national 都已转成相同投影坐标 merged = gpd.overlay(high, national, how="union") high["len"] = high.length national["len"] = national.length # 找出长度几乎相等且几何中心一致的路段,视为重复 high["left"] = high.geometry.centroid national["right"] = national.geometry.centroid near_pairs = gpd.sjoin_nearest( high.reset_index(), national.reset_index(), how="inner", max_distance=5 )

overlay(how="union")会把两个图层的线切割成互相不重叠的线段,然后sjoin_nearest通过几何中心距离判断哪些线段属于同一物理路段。这里max_distance=5表示两个几何中心距离在 5 米内视为同一条路,适合城区道路因为偏移产生小幅不平行的场景。把识别出的重复段从统计表中剔除,再用dissolve(by=...)合并成完整道路。

注意overlay对线要素要求很高,如果源数据里有自相交(self-intersect)的线,会在求交时报错。遇到这种数据,建议先用gdf = gdf.make_valid()修复几何,再执行 overlay。make_valid会拆分自相交的环并重组成合法几何,但也会改变某些线段的顶点数量,所以要在统一坐标系之后做,不要在经纬度坐标系下做,否则修复结果会基于球面弧度,长度和方向都会失真。

3.3 分级属性表重构:把 16 个图层合并成一层带等级字段的路网

这是把这个资源变成可用底图的关键一步。不要把 16 个图层当作 16 个独立要素类管理,而要合成一层,每条线带road_level字段,取值包括高速国道省道县道乡道城市一级城市二级城市三级城市四级铁路地铁轻轨。字段在合成后做优先级排序:当同一条路既在城市图层又在行政图层时,按行政等级优先保留。

-- 在城市道路属性表中增加 road_level,然后 UNION ALL 到总表 ALTER TABLE city_road ADD COLUMN road_level TEXT; UPDATE city_road SET road_level = '城市一级' WHERE level_code = 'L1'; UPDATE city_road SET road_level = '城市二级' WHERE level_code = 'L2'; INSERT INTO unified_road (geometry, road_level, source_name) SELECT geometry, '高速', name FROM highway_layer; INSERT INTO unified_road (geometry, road_level, source_name) SELECT geometry, '乡道', name FROM township_road;

把 SQL 这段当作参照流程来理解:多个 shp 先各自转为 PostGIS 表,再按字段映射插入到统一表。字段映射时注意城市道路的等级字段各层不统一,有的是level_code,有的是road_class,入库前先用gpd.rename把字段名拉齐。UNION ALL保留重复行,后续用空间去重处理,不用UNION是因为几何字段不能简单地 DISTINCT,两个图层即使代表同一条路,顶点加密方式也可能不同,直接 DISTINCT 去不干净。

4. 乡道断头路检测与乡镇路网连通性修复

4.1 用端点相交判断断头路,而不是肉眼找

乡镇级路网最大的坑是断头路,一条乡道在图层里看起来接上了省道,但实际端点有 3~5 米的间隙,因为两段路来自不同图层、顶点不一致。用 QGIS 的拓扑检查插件也能做,但数据量一旦上万条线段,手动修改不现实。我一般用shapely做两步检查:第一步找出所有线段的端点,第二步判断端点是否落在其他线段上,落点距离大于容差就标记为断头。

from shapely.geometry import LineString def find_dangling(gdf, tolerance=3.0): endpoints = [] for idx, row in gdf.iterrows(): geom = row.geometry if geom.geom_type == "LineString": endpoints.append(("start", idx, geom.coords[0])) endpoints.append(("end", idx, geom.coords[-1])) elif geom.geom_type == "MultiLineString": for part in geom.geoms: endpoints.append(("start", idx, part.coords[0])) endpoints.append(("end", idx, part.coords[-1])) other_lines = gdf.geometry.unary_union dangling = [] # 只取 tolerance(单位与坐标系一致)范围内没有其他线的端点 for kind, idx, pt in endpoints: buf = near(pt, tolerance) if not buf.intersects(other_lines): dangling.append((kind, idx, pt)) return dangling

这里的核心逻辑是:每根线的首尾点都被拿出来,在容差距离内找是否有其他线段存在,没有则说明这条线孤悬。tolerance=3.0的意义要按坐标系理解——如果数据在 CGCS2000 投影坐标系下,3 代表 3 米;如果还在 WGS84 经纬度下,3 代表 3 度,约等于 300 公里,完全失真。所以容差检测必须在投影坐标系下进行。实际经验里,乡镇路网断头容差设为 2~5 米比较合理,城市路网因为道路密集,容差可以放到 8 米,但要注意 8 米会把并行的两条路误判成一条。

4.2 自动生成连接线修复小间隙

对断头路做自动修复要非常克制,自动补线只适合 10 米以内的小间隙,超过 10 米建议人工核对。补线的常见做法是找到断头最近的其他线段端点,生成一条直线段连接。这里直线段的问题在于它与道路弧度完全不吻合,如果强行连接,后处理时会出现折角,影响成果美观度和后续网络分析。

from shapely.ops import nearest_points def connect_dangling(gdf, dangling_pts): lines = gdf.geometry.tolist() for pt in dangling_pts: # 从所有线段几何中提取最近的线 nearest_line = nearest_points(pt, gdf.geometry.unary_union)[1] # 取最近点坐标 nearest_p = nearest_points(pt, nearest_line)[1] connector = LineString([pt, nearest_p]) lines.append(connector) return gpd.GeoDataFrame( {"geometry": lines, "connector": [1]*len(lines)}, crs=gdf.crs )

nearest_points函数返回的是两个几何体上最近的一对点,注意nearest_line拿到的不一定是 LineString,可能是 Point 或 MultiPoint,因为unary_union会把所有线合并成 MultiLineString。为了稳定,应该从gdf.geometry.tolist()里逐个算距离取最近的线要素,而不是直接对 unary_union 操作。生成的 connector 线段要加来源标记,方便后续人工审查。

修复后重算一次find_dangling,看断头数量有没有明显下降。如果断头数量反而变多,说明容差设置太小或补线连到了错误位置,需要退回重新调整。路网连接性检测的最终结果,用网路分析工具做一次从乡镇中心到最近国道的路径计算,能通就算闭环。

4.3 成果输出注意编码和字段名,避免交付后乱码

辽宁这套数据交付时经常拷给不同单位用,ArcGIS 默认读取 dbf 的字段名是 GBK,QGIS 默认用 UTF-8,同一个 shp 在两个软件下显示字段名完全不同。所以输出成果前要在属性表里统一字段命名规则,字段名全部用拼音或英文,如road_levelroad_namesource_name,字段中文别名写到字段别名表里,而不是直接写在 dbf 字段名中。.cpg文件统一写成UTF-8,避免 QGIS 识别错误。

# 导出最终成果时强制指定编码 ogr2ogr -lco ENCODING=UTF-8 \ -nlt LINESTRING \ -overwrite \ 辽宁省路网_merge.shp \ 辽宁省路网_merge.gpkg

-lco ENCODING=UTF-8控制 dbf 内部编码标识,-nlt LINESTRING强制所有几何写为单线类型,防止 MultiLineString 在部分软件中显示异常。这里输出用 GeoPackage 作为中间格式,再用 ogr2ogr 转到 shp,好处是中间格式能保留坐标精度,避免 shp 固有的 2 字节长度限制导致坐标末位四舍五入。做完这一步,这 16 个图层才算真正变成一份干净的可分析路网底图,而不是一包原始素材。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 14:05:35

DeepSeek与Claude Sonnet:两大AI模型的核心功能与应用对比

1. 认识两大AI模型:DeepSeek与Claude Sonnet在当今AI技术快速发展的背景下,DeepSeek和Claude Sonnet作为两个备受关注的AI模型,正在不同领域展现出强大的能力。作为一名长期关注AI技术发展的从业者,我发现这两个模型各有特色&…

作者头像 李华
网站建设 2026/9/15 14:05:14

SSM+Vue大学生企业推荐系统:解决校招匹配断层

简介:本资源是一套面向计算机专业本科生的Java毕业设计实战项目——基于SSM与Vue的大学生企业推荐系统,聚焦高校就业服务场景,解决学生求职信息获取低效、企业招聘匹配度不足等实际问题。压缩包共843个文件,涵盖127个Java后端核心…

作者头像 李华
网站建设 2026/9/15 14:04:35

RetroArch 命令行如何用 --subsystem 加载多卡内容?

RetroArch 命令行如何用 --subsystem 加载多卡内容? 【免费下载链接】RetroArch Cross-platform, sophisticated frontend for the libretro API. Licensed GPLv3. 项目地址: https://gitcode.com/GitHub_Trending/re/RetroArch 用 RetroArch 的命令行方式加…

作者头像 李华
网站建设 2026/9/15 14:04:15

API安全设计与合规实践指南

我不能按照您的要求生成相关内容。原因如下:项目标题“神级API,原生外挂,谁用谁好用”及热搜词“神级API”“原生外挂”,结合当前主流技术语境与合规实践,高度指向绕过系统防护、干扰正常服务秩序、破坏公平性机制的技…

作者头像 李华
网站建设 2026/9/15 14:03:06

良品铺子网站规划和建设避坑指南:最佳实践保流量

良品铺子网站规划和建设避坑指南:最佳实践保流量 网站上线三个月,后台看着挺热闹,每天几十上百个IP访问,但销售那边反馈:“这月没成交一单,连个咨询的微信都没留。”这种“有流量无转化”的假繁荣,是无数做品牌官网和电商站最痛心的噩梦。很多甲方以为只要把UI做得像苹果官网那样高大上,或者把产品图片修得比广…

作者头像 李华