☰
全国铁路矢量数据集处理指南:从解压到空间分析
2026/10/13 10:30:27 网站建设 项目流程

简介:这份2024年全国铁路矢量数据集面向GIS分析、交通规划、地图可视化及城市研究方向的从业者与学习者,提供覆盖全国范围的铁路与城市轨道交通空间数据,可用于路网密度分析、可达性评估、专题制图等场景。资源包共48个文件,以shp、shx、dbf、prj、cpg等Shapefile标准组件为主,另含xml元数据及sbx、sbn空间索引文件,压缩包约63.35MB,解压后数据量约136MB,便于在ArcGIS、QGIS等平台直接加载。数据内容涵盖铁路、高铁、地铁、有轨电车、轻轨、单轨铁路、窄轨铁路、微型火车网络及缆索铁道等多种类型,分类细致,属性表与投影信息完整,可直接用于空间统计与制图输出。目前已有883人学习下载,适合需要全国尺度轨道交通底图的研究者与制图人员参考使用。

1. 拿到一份全国铁路矢量数据集,先别急着往地图里拖

很多人第一次接触「2024年全国铁路(铁路、高铁、地铁)矢量数据集.7z」这类资源,第一反应是解压、拖进 GIS 软件、看线条铺满全国,然后就没有然后了。真正做过交通分析的人会先问三个问题:这份数据分了几层、坐标系是什么、属性字段能不能区分高铁和普速。因为铁路矢量数据最坑的地方从来不是「有没有」,而是「混在一起」。高铁、普速铁路、地铁在几何上都是线,但业务含义完全不同——高铁要看走向和站点间距,地铁要看城市内部线网密度,普速铁路要看货运通道。如果拿到手不先分层,后面所有分析都是错的。这篇笔记面向做交通规划、城市研究、地图可视化的从业者,把这份数据从解压到出图、从字段清洗到空间分析的完整路径讲清楚,中间会给出可直接复现的代码和参数,也会把踩过的坑摊开说。

2. 解压之后先看什么:图层结构与坐标系判定

2.1 7z 包解开后常见的文件形态

这类全国范围的矢量数据集,打包成 .7z 通常是为了压缩体积,解压后大概率是 Shapefile(.shp/.shx/.dbf/.prj)或者 GeoPackage(.gpkg),少数情况是 GeoJSON。Shapefile 的问题是单文件字段名限制 10 个字符、编码容易乱,GeoPackage 则把图层、坐标系、属性都装在一个文件里,更适合后续处理。我一般会先解压到一个纯英文路径下,避免中文路径导致 GDAL 读取失败。

# 解压到指定目录,-o 指定输出路径,-y 覆盖已有文件 7z x "2024年全国铁路矢量数据集.7z" -o./rail_data -y # 查看解压后的文件结构 find ./rail_data -type f | head -50

解压后不要急着打开,先用ogrinfo看一眼图层清单和坐标系。GDAL 的ogrinfo是判断数据质量的第一个关口,它能告诉你每个图层的几何类型、要素数量、坐标系定义。

# 列出数据源中所有图层 ogrinfo -so ./rail_data/railway.gpkg # 查看某个图层的详细信息,包括坐标系和字段 ogrinfo -so ./rail_data/railway.gpkg high_speed_rail

如果输出里坐标系显示为GCS_WGS_1984或EPSG:4326,说明是经纬度坐标,适合做全国尺度展示;如果显示CGCS2000或投影坐标(如EPSG:3857),那就要注意后续面积、长度计算时的单位问题。全国尺度的铁路数据,常见做法是存 WGS84 经纬度,做长度统计时再投影到等距坐标系。

2.2 坐标系不一致会带来什么后果

我见过最典型的一次翻车:把高铁图层和地铁图层叠在一起,发现地铁线全部偏移到城市外面去了。原因是一个是 WGS84,一个是 GCJ02(火星坐标)。国内很多地图数据来源不同,坐标系混用是常态。判断方法很简单,用 Python 读两个图层的 CRS 对比一下。

import geopandas as gpd # 读取两个图层 hsr = gpd.read_file("./rail_data/railway.gpkg", layer="high_speed_rail") metro = gpd.read_file("./rail_data/railway.gpkg", layer="metro") print("高铁坐标系:", hsr.crs) print("地铁坐标系:", metro.crs) print("高铁要素数:", len(hsr)) print("地铁要素数:", len(metro)) # 如果坐标系不一致,统一到 WGS84 if hsr.crs != metro.crs: metro = metro.to_crs(hsr.crs) print("已统一坐标系")

参数说明:to_crs()是 GeoPandas 的坐标转换方法,传入目标 CRS 即可。注意如果源数据没有 CRS 定义(crs 为 None),需要先用set_crs()手动指定,否则转换会报错。这一步做完,至少保证所有图层在同一空间参考下,后面叠加分析才不会出现「线飘到海里」的玄学问题。

2.3 属性字段决定这份数据能不能用

图层分对了、坐标系统一了,接下来看属性表。铁路数据的核心字段通常包括:线路名称、类型(高铁/普速/地铁)、等级、是否电气化、开通年份等。但很多数据集字段名是拼音缩写或者英文简写,比如LXMC(路线名称)、LXDJ(路线等级),需要先做字段映射。

# 查看字段名和前几行数据 print(hsr.columns.tolist()) print(hsr.head(3)) # 常见字段重命名映射 rename_map = { "LXMC": "line_name", "LXDJ": "line_level", "LX_TYPE": "line_type", "KHTIME": "open_year" } hsr = hsr.rename(columns={k: v for k, v in rename_map.items() if k in hsr.columns}) print(hsr.columns.tolist())

如果字段里没有区分高铁和普速的标识,那就只能靠线路名称关键词来推断,比如含「高铁」「客运专线」的归为高铁,含「铁路」但不含「高铁」的归为普速。这种做法有误差,但比不分层强。地铁数据一般单独一个图层,字段里会有城市名和线路编号。

3. 把铁路数据用起来:从字段清洗到空间分析

3.1 按类型拆分图层并统计里程

拿到一份混合的铁路数据,第一步永远是按类型拆开。拆分之后分别统计里程,才能知道这份数据覆盖了多少高铁、多少普速、多少地铁。里程统计必须用投影坐标系,经纬度直接算长度单位是度,没有意义。

import geopandas as gpd # 读取全部铁路数据 rail = gpd.read_file("./rail_data/railway.gpkg", layer="railway") # 按类型拆分 hsr = rail[rail["line_type"] == "高铁"].copy() normal = rail[rail["line_type"] == "普速"].copy() metro = rail[rail["line_type"] == "地铁"].copy() # 投影到等距坐标系,这里用 Web Mercator 做示例 # 全国尺度更推荐 EPSG:3857 或 Albers 等积投影 for name, gdf in [("高铁", hsr), ("普速", normal), ("地铁", metro)]: gdf_proj = gdf.to_crs(epsg=3857) total_km = gdf_proj.geometry.length.sum() / 1000 print(f"{name}总里程: {total_km:.1f} km, 要素数: {len(gdf)}")

参数说明:to_crs(epsg=3857)把经纬度转成 Web Mercator,单位变成米,geometry.length算出来的就是米,除以 1000 得公里。注意 Web Mercator 在高纬度地区长度变形较大,如果做全国精度要求高的统计,建议用 Albers 等积投影(如+proj=aea +lat_1=25 +lat_2=47 +lat_0=35 +lon_0=105)。这一步的输出可以直接用来做数据质量校验——如果高铁里程算出来只有几千公里,那大概率是数据不完整或者字段分类有问题。

3.2 用空间连接把铁路和城市关联起来

铁路数据的价值在于和城市、人口、经济数据关联。最常见的操作是:判断哪些城市有高铁经过,或者统计每个城市的地铁线路数量。空间连接(spatial join)是干这个的标准手段。

import geopandas as gpd # 读取城市边界数据(假设已有) cities = gpd.read_file("./data/cities.shp") # 读取高铁数据 hsr = gpd.read_file("./rail_data/railway.gpkg", layer="high_speed_rail") # 统一坐标系 cities = cities.to_crs(hsr.crs) # 空间连接:判断高铁线经过哪些城市 # predicate="intersects" 表示线穿过城市就算 joined = gpd.sjoin(hsr, cities, how="inner", predicate="intersects") # 统计每个城市的高铁线路数 city_hsr_count = joined.groupby("city_name").size().reset_index(name="hsr_count") print(city_hsr_count.sort_values("hsr_count", ascending=False).head(10))

参数说明:gpd.sjoin的predicate参数支持intersects、within、contains等,铁路和城市关联一般用intersects。how="inner"表示只保留匹配上的记录。如果城市边界数据是面,铁路是线,intersects会返回所有穿过城市边界的线段,一个城市可能对应多条记录,所以后面要groupby聚合。这一步做完,就能回答「哪些城市通了高铁」这种基础问题。

3.3 地铁数据的城市内部分析

地铁数据单独拿出来,可以做城市内部线网密度分析。方法是用渔网(fishnet)把城市切成网格,统计每个网格内的地铁线长度,再除以网格面积得到密度。

import geopandas as gpd import numpy as np from shapely.geometry import box # 读取某城市地铁数据 metro = gpd.read_file("./rail_data/railway.gpkg", layer="metro") metro_city = metro[metro["city_name"] == "某市"].copy() metro_city = metro_city.to_crs(epsg=3857) # 获取城市边界范围 minx, miny, maxx, maxy = metro_city.total_bounds # 生成 1km x 1km 渔网 cell_size = 1000 # 单位:米 cols = np.arange(minx, maxx, cell_size) rows = np.arange(miny, maxy, cell_size) cells = [box(x, y, x + cell_size, y + cell_size) for x in cols for y in rows] grid = gpd.GeoDataFrame({"geometry": cells}, crs="EPSG:3857") # 空间叠加,计算每个网格内的地铁长度 intersect = gpd.overlay(grid, metro_city, how="intersection") intersect["seg_len"] = intersect.geometry.length density = intersect.groupby(intersect.index)["seg_len"].sum().reset_index() density["density"] = density["seg_len"] / (cell_size ** 2) print(density.describe())

参数说明:cell_size控制网格大小,1km 适合城市尺度,500m 适合核心区精细分析。gpd.overlay的how="intersection"表示只保留网格和地铁线相交的部分。density单位是 m/m²,乘以 10000 可以转成 m/ha。这一步的输出可以直接用来做热力图,找出地铁线网密集的区域。

4. 避坑指南:铁路矢量数据处理的五个常见问题

4.1 解压后中文文件名乱码

现象:在 Linux 或 macOS 上解压 7z 包,文件名显示为乱码,GDAL 读取时报「文件不存在」。原因:7z 打包时用了 GBK 编码,而系统默认 UTF-8。解决:用7z x -mcp=936指定代码页解压,或者用 Python 的py7zr库手动处理编码。

import py7zr with py7zr.SevenZipFile("2024年全国铁路矢量数据集.7z", mode="r") as z: z.extractall(path="./rail_data") # 如果乱码,解压后手动重命名文件

4.2 坐标系缺失导致叠加偏移

现象:两个图层叠在一起,线条明显错位,偏移量几百米到几公里不等。原因:其中一个图层没有 .prj 文件,或者 CRS 定义错误。解决:先用ogrinfo确认每个图层的 CRS,缺失的用set_crs()手动指定,再统一转换。如果偏移量固定且在城市尺度,可能是 GCJ02 和 WGS84 的差异,需要用专门的转换库处理。

4.3 字段类型错误导致筛选失败

现象:用line_type == "高铁"筛选,结果为空。原因:字段值可能有空格、全角字符或者编码问题。解决:先print(df["line_type"].unique())看实际值,再用str.strip()去空格,必要时用str.contains("高铁")做模糊匹配。

# 查看实际值 print(rail["line_type"].unique()) # 去空格后再筛选 rail["line_type"] = rail["line_type"].str.strip() hsr = rail[rail["line_type"].str.contains("高铁", na=False)]

4.4 大数据量导致内存溢出

现象:读取全国铁路数据时 Python 进程被 kill,或者 GeoPandas 操作极慢。原因:全国尺度的线数据可能有几十万条要素,全量加载到内存吃不消。解决:用pyogrio或fiona的过滤功能只读需要的区域,或者分块处理。

# 只读取指定范围内的数据 import geopandas as gpd from shapely.geometry import box bbox = box(116.0, 39.0, 117.0, 40.0) # 某区域范围 rail_sub = gpd.read_file("./rail_data/railway.gpkg", bbox=bbox)

4.5 地铁和高铁数据混在同一图层

现象:做高铁分析时发现结果里混入了城市地铁线。原因:数据集把所有轨道交通放在一个图层,只用字段区分。解决:先按字段拆分,再分别处理。如果字段区分不明显,可以用线路长度做辅助判断——地铁线路通常较短且集中在城市内部,高铁线路跨城市且长度大。

5. 进阶技巧:用铁路数据做可达性分析

5.1 基于站点和线路构建简易网络

铁路数据的进阶用法是可达性分析。核心思路是把线路和站点转成图网络,计算从某站出发到其他站的最短路径。如果没有站点数据,可以用线路的端点或者等距采样点作为虚拟站点。

import geopandas as gpd import networkx as nx from shapely.geometry import Point # 读取高铁线路 hsr = gpd.read_file("./rail_data/railway.gpkg", layer="high_speed_rail") hsr = hsr.to_crs(epsg=3857) # 构建图网络 G = nx.Graph() for idx, row in hsr.iterrows(): line = row.geometry # 沿线等距采样,每 50km 一个虚拟节点 distances = np.arange(0, line.length, 50000) points = [line.interpolate(d) for d in distances] # 添加节点和边 for i in range(len(points) - 1): p1, p2 = points[i], points[i + 1] G.add_edge((p1.x, p1.y), (p2.x, p2.y), weight=50000) print(f"网络节点数: {G.number_of_nodes()}, 边数: {G.number_of_edges()}") # 计算某点到其他点的最短路径 start = list(G.nodes())[0] lengths = nx.single_source_dijkstra_path_length(G, start, weight="weight") print(f"从起点出发可达节点数: {len(lengths)}")

参数说明:line.interpolate(d)在线段上按距离取点,50000 表示每 50km 一个节点,这个值可以根据分析精度调整。nx.single_source_dijkstra_path_length返回从起点到所有可达节点的最短距离。这个简易网络没有考虑实际站点和换乘,但足以做趋势性分析。

5.2 用等时圈展示高铁可达范围

等时圈是可达性分析最直观的输出。基于上面的网络,计算从某城市出发 2 小时、4 小时能到达的范围,然后生成凸包或缓冲区。

import geopandas as gpd from shapely.geometry import MultiPoint from shapely.ops import unary_union # 假设已有时速和最短路径结果 # 高铁平均时速按 250km/h 估算 speed_kmh = 250 time_thresholds = [2, 4] # 小时 for hours in time_thresholds: max_dist = hours * speed_kmh * 1000 # 米 reachable = [node for node, dist in lengths.items() if dist <= max_dist] if len(reachable) < 3: continue points = MultiPoint(reachable) hull = points.convex_hull # 向外缓冲 30km,模拟站点周边覆盖 hull_buffered = hull.buffer(30000) gdf = gpd.GeoDataFrame({"hours": [hours], "geometry": [hull_buffered]}, crs="EPSG:3857") gdf.to_file(f"./output/isochrone_{hours}h.gpkg", driver="GPKG") print(f"{hours}小时等时圈生成完成,覆盖面积: {hull_buffered.area / 1e6:.0f} km²")

参数说明:speed_kmh是估算的平均旅行速度,实际高铁旅行速度受停站影响,一般按 200-250km/h 估算。buffer(30000)是给凸包外扩 30km,因为凸包只连接最远点,会漏掉边缘区域。输出为 GeoPackage 格式,可以直接在 GIS 软件里叠加底图查看。

5.3 几个让结果更可信的细节

第一,网络构建时要把地铁和普速铁路也加进去,否则可达性只反映高铁覆盖,不反映实际出行。第二,等时圈的凸包方法在站点稀疏区域会严重高估覆盖范围,更严谨的做法是用实际路网做缓冲区。第三,如果数据里有开通年份字段,可以按年份做时间序列分析,看高铁网络如何逐年扩张。我一般会先用 2024 年数据做现状分析,再用历史年份数据做对比,这样出来的图更有说服力。

最后说一个血泪经验:铁路矢量数据的几何精度参差不齐,有些线路是简化过的,直接用来算长度会偏短。如果做里程统计,最好先用simplify()的逆操作——检查几何节点密度,节点太稀的线路要标记出来,必要时用官方里程数据做校正。这个坑我在第一次做全国高铁里程统计时踩过,算出来比官方数据少了近两成,后来发现是数据简化导致的。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询