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

Python处理OSM数据:如何从无地址way获取实体地址?

问题背景

我刚接触地理编码领域,需要为一组地址查找最近的住宅建筑,操作流程如下:

  1. 使用geopy.geocoders.Nominatim获取地址基础数据,包括display_name、osm_id、osm_type等。
  2. 通过OSMPythonTools.api.Api,用osm_query: str = fr"{osm_type}/{osm_id}"获取建筑详细信息(楼层数、公寓数量等),并保存到pandas DataFrame中。
  3. 用osmnx获取目标地址1公里范围内的所有住宅建筑几何数据,核心代码如下:
import os
from pprint import pprint
from collections import defaultdict

# geodata
import pandas as pd
from pandas import DataFrame
from OSMPythonTools.api import Api as OSM_Api
from OSMPythonTools.nominatim import Nominatim as OSM_Nominatim
from geopy.geocoders import Nominatim as geopy_Nominatim
import osmnx as ox

# 初始化引擎
geopy_geolocator: geopy_Nominatim = geopy_Nominatim(user_agent="my_app")
geopy_geocode: geopy_geolocator.geocode = geopy_geolocator.geocode
api_OSM: OSM_Api = OSM_Api()

# 加载测试地址
df_addresses: DataFrame = pd.read_csv("test_addresses.csv", sep = ";")
addresses_to_analyze: dict = df_addresses["full_address"].to_list()
addresses_data: dict[list] = defaultdict(list)

# 收集地址数据
for i in addresses_to_analyze:
    addresses_data["full_address"].append(i)
    
    raw_geopy_geocode_response: dict = geopy_geocode(i)

    if raw_geopy_geocode_response:
        raw_geopy_geocode_response: dict = geopy_geocode(i).raw

        osm_address = raw_geopy_geocode_response.get("display_name")
        osm_id: int = raw_geopy_geocode_response.get("osm_id")
        osm_type: str = raw_geopy_geocode_response.get("osm_type")
        place_class: str = raw_geopy_geocode_response.get("class")
        place_type: str = raw_geopy_geocode_response.get("type")

        osm_query: str = fr"{osm_type}/{osm_id}"
        raw_osm_geocode_response: dict = api_OSM.query(osm_query).tags()

        building_levels: str = raw_osm_geocode_response.get("building:levels")
        building_flats: str = raw_osm_geocode_response.get("building:flats")

        addresses_data["osm_address"].append(osm_address)
        addresses_data["osm_id"].append(osm_id)
        addresses_data["osm_type"].append(osm_type)
        addresses_data["place_class"].append(place_class)
        addresses_data["place_type"].append(place_type)
        addresses_data["building_levels"].append(building_levels)
        addresses_data["building_flats"].append(building_flats)

    else:
        addresses_data["osm_address"].append(None)
        addresses_data["osm_id"].append(None)
        addresses_data["osm_type"].append(None)
        addresses_data["place_class"].append(None)
        addresses_data["place_type"].append(None)
        addresses_data["building_levels"].append(None)
        addresses_data["building_flats"].append(None)

df_osm_data = pd.DataFrame.from_dict(addresses_data)

# 提取测试地址并获取周边建筑
test_address = df_osm_data.loc[0, "osm_address"]
ox_gdf = ox.geometries_from_address(
    address = test_address,
    tags = {"building": ["house", "apartments", "residential", "detached"], "place": "house", "amenity": False}
)

df_gdf = pd.DataFrame(ox_gdf)
df_gdf.reset_index(inplace=True)
df_gdf.to_excel("osmnx_geometries_perimeter.xlsx")

遇到的问题

部分住宅建筑条目(way类型)只有建筑类型,没有地址信息。在浏览器中查看该way的osmid时,仅显示组成多边形的节点,但右键点击多边形选择“显示地址”后,会得到一个带地址的新node(该node不在原way的节点列表中)。同时,df_gdf中存储的节点列表也和这个新node不匹配。

示例

某条无地址的way包含以下节点:

3938255237
3938255220
3938255221
3938255209
3938255217
3938255224
3938255223
3938255230
3938255236
3938255251
3938255237

右键点击后得到的带地址node,其id不在上述列表中。

咨询问题

  1. 是否可以通过重新评估osmid来获取地址?如果可以,该如何实现?
  2. Nominatim返回的place_id是否有助于解决该问题?

解决方案

1. 重新评估osmid获取地址的实现方式

可以通过两种方式实现:

方式一:基于建筑中心的反向地理编码

先计算无地址建筑way的几何中心,再用反向地理编码获取对应地址:

# 计算每个建筑的中心坐标
ox_gdf["center"] = ox_gdf.geometry.centroid
ox_gdf["lon"] = ox_gdf.center.x
ox_gdf["lat"] = ox_gdf.center.y

# 为无地址建筑执行反向地理编码
def get_address_from_coords(lat, lon):
    try:
        location = geopy_geolocator.reverse((lat, lon), exactly_one=True)
        return location.raw.get("display_name") if location else None
    except Exception:
        return None

# 填充无地址条目的地址信息
ox_gdf["address"] = ox_gdf.apply(
    lambda row: get_address_from_coords(row["lat"], row["lon"]) 
    if pd.isna(row.get("addr:full")) else row.get("addr:full"),
    axis=1
)

方式二:空间关联查询地址node

通过OSM的空间查询,查找与目标way邻近的带地址node:

from OSMPythonTools.overpass import Overpass

overpass = Overpass()

def get_nearest_address_node(way_geometry):
    # 获取建筑的边界框
    bbox = way_geometry.bounds
    # 查询边界框内带地址标签的node
    query = f"""
    [out:json];
    node["addr:housenumber"]({bbox[1]},{bbox[0]},{bbox[3]},{bbox[2]});
    out body;
    """
    result = overpass.query(query)
    if result.elements():
        # 取距离建筑中心最近的node
        center = way_geometry.centroid
        nearest_node = min(result.elements(), key=lambda elem: center.distance(elem.geometry()))
        # 拼接地址信息
        tags = nearest_node.tags()
        return f"{tags.get('addr:street', '')} {tags.get('addr:housenumber', '')}, {tags.get('addr:city', '')}"
    return None

# 为无地址建筑匹配地址
ox_gdf["address"] = ox_gdf.apply(
    lambda row: get_nearest_address_node(row["geometry"]) 
    if pd.isna(row.get("addr:full")) else row.get("addr:full"),
    axis=1
)

2. Nominatim的place_id的作用

place_id可以直接解决这个问题,具体逻辑:

  • place_id是Nominatim对地址实体的唯一标识,关联了所有与该地址相关的OSM元素(包括建筑way和对应的地址node)。
  • 通过place_id调用Nominatim详情接口,可获取该地址关联的所有元素及完整地址标签。

示例代码:

import requests

def get_full_address_from_place_id(place_id):
    url = f"https://nominatim.openstreetmap.org/details?place_id={place_id}&format=json"
    headers = {"User-Agent": "my_app"}
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        data = response.json()
        addr_tags = {k: v for k, v in data.get("tags", {}).items() if k.startswith("addr:")}
        if addr_tags:
            return ", ".join([
                f"{addr_tags.get('addr:street', '')} {addr_tags.get('addr:housenumber', '')}",
                addr_tags.get('addr:postcode', ''),
                addr_tags.get('addr:city', '')
            ]).strip(", ")
    return None

# 收集数据时保存place_id
for i in addresses_to_analyze:
    # ... 原有代码 ...
    if raw_geopy_geocode_response:
        place_id = raw_geopy_geocode_response.get("place_id")
        addresses_data["place_id"].append(place_id)
    else:
        addresses_data["place_id"].append(None)

内容的提问来源于stack exchange,提问作者Soren V. Raben

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:17:00