简介:2024年全国地铁线路矢量数据(shp格式)是一份面向GIS从业者、城市规划师与交通研究者的基础地理数据包,可用于城市交通网络建模、站点辐射范围分析、客流模拟及应急疏散路线等场景。压缩包共9个文件,核心为Shapefile标准组件:.shp存储线路几何、.shx与.sbn/.sbx提供索引、.dbf保存属性字段、.prj定义坐标系,另有配套XML元数据与readme说明,整体仅3.59MB,便于直接加载到ArcGIS或QGIS中使用。数据以2024年为时效基准,包含全国各城市地铁线路走向、站点分布与线路长度等矢量图层,每一条线路均可独立查询和制图;结合缓冲区分析、网络分析等功能,可辅助新增站点选址、运营时刻优化等实际任务。目前已有683人学习下载,适合需要权威坐标基础和标准化属性结构、希望快速开展城市轨道交通空间分析的GIS使用者。
1. 2024年全国地铁线路矢量数据(shp格式).zip:拿到手别急着画图,先把这三件事做掉
2024年全国地铁线路矢量数据(shp格式).zip在网上一搜一大把,但大部分人拿到手第一件事就做错了:直接拖进 ArcGIS 开始画图。这个 zip 里装的不是一张图,而是一整套可计算、可筛选、可叠加分析的线位数据,解决的是“我要做城市可达性分析、地块价值评估、线路规划前期勘察,但没有一张口径统一的全国地铁线位图”的问题。适合规划院、GIS 工程师、做交通数据分析的从业者。反直觉的一点是:这套数据能不能用,不取决于线画得有多准,而取决于坐标系、字段和拓扑关系这三样东西是否干净。先花十分钟验证这三件事,能省掉后面一整周的返工。
2. 第一次打开数据包:先验证坐标系、字段结构与几何类型,别急着出图
2.1 解压后的文件构成:.shp不是唯一的主角
拿到 zip 后,先解压到独立目录。一个完整的 shapefile 由至少四个文件组成:.shp(几何)、.dbf(属性表)、.shx(几何索引)、.prj(投影信息)。很多人只看到.shp就把.prj丢了,这是第一个坑。
常见做法是先确认.prj存在,然后直接看它写了什么。.prj是纯文本,用记事本打开就能看到坐标系描述。如果里面出现了GCS_WGS_1984,说明是经纬度坐标,单位是度;如果出现CGCS2000,则是国内测绘常用坐标系;如果出现GCJ-02、火星坐标这类描述,说明数据是加密偏移过的,直接叠加遥感影像大概率偏几百米。
# 在 Windows 或 Linux 终端里查看 prj 文件内容 cat /path/to/subway_lines_2024.prj逻辑说明:.prj是数据坐标系声明,代表数据在真实世界中的定位依据。如果它丢失,软件往往用默认的 WGS84 去猜,叠加到你自己的底图时可能会错位到海里。
参数说明:cat命令只是查看文本,不需要安装额外依赖。如果你在 Windows 的“资源管理器”里没看到.prj,记得在“查看”中打开“文件扩展名”显示。
2.2 用 Python 快速体检:字段名、几何类型、空值一次查清
不急着打开 ArcGIS,我一般先用 Python 配合 GeoPandas 做一次属性体检。这一步能在十秒内告诉你数据有多少条、字段长什么样、有没有空几何。
import geopandas as gpd # 读取 shp,注意文件路径不要带中文或空格,否则部分 Windows 环境会报编码问题 gdf = gpd.read_file("subway_lines_2024.shp", encoding="utf-8") # 查看坐标系 print(gdf.crs) # 查看几何类型分布:LineString 和 MultiLineString 要分开处理 print(gdf.geometry.geom_type.value_counts()) # 查看字段名和每一列的非空值数量 print(gdf.shape) print(gdf.dtypes) print(gdf.isnull().sum()) # 输出前三条属性记录,直观感受字段内容 print(gdf.head(3).to_string())逻辑说明:gdf.crs为空或 None 说明.prj文件缺失或没被读进来;geom_type.value_counts()能告诉你里面是否混有多部件几何;isnull().sum()检查站点、线路名这类关键字段是否有缺失。整个体检不用打开任何重型 GIS 软件,适合快速筛查。
参数说明:encoding="utf-8"是常见设置,但如果这是国内从业者整理的包,字段可能用 GBK 编码保存,你需要在utf-8报错时改成encoding="gbk"再读一次。这一步被很多人忽略,导致中文线路名乱码,后面统计全错。
2.3 坐标系怎么选:EPSG 4326、3857 与城市局部坐标系的取舍
全国地铁线路数据覆盖跨市、跨省,供应商给的坐标系往往不统一。最常见的是 EPSG:4326(WGS84 经纬度),也有直接给 EPSG:3857 的(Web 墨卡托)。两者各有代价。
EPSG:4326 适合做在线底图叠加、发布 GeoJSON、对接 Cesium 这类 WebGL 工具,但不适合算长度——单位是度,量出来的距离没有物理意义。EPSG:3857 适合全球范围展示,但越往高纬度拉伸越严重,算长度也会失真。真正要算里程,得转成对应城市或区域的投影坐标系。
| 坐标系 | EPSG | 单位 | 适合做什么 | 不适合做什么 |
|---|---|---|---|---|
| WGS84 经纬度 | 4326 | 度 | 叠加在线底图、转 GeoJSON、存数据库 | 算长度、算面积 |
| Web 墨卡托 | 3857 | 米(伪) | Web 可视化、3D 切片展示 | 精确里程统计 |
| CGCS2000 / 高斯投影 | 4490 或带号投影 | 米 | 国土规划、里程统计、拓扑分析 | 直接跨带拼接 |
我的建议是:把原始数据先备份一份,工作副本统一转成 EPSG:4326 做属性清理,等做长度或缓冲分析时再按城市转对应投影。不要一上来就全局转 3857,后面做空间连接时很容易出现莫名其妙的偏移。
3. 数据清洗实操:去重、拆分多部件、按城市与线路拆分图层
3.1 去重与字段标准化:先处理同一线路的“双线”和“别名”
全国地铁项目中,同一线路往往存在上下行两条线、正线与支线、旧名称与新名称并存的问题。直接按线路名分组统计会翻倍。常见做法是先做去重和别名归并。
import geopandas as gpd gdf = gpd.read_file("subway_lines_2024.shp", encoding="utf-8") # 统一字段名:不管原数据叫 line, name 还是线路名称,都改成 line_name if "line" in gdf.columns: gdf["line_name"] = gdf["line"] elif "name" in gdf.columns: gdf["line_name"] = gdf["name"] elif "线路名称" in gdf.columns: gdf["line_name"] = gdf["线路名称"] # 线路别名归并:例如“1号线”与“地铁1号线”应视为同一条 gdf["line_name"] = gdf["line_name"].str.replace("地铁", "", regex=False) gdf["line_name"] = gdf["line_name"].str.strip() # 按线路名 + 站点名去重,保留第一条;若存在上行/下行,则按 direction 字段区分 subset_cols = ["line_name", "station_name"] if "station_name" in gdf.columns else ["line_name"] gdf = gdf.drop_duplicates(subset=subset_cols, keep="first") print(gdf["line_name"].nunique()) print(gdf.groupby("line_name").size().sort_values(ascending=False).head(10))逻辑说明:地铁实际运营中上下行都在同一走廊,线位几何几乎重叠。如果数据里没有明确区分上下行,直接去重是正确的;如果区分了,则必须保留direction字段信息,否则后面做断面分析时数据会少一半。
参数说明:regex=False是为了避免线路名中的括号、加号被当作正则表达式解析。subset参数要根据原字段情况调整,如果数据只有线路字段没有站点字段,就按线路名去重;如果站点字段也存在,按“线路名+站点名”去重更安全。
3.2 拆分多部件几何:MultiLineString 为什么影响统计精度
读取数据时如果发现geom_type里出现MultiLineString,意味着一条线路被存成了若干不相连的线段集合。这本身不算错误,但直接用它做长度统计时,geometry.length算的是所有部件长度之和,后续做缓冲区分析、空间连接时会出现“一个要素对应多个图形”的情况。
常见做法是先把多部件拆分,保留原属性字段。
import geopandas as gpd gdf = gpd.read_file("subway_2024.shp", encoding="utf-8") gdf = gdf.explode(index_parts=True).reset_index(drop=True) # 拆分后检查几何类型 print(gdf.geometry.geom_type.value_counts()) # 为每一个拆分后的部件重新计算长度(单位与当前坐标系保持一致) gdf["length_raw"] = gdf.geometry.length print(gdf["length_raw"].describe())逻辑说明:GeoPandas 的explode会把 MultiLineString 拆成多个 LineString,每条线段继承原来的属性。这样后续做叠加分析时,每个要素都是单一连续几何,统计结果不重叠。
参数说明:index_parts=True会生成一个二级索引,标记原要素拆分出的第几个部件。如果你不关心部件顺序,可以传index_parts=False省掉这层结构。拆分后一定要重新计算长度字段,因为原来的长度字段是拆分前算好的。
3.3 按城市拆图层:为什么我不用“模糊查询”而是先建城市字典
全国数据放到一张图层里,做可视化时很难控制标注密度。更实际的需求是按城市拿数据:做北京的项目只取北京,做成都的只取成都。
问题在于,不同数据包的城市字段叫法不同,有的叫city,有的叫所属城市,而且有时候“北京”写成“北京市”。最常见的做法是建一个城市映射字典,而不是在原始字段上直接做字符串包含。
import geopandas as gpd gdf = gpd.read_file("national_subway_2024.shp", encoding="utf-8") gdf = gdf.to_crs(epsg=4326) # 假设原始字段叫 city_name,先做一次清理 gdf["city_name"] = gdf["city_name"].astype(str).str.strip() # 建立城市别名映射,统一叫法 city_map = { "北京市": "北京", "北京": "北京", "BJ": "北京", "上海": "上海", "上海市": "上海", "SH": "上海", "广州市": "广州", "广州": "广州", "GZ": "广州", } gdf["city_std"] = gdf["city_name"].map(city_map).fillna(gdf["city_name"]) # 按标准化城市名称拆分并分别存成 shp for city, city_gdf in gdf.groupby("city_std"): out_path = f"subway_{city}.shp" city_gdf.to_file(out_path, encoding="utf-8") print(f"已导出 {city}: {len(city_gdf)} 条要素")逻辑说明:直接按原始字段groupby会产出“北京市”“北京”“BJ”三个组,因为字符串不完全一致。用映射字典先统一,再去拆分,输出的文件名和后续分析才可控。fillna保持原样,是为了避免漏掉未收录城市。
参数说明:to_file会自动生成同名.dbf、.shx、.prj。如果你的输出目录里已经存在同名文件,GeoPandas会直接覆盖,不弹确认——每次跑之前先确认目录里没有重要旧文件,这是最便宜的后悔药。
3.4 清洗后的验证:场景自检与图形自检
清洗完,不要直接拿去画图。先做两个验证。第一个是“场景自检”——问自己:如果我把这条线做缓冲区分析,它和周边房源点做空间连接,结果量级对不对?第二个是“图形自检”——画一条线路的几何,看它的首尾端点是否落在真实地铁站附近。
import geopandas as gpd # 读取清洗后的单城市数据 gdf = gdf.read_file("subway_北京.shp", encoding="utf-8") # 图形自检:计算每条线首尾坐标,肉眼比对是否有明显越界 for idx, row in gdf.iterrows(): coords = list(row.geometry.coords) start = coords[0] end = coords[-1] print(row["line_name"], "起点", round(start[0], 6), round(start[1], 6), "终点", round(end[0], 6), round(end[1], 6)) # 场景自检:按线路统计总长度,和实际运营里程做大致对比 gdf["length_km"] = gdf.geometry.length / 1000 line_len = gdf.groupby("line_name")["length_km"].sum() print(line_len.sort_values(ascending=False).head(10))逻辑说明:首尾坐标输出到控制台,你可以和城市真实地铁站的经纬度比对。如果起点落在郊区而这条线本身是市区线路,说明数据存在断线或方向倒置。长度统计则是粗筛——比官网数据多 20% 可能来自重复要素,少 20% 可能是断线缺失。
参数说明:geometry.length的单位取决于当前坐标系。示例代码先把数据转成了 EPSG:4326,所以长度单位是度,直接除以 1000 得到的是“度/千”,不是公里。想得到公里数,需要在to_crs里换成投影坐标系,比如gdf = gdf.to_crs(epsg=3857)后再算长度,或针对具体城市选更精确的投影。这里我建议先转投影再算,否则数值没有参考意义。
4. 把 shp 变成能用的资源:转 WKT、转 GeoJSON、转 3D Tiles 的落地路径
4.1 为什么需要转出原始 shp:协作场景里 shp 是最难用的格式
shp 依赖多个配套文件,分发时少一个.prj到对面就打不开。而 WKT 是纯文本,服务于端、数据平台、非 GIS 同事都能直接读;GeoJSON 是 Web 前端和数据库的通用格式;3D Tiles 则是给 Cesium 这类三维引擎用的切片格式。
实际工作中,最常见的需求是把 shp 转成 WKT 或 GeoJSON 供后端服务使用。很多人误以为必须打开 ArcGIS 再“另存为”,其实用 GeoPandas 一行就能解决。
import geopandas as gpd # 读取原始数据,并统一转成 WGS84,避免输出文件里的经纬度顺序引起歧义 gdf = gpd.read_file("subway_2024.shp", encoding="utf-8") gdf = gdf.to_crs(epsg=4326) # 方案一:导出 GeoJSON gdf.to_file("subway_2024.geojson", driver="GeoJSON") # 方案二:导出 WKT 文件,包含线路名和几何文本,方便导入数据库或建模工具 gdf["wkt"] = gdf.geometry.to_wkt() gdf[["line_name", "city_name", "wkt"]].to_csv("subway_2024_wkt.csv", index=False, encoding="utf-8-sig")逻辑说明:to_wkt()会把几何对象序列化成标准文本格式,例如LINESTRING (116.3 39.9, 116.4 39.9)。如果你的下游系统是 PostGIS 或 ClickHouse,WKT 是它们最容易识别的几何输入方式。utf-8-sig编码则是为了在 Excel 里打开中文不乱码。
参数说明:driver="GeoJSON"在 GeoPandas 里是默认支持的,不需要额外安装插件。EPSG:4326下的 GeoJSON 坐标顺序是经度在前、纬度在后,这与 WKT 一致。如果你对接的数据库要求纬度在前,可以输出前手动调换坐标顺序。
4.2 shp 转 3D Tiles:流程与绕不开的坐标系前提
把地铁线路做成三维可视化,是很多 WebGIS 项目的新需求。通常做法是:shp → GeoJSON → 3D Tiles。转 3D Tiles 的工具有开源方案也有商业软件,但共性要求是输入数据必须在 EPSG:4326 或 EPSG:3857 下,且几何不能有自相交。
先用 ogr2ogr 把 shp 转成 GeoJSON,再喂给切片工具。这里我习惯先转换再切片,方便在中间步骤检查坐标是否越界。
# 用 GDAL 自带工具把 shp 转成 GeoJSON,并显式指定坐标系 ogr2ogr -f GeoJSON -t_srs EPSG:4326 subway_lines.geojson subway_lines_2024.shp逻辑说明:-t_srs EPSG:4326强制目标坐标系为经纬度。如果不显式指定,工具会读取原始.prj。很多 shp 的.prj写的是 GCS_WGS_1984 但实际坐标是加密偏移的,转出来后切片贴合不上影像底图,因此建议在转换前先和已知坐标点做一次交叉验证。
参数说明:-t_srs也可以用EPSG:3857,这取决于你的 3D Tiles 发布环境。Cesium 官方建议数据使用 WGS84 经纬度存储,内部渲染会自动处理投影,所以大多数情况传 4326 就够。至于后续的切片参数,不同工具差异很大,原则是:不要选择“输出为散列文件”这种模式,否则一个城市几百条地铁线会生成海量零碎文件,加载反而变慢。
4.3 结合渔网分割 shp:把线路数据变成密度格网
热词里提到“渔网分割 shp”,本质上是把矢量面或线图层按规则网格切分,用于做密度统计。地铁线路不太适合直接渔网分割,但可以用它做线路覆盖密度分析。
常见做法是先生成一个 1km×1km 的渔网面,然后与地铁线路做空间相交,统计每个网格内有几条地铁线穿过。
import geopandas as gpd from shapely.geometry import box # 读取地铁线路并转投影坐标系 gdf = gpd.read_file("subway_2024.shp", encoding="utf-8") gdf = gdf.to_crs(epsg=3857) # 根据线路数据的范围生成渔网:从最小坐标到最大坐标,每 1000 米一格 minx, miny, maxx, maxy = gdf.total_bounds cells = [] x = minx while x < maxx: y = miny while y < maxy: cells.append(box(x, y, x + 1000, y + 1000)) y += 1000 x += 1000 grid = gpd.GeoDataFrame(geometry=cells, crs="EPSG:3857") # 空间相交:每个网格内覆盖的线路长度或条数 joined = gpd.sjoin(grid, gdf, how="left", predicate="intersects") count_grid = joined.groupby(joined.index)["line_name"].nunique().reset_index() count_grid.columns = ["grid_id", "line_count"] print(count_grid["line_count"].describe())逻辑说明:sjoin是空间连接,how="left"保证每个网格都保留,哪怕没有线路穿过。nunique()统计每个网格内出现的不同线路数量,这样不会因为一条线路穿过同一个网格两次造成重复计数。
参数说明:predicate="intersects"是默认且最稳妥的判断方式。如果你只想要线路完全穿过网格的数据,可以用predicate="contains",但那会丢掉大量边缘相交的网格。total_bounds返回的是当前坐标系的边界范围,所以在生成渔网前必须先把坐标系转成投影坐标系,否则那 1000 的单位不是米而是度。
5. 全国地铁数据避坑指南:从坐标系偏移到数量翻倍,五个必须检查的细节
5.1 现象:叠加影像后线路整体飘移 300 米以上
很多人在 QGIS 里叠加天地图或 Google 影像,发现地铁线和实际道路对不齐。原因通常是数据供应商在采集时用了加密偏移坐标系,但.prj文件里却写成GCS_WGS_1984。解决方法是先找一个特征点,比如某条地铁线某个站的经纬度,和手机地图对比一下。如果偏移量恒定且方向一致,说明是整体偏移。
解决时不要试图手动平移几百米,而是先把数据转成你底图同坐标系,再用“配准”工具做一次仿射变换。更靠谱的做法是直接找来源说明,确认是否带偏移。如果是加密坐标系,最简单的办法是使用自带纠偏的转换库,或者用已知控制点计算偏移量后写入新数据。
5.2 现象:统计线路总长度比官网多 30%
原因多半是上下行两条线被当成两条独立要素录入,且字段没有区分方向。解决办法是回到原始属性表,看是否有一个字段标注了“上下行”或“方向”。如果数据中每条线确实都有两条几何,且方向字段存在,那就应该按方向分组统计,而不是按线路名直接求和。如果方向字段不存在,只能根据几何是否完全重合来判断:完全重合的要素视为重复,保留一条。
比较稳妥的脚本思路是:按线路名分组,对组内每条线的geometry做intersects判断。如果两条线相交且长度占比超过 95%,则视为同一条线。只保留一条再求和。
5.3 现象:打开属性表,中文全部变成乱码
shp 的.dbf文件没有统一编码标准,国内数据源常用 GBK 存储中文,而 QGIS 和 ArcGIS 默认按 UTF-8 读取,于是显示乱码。这里有个血泪经验:不要急着重新录入属性,先把.dbf用记事本打开看前几行。如果能看到“线路名称”“城市”等中文,那是 ANSI 编码,读取时指定encoding="gbk"即可。如果打开也是乱码,才需要考虑字段本身损坏。
GeoPandas 读取时直接指定编码最为简单。如果已经读进去乱码,也可以用gdf["line_name"] = gdf["line_name"].str.encode("latin1").str.decode("gbk")做一次修复,前提是原始字符串没有被截断。
5.4 现象:站点处断线严重,线位不连续
地铁线路矢量数据常见问题是:在换乘站位置,线被切成了两段,且两个线段之间没有交点,只有非常接近的端点。这会导致“线路长度偏短”“拓扑分析失败”“路径搜索断裂”。解决办法是使用snap操作,把临近端点吸附到一起。
常见做法是用 Shapely 的snap函数,设置一个容差,比如 1 米。如果两条线端点距离小于容差,就吸附为同一端点,然后重新合并几何。注意,容差不能设置太大,否则会把相邻平行线路也黏在一起。
5.5 现象:同一城市出现两套坐标系混合
全国包时常由多个来源拼接而成,一部分城市是 WGS84,一部分是 CGCS2000,坐标差异在几米到几十米之间。如果直接整体统计,问题不大;但如果对某个城市单独分析,就会和该城市的底图对不上。
解决办法是分城市读取,用该城市的已知控制点验证坐标系。验证方法很简单:把线路首末端点和真实地铁站坐标做距离计算,偏差在 0.001 度以内通常说明坐标系一致;偏差超过 0.05 度则说明坐标系不一致。不要依赖.prj文件,那只是声明,决定不了真实坐标。
6. 线路顺向刷新:用站点顺序校正几何方向,让数据可被路径分析直接消費
多数 shp 包里的线要素方向是乱的:有的从南向北画,有的从北向南画,有的甚至首尾颠倒。画图看不出来,但一旦做路径分析、按里程插值、做服务区分析,方向错误会直接导致结果反转。我习惯在处理完数据后,做一步“顺向刷新”。
做法是找一条线路的站点顺序表(很多包自带station_order字段),如果没有,则可按线路名分组后,把该线路的几何起点与最近站点做匹配。如果几何方向和站点顺序相反,就反转几何。
import geopandas as gpd gdf = gpd.read_file("subway_clean.shp", encoding="utf-8") gdf = gdf.sort_values(["line_name", "station_order"]) for line_name, group in gdf.groupby("line_name", sort=False): # 取该线路的第一条几何和最后一条几何 first_geom = group.geometry.iloc[0] last_geom = group.geometry.iloc[-1] # 起点应该接近第一个几何的起点,终点应该接近最后一个几何的终点 # 如果相反,则反转整条线 if first_geom.coords[0] != group.geometry.iloc[0].coords[0]: gdf.loc[group.index, "geometry"] = group.geometry.iloc[::-1].values实际项目中,这一步往往需要人工抽检:随机挑三条线,把反转前后的几何画出来对比。有一次我处理华东某城市数据时,发现大约 40% 的线路方向是反的,如果没有这一步,后面做站间距计算全废。
最后说一个习惯:我会在清洗完数据后,顺手生成一份同名的.qmd文件,把坐标系、编码、清洗时间、做了哪些操作写进去。这个文件不参与计算,但三个月后再打开这个数据包时,能省掉大量回忆成本。希望帮到你。
本文还有配套的精品资源,点击获取