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

文章详情

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

郑州市OSM道路矢量处理指南:提取裁剪投影分级与避坑

郑州市OSM道路矢量处理指南:提取裁剪投影分级与避坑 简介这是一份面向GIS应用与城市规划研究的郑州市道路矢量数据集基于OSM开源数据整理已处理坐标投影和字段编码可直接用于QGIS、ArcGIS等平台。压缩包共9个文件、4.49MB核心为郑州市道路数据.shp道路线几何配套.dbf存储等级、名称等属性.prj定义坐标系.cpg保证中文正常显示.sbn/.shx等索引加速读取另有OSM类别对照表jpg供参考。已有395人学习下载适合入门至进阶GIS用户及交通研究者。借助该资源可免去自行爬取和清洗数据的步骤直接获得郑州市完整道路网络支持路网结构分析、交通热点识别、最短路径规划还可与人口、建筑等图层叠加开展综合评估为城市管理决策提供可靠数据基础。1. 一份能直接用的郑州市OSM道路矢量从下载到处理要过的四道坎先说一个反直觉的结论OpenStreetMap 里并没有一张现成的「郑州市道路图」。原始 OSM 数据由点、线、关系三种对象组成道路靠highway*标签区分拿到手里是一堆被切成无数小段的线要素直接扔进 GIS 软件画出来的路网又碎又乱坐标系还是全球通用的 WGS84 经纬度。这份「郑州市 OSM 道路矢量数据已处理」要解决的就是这个问题把散乱的 OSM 道路提取出来、裁剪到郑州市范围、统一坐标系和字段让它能直接进 QGIS、ArcGIS 做路网分析。适合做城市规划、可达性分析、交通模拟、制图底图的从业者也适合刚接触 OSM 数据、不想从零踩一遍原始数据处理流程的初学者。看完这篇你会知道这类「已处理」资源该检查什么、怎么改参数适配自己的项目以及最容易翻车的位置在哪。2. 为什么 OSM 道路层要做二次处理标签语言和数据底数2.1 OSM 不是「一张图」是一堆带标签的几何OpenStreetMap 的原始数据通常以.osm.pbf或.osm格式分发内部结构是点nodes、线ways、关系relations三类对象。道路并不是独立的数据层而是带highway标签的线要素集合。同一个名字的马路在真实世界里是一条连续的路在 OSM 里可能被交叉路口、行政边界、施工分段切成几十段每一段都有独立的wayID 和独立的坐标串。这种结构带来的直接后果是如果你把原始.pbf直接拖进 QGIS看到的不是路网而是一堆互相不连接的短线段。量算长度时每条线段都只算自己那一小截做网络分析时断头路多到离谱做制图时线宽无法按道路等级统一渲染。所以「已处理」三个字的价值不在于多了几个文件而在于把原始数据里隐含的语义整理成了能直接用的图层。2.2 字段里藏着哪些信息highway、name、name:zh、onewayOSM 道路的可用信息量其实被很多人低估。除了几何坐标每个way都带一组 tag最常见的几个字段如下字段典型值说明常见的坑highwayresidential、primary、trunk、service道路物理等级是分级筛选的核心值有几十种不是每种都能画成路网name花园路、陇海路道路名称繁体简体和拼音混杂大量道路此字段为空name:zh花园路中文名单独存放部分区域优先使用直接渲染时容易漏字onewayyes / no单行线标记缺失不代表双向需结合车流方向判断lanes2 / 3 / 4车道数量实测路段人工录入误差常见surfaceasphalt、concrete路面材质对车辆通行分析有意义maxspeed60 / 80限速单位为 km/h很多城市道路没有录入bridge、tunnelyes桥梁与隧道标记影响后续的三维建模和高度分层layer-1 / 0 / 1层叠顺序代表立交和高架关系不加筛选时高架桥与地面道路会重叠显示所谓「已处理」业内通行做法是把highway*的线要素单独提取成一个图层用行政边界裁剪到目标城市剔除highwayservice这类不参与主路网统计的要素再把name和name:zh做了合并最后统一坐标系。不同来源的处理深度不一样你在拿到资源后先确认这几项做到哪一步比直接开地图看颜色重要得多。2.3 用工具完成初步提取osmium 的一次实操如果你拿到的还是原始.osm.pbf第一步是提取全部道路要素。常见做法是用 osmium 工具它是命令行的 OSM 数据处理利器处理速度快适合批处理。osmium tags-filter input.osm.pbf highway* \ -o road_layer.osm.pbf \ --overwrite这条命令的逻辑是读取全量 OSM 原始包把带有highway标签的节点和线要素筛出来单独输出为一个新的 PBF 文件。注意tags-filter的默认行为是保留与标签匹配的节点和与之关联的线。加--overwrite是因为目标文件已存在时不强制覆盖会报错批量跑脚本时提前加上省一次失败。筛完highway*还不够此时文件里还包含highwayfootway、highwaycycleway这类非机动车路。做城市机动车路网分析时一般要排除掉这些osmium tags-filter road_layer.osm.pbf \ highwayprimary,secondary,tertiary,trunk,motorway,residential,unclassified,living_street \ -o car_road_layer.osm.pbf这里tags-filter后面的参数直接跟逗号分隔的标签值等价于白名单。逻辑是把候选道路等级写进去其余全部过滤掉。参数顺序上-o指定输出路径最后一项是输出文件。跑完这一步得到的才是真正能拿去做连通的机动车路网。3. 裁剪与坐标系把全球数据变成可量算的「郑州市范围」3.1 坐标系三个常见选择WGS84、CGCS2000、UTMOSM 原始数据统一使用 WGS84 经纬度坐标系坐标值范围是经度 -180 到 180、纬度 -90 到 90。经纬度适合描述「在哪里」却不适合计算「有多长、多大面积」因为不同纬度下 1 度经度对应的实际距离不同。做郑州市路网的长度统计、缓冲区分析、密度计算必须转换到投影坐标系。针对郑州行业内常见三种选择我分别列出适用场景坐标系单位适用场景局限WGS84 / EPSG:4326度底图显示、在线地图互操作测不了距离和面积CGCS2000 3 度带高斯投影米国内测绘与规划成果统一坐标系需要正确选择带号选错会偏差约数百米UTM Zone 49N 或 50N米全球通用跨省区域对比郑州市区恰好靠近 49N 与 50N 分界选错会有微妙偏差郑州位于东经 113.6 度左右按中国城建与测绘常见标准用中央经线为 114 度的 3 度带高斯投影比较合适。如果你手里的数据要跟规划局成果叠加多半会落到 CGCS2000 3 度带如果只是自己做分析、不外发成果选 UTM 也可行。我一般会优先确认待叠加底图的坐标系再决定投影方式而不是上来就拍脑袋选一个。3.2 用边界文件裁剪ogr2ogr 的指定方案原始 OSM 是全球数据即使提取了郑州道路范围也是整个文件覆盖区域。裁剪这一步最稳的工具是用 GDAL 自带的ogr2ogr它支持按空间范围直接裁剪也支持用 Polygon 边界文件裁剪。ogr2ogr -t_srs EPSG:4490 \ -clipsrc zhengzhou_boundary.shp \ -spat 112.5 34.2 114.2 35.1 \ -lco ENCODINGUTF-8 \ zhengzhou_road_4326.shp car_road_layer.osm.pbf拆开看这条命令-clipsrc后面跟边界矢量文件作用是只保留落在边界范围内的要素-spat后跟四个数字分别是最小经度、最小纬度、最大经度、最大纬度作用是在裁剪前先框定候选范围避免全表扫描-t_srs EPSG:4490是输出坐标系的定义这里先用 CGCS2000 地理坐标系做过渡下一步再投影-lco ENCODINGUTF-8是让输出文件的属性表编码为 UTF-8防止中文字段名出现乱码实测 ArcGIS 老版本对编码敏感这一项建议保留。注意-clipsrc可以接范围字符串也可以接文件接文件时格式取决于 GDAL 能识别的矢量格式。边界文件质量直接影响裁剪结果务必确认边界是精确到行政区划的而不是用四个角点画的矩形框。3.3 投影转换怎么做从经纬度到米多次转换容易累积误差但 OSM 数据是公开众源数据精度本身就在米级没必要过度追求一次到位。我建议至少分两步走第一步统一到 CGCS2000 地理坐标系第二步再投影到高斯投影平面坐标系。ogr2ogr -t_srs EPSG:4547 \ -overwrite \ zhengzhou_road_cgcs2000_3deg.shp zhengzhou_road_4326.shp这里-t_srs指定了目标投影坐标系的 EPSG 代码。注意我没有写错郑州对应的是 3 度带第 38 带的中央经线带但不同机构对EPSG:4547的定义可能与我的描述不完全一致使用前先在 QGIS 里查看数据框坐标单位确认单位变成「米」再往下走。-overwrite参数的作用是允许覆盖已有输出文件避免重复运行报错。投影完成后有一个需要立刻做的检查在 QGIS 里加载新旧两个图层随机选一条道路加载属性表对比其SHAPE_Length字段。如果数值从 0.002 变成 200 多说明投影生效如果数字还是 0.002 量级说明-t_srs没有真正生效图层属性仍显示为经纬度。这是我反复见过的翻车现场。4. 把 175 类道路归成 7 层字段提取与分级筛选4.1 highway 值不是越多越好常见的公路分级OSM 的highway标签值合计有几十种直接查看属性表时会发现道路等级多到没必要全部保留。做分析时业内一般会归并成几个层级。以下是我常用的映射关系OSM 原始值归并层级中文说明在城市路网分析中的用途motorway、motorway_link1高速与快速路长距离出行骨架trunk、trunk_link2主干道城市主要通道primary、primary_link3次干道区域连接secondary、secondary_link4支路局部路网tertiary、tertiary_link5一般道路密度分析基础unclassified、residential6居住区道路可达性分析重要层living_street7生活性街道步行与慢行系统service、track、construction排除内部道路/施工区域不参与城市主干路网统计service要不要剔除取决于项目目标。做小区内部的停车位分析可能还需要它但做宏观路网密度、连通度分析时必须剔除否则一条加油站内部的service路会被当成一条城市道路参与统计结果虚高。4.2 用 SQL/Python 做映射获取到道路图层后下一件事是新增一个字段class_level把原始highway值映射成数字等级。PostGIS 环境下用一条CASE WHEN就能完成ALTER TABLE zhengzhou_road ADD COLUMN class_level integer; UPDATE zhengzhou_road SET class_level CASE WHEN highway IN (motorway, motorway_link) THEN 1 WHEN highway IN (trunk, trunk_link) THEN 2 WHEN highway IN (primary, primary_link) THEN 3 WHEN highway IN (secondary, secondary_link) THEN 4 WHEN highway IN (tertiary, tertiary_link) THEN 5 WHEN highway IN (unclassified, residential) THEN 6 WHEN highway IN (living_street) THEN 7 ELSE NULL END;这段 SQL 的逻辑是先加一个整数字段然后把highway字段的字符串逐个匹配到对应数字等级匹配不上的写入 NULL方便后续直接过滤。不要小看这一步后续做分级样式渲染、分级统计长度、做费用模型就全靠这个字段。参数上唯一的可变点就是CASE WHEN中的映射表不同项目要求不同比如一些规划项目把trunk提到与motorway同级直接改数字即可。如果你不习惯数据库用 Python 的geopandas也能做同样的事这里给一个常见写法import geopandas as gpd gdf gpd.read_file(zhengzhou_road_4326.shp) level_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: 6, residential: 6, living_street: 7 } gdf[class_level] gdf[highway].map(level_map) gdf gdf.dropna(subset[class_level])map的用法是直接把highway列值替换成level_map对应的数字无法映射的自动得到NaN后面的dropna(subset[class_level])把无关要素整行删掉。所以这个脚本同时做了映射和过滤产出的gdf已经是可以直接导出的分层路网了。4.3 分级统计与可视化分级完成后最直观的验证方法是按class_level做符号化渲染。QGIS 里右键图层属性符号分级选择class_level将高速设为红色粗线、主干道橙色、次干道黄色、支路灰色一张可读性很好的城市路网图就出来了。这样做还有一个额外收益一眼能看出哪里断线异常。比如某区域主干道颜色突然消失那多半是该区域 OSM 数据缺得厉害而不是城市里没有路。长度统计用一句带GROUP BY的 SQL 就能得到每级道路的总公里数、平均线段长度、以及占路网总长度的比例这些数字可以直接写进分析报告。重点看高速和主干道的平均线段长度如果主干道平均线段长度只有 30 米大概率是数据没有合并完整线被打碎得厉害后续做网络分析前要先按同名字段做Dissolve融合。5. 避坑记录路网数据最容易翻车的五个位置5.1 投影前就量算距离数字全部虚高现象在 QGIS 里打开道路图层用测量工具量一条主干道的长度显示为「0.002」度转成公里约等于 0.22 公里与实际 220 米完全不符量十次发现结果毫无规律。原因图层保持在 WGS84 经纬度坐标系测量工具返回的是球面角度差不是平面距离不同纬度的经度距离不同所以在郑州量出来的 0.002 度和在哈尔滨量出来完全不同。解决任何投影转换之前禁止做距离和面积量算。先确认图层右下角坐标单位显示为「米」或「Map Units」后再测。如果发现已经误用了度数数据做分析不要试图用乘法换算直接回退到原始经纬度数据重新走一遍 3.3 节的投影流程。5.2 道路在郑州市界处被整段截断现象沿郑州边界的道路往往从一个长线变成了两条短线每条都从边界线开始或结束路网在行政边界处看起来像被刀切过。原因裁剪用的是行政边界文件而 OSM 里的长道路本来就跨越多个区域ogr2ogr -clipsrc的默认行为是保留边界线以内的部分所以每条跨界的道路都在边界位置被切断。切断本身不影响通路但后续做网络分析时断头会导致路径无法跨越边界。解决做内部路网分析时在裁剪前先对原始道路做一次Dissolve融合或把裁剪工具的-clipsrc换成-spat并勾选保留相交要素让跨界道路保留到边界外一小段再用。5.3 施工道路和未铺装道路混进统计现象路网总里程统计结果比城市公布的道路里程高出 10% 以上仔细查发现数据里包含大量highwayconstruction、highwaytrack和highwaypath的要素。原因这些标签在 OSM 里代表施工围挡内的临时道路、田间机耕道和山间土路它们在数据层面上确实是线要素也带highway标签但不是可正常通行的市政道路。解决任何分级映射前先执行一步白名单过滤。最稳妥的做法是在第 4 章的 SQL 中把CASE WHEN之外的ELSE NULL改成直接DELETE FROM zhengzhou_road WHERE highway NOT IN (...)从源头排除。有个血泪经验做年度路网里程对比时连续两年数据量和统计口径不一致查到最后发现是某次没有剔除track害得整个结论要重做。5.4 name 字段空白但 name:zh 字段有中文名现象道路渲染出来后大量标注缺失主要道路没有名字打开属性表发现name字段为空name:zh字段却有「中原路」这类中文名。原因OSM 的多语言名称存储是分开的部分城市优先写入name:zh而基础name字段没有被填。QGIS 默认显示的是name字段所以中文路面标注大量漏字。解决用字段计算器if(name IS NULL OR name, name:zh, name)生成一个新字段display_name用它做标注。不要直接删除name字段因为部分工具和脚本组合依赖标准字段名。这类数据在「已处理」资源里通常已经合并过但保不齐边界角落还有漏网拿到手后跑一次统计确认。5.5 重复要素叠加导致分析结果「飘」现象统计某片区道路长度时同一个路口到另一路口的同一段路被计算了两遍实际 2 公里算出来 4 公里渲染时线重叠看不到问题一查属性表才知道 ID 不同、几何几乎相同。原因边界处存在两版数据的叠加残留或 OSM 中的关系对象被拆成两个重复way。众源数据在个别区域历史上出现过重复绘制没有人工清理。解决用ST_Equals或 QGIS 的「查找重复几何」工具先做一遍查重对重复要素只保留FID最小的那条。这类问题做总量统计影响不大但做单条道路长度校验时会把人逼疯。6. 最后一道工序用三句话校验「已处理」数据拿到「已处理」资源后我建议你把它当作半成品对待先用三个指标验收再投入正式分析。第一句看数量级用属性表统计要素总数和总长度。郑州市二环以内的市政道路总里程通常在 1000 公里量级全市域涉路网在 5000 到 9000 公里之间。如果你的数据只有几百公里那多半是只保留了主干道没含支路如果有几万公里多半是没剔除track和service。两种都要回到第 4 章重新检查过滤逻辑。第二句看分级占比查询上一节生成的class_level字段统计每一等级的长度占比。正常的城市路网配比大致是高速和快速路占 3% 到 5%主干道 10% 到 15%次干道 15% 到 20%支路及以下占大头。如果主干道占比超过 30%分级映射一定出了问题最常见的翻车点是trunk被定义成主干道导致两级要素被合在一起。第三句随机抽查在 QGIS 里随机取三个片区与百度地图或天地图影像叠加对比。抽查主要看两类异常一是路网形状和影像差异大二是片区内部整片没有路。前者通常是坐标系偏移后者通常是 OSM 在郑州局部区域的数据缺失这种缺失是数据源头问题不是你处理的问题后续分析报告里应明确标注数据覆盖范围。这三板斧只需要十分钟但能拦住大部分低级错误。最后补一个常用的小脚本用geopandas一次性输出上述两个数字省去来回切换软件的时间import geopandas as gpd gdf gpd.read_file(zhengzhou_road_final.shp) gdf[length_km] gdf.geometry.length / 1000 print(要素总数, len(gdf)) print(道路总里程km, round(gdf[length_km].sum(), 2)) level_stats gdf.groupby(class_level)[length_km].sum() print(分级里程km) print(round(level_stats, 2))这个脚本的逻辑是依次输出三个信息要素总数、总里程、分级里程。注意其中gdf.geometry.length返回的是投影坐标系的单位米所以在投影前使用会得到错误结果。你可以直接改成自己熟悉的字段名运行前确保坐标系单位是米就行。从那以后我每次拿到任何来源的 OSM 路网数据无论对方是否标注「已处理」都会强制走一遍这三个校验步骤再决定要不要信任。路网数据这东西玄学概率比你想象的高参数文档写得很漂亮实际一叠加就原形毕露。认真核对一遍总比分析做到最后才发现底图数据有问题划算得多。希望这篇笔记能帮你在数据处理上少折腾几个来回。本文还有配套的精品资源点击获取
返回列表