资讯动态

全国铁路SHP数据处理:坐标系投影、拓扑打断与最短路构建

发布时间:2026/10/26 3:34:34 来源:尧图企业网站定制
简介全国铁路SHP数据.zip面向地理信息、城市规划、交通工程等方向的学习者与研究人员提供可载入ArcGIS、QGIS等软件的全国铁路线路和2016年高铁站点矢量数据便于完成线路分布分析、站点选址、区域交通研究及专题制图。压缩包共12个文件大小约11.37MB以shp、dbf、prj、shx等格式为主分别承载空间几何、属性字段、坐标定义与索引信息便于准确配准和快速读取其中属性表可用于线路与站点信息查询投影文件有助于统一空间参考、减少图层错位。资源已有2769人学习下载适合GIS入门练习、课程设计、制图作业及科研前期底图准备对交通网络与城市研究具有参考价值。结合铁路线长度统计、高铁站分布模式识别、最近站点查询等空间分析并与人口、经济等数据叠加可服务区域发展研究与规划决策。1. 全国铁路SHP数据不是底图先想清楚你要拿它算什么把「全国铁路SHP数据.zip」解压出来多数人第一件事是拖进 QGIS 看一眼觉得线画得挺全然后就关掉了。这份东西的本质是一组线状要素矢量数据每条记录是一条折线几何配一行属性说明这条线叫什么、属于哪类线路。它能干的事很具体——做路网距离与可达性计算、做站点接驳缓冲区、给物流路径做可视化底图、在内网离线环境里画一张能缩放到全国的铁路示意图。它不能干的事同样具体没有车次时刻、没有客货运量、没有上下行区分几何上也未必真的连通。所以第一步不是画图是决定你要拿它算里程、算最短路还是只当底图。三种用途对数据质量的要求差着一个数量级先定用途再动手能省掉后面一大半返工。2. 拆包先验骨架SHP 五件套与 DBF 字段的 10 字符困局2.1 .shp/.shx/.dbf/.prj/.cpg 各管什么少一个会出什么事Shapefile 从来不是一个文件而是一组同名的伴生文件。压缩包里如果只挑了.shp解出来后面所有诡异现象都能从这里找到根。先把每个角色认清楚扩展名作用缺失后的典型症状.shp存几何坐标直接打不开没有任何兜底.shx几何索引记录每条记录在 shp 中的偏移部分工具能靠扫描重建但读取速度骤降大文件容易失败.dbf属性表dBASE 格式只剩几何没有名字无法按线路筛选.prj坐标系定义通常是 ESRI WKT软件按未知坐标系处理长度单位变成「度」.cpg声明 dbf 的字符编码中文属性大概率乱码.sbn/.sbx空间索引可选不影响正确性只影响查询速度.prj和.cpg是重灾区因为它们「看起来不重要」。一份全国范围的铁路线数据.prj里通常写着地理坐标系经纬度而不是投影坐标系这意味着你打开就量长度量出来的是度数。.cpg里如果写的是 UTF-8而 dbf 里实际存的是 GBK那打开就是一堆问号——这种编码错配在跨软件流转的数据包里极其常见。判断方法很朴素解压后先ls一遍看伴生文件是否齐全再用文本编辑器打开.prj和.cpg直接看内容.cpg一般就一行UTF-8或GBK/936。这一步花不了一分钟但能提前排除后面一半的排查工作量。2.2 用 geopandas 一次读出图层、字段与几何体检报告不要急着画图先出一份体检报告。下面的脚本把图层名、坐标系、字段、几何类型分布、无效几何、空几何一次性打出来跑完你就知道这份数据能不能直接进下一步。import geopandas as gpd import pyogrio SHP 全国铁路.shp # 1) 先看不读几何速度极快确认图层名与几何类型 print(pyogrio.list_layers(SHP)) # 2) 指定编码读取避免中文乱码engine 用 pyogrio 比 fiona 快数倍 gdf gpd.read_file(SHP, enginepyogrio, encodingUTF-8) print(坐标系:, gdf.crs) print(要素数:, len(gdf)) print(字段:, list(gdf.columns)) # 3) 几何体检 report { 几何类型分布: gdf.geom_type.value_counts().to_dict(), 空几何: int(gdf.geometry.is_empty.sum()), 无效几何: int((~gdf.geometry.is_valid).sum()), 自相交: int((~gdf.geometry.is_simple).sum()), } for k, v in report.items(): print(f{k}: {v})这段代码有三个关键参数值得说清楚。encoding必须和.cpg声明一致如果.cpg写的是 GBK 而你传 UTF-8读出来照样是乱码此时改成encodingGBK再试两种都试一遍是正确的排查姿势。enginepyogrio是 GDAL 的向量读写封装对全国量级的线数据读取速度通常比默认引擎快三到五倍代价是需要额外装包。第三步的is_valid检查很关键铁路线数据在拼接来源不同时期的数据时常出现自相交、重复点、零长度段这些几何在后续打断和构图时会直接抛异常或者更糟——静默产生错误的拓扑。如果体检报告里无效几何占比超过千分之一不要急着make_valid一把梭。先按几何类型分组看看无效集中在哪一类线路上往往是某一段外部导入数据的坐标精度问题定位到源头再修比全局修复安全得多。2.3 字段名被截断之后建一张映射表把 NAME/TYPE 翻译回来dBASE 格式对字段名的限制是 10 个字符而 Shapefile 规范里字段名还只能包含字母数字下划线。中文原始字段名在导出成 Shapefile 时要么被截断成NAME_1、NAME_2要么被转成拼音首字母。你拿到的属性表里出现一堆XZQHMC、XLMC这种缩写不是作者故弄玄虚是格式限制的产物。处理办法是建一张映射表把缩写、字段位置和业务含义对上再统一改名。判断哪个字段对应什么最可靠的方式是抽样看值而不是猜缩写。import geopandas as gpd gdf gpd.read_file(全国铁路.shp, enginepyogrio, encodingUTF-8) # 先抽样看每个候选字段的前几个值人工确认真实含义 for col in gdf.columns: if col geometry: continue sample gdf[col].dropna().astype(str).head(3).tolist() print(f{col} - {sample}) # 确认后再改映射表按你的实际字段调整 FIELD_MAP { NAME: line_name, TYPE: line_type, LENGTH: src_length, XZQHMC: province, } gdf gdf.rename(columns{k: v for k, v in FIELD_MAP.items() if k in gdf.columns})抽样这一步不能省。字段名被截断后NAME和NAME_1谁是谁完全没有规律有的数据包里NAME是线路名、NAME_1是所属路局有的正好反过来。直接按缩写硬套映射等于给自己埋一颗几个月后才炸的雷。字段确认完之后顺手做一次值分布统计尤其看line_type这类分类字段有几个取值、各占多少能快速发现分类体系是否和你预期的客专/普速/支线一致。3. 坐标系选错铁路长度能差出一个数量级3.1 对 EPSG:4326 直接求长度得到的数字没有任何物理意义经纬度坐标下的geometry.length返回的是度数。一条横跨若干纬度的干线算出来可能是二十几「度」——既不是公里也不是米拿去和里程表对比毫无意义。更麻烦的是这个错误不会报错它会安静地输出一个看起来像数字的东西。正确做法是分两步先确认数据本身的坐标系看.prj多数全国铁路数据给的是 WGS84 或 CGCS2000 经纬度EPSG:4326 / 4490再投影到一个长度变形可接受的平面坐标系再算。如果只是想快速估一个量级可以用球面距离公式但球面距离和平面距离在局部仍有差异。实测经验是中纬度地区球面距离与合适投影下的平面距离相差通常在千分之几量级做统计够用做工程结算不够用。做工程就老老实实投影。3.2 三度带投影下的批量换算与偏差自检全国范围的数据不能用一个投影带硬套。经度跨度大单一投影在边缘处的长度变形会显著放大。常见做法是按三度带分带处理每条线按自身中心经度归属到某个带用该带的中央经线建投影。import geopandas as gpd from pyproj import CRS import numpy as np gdf gpd.read_file(全国铁路.shp, enginepyogrio, encodingUTF-8) gdf gdf.to_crs(EPSG:4326) # 先统一到经纬度 def tmerc_crs(cm: float) - CRS: 按中央经线构造三度带高斯-克吕格投影 return CRS.from_proj4( fprojtmerc lat_00 lon_0{cm} k1 fx_0500000 y_00 ellpsGRS80 unitsm no_defs ) gdf[cm] gdf.geometry.centroid.x.apply(lambda lon: round(lon / 3) * 3) gdf[len_m] np.nan for cm, sub in gdf.groupby(cm): proj sub.to_crs(tmerc_crs(cm)) gdf.loc[sub.index, len_m] proj.geometry.length gdf[len_km] gdf.len_m / 1000 print(gdf[len_km].describe())参数含义需要拆开讲。lon_0是中央经线用要素质心经度四舍五入到 3 的倍数得到这样每条线都落在自己最合适的带里靠近中央经线处投影长度变形接近零。k1是中央经线比例因子高斯投影取 1。x_0500000是东伪偏移避免坐标出现负值。ellpsGRS80对应 CGCS2000 用的椭球如果原数据是 WGS84椭球参数差异带来的长度影响在米级以下工程上可忽略。三度带边缘的最大长度相对变形在万分之一量级对铁路里程这种以公里计的尺度误差量级是零点几公里到几公里比直接拿度数算靠谱得多。自检方法是随机抽十几条线把投影算出来的长度和属性表里原有的长度字段对比算相对偏差。偏差集中在 ±2% 以内说明整体没问题出现个别偏差超过 20% 的通常是那条线本身几何有问题或者属性里的长度根本是另一套口径这时候别急着改代码先去查那几个异常要素。3.3 几何长度、运营里程、径路长度是三笔账这是我在实际项目里被反复教育过的一课。几何长度是折线在平面上的长度运营里程是线路投入运营时按规则计取的里程包含曲线、坡度修正和特定的计取规则径路长度是列车实际走行路径的长度受联络线、绕行影响。三者数值不同是正常的相同才是巧合。更要命的是复线问题。一条双线铁路在数据里如果是两条平行折线各画一遍几何长度加起来正好翻倍如果画成一条中心线长度又只有单线。数据包里到底是哪种表达必须靠抽样打开看不能靠属性字段判断。我一般会在做任何汇总统计之前先按线路名分组看同名线路的几何条数和总长人工核对几条典型线路确认这批数据的表达口径再决定复线要不要除二。这个核对不做后面所有汇总数字都要打个问号。4. 把画出来的线变成能跑最短路的路网打断、吸附与连通分量4.1 视觉相交不等于拓扑相连悬挂节点是怎么来的在屏幕上看到两条线交叉不代表它们在拓扑上相交。铁路数据在制图阶段是按线路组织的一条干线可能是一根几千个点的超长折线另一条支线从它中间穿过去两者的几何在交叉点上未必有共同顶点。用这种数据构图结果就是两条线各走各的最短路绕出一大圈。除了未打断还有两类常见问题。一是悬挂节点线的端点悬在另一条线旁边几米处肉眼几乎看不出间隙但拓扑上完全断开。这类问题在拼接多来源数据时格外多因为不同来源的采集精度不同。二是重复线同一段线路被两份数据各画一遍构图时要么产生平行边要么在合并时被错误地当成两条不同路径。这两类问题不解决最短路算出来的结果就是玄学跑通一次不能代表下次还对。4.2 用 unary_union 打断、按端点吸附构图处理流程是三步先合并几何做节点化打断再按容差把端点吸附成同一节点最后建图。import geopandas as gpd import networkx as nx from shapely.ops import unary_union gdf gpd.read_file(全国铁路.shp, enginepyogrio, encodingUTF-8) gdf gdf.to_crs(tmerc_crs(105)) # 先投影后续所有距离单位都是米 # 1) 在交点处打断unary_union 会把相交的线切成共享端点的段 merged unary_union(gdf.geometry.values) segments list(merged.geoms) if merged.geom_type MultiLineString else [merged] # 2) 按端点吸附建图tol 为吸附容差米 def build_graph(lines, tol5.0): G nx.Graph() for geom in lines: coords list(geom.coords) for a, b in zip(coords[:-1], coords[1:]): na (round(a[0] / tol), round(a[1] / tol)) # 网格化吸附 nb (round(b[0] / tol), round(b[1] / tol)) if na nb: continue # 跳过零长度段 w ((a[0] - b[0]) ** 2 (a[1] - b[1]) ** 2) ** 0.5 if G.has_edge(na, nb) and G[na][nb][weight] w: continue # 重复边只保留较短的一条 G.add_edge(na, nb, weightw) return G G build_graph(segments, tol5.0) print(节点数:, G.number_of_nodes(), 边数:, G.number_of_edges())unary_union是这一步的核心它会把所有相交的线在交点处切开输出一堆共享端点的线段这是拓扑化最关键的一刀。tol是吸附容差用到米为单位时取 5 米是个经验起点太小抓不住精度差异造成的悬挂太大可能把并行的双线错误地吸成一条。全国范围的数据建议先取 5 米跑一遍看连通分量如果分量碎得离谱再往上试 10 米、20 米。网格化吸附除以 tol 再取整是最省事的做法缺点是网格边界附近的点可能吸附不上工程上够用追求严谨可以换 KD 树做半径查询。4.3 连通分量与悬挂节点统计先判断这张网能不能用图建好之后不要马上跑最短路先看结构。两个指标最有诊断价值连通分量的数量和规模分布以及度为 1 的节点占比。comps sorted(nx.connected_components(G), keylen, reverseTrue) print(连通分量数:, len(comps)) print(最大分量节点数:, len(comps[0])) print(前 10 个分量规模:, [len(c) for c in comps[:10]]) print(孤立小分量(5节点)数:, sum(1 for c in comps if len(c) 5)) deg1 [n for n, d in G.degree() if d 1] print(度为 1 的节点数:, len(deg1), 占比:, round(len(deg1) / G.number_of_nodes(), 4))理想情况下全国铁路网的最大连通分量应该覆盖九成以上的节点。如果最大分量只占三成说明打断和吸附这一步没做好或者数据本身就是分省分片存储的。此时不要往下走回头调tol或者先按空间范围分块处理。度为 1 的节点是悬挂节点的直接体现。一条线的末端如果接在另一条线的中间打断之后那个端点会成为度为 3 的节点这是健康的而悬在半空、没接上任何东西的端点度为 1。健康的路网里度为 1 的节点应该只出现在真正的尽头线专用线、尽头式支线上占比通常在个位数百分比。如果这个比例到了两成以上说明大量端点没接上最短路会大面积失效。我一般的处理是把度为 1 的节点拿去做一次最近邻查询如果 50 米内有可吸附的边就补一条虚拟连接边权重按距离给然后重新检查分量数是否下降。5. 五个翻车现场铁路 SHP 从能看变成能算之间的排查清单5.1 属性表中文全是乱码改编码也没用现象用gpd.read_file读出来中文字段值全是问号或方块换 UTF-8、GBK 都试了还是不对。原因.cpg文件缺失或者.cpg声明的编码和 dbf 实际存储的编码不一致。缺.cpg时 GDAL 对 dbf 默认按 Latin-1 处理中文必然乱码而.cpg里写 UTF-8 但内容实际是 GBK 的情况在有.cpg的前提下反而更难排查因为工具会信任声明。解决先看.cpg内容没有就手动创建一份试 UTF-8读出来还是乱码就换 GBK 重试。也可以在读取前设环境变量SHAPE_ENCODINGGBK。如果要彻底避免这类问题读出正确内容后立刻转成 GeoPackage 或 GeoJSON 落一份这些格式自带 UTF-8 声明后续就不用再跟编码纠缠。这也是我处理任何外来的 shapefile 时的固定动作先转一次格式把编码问题一次性关掉。5.2 字段名对不上join 之后大面积空值现象把铁路线和站点表按线路名关联结果九成以上匹配不上或者匹配出一堆错位的记录。原因dBASE 的 10 字符字段名限制导致原始字段被截断重命名你的关联键可能根本不是你以为的那个字段另一种常见情况是线路名本身存在前后空格、全半角差异、繁简混用肉眼看着一样实际字符串不相等。解决先做字段抽样确认语义再做字符串规范化——strip()、全半角统一、去掉括号内的附加说明最后才 join。规范化之后仍匹配不上的那部分单独导出来人工看往往能发现数据源本身的命名体系差异比如一边叫「上行线」一边叫「上」。这一步做完再统计匹配率低于八成说明键选错了不要硬凑。5.3 最短路结果绕远明明有直连却走了大圈现象起终点相邻算出来的路径却绕出几十公里。原因交叉处几何未打断视觉上的十字路口在拓扑上是两条互不相交的独立线图里根本没有那条边。或者打断做了但端点吸附容差太小差几米的悬挂没接上。解决先查连通分量看起终点是否在同一个分量里不在就说明是拓扑断裂。再定位断裂位置取路径两端点做最近邻查询找出附近本该相连却未相连的节点对补虚拟边后重算。把tol从 5 米调到 20 米重跑一遍如果绕远现象明显减少说明就是精度差异导致的悬挂。这个排查顺序我踩过几次坑之后固定下来了先看分量再看悬挂最后才怀疑权重。5.4 坐标数值差了十万倍或者全是负数现象读进来之后线跑到地图外长度算出来是几百万。原因.prj缺失导致坐标系未被识别程序按未知坐标系处理且不做任何转换或者是投影坐标被误当成经纬度再投了一次数值放大到几百倍。解决先看坐标数值的量级——经度在 -180 到 180 之间、纬度在 -90 到 90 之间的是经纬度数值在几十万到几百万、且 y 值有正负的是投影坐标。量级确认之后手工指定 CRS而不是依赖.prj。误投影两次的情况靠量级也能看出来重新用正确的源坐标系覆盖即可。5.5 汇总里程比公开数字大了将近一倍现象把所有线要素长度加总得到的数字和公开的运营里程差了接近一倍。原因复线被画成了两条平行折线各算一遍或者数据里同时包含了正线和站线、联络线站线在车站范围内密集重复累积出大量额外长度。解决先按线路名分组看几何条数同名线路有两条平行几何的基本可以确认是复线表达。再按线型字段过滤去掉站线、段管线这类非正线要素后再汇总。做这两步之后通常能对上量级。如果还有明显差异那就是口径问题得回去查数据的制取说明而不是继续调代码——几何长度和运营里程本来就不是一个东西强行对齐只会得到假精度。6. 入库与切片之后怎么验收三条对得上的校验线数据在本地跑通只是第一步真正交付前一般要落进数据库或切成前端能用的瓦片。入库这一环最省事的是ogr2ogr它把编码、字段类型、几何类型的转换一并处理ogr2ogr -f PostgreSQL \ PG:host127.0.0.1 port5432 dbnamegis usergis \ 全国铁路.shp \ -nln rail_line -nlt MULTILINESTRING \ -lco GEOMETRY_NAMEgeom -lco FIDgid -lco PRECISIONNO \ -t_srs EPSG:4326 -progress-nlt MULTILINESTRING强制统一几何类型避免混入 LineString 导致建表失败-lco PRECISIONNO关掉字段精度约束否则 dbf 里那些带小数的字段建成 numeric 时会被截断-t_srs做一次统一投影入库后所有人看到的都是同一套坐标。切片用mapshaper更轻mapshaper 全国铁路.shp -simplify 30% keep-shapes \ -o formatgeojson precision0.00001 rail.geojsonkeep-shapes防止抽稀把短支线抽没precision控制坐标小数位全国尺度的线数据给到 5 位小数约 1 米足够再高只是白白撑大体积。验收我固定看三条线。第一条是抽样对比随机抽 10 条有明确里程的线路把几何长度和已知里程列出来算相对偏差偏差集中在 ±3% 以内算过关孤立的异常值回去查几何。第二条是拓扑回归入库后按同样的容差重新查一次连通分量数和本地跑的结果比对数量应该完全一致不一致说明入库过程改了精度或几何类型。第三条是可视化抽检把切片结果叠到已知的行政区划底图上随机缩放到五个区域看线路走向是否合理——这条最土但抓到过好几次投影参数写错导致的整体偏移。说个我自己的习惯任何一份外来矢量数据我都会在第一次处理完之后把「原始编码 源坐标系 投影参数 吸附容差 连通分量数」这五个值写进一个 README 放在同目录下。半年后别人接手或者自己回头复现不用再猜当初为什么取了 5 米。这个习惯是踩了太多次「同样的数据跑出两个结果」之后才养成的比任何排查技巧都省时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑