Anleitung

Die Overpass API in Python

Drei Wege, Overpass aus Python abzufragen, vom einfachen requests bis zu den Paketen overpy und overpass, mit Server-URL, Wiederholungen bei 429 und 504, GeoJSON und pandas. Jedes Beispiel hier lief gegen overpass.ch.

Overpass ist ein einziger HTTP-Endpunkt. Senden Sie Ihre Abfrage per POST als Formularfeld data, und lesen Sie das JSON:
import requests

r = requests.post("https://api.overpass.ch/v1/IHR_API_SCHLÜSSEL/interpreter",
                  data={"data": '[out:json];node["amenity"="drinking_water"](46.00,7.73,46.03,7.77);out;'},
                  timeout=120)
r.raise_for_status()
print(len(r.json()["elements"]))

1. requests, mit Wiederholungen

Für Skripte und Dienste genügt requests allein. Ergänzen Sie eine Wiederholung für die Antworten, die «später noch einmal versuchen» bedeuten: 429 (Rate-Limit), 503 und 504 (Server ausgelastet oder Timeout). Manche Fehler meldet Overpass auch innerhalb einer erfolgreichen Antwort, in remark:

import os
import time
import requests

URL = f"https://api.overpass.ch/v1/{os.environ['OVERPASS_CH_KEY']}/interpreter"

def overpass(query, tries=5):
    for attempt in range(tries):
        r = requests.post(URL, data={"data": query}, timeout=200)
        if r.status_code in (429, 503, 504) and attempt < tries - 1:
            time.sleep(float(r.headers.get("Retry-After", 10 * 2 ** attempt)))
            continue
        r.raise_for_status()
        data = r.json()
        if "runtime error" in data.get("remark", ""):
            raise RuntimeError(data["remark"])   # z. B. "Query timed out"
        return data

data = overpass("""
[out:json][timeout:60];
area["ISO3166-2"="CH-VS"]->.valais;
nwr["tourism"="alpine_hut"](area.valais);
out center;
""")
for el in data["elements"]:
    print(el["tags"].get("name"), el.get("lat") or el["center"]["lat"])
  • Eine Abfrage nach der anderen. Parallele Anfragen sind die häufigste Ursache für 429 auf öffentlichen Servern. Auf overpass.ch legt Ihr Tarif fest, wie viele Abfragen gleichzeitig laufen dürfen.
  • Retry-After sagt, wie viele Sekunden Sie warten müssen. Öffentliche Server senden ihn nicht immer, daher die Verdoppelung als Rückfall.
  • Mehr zu jedem Fehler: Fehler der Overpass API erklärt.

2. overpy

overpy macht aus der Antwort Python-Objekte mit Nodes, Ways und Relationen und wirft für jede Art von Fehler eine Ausnahme. Übergeben Sie die vollständige URL des Interpreters:

import overpy

api = overpy.Overpass(url="https://api.overpass.ch/v1/IHR_API_SCHLÜSSEL/interpreter")
result = api.query("""
[out:json][timeout:25];
way["highway"="path"](46.015,7.74,46.02,7.75);
(._;>;);
out body;
""")
for way in result.ways:
    nodes = way.get_nodes(resolve_missing=False)
    print(way.id, way.tags.get("name"), len(nodes), "nodes")

Fehler kommen als Ausnahmen wie overpy.exception.OverpassBadRequest (ein Syntaxfehler) und OverpassTooManyRequests. Getestet mit overpy 0.7.

3. overpass (GeoJSON)

Das Paket overpass baut aus Ihrem Filter eine vollständige Abfrage und kann direkt GeoJSON liefern, praktisch für Karten:

import overpass

api = overpass.API(endpoint="https://api.overpass.ch/v1/IHR_API_SCHLÜSSEL/interpreter", timeout=60)
geojson = api.get('nwr["amenity"="drinking_water"](46.00,7.73,46.03,7.77)',
                  verbosity="center", responseformat="geojson")
print(len(geojson["features"]))

Version 0.8 warnt, dass API und die Argumente responseformat und verbosity in 1.0 ersetzt werden. Die Aufrufe oben funktionieren in 0.8.2.

Nach pandas

Für Tabellen verlangen Sie von Overpass CSV und lesen es direkt ein. Die Spalten sind die Tags, die Sie aufzählen, dazu ::id, ::lat und ::lon für ID und Position des Elements:

import io
import pandas as pd

r = requests.post(URL, data={"data": """
[out:csv(::id, ::lat, ::lon, name, ele; true; ",")][timeout:25];
node["natural"="peak"]["name"](45.90,7.60,46.10,7.95);
out;
"""}, timeout=120)
r.raise_for_status()
peaks = pd.read_csv(io.BytesIO(r.content))   # Bytes: pandas liest sie als UTF-8

Die Spalten heissen @id, @lat, @lon, name und ele. Die Bytes zu übergeben funktioniert mit jedem Server. Overpass sendet CSV als UTF-8, ohne das im Content-Type anzugeben, und requests dekodiert r.text dann als Latin-1, was Namen wie Dent d'Hérens verstümmelt. Auf overpass.ch funktioniert auch r.text, weil wir charset=utf-8 ergänzen.

Aus JSON liefert pd.json_normalize(data["elements"]) eine Spalte pro Tag (tags.name, tags.ele, …).

Höhen in derselben Anfrage

Auf overpass.ch hängen Sie elevation=1 an die URL, und jeder Node, jeder Mittelpunkt und jeder Geometriepunkt einer JSON-Antwort erhält eine elevation in Metern. Jede braucht eine Höhenabfrage aus Ihrem Kontingent:

r = requests.post(URL, params={"elevation": 1}, data={"data": query}, timeout=200)

Gipfel, Hütten, Bergbahnen und Routenprofile zeigen die Beispiele mit Höhen.

OSMnx und OSMPythonTools

  • OSMnx hat eigene Einstellungen für den Server; siehe die Anleitung zu OSMnx.
  • OSMPythonTools liest vor jeder Abfrage die Statusseite des Servers und erwartet das Textformat von overpass-api.de. overpass.ch antwortet in diesem Format, Sie müssen also nur den Endpunkt setzen (mit einem Schrägstrich am Ende):
    from OSMPythonTools.overpass import Overpass
    
    op = Overpass(endpoint="https://api.overpass.ch/v1/IHR_API_SCHLÜSSEL/")
    peaks = op.query('node["natural"="peak"](45.90,7.60,46.10,7.95); out;', timeout=25)
    Auf Servern, deren Statusseite es nicht lesen kann, bricht es mit «could not fetch or interpret status of the endpoint» ab.