ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

北京道路矢量数据包处理:Shapefile结构与坐标系避坑指南

北京道路矢量数据包处理:Shapefile结构与坐标系避坑指南 简介面向GIS开发、城市规划、交通分析与地图制图工作者北京城区道路矢量数据包覆盖北京市主城区主要道路提供精细化的路网矢量底图解决在路径分析、城市建模时常遇到的缺少统一规范、可立即使用的道路数据的问题。压缩包共22个文件约8.08MB包含shp、shx、dbf、prj、cpg、sbn、sbx、shp.xml等完整组件另附道路整体与局部预览图及说明性文本文件命名统一、目录结构简洁开箱即用。数据涵盖主干道、次干道、支路及部分街巷坐标系为WGS84或CGCS2000以prj文件为准属性字段包含道路名称、等级、方向、车道数等信息可用于交通规划、路径分析、地图制图、城市建模等场景也可作为Shapefile学习样例。目前已有70人学习下载适合需要快速获取规范路网底图的GIS从业者、规划研究人员及交通分析爱好者。1. 北京城区道路矢量数据包拿到shp全套文件后的第一道坎拿到一份北京城区道路矢量数据包打开压缩包看到十几个同名不同后缀的文件多数人的第一反应是把那个.shp拖进GIS软件。这个动作本身不难难的是拖进去之后才暴露的问题坐标系对不上、属性表乱码、道路线跑到了偏离大陆很远的地方而这些事故几乎都源于同一个误解——把Shapefile当成单个文件。这份数据包的本质是GIS矢量数据用线要素表达北京城区道路网的位置、走向与道路属性适合用来做城市路网底图、路径规划前置分析、道路可视化等场景。下面内容会把文件结构、坐标系、读取与转换、常见坑位一条条讲清楚。新手能照着跑通熟手能直接对号入座避免在同一个地方反复翻车。2. 拆开Shapefile全套文件后缀职责、道路要素与坐标系确认2.1 .shp/.shx/.dbf/.prj/.cpg各管一段全套文件不是“一个文件”Shapefile是GIS矢量数据格式里流传最广、误解也最多的一种。严格说它是一组必须配套使用的文件集合主文件是.shp里面按记录存着每个要素的几何坐标.shx是几何索引帮助软件快速定位第N个要素.dbf是属性表道路名称、等级、车道数这类信息全在这里。三者缺一数据就不完整。除了这三个还有两个经常被忽略但很重要的文件.prj记录坐标系定义.cpg记录属性表字符编码。缺少.prj软件只能靠猜坐标系缺少.cpg属性表里的中文极易乱码。拿到数据包后建议先做一步检查列出整个目录的内容。ls -lh /path/to/beijing_roads/-lh表示用长格式并显示人类可读的文件大小。理想情况下目录里能看到成对的.shp、.shx、.dbf以及可选的.prj、.cpg、.sbn、.sbx。其中.sbn/.sbx是空间索引不是必需文件。如果只有.shp和.shx属性表已经丢了一半如果连.prj都没有坐标系方向就要花时间排查。检查完毕后把整套文件按原目录拷贝出来不要只拖主文件。提示.shp、.shx、.dbf三个文件必须同名且在同一目录软件才能识别为一个图层。改主文件名时三个后缀要一起改。容易踩的还有另一个操作有人为了省事把.shx改名为.shp结果软件提示文件头无效。原因是.shx和.shp的二进制头结构不同改了后缀也骗不过解析器。我一般打zip包时直接压缩整个文件夹而不是单文件压缩能省掉很多传输后找不到.dbf的事故。2.2 道路图层里有什么线要素、属性字段与图层范围道路矢量数据的几何类型通常是PolyLine线要素。一条道路可能对应一条简单线也可能由多个线段组成MultiLineString。北京城区道路数据包的字段组织常见做法是包含道路名称、道路等级、长度、宽度、车道数等但不同来源的字段名和值域差异很大拿到手先看一眼字段结构再动手。ogrinfo -so -al beijing_roads.shp-so表示只输出概要-al表示查看所有图层。这条命令由GDAL工具自带不需要启动图形界面。运行结果会列出几何类型、要素数量、图层范围以及每个字段的名称和类型。特别注意Layer Extent这一行范围里x在115到118、y在39到41附近说明是经纬度坐标数值是上百万量级则是投影坐标单位是米。字段层面我最关心两个字段一个是道路等级它决定后续筛选和符号化的分类逻辑另一个是道路名称它常用来做标注和关联外部数据。等级字段的值域可能是“高速/国道/省道/城市主干道/次干道/支路”也可能是数字编码需要先看属性表确认。别急着做分析先用三分钟把字段语义摸透后面能省下大把返工时间。2.3 坐标系先确认CGCS2000、WGS84与本地投影的关系坐标系是被当成黑匣子最多的地方。地理坐标系用经纬度表达位置例如WGS84和CGCS2000都是地理坐标系单位是度投影坐标系则是把地球表面摊平成平面单位是米例如Web墨卡托。北京城区道路数据常见的坐标形态有几种WGS84经纬度、CGCS2000经纬度以及本地城建坐标系下的投影坐标。三种形态之间不能直接叠加。我拿到数据包后第一件事就是用Python把坐标系和范围读出来而不是先打开软件看图层。代码很短但信息量足够判断后续所有操作的方向。import geopandas as gpd roads gpd.read_file(beijing_roads.shp) print(坐标系:, roads.crs) print(数据范围:, roads.total_bounds) print(几何类型:, roads.geometry.geom_type.unique())roads.crs输出None说明文件缺少.prj输出EPSG:4326或EPSG:4490则分别是WGS84或CGCS2000地理坐标输出EPSG:3857则是Web墨卡托。total_bounds返回[xmin, ymin, xmax, ymax]四个值用取值范围即可判断是经纬度还是投影坐标。道路数据的所有空间操作都依赖坐标系这一步不确定后续裁切、缓冲区、长度计算的结果都会跟着错。3. 把数据用起来桌面GIS加载与Python读取两条路径3.1 桌面GIS加载拖入shp之前先看三点对于不想写代码的从业者用桌面GIS软件加载Shapefile是第一选择。常见做法是把.shp文件直接拖进软件画布或通过菜单里的“添加矢量图层”选择文件。加载成功只是起点真正要检查的是三件事。第一图层面板里显示的坐标系是否与数据来源说明一致。如果软件弹出“选择坐标系”对话框说明这个shp缺少.prj这时候不要随便选一个填上而应回到源头确认原始坐标系。第二打开属性表看一眼字段内容是否正常道路名称能否正确显示出现方块字或乱码就是编码问题。第三叠加一份在线底图看看道路线是否落在北京城区的实际位置上这一步能在十分钟内发现坐标系错位。提示如果道路线与底图错位几公里到几十公里大概率是地理坐标与投影坐标混用如果是角度旋转式的偏移则可能是参考椭球不一致要优先处理坐标系定义。3.2 用GeoPandas读shp字段、范围与数据质量一眼看全Python侧我常用GeoPandas读写Shapefile它对文件集合的处理封装得很干净。读取后先做五项基础检查要素数量与字段数量、字段名列表、前几行属性内容、几何合法性、坐标系。import geopandas as gpd roads gpd.read_file(beijing_roads.shp, encodingutf-8) print(要素数x字段数:, roads.shape) print(字段列表:, roads.columns.tolist()) print(前3条记录:) print(roads.head(3)) print(有效几何占比:, roads.geometry.is_valid.sum(), /, len(roads))read_file的encoding参数用于指定属性表编码。数据包源用的是GBK时utf-8会读出乱码把encoding改为gbk即可。shape返回元组第一个是要素数第二个是字段数能快速评估数据包规模。is_valid是shapely层面的几何自检返回每个几何是否合法数值低于总要素数说明存在自相交、重复点这类问题后面做空间计算前要先清洗。我习惯在读取之后顺手统计道路等级的分布这能看出数据包覆盖的层次是否完整。只有高速公路没有城市道路和只有支路没有快速路决定了这份数据适不适合你的场景。这一步五分钟能跑完但能避免拿一份字段结构完全不同的数据硬套分析流程。3.3 按道路等级筛选与按范围裁切两个高频操作数据包拆开后最常见的两个操作是按等级筛选和按范围裁切。前者用于只保留高速、快速路等主干路网后者用于把研究范围缩小到特定城区。两个操作都要在坐标系确认之后执行。# 筛选城市主干道及以上等级 selected roads[roads[level].isin([高速, 快速路, 主干道])] print(筛选后要素数:, len(selected)) # 按经纬度范围裁切 from shapely.geometry import box bounds box(116.2, 39.8, 116.5, 40.0) clipped gpd.clip(roads, bounds) print(裁切后要素数:, len(clipped))示例里的level是属性字段名实际使用时按数据包的字段结构替换box中的四个数依次是xmin, ymin, xmax, ymax。gpd.clip在执行前会检查输入与裁剪框的坐标系如果不一致直接报错这反而是好事能逼你先把坐标系统一。筛选和裁切都建议另存为新文件保留原始数据包不动后续做错了有后悔药。另一个容易踩的边界是裁切后道路线只剩半截要素被打断后长度字段还是原始值。如果后续要算裁切范围内的道路里程需要重新计算几何长度而不是直接用属性表里的长度字段。投影坐标系下用geometry.length即可经纬度下要先转投影否则长度单位是度数值没有实际意义。4. 坐标转换与格式互操作把shp导出WKT、GeoJSON并重投影4.1 ogr2ogr一行命令坐标对齐与编码修正一次完成shp格式矢量数据导出为其他格式是数据流水线里的常规动作。GDAL里的ogr2ogr命令是处理这类转换最稳当的工具一条命令可以把坐标系、编码、目标格式一次搞定。以北京城区道路数据包转Web墨卡托为例ogr2ogr -t_srs EPSG:3857 -lco ENCODINGUTF-8 \ beijing_roads_3857.shp beijing_roads.shp-t_srs指定目标坐标系EPSG:3857是Web墨卡托适合叠加在线底图-lco ENCODINGUTF-8让输出的属性表使用UTF-8编码避免下游再乱码。命令后段的两个参数依次是输出文件和输入文件。转换完成后用ogrinfo -so验证输出的坐标系和要素数确认没有丢要素。常见误区是把转换和修复混在一起。如果输入shp本身缺少.prjogr2ogr默认按未知坐标系处理结果可能仍然错位。先确认输入坐标系再转比转完后再发现错位要省时间。我通常先执行一次ogrinfo -so用输出里的范围反推坐标形态再决定目标坐标系编号。4.2 Python批量把shp导出为WKT文本几何与属性一起带走WKT是文本形式的几何表示适合存储、交换、入库。热词“shp格式矢量数据导出为wkt”指的就是这件事。用GeoPandas做这一步非常直接读取shp后取每个几何对象的WKT字符串连同属性一起导出为CSV。import geopandas as gpd roads gpd.read_file(beijing_roads.shp, encodinggbk) roads[wkt] roads.geometry.apply(lambda g: g.wkt) out roads[[name, level, wkt]] out.to_csv(beijing_roads_wkt.csv, indexFalse, encodingutf-8-sig)geometry.apply(lambda g: g.wkt)逐个几何对象生成WKT字符串MultiLineString要素会生成以MULTILINESTRING开头的文本数据库里如果要按单线处理还需要先拆分。to_csv的encoding用utf-8-sig是给Excel准备的带BOM后中文列名和属性在Windows上打开不乱码。这里有个细节如果目标系统只接受单个要素对应一条记录而数据包存在多部件线要素导出前先用explode()拆开否则WKT里会保持MULTILINESTRING形式下游解析时容易漏。拆完后重新计算要素数量确认拆分后的数据量符合预期再继续。4.3 目标坐标系怎么选底图、量算与存储各用各的坐标系不是越新越好而是由下游场景决定。存储和交换时保留原始坐标系最安全叠加在线底图时用Web墨卡托做长度面积量算时用本地投影坐标系。三者使命不同混用就会得到离谱结果。以北京城区道路数据包为例常见取舍如下使用场景推荐坐标系原因存储与交换原始坐标系避免重复投影累积误差前端底图叠加EPSG:3857与在线底图切片方案一致长度与面积量算本地投影坐标系距离变形最小量算场景里北京适合带中央经线的高斯投影坐标系或等距投影具体EPSG编码由数据原始范围决定不要照抄别的城市。一个保险做法是用GeoPandas的estimate_utm_crs自动估算UTM分带roads_proj roads.to_crs(roads.estimate_utm_crs()) roads_proj[length_m] roads_proj.geometry.length print(roads_proj[[name, length_m]].head())estimate_utm_crs根据数据范围的几何中心自动选择UTM带号对北京城区道路这种范围适中的数据集足够可靠。geometry.length在投影坐标系下返回米制长度这是量算的基础。注意不要在Web墨卡托下量算长度它的纬向拉伸会导致结果明显偏大。这一章的三个操作是串行关系先确认坐标再转换最后导出目标格式顺序反了会多出很多返工。5. 道路数据避坑记录五个高频问题的现象、原因与解法这些坑分布在文件读取、编码、坐标系、拓扑和量算五个环节每一条都是处理这类路网数据的常见事故。按现象、原因、解决的顺序记录遇到问题可以直接对号入座。5.1 现象一只拷了.shp属性表全空或图层打不开现象数据包解压后只把后缀为.shp的文件拷到工作目录在GIS软件里加载要么属性表一片空白要么直接报无法读取。原因不复杂.shp存的是几何坐标属性记录都在.dbf里.shx是几何索引缺少它软件难以按记录号定位几何。三个文件必须放在一起缺一不可。解决传输或拷贝时以整个文件夹为单位打包用zip压缩整个目录解压后不要移动单个文件。如果手头只有.shp没有.dbf属性表已经无法恢复只能回到源头重新获取。这个坑在团队协作里特别常见文件传输时平台自动只传有图标的.shp收到的人一脸懵。预防手段是分发前先执行一次ogrinfo -so确认完整可读再出手。5.2 现象二属性表中文乱码或字段名变成问号现象打开属性表后路名显示成问号或乱码符号。原因是属性表编码不匹配数据包内的.dbf大概率是GBK编码而软件或库默认按UTF-8解读也曾出现缺少.cpg文件导致编码信息丢失的情况。读取时指定编码就能解决。import geopandas as gpd roads gpd.read_file(beijing_roads.shp, encodinggbk) roads.to_file(beijing_roads_utf8.shp, encodingutf-8)先用encodinggbk读出来若仍乱码再尝试gb18030和utf-8对道路名称字段做逐一验证。另存为UTF-8后to_file会自动写出配套的.cpg文件下游再用encodingutf-8读取即可一劳永逸。编码问题没有银弹唯一可靠的排查路径就是逐个编码试读确认中文正常后再做批量处理。5.3 现象三道路线“飞”到了地图上不该在的地方现象加载道路图层并叠加在线底图后道路线出现在海里、别的省份或者整体偏离真实位置若干公里。原因基本是坐标系定义缺失或指定错误数据实际是CGCS2000经纬度却被按WGS84加载或数据是投影坐标却被当成经纬度。加载环节软件没有拿到正确的坐标系定义。解决先打印total_bounds看范围数值形态。x在116附近、y在39附近是经纬度x是几万到几十万是投影坐标。知道形态后再到.prj或数据来源说明里确认具体坐标系用to_crs转到目标坐标。最忌讳的是在软件里手动选一个看起来差不多的坐标系那等于把错误固定下来后面所有分析都会跟着偏。5.4 现象四路口看似相交拓扑上却不连通现象路网在十字路口画面上是相交的但做连通性分析或路径规划时算法认为路网是断的。原因有两个一是数据制作时没有在交点处打断线并创建共享节点两条路只是视觉相交二是立交桥场景中上下两层路本来就不连通数据里又没有Z值或层级字段去区分。平面拓扑和真实通行关系是两回事。解决如果数据包带道路等级字段先确认快速路与地面道路的连通规则再做拓扑预处理。工具上可以用QGIS的“打断线于线”功能或PostGIS里的ST_Node做节点化处理处理后检查相交点处是否产生共享节点。若场景需要区分立体交叉则需要Z值数据或道路高程属性单纯平面shp满足不了。5.5 现象五量算长度和真实距离差得离谱现象在Web墨卡托底图上量某条路长度数值比导航距离大出不少。原因不在数据而在测量坐标系Web墨卡托在纬度越高处拉伸越严重北京一带纬度较高长度误差会被放大。这不是数据包的错是量算工具的坐标系选错了。解决量算前把数据转到本地投影坐标系再算长度。GeoPandas的estimate_utm_crs可以自动估算合适分带配合geometry.length即得米制结果。如果不想引入投影坐标也可以直接用球面距离公式在经纬度上逐段计算但代码更繁琐。我的习惯是量算一律用投影坐标底图叠加统一用3857存储保持原始坐标系三者分开各干各的翻车率最低。6. 拿到数据包先自检一个三分钟摸清数据底细的脚本上面这些坑很多都可以在动手分析前用一个自检脚本提前暴露。我每次拿到新的道路数据包第一件事不是打开软件看图层而是跑一遍下面这段自检把几何类型、坐标系、范围、空几何、重复要素一次性打印出来。数据包值不值得用、要用在哪跑完心里就有底。import geopandas as gpd roads gpd.read_file(beijing_roads.shp, encodingutf-8) print(要素数:, len(roads)) print(几何类型:, roads.geometry.geom_type.unique()) print(坐标系:, roads.crs) bounds roads.total_bounds print(范围:, bounds) if 100 bounds[0] 125: print(判断: 经纬度坐标, 适合叠加底图) elif abs(bounds[0]) 10000: print(判断: 投影坐标, 单位是米) print(空几何数量:, roads.geometry.isna().sum()) print(重复要素数量:, roads.geometry.duplicated().sum())空几何会在空间连接时被静默跳过重复要素会让统计结果翻倍这两项是数据质量里最容易阴人的。范围判断帮你一眼确认坐标形态避免在坐标系错位上浪费时间。脚本里的isna检查几何字段是否为空duplicated检查几何对象是否重复这两个方法属于GeoPandas自带能力不需要额外装库。这段脚本我保存成固定文件放在工具目录里每次更新数据都先跑一遍。坐标系和几何类型都在预期内才继续做筛选和转换但凡有一个异常就回到源头核对。做道路数据方向前期多花三分钟自检比后期为一次错位的分析返工几小时划算得多。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进