You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python将GeoPandas节点/边数据写入osm.pbf?附报错解决

问题分析与解决方案

一、Osmosis转换失败的核心原因:OSM XML格式严重违规

你生成的OSM文件完全不符合OpenStreetMap XML规范,导致Osmosis无法识别,具体问题包括:

  1. 标签错误:路网边对应的OSM标签是<way>,而非你代码里的<ways>,单复数错误直接触发解析失败。
  2. Way结构错误:
    • OSM的<way>必须通过<nd ref="节点ID"/>子元素引用组成路径的节点,不能直接把u/v作为属性。
    • 道路属性(如highway、maxspeed等)需要用<tag k="属性名" v="属性值"/>子元素存储,而非直接挂载在<way>标签上。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:30:03