Mit overpass die role von membern einer relation ausgeben

Ich habe mich etwas bei Wanderwegen umgesehen. Dabei habe ich auch Werte von role gesehen, die im Wiki nicht aufgeführt sind. Bisweilen waren diese Werte auch einfach sinnlos (“x”, “wurr”, “2” etc.)
Jetzt würde ich mir gerne einen Überblick über alle (in einer ausgewählten Gegend) verwendeten Werte von role verschaffen.
Wie mache ich das?

[out:json][timeout:25];
{{geocodeArea:Niederursel}}->.searchArea;
relation["type"="route"][route~"foot|hiking"] ["name"~"Ursel"](area.searchArea); 
(._;>;);
out body;

liefert viele viele Daten - unter anderem auch die gesuchten.
Jedoch kann ich in dieser Form mit den Daten nichts anzufangen. Besser wäre eine Ausgabe als csv.
Optimal wäre folgende Ausgabezeile: id der relation, id des members, type(member), role(member), name(member)

Also hab ich es mit dieser Abfrage versucht:

[out:csv(::id, ::type, role, name)] [timeout:25];
{{geocodeArea:Niederursel}}->.searchArea;
relation["type"="route"][route~"foot|hiking"][name~Ursel] (area.searchArea); 
foreach->.a (
  out; 
  way(r.a); 
  out;
);

Warum wird in dieser Form role nicht angezeigt? Wie muß ich die Abfrage formulieren, um role angezeigt zu bekommen? (Durch die erste Abfrage weiß ich, daß in der Antwort ways mit role vorkommen!)

1 Like

Warum das nicht geht ist klar, die Rolle ist je kein Attribut des ways.
Habe aber leider keine Idee wie man die Rolle die man mit role() bestimmen kann im csv ausgegeben bekommt

Das skaliert zwar nicht, aber da fast alle Wege keine Rolle haben, kannst du dir diejenigen mit Rolle ausgeben lassen und dann manuell prüfen. Sind bloß eine Handvoll:

[out:csv(::id, ::type, role, name)] [timeout:25];
{{geocodeArea:Niederursel}}->.searchArea;
relation["type"="route"][route~"foot|hiking"][name~Ursel] (area.searchArea); 
foreach->.a (
  out; 
  (way(r.a); - way(r.a:"");); 
  out;
);

Ich habe die (GeoJSON-) Daten der Abfrage mal an die Google-Gemini-KI übergeben. Aufgabe sollte es sein die gewünschte Ausgabe im CSV-Format zu erzeugen. Gemini hat dazu ein lauffähiges python-Programm generiert. Das Programm erzeugt eine CSV-Ausgabe. Kannst du das Ergebnis mal bewerten?

Gemini-Query

Prompt to Gemini:

Die Openstreetmap-Daten liegen im GeoJSON-Format vor.
Erstelle eine CSV-Datei mit folgendem Aufbau:
id der relation, id des members, type(member), role(member), name(member)

Response from Gemini:

import json
import csv

def geojson_to_csv(geojson_data, csv_file):
    """
    Konvertiert GeoJSON-Daten mit Relationen in eine CSV-Datei.

    Args:
        geojson_data (dict): GeoJSON-Daten als Python-Dictionary.
        csv_file (str): Pfad zur CSV-Ausgabedatei.
    """

    with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(["relation_id", "member_id", "member_type", "member_role", "member_name"])

        for feature in geojson_data['features']:
          if feature['type'] == 'Feature' and 'properties' in feature and '@id' in feature['properties']:
            
            if feature['properties']['@id'].startswith('relation/'):
                relation_id = feature['properties']['@id'].split('/')[1]
                
                # Extrahiere Mitglieder aus Relationen
                if 'geometry' in feature and feature['geometry']['type'] == 'MultiLineString':
                  for rel_data in feature['properties'].get('@relations',[]):
                    if 'rel' in rel_data:
                       
                      
                      reltags = rel_data['reltags']
                      
                      member_id = rel_data['rel']
                      
                      
                      writer.writerow([relation_id, member_id, 'relation', rel_data.get('role', ''), reltags.get('name', '')])
                      

            elif feature['properties']['@id'].startswith('way/'):
              way_id = feature['properties']['@id'].split('/')[1]
              # Extrahiere Relationen aus Ways
              for rel_data in feature['properties'].get('@relations', []):
                
                if 'rel' in rel_data:
                  relation_id = rel_data['rel']
                  reltags = rel_data.get('reltags', {})  # Sorge für leeres Dictionary, falls 'reltags' fehlt
                  member_name = feature['properties'].get('name', '') 
                  writer.writerow([relation_id, way_id, 'way', rel_data.get('role', ''), member_name])

if __name__ == '__main__':
    geojson_file = 'oberursel_muehlenwanderweg.geojson'  # Ersetze mit dem Pfad zu deiner GeoJSON-Datei
    csv_output_file = 'oberursel_muehlenwanderweg.csv'

    with open(geojson_file, 'r', encoding='utf-8') as f:
        geojson_data = json.load(f)

    geojson_to_csv(geojson_data, csv_output_file)
    print(f"Die CSV-Datei wurde unter '{csv_output_file}' erstellt.")

Erläuterung:

  1. Importiere Bibliotheken: Importiere json zum Verarbeiten von GeoJSON und csv zum Schreiben von CSV-Dateien.
  2. Funktion geojson_to_csv:
    • Akzeptiert GeoJSON-Daten und den Pfad zur CSV-Ausgabedatei.
    • Öffnet die CSV-Datei im Schreibmodus.
    • Schreibt die Kopfzeile in die CSV-Datei.
    • Iteriert durch jedes Feature in den GeoJSON-Daten.
      • Wenn der Feature-Typ ‘Feature’ ist, das Feature properties und id enthält:
        • Extrahiere die relation_id.
        • Iteriere durch die Mitglieder der Relationen
        • Extrahiere member_id, member_type, member_role und member_name
        • Schreibe die extrahierten Informationen als neue Zeile in die CSV-Datei.
  3. Hauptteil des Skripts (if __name__ == '__main__':):
    • Definiert den Pfad zur GeoJSON-Eingabedatei (geojson_file) und zur CSV-Ausgabedatei (csv_output_file).
    • Lädt die GeoJSON-Daten aus der Datei.
    • Ruft die Funktion geojson_to_csv auf, um die Konvertierung durchzuführen.
    • Gibt eine Bestätigungsmeldung aus.

Wie man das Skript verwendet:

  1. Speichere das Skript als Python-Datei (z.B., geojson_converter.py).
  2. Stelle sicher, dass deine GeoJSON-Datei unter dem Pfad oberursel_muehlenwanderweg.geojson verfügbar ist, oder ändere den Pfad im Skript.
  3. Führe das Skript aus der Befehlszeile aus: python geojson_converter.py
  4. Eine CSV-Datei mit dem Namen oberursel_muehlenwanderweg.csv wird in dem gleichen Verzeichnis erstellt.

Diese CSV-Datei enthält die id der Relation, die id des Members, den type, die role des Members und den Namen des Members.

Erzeugte CSV-Datei

relation_id,member_id,member_type,member_role,member_name
78067,4730851,way,Sebastian-Kneipp-Straße
78067,4769403,way,
78067,4769404,way,
78067,4769442,way,
78067,4815887,way,
78067,4913286,way,Kaltmühle
78067,4913288,way,
78067,5037998,way,Wiesenaustraße
78067,5038003,way,Aumühlenstraße
78067,5038784,way,
78067,5043806,way,
78067,5070120,way,
78067,5070302,way,Gattenhöferweg
78067,5081241,way,
78067,5081242,way,
78067,5083298,way,
78067,5083507,way,Mühlweg
78067,5094383,way,
78067,5094384,way,
78067,5097199,way,
78067,5097203,way,
78067,5097204,way,
78067,5201126,way,Korfstraße
78067,5201257,way,Korfplatz
78067,5576393,way,Bischof-Brand-Straße
78067,5577337,way,Bischof-Brand-Straße
78067,5577780,way,
78067,5577784,way,
78067,5577786,way,
78067,5927965,way,alternative,An der Untermühle
78067,7989977,way,
78067,9863552,way,
78067,22807704,way,Hohemark
78067,22807706,way,alternative,
78067,22807707,way,Kaskadenweg
78067,22813874,way,
78067,22813875,way,
78067,22994188,way,
78067,23128058,way,Eppsteiner Straße
78067,23128132,way,Weidengasse
78067,23128509,way,
78067,23301886,way,
78067,23301887,way,
78067,23301888,way,
78067,23925252,way,
78067,23926807,way,
78067,24050315,way,
78067,24050319,way,
78067,24050325,way,
78067,24147768,way,
78067,24147770,way,
78067,24228344,way,Sandweg
78067,24279821,way,An der Waldlust
78067,24344588,way,alternative,
78067,24344589,way,
78067,24344591,way,
78067,25151225,way,
78067,25151226,way,
78067,25151227,way,
78067,25151228,way,
78067,25151233,way,
78067,25410438,way,
78067,25410861,way,
78067,25410862,way,
78067,25445233,way,Wiederholtstraße
78067,25599854,way,Hohemark
78067,25599910,way,Unterer Maßbornweg
78067,25732720,way,
78067,25732726,way,Unterer Panoramaweg
78067,25732745,way,Bachpfädchen
78067,26188341,way,
78067,26607417,way,Urselbachstraße
78067,27234172,way,
78067,27234209,way,Sankt-Ursula-Gasse
78067,27261965,way,
78067,27819064,way,Im Portugall
78067,28834404,way,Weidengasse
78067,30333726,way,
78067,30333846,way,
78067,31054884,way,
78067,31102207,way,alternative,
78067,31102211,way,
78067,31102218,way,Altkönigstraße
78067,31102220,way,Hollerberg
78067,31102221,way,An der Herrenmühle
78067,31102223,way,Obere Hainstraße
78067,31102224,way,Wiederholtstraße
78067,31102225,way,Oberhöchstadter Straße
78067,31102227,way,Aumühlenstraße
78067,31102228,way,
78067,31102236,way,
78067,31102237,way,Zimmersmühlenweg
78067,31102239,way,Kurmainzer Straße
78067,31102241,way,Urselbachstraße
78067,31102242,way,An der Untermühle
78067,31102244,way,Krebsmühlenweg
78067,31102245,way,Hinter der Krebsmühle
78067,31102247,way,
78067,31102253,way,
78067,31102255,way,
78067,31102256,way,Krautgartenweg
78067,31102259,way,
78067,33342494,way,Obergasse
78067,35070270,way,Obergasse
78067,35145335,way,
78067,45733492,way,
78067,55790312,way,Alfred-Lechler-Straße
78067,92907737,way,Krebsmühlenweg
78067,92907801,way,Krebsmühlenweg
78067,92948029,way,
78067,94455992,way,Gattenhöferweg
78067,116119188,way,Zimmersmühlenweg
78067,116119216,way,Zimmersmühlenweg
78067,143757942,way,Weidengasse
78067,171660184,way,
78067,171660185,way,
78067,173216182,way,
78067,192053893,way,
78067,251383883,way,
78067,272783164,way,Sandweg
78067,418998465,way,Oberhöchstadter Straße
78067,426840149,way,Bischof-Brand-Straße
78067,426840150,way,Bischof-Brand-Straße
78067,429532274,way,Gattenhöferweg
78067,429532285,way,Unterer Panoramaweg
78067,429534143,way,alternative,
78067,431521981,way,Obergasse
78067,432054959,way,Hollerberg
78067,432054960,way,Hollerberg
78067,442768257,way,
78067,450844839,way,
78067,482744029,way,Korfstraße
78067,519646606,way,
78067,519646608,way,
78067,519646609,way,
78067,519646610,way,
78067,702492896,way,
78067,787125767,way,
78067,891241812,way,Bachpfädchen
78067,996151367,way,Korfstraße
78067,1038076860,way,
78067,1055433105,way,
78067,1055433106,way,
78067,1069855938,way,
78067,1100906855,way,
78067,1106004776,way,Obergasse
78067,1106135654,way,Krautgartenweg
78067,1106135655,way,Krautgartenweg
78067,1107739126,way,Kaltmühle
78067,1132198293,way,Bischof-Brand-Straße
78067,1195313909,way,
78067,1195368697,way,
78067,1195368713,way,
78067,1195368714,way,
78067,1195368719,way,
78067,1195368720,way,
78067,1195368724,way,
78067,1195368725,way,
78067,1202546024,way,
78067,1202546025,way,Neumühlenweg
78067,1202546027,way,Neumühlenweg
78067,1202546029,way,
78067,1202546030,way,
78067,1202546032,way,
78067,1202546035,way,
78067,1202546037,way,
78067,1202546038,way,Hospitalstraße
78067,1202546039,way,Strackgasse
78067,1202546041,way,Sankt-Ursula-Gasse
78067,1202546042,way,Sankt-Ursula-Gasse
78067,1202546043,way,
78067,1202546044,way,Alfred-Lechler-Straße
78067,1202546045,way,alternative,
78067,1243411464,way,
78067,1243412149,way,
78067,1292640353,way,Korfstraße
78067,1305198969,way,Krebsmühlenweg

2 Likes

Hmm, eine ungültige CSV-Datei.

Leere Elemente sind nicht vorhanden. Es ist nicht ohne weiteres zu sehen, was eine Rolle und was ein Name ist - was genau die Zielsetzung ist. Ja, man kann gezielt raten.

Die Idee ist gar nicht so schlecht.
Die Abfrage

[out:csv(::id, ::type, role, name)] [timeout:25];
{{geocodeArea:Niederursel}}->.searchArea;
relation["type"="route"][route~"foot|hiking"][name~Ursel] (area.searchArea); 
foreach->.a (
  out; 
  (way(r.a); - way(r.a:"");); 
  out;
);

liefert alle ways mit role. Man sehe sich einen dieser Wege an.
Role ist forward.

[out:csv(::id, ::type, role, name)] [timeout:25];
{{geocodeArea:Hessen}}->.searchArea;
relation["type"="route"][route~"foot|hiking"] (area.searchArea); 
foreach->.a (
   way(r.a:"forward") ;  
  out;
);

liefert die Anzahl der ways (in Hessen) mit role forward

und

[out:csv(::id, ::type, role, name)] [timeout:25];
{{geocodeArea:Hessen}}->.searchArea;
relation["type"="route"][route~"foot|hiking"] (area.searchArea); 
foreach->.a (
    (
      way(r.a); 
     - (way(r.a:"");  
        way(r.a:"forward"); 
		);
    );
   out;
);

liefert alle ways mit einer role ungleich “” oder “forward”.

etwa 2h und einige händische Itterationen später weiß ich:
In Hessen gibt es ways mit folgenden roles (die Zahl dahinter ist die Anzahl)

forward 377
alternative 282
backward 236
approach  113
link 77
variant 49
parking 24
excursion 22
detour 20
start 11
last 11
" " (leerzeichen)
aside 6
shortcut 6
alternate  11
line 1
terminal  3
NW4
NW5
NW6
wand  1
fußweg  1
route 14
1  3
l (kleinL)
variation
zorner
first            8
Odenwald-Vogesen-Weg Teil 1                  1
x											5    großes x , kleines x?
2											2

Mit nodes und relationen funktioniert das analog.

Die Google-KI kann kein Python. Ich allerdings auch nicht.
Jedoch ist die Idee gar nicht so schlecht.

1 Wochenende später:
Dieser python-script funktioniert (in etwa) so, wie die KI (und ich) es mir vorgestellt habe.

import json
import csv

def geojson_to_csv(geojson_data, csv_file):
    """
    Konvertiert GeoJSON-Daten mit Relationen in eine CSV-Datei.

    Args:
        geojson_data (dict): GeoJSON-Daten als Python-Dictionary.
        csv_file (str): Pfad zur CSV-Ausgabedatei.
    """

    with open(csv_file, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(["relation_id", "member_id", "member_type", "member_role"])

        schalt = ""     
        for feature in geojson_data:          
            #nach den membern kommt die Zeile mit "  "tags": {"    und einige Zeilen später die nächste Relation
            if '  "tags": {' in feature:
               schalt=""
                   
            match schalt:
                case "rel":
                    rel_id = feature[8:-2]  #Komma an letzter Stelle abschneiden                 
                    schalt = "member"                    
                case "member":    
                    if "ref" in feature:                     
                        member_id = feature[13:-2] 
                    if "type" in feature:          
                        typ= feature[15:-3] # way oder node  
                    if "role" in feature:   
                        role = feature[15:-2] 
                        writer.writerow([rel_id, member_id, typ, role])  # ausgabezeile schreiben - ohne name(member)
            if "relation" in feature:         # gesucht wird nach der Zeile   "type": "relation",
                   schalt="rel"               # in der nächsten Zeile steht dann die id der relation:   "id": 17991861,

            
  #        name(member)? erst mal nicht -  Der steht viel weiter vorne im geojson.
  #        Möglichkeit 1: zu jedem member die id merken und die Datei nochmal durchlesen
  #        Möglichkeit 2: man merkt sich vorher von jedem node / way / relation den Namen
  #        arrays / Listen / Vektoren in python? vielleicht später...
  #        Möglichkeit 3: Mit der api arbeiten und direkt aus der DB holen. Hust. noch viel später!   

if __name__ == '__main__':
    geojson_file = 'Niederursel.geojson'  # Ersetze mit dem Pfad zu deiner GeoJSON-Datei
    csv_output_file = 'Niederursel.csv'

    geojson_data = open(geojson_file, 'r')
    geojson_to_csv(geojson_data, csv_output_file)
    print(f"Die CSV-Datei wurde unter '{csv_output_file}' erstellt.")

Edit: Testausgabe aus dem script entfernt

Möglichkeiten, die Daten direkt von der Overpass API zu bekommen:

[out:csv(::id,roles)];
area[name="Niederursel"];
rel(area)[route~"foot|hiking"];
convert info ::id=id(),roles=lrs_union("", per_member("{"+role()+"}"));
out;

liefert in CSV eine Liste der vorkommenden Roles. Die geschwungenen Klammern kann man auch weglassen, sie helfen aber mitunter bei Werten in role, die Sonderzeichen enthalten.

[out:csv(::id,roles)];
area[name="Niederursel"];
rel(area)[route~"foot|hiking"];
convert info ::id=id(),roles=per_member("{"+mtype()+"_"+ref()+": "+role()+"}");
out;

listet alle Daten des Member-Eintrags; auch hier können die Trennzeichen nach Zweckmäßigkeit geändert werden.

Als Idee noch können auch die Member mit leerer Rolle ignoriert werden:

[out:csv(::id,roles)];
area[name="Niederursel"];
rel(area)[route~"foot|hiking"];
convert info ::id=id(),roles=lrs_union("", per_member(
    role() == "" ? ""
    : "{"+mtype()+"_"+ref()+": "+role()+"}"));
out;
2 Likes

oha - Danke.
Die Möglichleiten von overpass sind unerschöpflich.
Ich geh dann mal wieder ne Runde im Wiki nachlesen!