ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

全国地铁线路矢量数据(shp)处理指南:坐标系校验、清洗与可视化

2026/10/8 16:40:36 拓冰建站 浏览量
全国地铁线路矢量数据(shp)处理指南:坐标系校验、清洗与可视化 简介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, encodingutf-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 软件适合快速筛查。参数说明encodingutf-8是常见设置但如果这是国内从业者整理的包字段可能用 GBK 编码保存你需要在utf-8报错时改成encodinggbk再读一次。这一步被很多人忽略导致中文线路名乱码后面统计全错。2.3 坐标系怎么选EPSG 4326、3857 与城市局部坐标系的取舍全国地铁线路数据覆盖跨市、跨省供应商给的坐标系往往不统一。最常见的是 EPSG:4326WGS84 经纬度也有直接给 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, encodingutf-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(地铁, , regexFalse) 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(subsetsubset_cols, keepfirst) print(gdf[line_name].nunique()) print(gdf.groupby(line_name).size().sort_values(ascendingFalse).head(10))逻辑说明地铁实际运营中上下行都在同一走廊线位几何几乎重叠。如果数据里没有明确区分上下行直接去重是正确的如果区分了则必须保留direction字段信息否则后面做断面分析时数据会少一半。参数说明regexFalse是为了避免线路名中的括号、加号被当作正则表达式解析。subset参数要根据原字段情况调整如果数据只有线路字段没有站点字段就按线路名去重如果站点字段也存在按“线路名站点名”去重更安全。3.2 拆分多部件几何MultiLineString 为什么影响统计精度读取数据时如果发现geom_type里出现MultiLineString意味着一条线路被存成了若干不相连的线段集合。这本身不算错误但直接用它做长度统计时geometry.length算的是所有部件长度之和后续做缓冲区分析、空间连接时会出现“一个要素对应多个图形”的情况。常见做法是先把多部件拆分保留原属性字段。import geopandas as gpd gdf gpd.read_file(subway_2024.shp, encodingutf-8) gdf gdf.explode(index_partsTrue).reset_index(dropTrue) # 拆分后检查几何类型 print(gdf.geometry.geom_type.value_counts()) # 为每一个拆分后的部件重新计算长度单位与当前坐标系保持一致 gdf[length_raw] gdf.geometry.length print(gdf[length_raw].describe())逻辑说明GeoPandas 的explode会把 MultiLineString 拆成多个 LineString每条线段继承原来的属性。这样后续做叠加分析时每个要素都是单一连续几何统计结果不重叠。参数说明index_partsTrue会生成一个二级索引标记原要素拆分出的第几个部件。如果你不关心部件顺序可以传index_partsFalse省掉这层结构。拆分后一定要重新计算长度字段因为原来的长度字段是拆分前算好的。3.3 按城市拆图层为什么我不用“模糊查询”而是先建城市字典全国数据放到一张图层里做可视化时很难控制标注密度。更实际的需求是按城市拿数据做北京的项目只取北京做成都的只取成都。问题在于不同数据包的城市字段叫法不同有的叫city有的叫所属城市而且有时候“北京”写成“北京市”。最常见的做法是建一个城市映射字典而不是在原始字段上直接做字符串包含。import geopandas as gpd gdf gpd.read_file(national_subway_2024.shp, encodingutf-8) gdf gdf.to_crs(epsg4326) # 假设原始字段叫 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 fsubway_{city}.shp city_gdf.to_file(out_path, encodingutf-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, encodingutf-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(ascendingFalse).head(10))逻辑说明首尾坐标输出到控制台你可以和城市真实地铁站的经纬度比对。如果起点落在郊区而这条线本身是市区线路说明数据存在断线或方向倒置。长度统计则是粗筛——比官网数据多 20% 可能来自重复要素少 20% 可能是断线缺失。参数说明geometry.length的单位取决于当前坐标系。示例代码先把数据转成了 EPSG:4326所以长度单位是度直接除以 1000 得到的是“度/千”不是公里。想得到公里数需要在to_crs里换成投影坐标系比如gdf gdf.to_crs(epsg3857)后再算长度或针对具体城市选更精确的投影。这里我建议先转投影再算否则数值没有参考意义。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, encodingutf-8) gdf gdf.to_crs(epsg4326) # 方案一导出 GeoJSON gdf.to_file(subway_2024.geojson, driverGeoJSON) # 方案二导出 WKT 文件包含线路名和几何文本方便导入数据库或建模工具 gdf[wkt] gdf.geometry.to_wkt() gdf[[line_name, city_name, wkt]].to_csv(subway_2024_wkt.csv, indexFalse, encodingutf-8-sig)逻辑说明to_wkt()会把几何对象序列化成标准文本格式例如LINESTRING (116.3 39.9, 116.4 39.9)。如果你的下游系统是 PostGIS 或 ClickHouseWKT 是它们最容易识别的几何输入方式。utf-8-sig编码则是为了在 Excel 里打开中文不乱码。参数说明driverGeoJSON在 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, encodingutf-8) gdf gdf.to_crs(epsg3857) # 根据线路数据的范围生成渔网从最小坐标到最大坐标每 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(geometrycells, crsEPSG:3857) # 空间相交每个网格内覆盖的线路长度或条数 joined gpd.sjoin(grid, gdf, howleft, predicateintersects) 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是空间连接howleft保证每个网格都保留哪怕没有线路穿过。nunique()统计每个网格内出现的不同线路数量这样不会因为一条线路穿过同一个网格两次造成重复计数。参数说明predicateintersects是默认且最稳妥的判断方式。如果你只想要线路完全穿过网格的数据可以用predicatecontains但那会丢掉大量边缘相交的网格。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 编码读取时指定encodinggbk即可。如果打开也是乱码才需要考虑字段本身损坏。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, encodingutf-8) gdf gdf.sort_values([line_name, station_order]) for line_name, group in gdf.groupby(line_name, sortFalse): # 取该线路的第一条几何和最后一条几何 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文件把坐标系、编码、清洗时间、做了哪些操作写进去。这个文件不参与计算但三个月后再打开这个数据包时能省掉大量回忆成本。希望帮到你。本文还有配套的精品资源点击获取