简介:这份全国铁路SHP数据面向地理信息科学、城市规划与交通工程方向的学习者和从业者,提供中国铁路网络及2016年高铁站点的矢量地理数据,可用于空间分析、制图与区域发展研究。压缩包共12个文件,约11.37MB,以shp、shx、dbf、prj为核心类型:shp存储铁路线路与高铁站点的几何形状,shx提供记录索引以加速读取,dbf保存线路名称、长度、开通日期及站名、运营线路等属性,prj定义坐标系统,另有sbn、sbx索引与xml元数据辅助解析。数据同时覆盖铁路线与高铁站点两个图层,便于开展最近车站查找、线路总长计算、站点分布模式分析,并可结合人口、经济等外部数据做深入规划研究。目前已有2768人学习下载,适合作为GIS入门练习或交通地理课题的基础数据源。
1. 全国铁路SHP数据.zip:一份线状矢量底图能撑起哪些落地场景
拿到「全国铁路SHP数据.zip」这个标题,多数人第一反应是「不就是一份铁路线矢量吗」。但真正做过交通可视化、可达性分析或者物流路径规划的人会告诉你,全国尺度的铁路线状数据,难点从来不在「有没有」,而在「能不能用、怎么用、用了之后坐标对不对」。这份数据通常以 Shapefile 格式组织,包含铁路线路的几何线串和若干属性字段,坐标系多为地理坐标(如 CGCS2000 或 WGS84),覆盖国铁干线、支线乃至部分专用线。它能解决的核心问题是:给任何需要「铁路网络」作为空间基底的项目提供一个可加载、可裁剪、可拓扑分析的起点。适合谁?做GIS可视化的、做交通可达性研究的、做多式联运路径规划的、以及需要把铁路叠加到行政区划或地形底图上的从业者。这一章先把「它是什么、能干什么」讲清楚,后面几章再拆怎么读、怎么转、怎么避坑。
2. 从zip到可分析图层:SHP读取、坐标系判定与字段清洗
2.1 为什么不能直接双击打开就完事
Shapefile 不是一个单文件,而是一组同名文件的集合:.shp存几何,.shx存索引,.dbf存属性,.prj存坐标系定义,有时还有.cpg指定编码。很多人解压后只看到.shp就拖进软件,结果属性表乱码或者坐标飞到太平洋——前者是.cpg缺失或编码不对,后者是.prj没被正确识别。全国铁路数据因为跨度大,常见坐标系有 CGCS2000(EPSG:4490)、WGS84(EPSG:4326),偶尔也有用 Web Mercator(EPSG:3857)做展示的版本。判定方法很简单:用pyproj或gdalinfo读.prj内容,看它是不是GEOGCS开头。如果是PROJCS,说明是投影坐标,单位可能是米,这时候直接算长度没问题,但做跨区域叠加就要先统一。
我一般会先跑一段最小读取代码,把几何类型、坐标系、字段名和记录数一次性打出来,避免后面反复试错。
import geopandas as gpd # 读取全国铁路SHP,注意路径指向.shp文件即可,同目录其他文件会自动关联 gdf = gpd.read_file("./全国铁路SHP数据/railway.shp") print("几何类型:", gdf.geom_type.unique()) # 通常是 LineString 或 MultiLineString print("坐标系:", gdf.crs) # 看是 EPSG:4490 还是 4326 print("字段列表:", gdf.columns.tolist()) # 关注名称、等级、长度等字段 print("记录数:", len(gdf)) print(gdf.head(3))这段代码的逻辑是:geopandas会自动读取同目录下的.prj和.dbf,所以只要路径对,坐标系和属性都能带出来。参数上唯一要注意的是路径不要用中文空格混排,Windows 下容易出编码问题。如果gdf.crs返回None,说明.prj缺失或损坏,这时候不能瞎猜,要根据数据来源判断——全国铁路公开数据绝大多数是 CGCS2000 地理坐标,可以手动指定gdf.set_crs(epsg=4490, inplace=True),但这一步必须谨慎,指定错了后面全错。
2.2 字段清洗:把「能看」变成「能用」
全国铁路 SHP 的属性字段通常包含线路名称、等级(高铁/普速/支线)、起讫点等,但字段名可能是英文缩写、拼音甚至乱码。清洗的目标是留下分析必需的列,并把名称标准化。常见做法是保留name、grade、length三列,其余按需丢弃。如果grade字段缺失,可以用线路名称关键词做规则映射,比如含「高铁」「客专」的归为高速,含「线」的归为普速。这一步没有统一标准,取决于你的分析粒度。
# 字段重命名与等级映射 gdf = gdf.rename(columns={"NAME": "name", "LEVEL": "grade"}) # 若grade缺失,用名称关键词补全 def infer_grade(row): n = str(row["name"]) if any(k in n for k in ["高铁", "客专", "高速"]): return "high_speed" elif any(k in n for k in ["支线", "专用"]): return "branch" return "normal" if "grade" not in gdf.columns or gdf["grade"].isna().all(): gdf["grade"] = gdf.apply(infer_grade, axis=1) # 计算每条线路长度(地理坐标下需投影到等距坐标系再算,单位米) gdf_utm = gdf.to_crs(epsg=3857) # 仅用于快速估算,正式分析建议用阿尔伯斯等积投影 gdf["length_km"] = gdf_utm.length / 1000 print(gdf[["name", "grade", "length_km"]].head())这里的关键参数是投影选择。epsg=3857是 Web Mercator,在高纬度地区长度变形大,只适合快速估算。如果要做正式的长度统计或缓冲区分析,应该用+proj=aea的阿尔伯斯等积投影,参数里中央经线取 105°E,双标准纬线取 25°N 和 47°N,这样全国范围变形最小。字段清洗完,数据才算真正「可分析」。
2.3 裁剪与分层:全国数据太大,先切出你的研究区
全国铁路线动辄几十万条记录,直接渲染会卡,直接做空间分析也慢。常见做法是按行政区或按矩形框裁剪。用geopandas的clip方法,配合一个省界或城市边界图层即可。如果没有边界数据,可以用cx索引器按经纬度范围切。
# 按经纬度矩形裁剪出华东区域(经度115-123,纬度28-35) east_china = gdf.cx[115:123, 28:35] # 或者用省界SHP做精确裁剪 province = gpd.read_file("./boundaries/province.shp") if province.crs != gdf.crs: province = province.to_crs(gdf.crs) clipped = gpd.clip(gdf, province) clipped.to_file("./output/railway_east.shp", encoding="utf-8")裁剪时注意clip会保留几何被切后的线段,属性不变。如果要做网络分析,裁剪后可能出现断线,需要后续用linemerge或拓扑修复。输出时指定encoding="utf-8"可以避免.dbf中文乱码,这是血泪经验——不指定的话,Windows 下默认 GBK,跨平台打开就翻车。
3. 把铁路线变成可计算网络:拓扑构建、节点提取与路径分析
3.1 线状数据为什么不能直接跑最短路
Shapefile 里的铁路线是「画」出来的,不是「连」出来的。两条线在视觉上交叉,但几何上可能没有共享节点,直接拿networkx建图会得到一堆孤立子图。要做路径分析,必须先做拓扑构建:把线在交点处打断,提取端点作为节点,线段作为边。常见工具是networkx配合shapely的unary_union和linemerge,或者用 PostGIS 的ST_Node函数。我一般用 GeoPandas + NetworkX 的组合,因为可控性强。
import networkx as nx from shapely.ops import unary_union, linemerge from shapely.geometry import Point # 合并所有线段,再在交点处打断 merged = unary_union(gdf.geometry) segments = list(linemerge(merged).geoms) if merged.geom_type == "MultiLineString" else [merged] G = nx.Graph() for seg in segments: coords = list(seg.coords) start, end = coords[0], coords[-1] # 用坐标元组作为节点ID,避免浮点误差可先round n1 = (round(start[0], 6), round(start[1], 6)) n2 = (round(end[0], 6), round(end[1], 6)) length = seg.length # 地理坐标下是度,需投影后重算 G.add_edge(n1, n2, weight=length, geometry=seg) print("节点数:", G.number_of_nodes(), "边数:", G.number_of_edges())这段代码的核心是unary_union把所有线合并成一个多重线对象,linemerge把首尾相接的线合并成更长的线,但不会在交点打断——所以更稳妥的做法是先用ST_Node或shapely.ops.split在交点处切分。参数上,round的精度取 6 位约等于 0.1 米,足够避免浮点误差导致的节点不匹配。weight用长度,但地理坐标下的长度是度,需要先投影到米制再算,否则最短路会偏向高纬度短度数的线路。
3.2 节点提取与站点匹配:把车站「挂」到网络上
如果你有车站点位数据,需要把站点匹配到最近的铁路线节点上,才能做「站到站」的路径分析。常见做法是找最近边,然后在边上插一个节点。geopandas的sjoin_nearest可以快速找到每个站点最近的线段,再用project和interpolate在线上定位。
# stations 是车站点图层,gdf 是铁路线图层 stations = gpd.read_file("./stations.shp").to_crs(gdf.crs) # 找每个站点最近的铁路线 joined = gpd.sjoin_nearest(stations, gdf, how="left", distance_col="dist") # 对每个站点,在最近线上做投影,得到线上最近点 def snap_to_line(row): line = gdf.loc[row["index_right"], "geometry"] pt = row["geometry"] return line.interpolate(line.project(pt)) stations["snapped"] = joined.apply(snap_to_line, axis=1)这里sjoin_nearest的distance_col会给出站点到线的距离,可以用来过滤太远的匹配(比如超过 5 公里就认为不靠谱)。project返回线上距离,interpolate返回该距离处的点。匹配完再把站点坐标替换成snapped,重新建图时把这些点作为节点加入。注意:全国铁路数据里有些线路是复线,站点可能同时靠近两条线,sjoin_nearest只返回最近的一条,需要人工校验或加规则。
3.3 最短路与可达性:从 A 站到 B 站到底怎么算
建好图之后,最短路就是nx.shortest_path一行的事。但实际做可达性分析时,往往要算「从某站出发 4 小时内能到哪些站」,这就需要按时间而不是距离做权重。铁路数据本身没有速度字段,常见做法是按等级赋速度:高铁 250 km/h,普速 80 km/h,支线 40 km/h,然后weight = length / speed。
# 按等级赋速度,计算时间权重 speed_map = {"high_speed": 250, "normal": 80, "branch": 40} for u, v, data in G.edges(data=True): grade = data.get("grade", "normal") speed = speed_map.get(grade, 80) data["time"] = data["weight"] / speed # weight是公里,time是小时 # 从北京站出发,4小时可达范围 source = (116.427, 39.903) # 示例坐标,实际用匹配后的节点 reachable = nx.single_source_dijkstra_path_length(G, source, cutoff=4, weight="time") print("4小时可达站点数:", len(reachable))cutoff参数控制最大时间,weight="time"指定用时间权重。这里有个坑:single_source_dijkstra_path_length返回的是节点到节点的最短时间,但节点是几何端点,不是车站,所以需要先把车站匹配到节点上再算。另外,如果图不是连通的,可达范围只覆盖当前连通分量,跨海或断线处会截断,需要手动补边或分区域处理。
4. 避坑与排查:全国铁路SHP最常见的5个翻车现场
4.1 坐标系「看起来对」但叠加不上
现象:铁路线和省界叠在一起,铁路整体偏移几百米甚至几公里。原因:.prj写的是 CGCS2000,但数据实际是 WGS84,两者在国内差几十米;或者数据是投影坐标但被误认为地理坐标。解决:用gdalinfo看.prj完整内容,确认GEOGCS还是PROJCS;再用已知地标(如某城市中心)对比经纬度,偏差超过 100 米就说明坐标系有问题。不要迷信.prj,它可能被错误修改过。
4.2 属性表中文乱码,字段名变成问号
现象:打开.dbf看到「???」或乱码。原因:Shapefile 的.dbf默认编码是 GBK,但geopandas默认按 UTF-8 读。解决:读取时指定encoding="gbk",或者先看.cpg文件内容。如果.cpg写的是UTF-8,那就用 UTF-8 读。输出时统一用encoding="utf-8"并生成.cpg,避免跨平台问题。
4.3 线太多导致渲染卡死
现象:QGIS 或 ArcGIS 加载全国铁路后缩放卡顿。原因:几十万条线没有空间索引,每次重绘都全量遍历。解决:用gdf.sindex建空间索引,或者按缩放级别做分层抽稀——低缩放级别只显示高铁和干线,高缩放级别再显示支线。也可以在输出时用to_file生成 GeoPackage,自带空间索引,比 Shapefile 快很多。
4.4 拓扑构建后出现大量孤立节点
现象:建图后节点数远大于预期,最短路算不通。原因:线在交点处没有共享节点,unary_union没有真正打断。解决:用shapely.ops.split在交点处切分,或者用 PostGIS 的ST_Node(ST_Collect(geom))一次性打断。打断后再linemerge,节点数会大幅下降。另一个原因是坐标精度不够,round位数太少导致相邻端点不匹配,调到 6 位以上。
4.5 裁剪后线路断裂,路径分析失效
现象:按省界裁剪后,跨省线路被切断,最短路绕行或不通。原因:clip只保留几何在边界内的部分,边界外的线段被丢弃。解决:裁剪前先做缓冲区,或者裁剪后手动把断点连上。更稳妥的做法是不裁剪几何,而是用属性筛选——按线路名称或编号筛选出研究区相关的线路,保留完整几何。如果必须裁剪,用intersection而不是clip,并记录断点位置,后续补边。
5. 进阶:用铁路SHP做等时圈与栅格化成本面
等时圈是可达性分析里最直观的产出:以某站为中心,30 分钟、1 小时、2 小时能覆盖的范围。用前面的图网络可以算出每个节点的最短时间,但要把时间映射到面上,需要做插值或栅格化。常见做法是:先对每个节点算时间,然后用反距离加权(IDW)或克里金插值生成连续表面,再按阈值提取等值线。如果嫌插值麻烦,也可以用scipy的griddata做最近邻填充,速度快但边缘锯齿明显。
import numpy as np from scipy.interpolate import griddata # 假设 nodes 是带 time 属性的节点列表 points = np.array([(n[0], n[1]) for n in reachable.keys()]) values = np.array(list(reachable.values())) # 生成全国范围的网格 grid_x, grid_y = np.mgrid[73:136:0.1, 18:54:0.1] grid_z = griddata(points, values, (grid_x, grid_y), method="linear") # 按 1/2/4 小时提取等值线 import matplotlib.pyplot as plt cs = plt.contour(grid_x, grid_y, grid_z, levels=[1, 2, 4]) plt.clabel(cs, inline=True) plt.show()这段代码的参数关键是网格分辨率0.1度,约 10 公里,全国范围约 630×360 个格点,计算量可接受。method="linear"比nearest平滑,但在数据稀疏区会出现 NaN,需要fill_value或后续掩膜。等时圈画出来后,可以叠加人口栅格或 GDP 栅格,算「1 小时覆盖多少人口」,这才是交通数据真正值钱的地方。
另一个进阶方向是栅格化成本面:把铁路线转成栅格,铁路经过的像元成本低,非铁路像元成本高,然后用skimage.graph的route_through_array做最小成本路径。这种方法适合做「新建线路选址」或「多式联运路径优化」,比纯图网络更灵活,因为可以叠加地形、土地利用等成本因子。我一般会先用矢量网络算基准路径,再用栅格成本面做敏感性分析,两者交叉验证。
最后说个习惯:每次拿到新的 SHP,先跑一遍gdf.info()和gdf.geom_type.value_counts(),确认几何类型和空值情况。全国铁路数据里经常混有MultiLineString和LineString,不统一的话后面linemerge会报错。还有,.zip解压后如果文件名带中文,在 Linux 下可能乱码,建议先重命名为英文再处理。这些细节不复杂,但漏一个就够你查半天。希望帮到你。
本文还有配套的精品资源,点击获取