多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

郑州市OSM道路矢量数据清洗实战:从坐标投影到WKT导出全流程

郑州市OSM道路矢量数据清洗实战:从坐标投影到WKT导出全流程 简介郑州市OSM道路矢量数据是一份已处理的开源道路网络地理信息数据集面向GIS学习者、城市规划与交通研究人员可用于路网可视化、空间查询及多源数据叠加分析。压缩包共9个文件约4.49MB以Shapefile为核心涵盖道路线性几何、属性与类别信息其中.shp保存几何要素.dbf记录道路等级、名称、方向等字段.prj定义坐标参考系统.shx提供空间索引另有OSM类别对照表便于理解道路分类标签元数据XML则补充了数据来源与制图信息。数据源自OpenStreetMap志愿者贡献经过整理与编码处理中文名称可正常显示适配QGIS、ArcGIS等常用GIS平台。已有395人下载学习适合需要快速获取郑州市路网底图并开展基础空间分析的入门及中级用户。用户可结合人口、公交等数据进一步探索城市交通布局、公共服务设施选址等应用场景也可借此熟悉Shapefile文件结构及OSM数据属性特征。1. 郑州市OSM道路矢量数据一份“已处理”数据背后的清洗逻辑与复用价值拿到一份“郑州市OSM道路矢量数据已处理”很多人的第一反应是直接拖进QGIS或者ArcGIS Pro里当底图用要么跑去跑一下OD分析。但我要先泼一盆冷水OpenStreetMap的原始数据从来不是一个“干净”的交通网络模型它更像是众包画出来的草图。那份“已处理”的数据真正处理了什么、没处理什么直接决定你能不能拿它做路径规划、路网密度分析或者地图制图。这份数据的价值在于省掉了你从Overpass API拉原始XML、再自己转shapefile的一整轮体力活。但OSM数据里有大量现实世界的语义信息——例如双向道路被拆成两条单线、交叉口在几何上根本没连通、不同等级道路的属性字段命名混乱——这些坑并不会因为“已处理”三个字自动消失。本文按我自己的核对习惯把这份数据从字段含义、投影转换、WKT导出到拓扑修复完整捋一遍最后给你一条可复现的本地操作链。2. 初始数据摸底OSM里的道路到底长什么样处理前先看三处2.1 OSM原始数据的“脏”脏在语义不在几何OSM的道路数据在几何上通常不脏线段本身是从GPS轨迹和航拍影像描出来的精度在城市范围内高到够用。真正脏的是属性语义。原始OSM用highway字段区分道路类型但这个字段的值有几十种从motorway到track到footway中间还夹杂着living_street这样在国内城市里几乎没有的值。更麻烦的是同一条城市主干道可能被切成几十段每一段上的name字段可能写“中原路”也可能写“中原西路”或者干脆为空。“已处理”的数据通常做了三件事过滤掉非机动车道如footway、cycleway、把highway映射成更简洁的fclass如primary、secondary、tertiary、把断头属性做了合并。但这里没有一个统一标准拿到数据后第一件事是看字段字典不能默认映射关系合理。import geopandas as gpd # 读取已处理的OSM道路矢量数据例如GeoPackage格式 gdf gpd.read_file(zhengzhou_osm_roads.gpkg, layerroads) print(gdf.columns.tolist()) # 典型输出会包含fclass, name, oneway, maxspeed, bridge, tunnel, geometry print(gdf[fclass].value_counts())逻辑说明这一步是在读数据前先摸底字段。fclass是我见到的已处理OSM数据里最常用的道路等级字段替代了原始OSM的highway。如果这份数据里根本没有fclass那说明处理程度很浅需要自己做映射。代码块最后打印的value_counts()用来快速查看道路类型分布如果出现path或steps这类非机动车道残留说明过滤得不够干净。2.2 坐标系是第一个坑WGS84经纬度不能直接算面积和距离郑州市东经约113.6度、北纬约34.7度OSM原始数据一律是WGS84经纬度坐标系EPSG:4326。这个坐标系适合全球定位但不适合做米制距离计算。比如计算郑州市二七区范围内的道路密度如果直接用经纬度算面积结果会偏小约20%因为纬线在34.7度处收缩了。我拿到任何“已处理”OSM数据的第一件事就是确认它是否已经做了投影转换。“已处理”通常意味着转成了适合当地的投影坐标系对郑州而言常见选择是CGCS2000 / Gauss-Kruger zone 38NEPSG:4548或者Web MercatorEPSG:3857适合Web可视化不适合精确面积计算。# 确认当前坐标系 print(gdf.crs) # 期望看到 EPSG:4548 或 EPSG:3857 # 如果没有投影用WGS84转CGCS2000 3度带郑州在38度带 if gdf.crs is None or gdf.crs.to_epsg() 4326: gdf gdf.to_crs(EPSG:4548) print(已转为CGCS2000当前单位为米)参数说明EPSG:4548 在ArcGIS和QGIS里的名称是“CGCS2000 / 3-degree Gauss-Kruger zone 38”中央经线114度对应郑州城区。为什么不用WGS84 / UTM zone 49NEPSG:32649理论上UTM也覆盖郑州但在国内测绘成果、政府拼图和互联网服务里CGCS2000是标准后续要和郑州市的国土空间规划数据叠加时统一用CGCS2000更省心。如果这份“已处理”数据是别人导出的很可能已经用了EPSG:4548就不需要再转。3. 深度清洗OSM道路前先做好三类字段处理3.1 从fclass过滤出可通行的路网不只删footway这么简单拿到“已处理”数据后最常见的误操作是只保留fclass里的motorway、trunk、primary、secondary、tertiary然后拿去画路网。但这种做法在郑州这样的大城市里会有问题城市内部大量日常通行依赖的是residential居住区道路和living_street比如郑州的国棉厂老街区、经纬路附近的居民区内部路。如果一刀切删掉路网会变得稀疏路径规划时明明能穿过去的小路会被判定为断头。我的选择是分成两层一层是干线网络motorway到tertiary用于宏观路网分析另一层是全量可通行路网加上unclassified、residential用于路径规划。# 构造二层路网干线 全部 trunk_classes [motorway, trunk, primary, secondary, tertiary] all_drivable gdf[gdf[fclass].isin(trunk_classes [unclassified, residential])].copy() # 处理oneway字段OSM原始值可能是yes/no/-1已处理常见是布尔值 # 如果是原始字符串需要映射 if all_drivable[oneway].dtype object: all_drivable[oneway_bool] all_drivable[oneway].map({yes: True, -1: True, no: False}) elif all_drivable[oneway].dtype bool: all_drivable[oneway_bool] all_drivable[oneway]踩坑预警oneway字段是所有OSM数据处理里最容易翻车的地方。原始值-1在OSM里表示逆行即道路元素绘制方向与实际通行方向相反很多粗处理的脚本只保留yes把-1当no处理导致单行道方向全反。我吃过大亏有次拿一份某省会城市OSM数据做公交路径分析结果所有单行线都能双向走预估算出来全乱。所以上面的代码特意处理了-1这个值。3.2 把长线段按交叉口打断成连通网络这是路径规划的前提OSM道路数据本质上是“线”不是“路网”。两条道路在空间上交叉不代表拓扑上连通因为OSM的编辑者可能在交叉处没有把线打断。这种问题在立交桥、匝道附近尤其突出。做路径规划之前必须先拓扑检查用PostGIS的ST_Node或者QGIS的“拓扑检查器”插件跑一遍找出所有“看似相触但实际未连接”的点。如果不做这一步后续用NetworkX或者pgrouting算最短路径时会算出大量异常绕路。# 在PostGIS中把整张道路表打断成节点连通网络 -- 假设导入到PostgreSQL的schema名为osms表名为zhengzhou_roads SELECT ST_Node(ST_Collect(geom)) AS geom FROM osms.zhengzhou_roads; -- 用节点网络构建拓扑表pgrouting后续可用 SELECT pgr_nodeNetwork(osms.zhengzhou_roads, 0.001, id, geom);3.3 多线合一与重复道路清洗立交桥和并行辅路的取舍“已处理”并不代表“已合并”OSM数据里经常出现一条快速路的主线和辅线被画成两条完全平行的线在线段属性上是两条独立的trunk。对制图来说画两条线没问题对网密度计算来说这会导致道路里程虚高。这里需要人为判断如果两条线间距小于15米并且平行并且属性上的name相同就大概率是主线辅路按需求决定是否合并为一条中心线。常见的做法是用ST_LineMerge或者按属性分组后求中心线QGIS中有“中心线”工具基于Voronoi图。-- 按name宽度阈值聚合近似平行的重复线这里只做标记不真的删 SELECT a.id AS a_id, b.id AS b_id, ST_Distance(a.geom, b.geom) AS dist FROM osms.zhengzhou_roads a JOIN osms.zhengzhou_roads b ON a.name b.name AND a.id b.id WHERE ST_Distance(a.geom, b.geom) 15 AND a.fclass b.fclass LIMIT 100;参数说明15米这个阈值不是拍脑袋定的。郑州市快速路标准断面中主辅路分隔带加上辅路宽度大约在10到20米之间取15米能覆盖大部分平行道路同时避免把两条相邻的独立小街道误判成重复。执行完这个查询后我需要把结果导成CSV人工抽查几十条确认哪些是真正的重复线再决定删除策略。绝对不自动按距离删因为有些两米宽的小巷子和主干道靠得很近删错了直接影响路网完整性。4. 做出可复用的郑州路网从shp到WKT的高保真导出方案4.1 为什么最终交付要导出WKT而不只存GeoJSON或shp很多客户或同事拿到OSM道路数据以后第一个动作是“给我一个shp”“给我一个GeoJSON”。但实际跑起来shp的字段名限长10字符GeoJSON的属性编码在部分老平台会乱码。WKTWell-Known Text是纯文本坐标表示既能嵌进SQL数据库又能用在线工具打开更关键的是很多自研的交通分析引擎、游戏引擎、仿真平台只认WKT。热词“shp格式矢量数据导出为wkt”背后的大量搜索需求就是老板要求把道路数据直接喂给某个私有C或Python引擎。代码逻辑看起来简单geometry列每行调用一次.wkt就能拿到坐标文本。但这里有个隐藏坑——坐标精度。默认WKT导出会保留15位小数对经纬度来说精度过剩对米制坐标来说则会导致数据膨胀。我通常把米制坐标保留3位小数即毫米级把经纬度保留6位小数约0.1米精度这样既满足精度要求又控制文件体积。import geopandas as gpd gdf gpd.read_file(zhengzhou_osm_roads.shp) # 确保是米制投影再导WKT否则导出的是经纬度使用方还得二次转换 gdf_metric gdf.to_crs(EPSG:4548) # 控制精度加属性 gdf_metric[wkt_text] gdf_metric.geometry.apply(lambda geom: geom.wkt) gdf_metric[wkt_text] gdf_metric[wkt_text].apply( lambda wkt: wkt.replace(.0000000, ).replace(.000000, ).replace(.00000, ).replace(.0000, ).replace(.000, ) ) # 导出精简字段 gdf_metric[[name, fclass, oneway_bool, maxspeed, wkt_text]].to_csv( zhengzhou_osm_roads_wkt.csv, indexFalse, encodingutf-8-sig ) print(gdf_metric[wkt_text].iloc[0])4.2 导出WKT要让坐标保留完整、拆分到位WKT有个拆分的需求一条多线MultiLineString在导出时要考虑是否需要拆成单线。OSM原始数据里如果一条马路在同一段被画成了两段并行线空间上可以是MultiLineString但绝大多数解析工具处理MultiLineString时会有兼容性问题比如有些自研引擎直接崩溃。我导WKT之前建议先按每条feature的独立线段explode一下。Geopandas的explode()方法可以把Multi-几何拆成单几何但要同步重置索引避免后续外键关联对不上号。# 按多线段拆成单线 gdf_single gdf_metric.explode(index_partsFalse).reset_index(dropTrue) # 重新统计看拆后数量是否合理正常会大于或等于拆前 print(f拆分前{len(gdf_metric)} 条拆分后{len(gdf_single)} 条)参数说明index_partsFalse这个参数很多人忽略。它表示拆分时不保留原几何的部件编号。如果设置为True炸开后每条线都会带上类似(0, 1)的后缀处理时反而麻烦。对于路网数据我几乎总是用index_partsFalse因为后续做路网拓扑时每一条线的id必须是唯一的。还有一点原始数据如果已经是单线LineStringexplode()不会变多也不会变少不用担心数据放大。4.3 文件结构设计给使用方一个不踩坑的交付目录数据使用者往往不是处理者他们拿到数据后不知道坐标系、不知道字段含义也不关心你怎么清洗的。但他们会做出一个动作用ArcGIS直接打开shp发现没投影信息然后认为你给的坐标是“错的”。所以我交付WKT或shp时都会附带一张最小的README表。文件/资源必填内容说明zhengzhou_roads_wkt.csvname, fclass, oneway_bool, wkt_textWKT为EPSG:4548米制坐标保留3位小数坐标说明.txt投影坐标系CGCS2000 3度带明确告知不要用WGS84读取字段字典.txtfclass取值和含义oneway的True/False使用方据此过滤道路等级数据日期与版本写清楚下线日期例如2025年1月快照OSM数据月度更新版本追溯必备5. 郑州市OSM道路数据处理的避坑与排查五个真实翻车记录5.1 现象路网显示缺了一块从中原区到高新区这段路全空原因分析。有一次我在处理郑州西三环附近的道路时发现西三环高架桥主路的部分线段缺失。排查后确认是OSM编辑者在制图时把高架桥的主线画在了layer1或者layer2的隧道/桥梁层上而数据清洗时直接按layer字段把桥上道路过滤掉了。很多“已处理”脚本习惯按layer删重边却误伤了立交桥主线。解决在处理前检查bridge和tunnel字段立交桥主路上的bridgeyes必须保留如果只清理重复线按name 空间距离合并即可不要用layer字段直接排除。5.2 现象onewayTrue的字段全部方向反了路径规划绕圈原因此前说过OSM的oneway-1被当成了no处理导致单行道双向可走。更隐蔽的情况是“已处理”数据在某些导出工具里把oneway-1直接翻转了几何方向但没有把oneway字段改回yes。结果就是线路几何方向与实际可行驶方向相反。解决写一段脚本检查所有oneway_boolTrue的线段把-1反转的几何统一转回正方向。具体做法是遍历每条线取起点终点判断这条路是左行还是右行。手动反转标准做法是shapely的reverse()。5.3 现象用QGIS打开shp点选要素时卡成狗图层渲染巨慢原因shp文件里包含了几万条只有几十米的零碎线段加上一些远在郑州市域之外的多边形残留——没错OSM数据里偶尔混有行政区划边界或者农田多边形过滤不干净就进了道路shp。渲染慢的另一个原因是空间索引缺失但shp的侧文件.shx丢掉了。解决导入后先用ST_IsValid检查几何有效性删除空几何和面积小于5平方米的多边形残片再用QGIS的重塑矢量图层重写一次shp强制重建索引索引。5.4 现象拿到的WKT坐标连起来描成一个“鬼画符”原因客户反馈导出的WKT在PostGIS里插进去画出来完全不成形状坐标数值明显超出经度纬度范围。后来检查发现导出WKT时把字符串存进CSVExcel打开后自动把坐标列转成了科学计数法导致WKT文本里出现1.136E这种记号解析引擎直接报错。解决图省事时我建议用制表符分隔的TXT或者JSON包装一层不要直接给CSV。CSV的Excel自动格式确实让人抓狂这是我一直坚持导WKT时用UTF-8 with BOM又得不断嘱咐对方“不要用Excel直接开”的血泪教训。5.5 现象不同数据源拼一起路网总对不齐和卫星图差出去近200米原因一份数据是OSM道路WGS84坐标另一份是郑州市国土局提供的疑似西安80或北京54坐标系数据。两者叠加对不齐是必然的。没有“已处理”脚本能帮你自动判断另一份数据的坐标系不能想当然默认同一投影。解决处理前要求所有数据源统一到EPSG:4548如果对方只能提供WGS84经纬度用GIS里的“定义投影”再“投影转换”两条命令不要直接在ArcGIS里右键图层属性改坐标系——那是错的那只是给数据张冠李戴一个坐标系统。6. 后续高级玩法把已处理路网接进自研路径引擎并验证计算结果的可靠度手上有了一份清洗干净、投影正确、拓扑连通的郑州市OSM路网最常见的进阶需求就是跑最短路径。这里我有三个习惯动作。第一个是用NetworkX把GeoDataFrame转成图时先看一下图的连通分量。一份城市级路网如果出现超过几百个孤立小分量说明道路断裂很严重路径规划结果会不可信。第二个做法是校准路网的“真实距离”。OSM属性里的maxspeed字段缺失率很高尤其在郑州老城区的小街道上几乎全空。所以计算时间成本前需要先估算默认城市支路限速30km/h、次干道40km/h、主干道50km/h快速路80km/h。这种粗略赋值带来的误差在2公里短途路径里可能超过30%但对10公里以上的通勤路径来说可靠性尚可。更精细的做法是用历史轨迹数据反推实际车速但那是另一个工程超过了本文的讨论范围。第三个验证方法是拿真实出行做校验。随机抽20个起点终点对用高德地图或百度地图的骑行/驾车规划结果和自建路网结果做对比。两者总距离偏差在80%以内的说明路网拓扑基本可靠偏差超过120%的优先检查是否在节点连接处有断头其次是检查单行道方向。我自己在郑州的项目里发生过一次高德给出15公里路径而本地模型算出22公里的情况最后定位到是北三环的某处立交匝道连接关系没建对——高架桥上桥和下桥的线都没有连通。真正把OSM道路数据用好靠的永远是“数据到了先摸脾气、投影统一先确认、拓扑修复别手软、交付WKT带说明”这四步。这四步做完后续做路网密度、等时圈、OD分析、货车路径规划都有了一个不再使绊子的地基。这个方向值不值得投入人力去深挖我的答案是肯定的。国内很多城市的路网数据更新快OSM数据月月有增量用半年更新一次自己手里的郑州路网成本低但价值稳定更重要的是我每次踩坑后积累的清洗脚本可以在全国任何一个城市复用把“已处理”三个字从灰色变成可信。希望帮到你。本文还有配套的精品资源点击获取
返回列表