Python处理OSM数据:如何从无地址way获取实体地址?
问题背景
我刚接触地理编码领域,需要为一组地址查找最近的住宅建筑,操作流程如下:
- 使用
geopy.geocoders.Nominatim获取地址基础数据,包括display_name、osm_id、osm_type等。 - 通过
OSMPythonTools.api.Api,用osm_query: str = fr"{osm_type}/{osm_id}"获取建筑详细信息(楼层数、公寓数量等),并保存到pandasDataFrame中。 - 用
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不在上述列表中。
咨询问题
- 是否可以通过重新评估
osmid来获取地址?如果可以,该如何实现? 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
相关产品推荐
相关产品推荐

