如何用Python将GeoPandas节点/边数据写入osm.pbf?附报错解决
问题分析与解决方案
一、Osmosis转换失败的核心原因:OSM XML格式严重违规
你生成的OSM文件完全不符合OpenStreetMap XML规范,导致Osmosis无法识别,具体问题包括:
- 标签错误:路网边对应的OSM标签是
<way>,而非你代码里的<ways>,单复数错误直接触发解析失败。 - Way结构错误:
- OSM的
<way>必须通过<nd ref="节点ID"/>子元素引用组成路径的节点,不能直接把u/v作为属性。 - 道路属性(如
highway、maxspeed等)需要用<tag k="属性名" v="属性值"/>子元素存储,而非直接挂载在<way>标签上。
- OSM的
- Node属性不规范:
changeset要求是数字类型(哪怕填0),不能用字符串'false';OSM节点通常还需要version、timestamp等元数据字段,缺失可能导致工具报错。
二、直接将GeoPandas数据转为.osm.pbf的工具
无需先转XML再用Osmosis,以下工具可直接完成转换,效率大幅提升:
- PyOsmium:OpenStreetMap核心处理库Osmium的Python绑定,支持直接读写
.osm.pbf格式,性能远超XML中转方案。 - osmconvert:命令行工具,可将GeoPandas导出的GeoJSON/CSV等格式直接转为
.osm.pbf,适合批量处理场景。 - GDAL/OGR:通过
ogr2ogr命令行工具,将GeoPandas数据(导出为GeoJSON/Shp等)转为合规OSM格式后直接生成PBF,需注意OSM规范适配。
三、最优转换方案:用PyOsmium直接生成.osm.pbf
跳过XML中转步骤,直接从GeoPandas节点/边数据生成.osm.pbf,是效率最高的实现方式:
步骤1:安装依赖
pip install osmium
步骤2:转换代码示例
import osmium as o import geopandas as gpd import pandas as pd class OSMWriter(o.SimpleWriter): def add_node(self, node_id, lat, lon, tags=None): # 创建合规OSM节点对象 node = o.osm.Node( o.osm.NodeLocation(lat, lon), id=node_id, version=1, timestamp=o.osm.Timestamp.now(), tags=o.osm.TagList(tags or []) ) self.add(node) def add_way(self, way_id, node_ids, tags=None): # 创建合规OSM路径对象,引用节点ID序列 way = o.osm.Way( id=way_id, version=1, timestamp=o.osm.Timestamp.now(), nodes=[o.osm.NodeRef(nd) for nd in node_ids], tags=o.osm.TagList(tags or []) ) self.add(way) def gpd_to_pbf(n_gdf, e_gdf, output_path): with OSMWriter(output_path) as writer: # 写入所有节点 for _, row in n_gdf.iterrows(): node_tags = [] # 可根据需求添加节点属性为标签,例如: # if pd.notna(row.get('name')): # node_tags.append(('name', str(row['name']))) writer.add_node( node_id=int(row['id']), lat=float(row['lat']), lon=float(row['lon']), tags=node_tags ) # 写入所有道路 for _, row in e_gdf.iterrows(): # 提取道路节点ID序列(若为复杂路径需调整为完整节点列表) node_ids = [int(row['u']), int(row['v'])] # 转换属性为OSM标签格式 way_tags = [] exclude_cols = ['u', 'v', 'key', 'geometry', 'id', 'osm_type', 'length'] for col in e_gdf.columns: if col not in exclude_cols and pd.notna(row[col]): way_tags.append((col, str(row[col]))) writer.add_way( way_id=int(row['key']), # 确保Way ID唯一 node_ids=node_ids, tags=way_tags ) # 使用示例 n_gdf = gpd.read_file('你的节点数据文件') e_gdf = gpd.read_file('你的边数据文件') gpd_to_pbf(n_gdf, e_gdf, 'output.osm.pbf')
注意事项
- 节点ID和道路ID必须是唯一整数,OSM格式不允许ID重复。
- 若你的边数据是多节点复杂路径,需调整
node_ids生成逻辑,按顺序传入完整节点ID序列。
四、备选方案:修正XML生成逻辑(不推荐)
若必须保留XML中转步骤,需严格按照OSM规范修改代码:
import xml.etree.ElementTree as ET import geopandas as gpd import pandas as pd from datetime import datetime def gpd_to_osm(n_gdf, e_gdf): root = ET.Element("osm") root.set("version", "0.6") root.set("generator", "MyNetworkGenerator") now = datetime.now().isoformat() # 写入节点 for _, row in n_gdf.iterrows(): node = ET.SubElement(root, 'node', attrib={ 'id': str(row['id']), 'lat': str(row['lat']), 'lon': str(row['lon']), 'version': '1', 'changeset': '0', 'timestamp': now, 'uid': '1', 'user': 'generator' }) # 添加节点标签(按需添加) # ET.SubElement(node, 'tag', attrib={'k': 'name', 'v': row['name']}) # 写入道路 for _, row in e_gdf.iterrows(): way = ET.SubElement(root, 'way', attrib={ 'id': str(row['key']), 'version': '1', 'changeset': '0', 'timestamp': now, 'uid': '1', 'user': 'generator' }) # 添加节点引用 ET.SubElement(way, 'nd', attrib={'ref': str(row['u'])}) ET.SubElement(way, 'nd', attrib={'ref': str(row['v'])}) # 添加属性标签 exclude_cols = ['u', 'v', 'key', 'geometry', 'id', 'osm_type', 'length'] for col in e_gdf.columns: if col not in exclude_cols and pd.notna(row[col]): ET.SubElement(way, 'tag', attrib={'k': col, 'v': str(row[col])}) tree = ET.ElementTree(root) tree.write("mynetwork.osm", encoding='utf-8', xml_declaration=True)
修正后再执行Osmosis命令即可,但该方案效率远低于直接用PyOsmium生成PBF。
内容的提问来源于stack exchange,提问作者data en
相关产品推荐
相关产品推荐

