ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OSM道路矢量数据清洗与坐标系转换:从原始路网到可分析路网的完整流程

OSM道路矢量数据清洗与坐标系转换:从原始路网到可分析路网的完整流程 简介基于OpenStreetMap项目的郑州市道路网络矢量数据已经过处理可直接作为Shapefile加载进QGIS、ArcGIS等GIS软件面向城市规划、交通研究等需要道路基础底图的开发者与研究者。资源包共9个文件压缩后仅4.49MB其中.shp存储道路线几何.dbf记录道路等级、名称等属性.prj定义坐标投影.sbn/.sbx/.shx用于空间与属性索引.cpg确保中文编码正确另附.xml元数据和jpg格式的OSM类别对照表方便理解道路分类标签。已有395人学习/下载。借助这份数据使用者能快速获得郑州市完整的道路网络拓扑与属性信息进行最短路径计算、交通热点识别、与人口或公交数据叠加分析等空间操作配套的类别对照表也降低了数据清洗与字段解读门槛适合作为GIS教学、交通规划或学术研究的可靠底图数据。1. 郑州市OSM道路矢量数据一份能直接开工的路网背后处理了什么做城市路网分析的人多少都有过这种经历从 OSM 导出一份道路矢量数据满心欢喜打开结果属性表里混着步行道、梯级、甚至只有两三米的小区内部路坐标系还是 WGS84 经纬度直接按米算长度全是错的。标题里“已处理”三个字意味着这份数据已经把最脏最累的活干完了裁到郑州市域、过滤掉非机动车道、统一坐标系、把 highway 标签映射成可用的道路等级。这份数据适合三类人做路网分析的研究者、做地图可视化的前端、以及给政企项目做底图校验的 GIS 工程师。本文不聊这份数据本身长什么样——我没法替作者逐字段核对——而是把“已处理”背后一套可复现的处理链路完整拆出来让你要么能直接信任这份数据要么自己也能从原始 OSM 加工出一份同级别的郑州路网。2. OSM 原始道路数据的底子字段、层级与坐标系统2.1 highway 字段才是路网骨架name 和 ref 只是参考OSM 的道路要素核心不是名字而是highway这个标签。它是 OSM 社区约定的一套道路分类体系从高速公路到田埂小路都覆盖。拿到任何一份 OSM 路网先不要急着看几何先把highway字段的取值分布统计一遍。我用 QGIS 或 Python 加载数据后第一件事一定是跑一次字段值计数看看数据里都有哪些类型。郑州这种城市的 OSM 路网里highway常见值大概包括motorway高速公路、trunk城市快速路郑州的中州大道、陇海快速路这类、primary主干道、secondary次干道、tertiary支路、residential居住区道路、service服务性道路、unclassified未分类道路、footway步行道、cycleway自行车道、path小径、track田间/林间道路。这里有个新手最容易踩的认知误区把 OSM 导出的所有highway要素都当作“道路”参与路网分析。实际上footway、cycleway、path根本不该进入机动车路网模型否则算出来的路径全是穿公园走的小路。做数据处理时highway字段既是分类依据也是过滤依据。import geopandas as gpd # 加载原始 OSM 道路数据 roads gpd.read_file(zhengzhou_osm_raw.shp) # 统计 highway 字段分布先看清家底 print(roads[highway].value_counts()) # 定义机动车道路类型白名单 drivable_types [ motorway, motorway_link, trunk, trunk_link, primary, primary_link, secondary, secondary_link, tertiary, tertiary_link, unclassified, residential, service ] # 过滤出可通行道路 drivable roads[roads[highway].isin(drivable_types)].copy() print(f原始要素数: {len(roads)}机动车道路要素数: {len(drivable)})这段代码先把数据整体读进来用value_counts()看每个highway类型的要素数量然后按白名单过滤。_link后缀代表匝道和连接路在路网分析中不能丢它们是高速出入口和立交桥的连通关键。过滤比例通常很惊人——一份城市级 OSM 数据里footway和path的要素数量往往超过机动车道路不过滤直接分析结果基本没法看。2.2 属性字段里藏着的可用信息不止是几何除了highwayOSM 道路要素还有一批高频使用的属性字段这里挑几个在郑州路网处理中真正用得上的说明。name是道路名称但在 OSM 里它很不可靠。郑州的“金水路”在不同路段可能被标注成“金水路”“金水东路”“金水路中段”甚至直接缺失。ref是道路编号比如 G107、S312、连霍高速的 G30这个字段比name稳定但也存在同一编号挂在多个路段上的情况。oneway字段标记单行道值是yes、no或true、false部分路段可能缺失。做路径规划或方向性分析时这个字段决定图模型的边是否有向。maxspeed是限速值单位默认 km/h但 OSM 里也有填none或空值的使用时需要兜底处理。lanes是车道数surface是路面材质asphalt、concrete、unpaved等这两个字段在做交通承载力分析时会被用到但同样有大量缺失。# 检查关键属性字段的缺失情况 key_fields [name, ref, oneway, maxspeed, lanes, surface] for field in key_fields: if field in drivable.columns: null_count drivable[field].isna().sum() print(f{field}: 缺失 {null_count} 条占比 {null_count / len(drivable) * 100:.1f}%)这段代码跑完你对一份 OSM 数据的“可信度”心里就有数了。处理时对缺失属性要有一套默认策略oneway缺失默认按双向算maxspeed缺失按道路等级推默认限速lanes缺失按等级给参考值。这些策略不是瞎猜而是各地道路交通设计规范里的推荐值处理的时候用等级映射比用空值安全得多。2.3 坐标参考系的陷阱WGS84 与投影坐标之间差着一个量级OSM 原始数据的几何存储格式是 WGS84 经纬度也就是 EPSG:4326。经纬度坐标的单位是度不能直接在几何上做长度、面积计算——在纬度 34.5 度的郑州1 度的经度长度和 1 度的纬度长度差了大约 0.83 倍直接算出来的欧氏距离毫无意义。标题写明“已处理”的数据处理链路里必然包含坐标参考系CRS转换这一环。郑州地区做米制分析我用过两种方案一是 CGCS2000 3 度带投影郑州经度范围大约在 112.8°E 到 114.1°E 之间对应 CGCS2000 3 度带第 38 带的中央经线 114°EEPSG 代码是 EPSG:4547二是 UTM 49NEPSG:32649精度也够。前者更贴合国内测绘成果的坐标系后者在 OSM 社区里更常见。# 检查当前坐标系 print(当前 CRS:, drivable.crs) # 投影到 CGCS2000 3-degree Gauss-Kruger zone 38中央经线 114°E drivable_projected drivable.to_crs(EPSG:4547) # 投影后验证一下长度是否合理 total_length_km drivable_projected.geometry.length.sum() / 1000 print(f投影后路网总长度: {total_length_km:.0f} 公里)判断投影是否正确可以直接看几何长度量级。郑州市域内机动车道路总长度在几千公里量级如果算出来几万公里甚至更高那大概率是几何包含了市域外数据或者坐标系处理出了问题。这里有一个玄学但好用的经验投影完随手算一下路网总长度如果量级离谱先别查属性回去看 CRS 和裁剪边界。3. 从原始导出到“已处理”一条可复现的处理流水线3.1 获取郑州 OSM 原始数据两种常见渠道加工一份城市级路网首先得有原始数据。常见做法是两种一是从 OSM 的定期镜像站下载所在区域的 pbf 文件——亚洲区域或中国区域——再做空间裁剪二是用 Overpass API 按行政边界或包围盒直接拉取指定范围的要素。我一般建议先下载大区域 pbf 再本地裁剪别直接用 Overpass 拉。原因很简单Overpass 请求有超时和数据量限制城市级全要素路网动辄几十万条线段直接拉取很容易超时而且拉回来的数据在边界上经常缺胳膊少腿。先有全量数据再自己做裁剪每一步都可控。得到 pbf 后用osmium工具按行政边界裁剪# 用郑州市行政边界 GeoJSON 裁剪全国/全省 pbf osmium extract --bbox112.75,34.35,114.15,34.90 \ -o zhengzhou_raw.osm.pbf china-latest.osm.pbf # 或者用多边形边界裁剪推荐贴合真实市域边界 osmium extract --polygonzhengzhou_boundary.geojson \ -o zhengzhou_raw.osm.pbf china-latest.osm.pbf--bbox方式快但拿到的是一个矩形范围把郑州周边县市的数据也圈进来了后面还得再处理。--polygon方式精确到市域边界但前提是你有一个可靠的边界文件——注意这里的边界坐标必须是 WGS84 经纬度如果用投影坐标系的边界文件osmium 会直接报错。裁剪完成后用 ogr2ogr 把 pbf 转成 shapefile 或 GeoPackage注意 OSM 的 pbf 不是普通矢量格式必须用专门工具转换。# 从 pbf 提取道路要素为 GeoPackage ogr2ogr -f GPKG zhengzhou_roads.gpkg zhengzhou_raw.osm.pbf \ -sql SELECT * FROM lines WHERE highway IS NOT NULL \ -nln roads_all这条命令把 pbf 中的lines图层读取出来只保留highway字段非空的要素。OSM pbf 里的道路在lines层点层是 POI面层是建筑物和地块别选错。转换后得到的roads_all仍包含所有类型的道路包括行人道后续再按业务需求做细分。3.2 清洗道路要素过滤、去重、字段规整拿到裁剪后的数据后清洗这步决定了后续分析的上限。完整的清洗动作我按顺序固定做四件类型过滤、几何去重、属性规整、字段映射。类型过滤就是把第 2 章那个 whitelist 用上把footway、cycleway、path、track这类非机动车要素剔除。这里注意一点track在郑州周边县市分布也不少如果是做城区路网建议直接过滤如果是做全域路网含乡村道路track需要保留它代表田间道路等级映射时算最低一级。import geopandas as gpd # 读取上一步导出的全要素路网 roads_all gpd.read_file(zhengzhou_roads.gpkg, layerroads_all) # 过滤机动车道路类型 drivable_mask roads_all[highway].isin(drivable_types) roads roads_all[drivable_mask].copy() # 按几何去重OSM 中同一条路可能被重复描绘 roads roads.drop_duplicates(subsetgeometry) # 去掉零长度要素有些编辑操作会留下退化几何 roads roads[~roads.geometry.is_empty (roads.geometry.length 0)] print(f清洗后剩余要素数: {len(roads)})drop_duplicates(subsetgeometry)处理的是 OSM 里常见的重复描绘问题——同一条路在不同时期被不同编辑者画了两次几何完全一致或高度重合。length 0过滤掉退化的点状要素和零长度线段这些要素在后续拓扑构建中会引发不可预知的错误。两行命令能省掉后面网络分析时一半的崩溃现场。属性规整这步要和业务需求结合。比如oneway字段OSM 里的值是yes/no/true/false混用还有空值。统一成1/0/-1三种状态后面构建有向图才清爽。原始值规范化值含义yes / true / 11单向沿几何方向no / false / 00双向空 / other0默认双向-1极少见-1单向逆几何方向maxspeed字段的处理逻辑也类似缺失值按道路等级填默认限速none值的路段按无明确限速的最高等级处理。这些默认值不完美但比让分析程序读到一个 NaN 直接崩溃强得多。3.3 道路分级映射为什么要把 highway 转成 road_classOSM 的highway分类粒度很细但很多业务场景不需要这么细。比如做城市噪声传播模拟只需要快速路、主干道、次干道、支路四个级别做物流路径规划只关心高速、快速、普通公路三个级别。把 highway 标签映射到业务分级是“已处理”数据里最体现功力的部分。# highway 到行业通用四级分类的映射 road_class_map { motorway: 1, # 高速 motorway_link: 1, trunk: 2, # 快速路 trunk_link: 2, primary: 3, # 主干道 primary_link: 3, secondary: 4, # 次干道 secondary_link: 4, tertiary: 5, # 支路 tertiary_link: 5, unclassified: 5, residential: 5, service: 6 # 服务性道路 } roads[road_class] roads[highway].map(road_class_map) # 未映射的值此时不应对存在标记为 0便于事后检查 roads[road_class] roads[road_class].fillna(0).astype(int) # 看看分级后的结果分布 print(roads[road_class].value_counts().sort_index())映射表里我把tertiary和unclassified、residential都归为支路级别因为在实际道路属性上这三者的通行能力差异不大。如果你做的是车流仿真可能需要更细的划分——比如给service单独一个级别甚至拆出alley巷弄这完全取决于业务模型需要多少个状态。分级映射没有绝对正确答案关键是映射逻辑要固化在代码里可审计、可修改、可复现。映射完之后还有一个关键操作按道路等级做几何融合。OSM 里的主干道经常被切成几十上百段每段长度甚至不足百米直接做网络分析会让节点数量爆炸。用dissolve把同等级、同路名、同 ref 的相邻线段合并成完整路段# 按 road_class name ref 融合相邻线段 dissolve_fields [road_class, name, ref] roads_dissolved roads.dissolve(bydissolve_fields, aggfuncfirst).reset_index() # 融合后计算每条路段的长度需已投影到米制坐标系 roads_dissolved[length_m] roads_dissolved.geometry.length print(f融合前要素数: {len(roads)}融合后: {len(roads_dissolved)})融合的做法在高速路和国道上效果最明显——G107 在郑州境内往往被切成上百段融合后变成几条完整贯通的路段。注意dissolve的by参数接受字段名列表aggfuncfirst表示融合后保留第一个要素的属性值这里因为融合字段本身已经包含在分组里其他属性如maxspeed取首个值即可基本不影响准确性。4. 避坑OSM 道路数据处理的五个经典翻车现场4.1 边界裁剪导致路网断裂市域边缘的路全成了断头路现象用 bbox 或行政边界裁剪后通往周边县市的路在边界处全部截断跨越市界的道路在郑州侧变成不连通的悬空端。做路网连通性分析时边界一带的节点度全部为 1统计结果完全失真。原因OSM 的道路要素是完整的折线跨市界的路一条要素可能横跨两个城市。按边界裁剪时几何被硬生生切断切断处不会自动生成新的拓扑节点。解决裁剪时不要把边界切得太死。我的做法是先按行政边界外扩 5 公里生成缓冲区用缓冲区做要素裁剪再对裁剪结果做拓扑修复——找到所有悬空端点如果悬空点距离另一条路的端点小于阈值一般取 10 米就把它们合并。这样既能保住边界处的连通性又不至于把周边县市几十万条小路都圈进来。4.2 G107 在数据里被拆成五十段属性和名称全是乱的现象在郑州路网里查 G107结果拉出来几十段要素长度从几十米到几公里不等name字段有的叫“G107”、有的叫“国道 107”、有的直接为空ref字段也是同样的混乱。原因OSM 是众包编辑不同人编辑的路段接合处属性不统一。更麻烦的是同一条路可能被不同编辑者各画了一段首尾相连但属性的写法各不相同。解决属性规整时不要只盯着name洗数据改为建立“ref 优先、name 兜底”的融合策略。处理流程上dissolve的分组字段里ref 有值就用 refref 缺失才考虑用 name。正是因为这种不确定性处理数据时必须保留融合前的原始属性备查不要把原字段覆盖掉。4.3 投影坐标弄错郑州的路网跑到了海里现象加载数据后发现郑州的路网和底图完全对不上图层跑到渤海去了或者整个路网旋转了一个奇怪的角度。原因几何坐标是 WGS84 经纬度EPSG:4326却被按 Web MercatorEPSG:3857或某个投影坐标来渲染或者投影时参数用错。这类问题在初学者身上反复发生因为 QGIS 和 ArcGIS 对无坐标系数据的默认处理方式不同一个默认按 WGS84 显示另一个默认按投影坐标显示。解决拿到数据的第一个动作永远是检查 CRS不是看数据范围。在 QGIS 里看图层属性里的坐标系在 Python 里用gdf.crs打印。凡是 OSM 原始数据一律先确认是 EPSG:4326再按目标坐标系显式转换。转换时写好目标 EPSG 代码不要选“默认”或“自动”。4.4 郑州主城区路网要素数量爆炸加载一次卡五分钟现象把“已处理”的郑州路网全部加载进 QGIS 或 Leaflet操作一下卡半天缩放都要等一两秒。原因城市级路网即使过滤掉步行道service和residential这两类低等级道路的要素数量也极其可观。郑州主城区的 service 道路可以占到全部机动道路要素的六成以上这些短小的内部路在可视化场景中往往不需要全部显示。解决处理时把路网按等级拆成多层输出——高速/快速路一层、主干道/次干道一层、支路/服务道路一层。做可视化时先加载高层级路网缩放级别深入到街区再加载低等级图层。做分析时按需只加载对应层不要一股脑全导入。这个分层策略看起来没什么技术含量实际使用中比任何性能优化都管用。4.5 同一条路被画了两遍路网长度统计直接翻倍现象统计郑州路网总长度算出来比实际里程多出一大截做路网密度分析某些区域密度高到不合常理。原因OSM 的编辑历史里存在重复绘制的线段——同一条路在不同时间被不同的人各画一遍几何完全重合或基本重合。这类重复要素在属性表里看不出问题但会在长度统计、面积分析、路径规划中造成双重计数。解决用几何去重加拓扑检查双层过滤。几何去重用drop_duplicates(subsetgeometry)处理完全重合的情况部分重合的做一次“线段重叠检查”——将图层与自身做空间连接intersects筛选出互相重叠超过 80% 的要素对人工抽检后决定合并还是删除。处理完后用不同等级道路的里程统计做个合理性验证郑州的高速公路里程、快速路里程应该和统计年鉴的数量级对得上对不上就说明还有重复没清干净。5. 拿到“已处理”数据后先做这三件事验证它是否真的可用5.1 三个步骤验证一份处理好的路网数据是否靠谱第一步验证坐标系。打开属性表看 CRS如果是投影坐标系随手量一条路的长度——中州大道从黄河桥到南四环大约 30 多公里如果量出来是 3 或 300坐标系十有八九有问题。第二步做拓扑检查。在 QGIS 里用拓扑检查插件Topology Checker跑一遍“dangling edges”规则看有没有大量悬空端点。城市路网中悬空端点的存在不可避免但悬空比例超过 5% 就要警惕说明数据准备阶段有系统性问题。第三步对照卫星影像抽检。随便挑五个区域把路网叠加在影像底图上看道路走向和实际建筑、田野的分布是否吻合。OSM 在中国的覆盖质量存在地域差异郑州主城区数据质量较好郊区可能有些路是失真的。以上三步都过了这份“已处理”数据基本可以信任。还有一条经验拿到任何第三方处理好的数据先跑一遍简单的统计分析——各等级道路里程分布、要素数量、平均长度。如果 output 的结果和你对郑州路网结构的认知出入太大别急着用先去问数据处理方要处理报告没有处理报告的数据就是个黑匣子。5.2 从路网到分析构建拓扑图做路径规划“已处理”数据的终点不是画图好看而是进入分析流程。我常用的进阶做法是把路网转成网络图做最短路径或等时圈分析。这一步用osmnx或networkx都可以核心是把路网几何转成图。import networkx as nx # 假设 roads_projected 是已处理、已投影的郑州市路网 GeoDataFrame G nx.Graph() # 遍历每条道路要素把端点作为节点整条线作为边 for idx, row in roads_projected.iterrows(): geom row.geometry if geom.geom_type LineString: start (geom.coords[0][0], geom.coords[0][1]) end (geom.coords[-1][0], geom.coords[-1][1]) weight row[length_m] G.add_edge(start, end, weightweight, lengthweight, road_classrow[road_class]) # 求中州大道附近两个点之间的最短路径 path nx.shortest_path(G, sourcestart_point, targetend_point, weightweight) print(f最短路径经过 {len(path)} 个节点总长度 {nx.shortest_path_length(G, start_point, end_point, weightweight):.0f} 米)这个构建过程把每条 LineString 的首尾坐标当作图的节点整条线的长度作为边权。需要提醒的是这种简单构建方式不会自动处理真实道路的交口——两条路几何交叉但 OSM 里没有公共节点时图里就不会有交叉点。更完整的方案是把节点捕捉到最近的道路端点上或者用专门的路网工具包处理。不过对于路段级别的宏观分析这种简化模型已经能给出有价值的结论。这些年处理过不少城市的 OSM 路网回头看最深刻的教训是三分数据七分处理OSM 的数据永远是“原材料”不是“成品”。别拿到手就用先把坐标系、字段、拓扑三关过了再谈分析。如果你手上这份郑州数据已经把这层功夫下足了那确实值得认真用起来。希望帮到你。本文还有配套的精品资源点击获取
返回列表