Workshop - Nachladen von Geokoordinaten

Folie - Nachladen von Geokoordinaten Folie - Nachladen von Geokoordinaten

In diesem Tutorial beschäftigen wir uns mit dem Nachladen von Geokoordinaten.

Einführung

Reconciling in der OpenRefine Dokumentation.
Spalten via URL hinzufügen in der OpenRefine Dokumentation
OpenRefine API in der lobid API Dokumentation.
Getty Research Institute zu OpenRefine Reconciliation.
Informationssammlung zu OpenRefine Wikidata Reconciliation.

Dieser Workshop wurde erstellt mit OpenRefine Version 3.5.0.
Dieser Workshop wurde zuletzt getestet mit OpenRefine Version 3.8.2.
Die Situation von Orten in den Datenquellen unterscheidet sich teils stark und ändert sich stets. Daher werden in diesem Tutorial unterschiedliche Strategien gezeigt.

OpenRefine unterstützt für den Abgleich mit Datenquellen die Reconciliation Service API. In dem Workshop wurde schon gezeigt, wie Daten mit der GND, Wikidata und Getty abgeglichen werden können. In diesem Teil des Workshops beschäftigen wir uns im ersten Teil, wie wir von Normdatenverknüpfungen zu Geokoordinaten kommen. Im zweiten Teil behandeln wir den allgemeinen Umgang mit Zugriff auf Daten von GeoNames und OpenStreetMap über allgemeine Geocoding APIs.

flowchart LR ort["Ort"] gnd["GND"] wikipedia["Wikipedia"] wikidata["Wikidata"] tgn["Getty TGN"] geonames["GeoNames"] coordinates[[Koordinaten]] ort --Recon--> gnd & wikidata & tgn ort --API--> geonames gnd --Extend--> wikipedia gnd <--Extend--> wikidata gnd --Extend--> geonames wikipedia --API--> coordinates & wikidata wikidata --Extend--> coordinates wikidata --Extend---> geonames wikidata --Extend--> tgn tgn --Extend--> coordinates geonames --API--> coordinates

In dem Diagramm sind verschiedene Wege gezeigt, die zum Nachladen von Geokoordinaten genommen werden können. Teilweise sind nicht bei allen Orten in den Datenquellen Geokoordinaten hinterlegt, so dass wir der Extend Funktionalität der Reconciliation Service Specification z.B. von der GND-ID zu Wikidata und von dort zu GeoNames “springen”.

Problemstellung

Die Situation der Orte in den einzelnen Datenquellen unterscheidet sich teilweise stark. In der folgenden Tabelle sind einige zufällig ausgewählten Orte aus Baden-Württemberg gelistet, um das Problem zu verdeutlichen.

DatenquelleWohnplatzGemeinde (historisch)Ortsteil (aktuell)Gemeinde (aktuell)
OrtKaltenbachMarlsburgMarlsburgMarlsburg-Marzell
GND7824222-84422940-94422938-0
WikidataQ65183925Q62032
Getty TGN71530127141027
GeoNames289365128740496555902
WikipediaKaltenbachMalsburg-Marzell
OrtLuisenheimMarzellMarzell
GND7818883-04422941-0
Wikidata
Getty TGN7129879
GeoNames2873068
Wikipedia
OrtAltdorfEttenheim-AltdorfEttenheim
GND4260757-710099367-94015632-1
WikidataQ47520099Q506871
Getty TGN72162621039035
GeoNames29580316558103
WikipediaAltdorfEttenheim

Der Wohnplatz Kaltenbach kam schon 1823 zur Gemeinde Malsburg dazu, welche dann 1974 zur Doppelgemeinde Marlsburg-Marzell wurde. Dies ist in der GND auch entsprechend “abgebildet”. Was quasi fehlt, ist ein separater Eintrag für Marlsburg als heutigen Teilort von Marlsburg-Marzell. Dies ist in der Tabelle am Beispiel der ehemaligen Gemeinde Altdorf gezeigt, für die es in der GND sowohl für die historische Gemeinde, als auch für den heutigen Teilort separate Eintragungen gibt.

In Wikidata und Wikipedia gibt es nur Hinweise auf Kaltenbach und die aktuelle Doppelgemeinde Marlsburg-Marzell. Für die vorangegangenen Gemeinden Marlsburg und Marzell gibt es auf Wikipedia (noch) keine Einträge.

Dafür sind Marlsburg und Marzell sowohl bei Getty TGN, als auch bei GeoNames zu finden, wobei sich das eher auf die aktuellen Teilorte bezieht. Die aktuelle Doppelgemeinde Marlsburg-Marzell ist bei Getty TGN nicht zu finden, wobei das auch mit Problemen mit dem Bindestrich bei Suchanfragen zu tun haben kann, worauf wir später in diesem Tutorial nochmal zu sprechen kommen.

Luisenheim als im Vergleich zu Kaltenbach weniger bedeutender und zu Marzell zugehöriger Wohnplatz ist beispielsweise nur in der GND zu finden.

Was im Zuge des Reconciling also passieren kann ist, dass man historische Orte wie Altdorf verknüpft. Die Verknüpfung zu anderen Datenquellen wie GeoNames oder Wikidata aber am aktuellen Ortsteil Ettenheim-Altdorf hängt. Oder man umgekehrt den aktuellen Teilort verknüpft, die Informationen zu GeoNames oder Wikidata aber mit dem historischen Ort verknüpft sind.

StuttgartWohnortStadtkreisRegierungsbezirk
GND4058282-64058287-5
WikidataQ1022Q8172
Getty TGN700442570773317075017
GeoNames669018932207853214105
WikipediaStuttgartRegierungsbezirk

Ein anderes Problem ist teilweise die Unterscheidung zwischen Wohnort, Stadtkreis, Regierungsbezirk und teilweise sogar Bundesland (Stadtstaaten), die verwaltungstechnisch unterschiedliche Ebenen einnehmen, aber teilweise die exakt gleiche Fläche abdecken.

Hier wird in der GND und Wikidata zum Beispiel bei Stuttgart nicht zwischen Wohnort und Stadtkreis unterschieden, bei Getty TGN und GeoNames aber schon.

Dies sind nur ein paar exemplarische Beispiele, die neben Duplikaten, fehlenden Daten und unterschiedlichen Schreibweisen zu Problemen bei der Arbeit mit Normdaten für Orte und deren Eignung für das Nachladen von Geoinformationen führen.

Daten laden

Wir benötigen die folgenden Dateien als OpenRefine Projekt:

💾 Wir benötigen die folgende Datei (Rechtsklick und “Ziel speichern unter…”):

Orte
OrtGNDWikidataGetty TGNGeoNamesWikipedia
Kaltenbach7824222-8Q6518392571530122893651https://de.wikipedia.org/wiki/Kaltenbach_(Malsburg-Marzell)
Luisenheim7818883-0
Marlsburg4422940-971410272874049
Marzell4422941-071298792873068
Marlsburg-Marzell4422938-0Q620326555902https://de.wikipedia.org/wiki/Malsburg-Marzell
Altdorf4260757-7
Ettenheim-Altdorf10099367-9Q4752009972162622958031https://de.wikipedia.org/wiki/Altdorf_(Ettenheim)
Ettenheim4015632-1Q50687110390356558103https://de.wikipedia.org/wiki/Ettenheim
Stuttgart4058282-6Q102270044253214105https://de.wikipedia.org/wiki/Stuttgart

Die oben besprochenen Orte gibt es, wie in der obigen Tabelle dargestellt, als CSV-Datei schon mit GND-ID, Wikidata QID, Getty TGN Identifikations-Nummer, GeoNames ID und Wikipedia Link. So können die einzelnen Schritte dieses Tutorials auch unabhängig voneinander durchgespielt werden.

Reconciliation Service API

Wie schon beschrieben, wird die Reconciliation Service API von OpenRefine direkt unterstützt, so dass es dafür entsprechende Komponenten in der Oberfläche von OpenRefine gibt. Über diese Komponenten können Daten mit Datenquellen abgeglichen werden, ohne dass dafür “programmiert” werden muss.

GND

Wie der Abgleich mit der lobid gnd API funktioniert, haben wir bereits in anderen Tutorials besprochen:

Prinzipiell sieht die GND Ontology ein Feld für Koordinaten vor, es gibt aktuell aber keinen Eintrag in der Linked-OpenData-Version der GND, wo diese Eigenschaft gesetzt ist. Die GND ist für Geokoordinaten trotzdem interessant, da die lobid gnd API sehr komfortabel zu benutzen ist und wir mit GND-IDs als Ausgangspunkt zu anderen Datenquellen “springen” können.

Dafür verwenden wir in unserem OpenRefine Projekt das Spaltenmenü “GND" "Reconcile" "Use values as identifiers…”. In dem Dialog wählen wir dann Service “GND reconciliation for OpenRefine”. Sollte der Service nicht in der Auswahl stehen, dann nochmal in 06 Reconciling mit OpenRefine und der GND nachlesen, wie man den Service hinzufügt.

Um basierend auf den Informationen in der GND Links zu anderen Datenquellen nachzuladen, verwenden wir wieder das Spaltenmenü “GND" "Edit column" "Add columns from reconciled values…” und suchen dort nach der Eigenschaft “Siehe auch”.

In der GND selbst sind eigentlich nur wenig Verlinkungen z.B. zu GeoNames gepflegt. Aber von der lobid GND Schnittstelle werden zusätzlich Rückverlinkungen z.B. von Wikidata auf die GND gesammelt und mit ausgeliefert.

Wir haben anschließend eine Record-Struktur, da es für einen Ort in der GND mehrere Verlinkungen zu anderen Datenquellen geben kann.

Mit einem Text Facet können wir die Datenquellen schnell identifizieren. Dafür Verwenden wir “Siehe auch" "Facet" "Custom text facet…” mit dem folgenden GREL-Ausdruck.

value.find(/https?:\/\/[^\/]+/)

Wie in dem Screenshot in Abbildung 1 zu sehen, haben wir unter anderem Verlinkungen zu Wikipedia, Wikidata und GeoNames. Die sind für uns interessant, da wir über diese höchstwahrscheinlich Geokoordinaten beziehen können.

Wie schon im Einleitungstext beschrieben, sind die Verlinkungen teilweise aber auch am vorherigen oder nachfolgenden Geografikum erstellt worden. Diese Orte können analog zu der Eigenschaft “Siehe auch” mit Vorheriges Geografikum und Nachfolgendes Geografikum nachgeladen werden. Je nach Organisationsstruktur lässt sich auch via Ort oder Administrative Überordnung des Geografikums ein passendes Geografikum mit Verlinkungen zu anderen Datenquellen nachladen.

Hier riskiert man es ungenauer zu werden. Nachfolgende oder übergeordnete Geographika tendieren dazu flächenmäßig deutlich größer zu sein, wodurch sich die Positionierung der Geokoordinaten im Vergleich zum eigentlich gemeinten Geografikum deutlich verschieben kann. Es kommt also auf den Anwendungsfall und die Dichte der beschriebenen Orte an.

Auf einer Europa-Karte wäre ein Geomarker für Kaltenbach mittig auf dem Gebiet von Marlsburg-Marzell weniger problematisch. Möchte man jedoch die einzelnen Teilorte von Marlsburg-Marzell unterscheiden, dann kann die Strategie des Nachladens von nachfolgenden bzw. übergeordneten Geografika zu Problemen führen.

Aus den Listen der Verlinkungen zu anderen Datenquellen lassen sich einzelne Spalten generieren. Das wird im Folgenden für Wikipedia, Wikidata und GeoNames gezeigt.

Wikipedia Url

Um eine Spalte mit den deutschsprachigen Wikipedia Urls zu erstellen gehen wir über das Spaltenmenü “Siehe auch" "Edit column" "Add column based on this column…” und verwenden in dem Dialog den folgenden GREL-Befehl.

if(value.contains("de.wikipedia.org"), value, "")

Wikidata QID

Um eine Spalte mit den Wikidata QIDs zu erstellen, gehen wir über das Spaltenmenü “Siehe auch" "Edit column" "Add column based on this column…” und verwenden in dem Dialog den folgenden GREL-Befehl.

if(value.contains("wikidata.org"), value.split("/")[-1], "")

GeoNames ID

Um eine Spalte mit den GeoNames IDs zu erstellen, gehen wir über das Spaltenmenü “Siehe auch" "Edit column" "Add column based on this column…” und verwenden in dem Dialog den folgenden GREL-Befehl.

if(value.contains("geonames.org"), value.split("/")[-1], "")

Wikidata

Wie der Abgleich mit der Wikidata Reconciliation Service API funktioniert, haben wir bereits in anderen Tutorials besprochen:

Um Daten von Wikidata nachladen zu können, verwenden wir in unserem Projekt das Spaltenmenü “Wikidata" "Reconcile" "Use values as identifiers…”. In dem Dialog wählen wir den Service “Wikidata reconci.link (de)”. Sollte der nicht in der Auswahl stehen, dann nochmal in 07 Reconciling mit OpenRefine und Wikidata nachlesen, wie man den Service hinzufügt. Eine Verwendung des deutschsprachigen Service hilft uns dabei, dass die Spaltennamen der erzeugten Spalten automatisch mit dem entsprechenden deutschsprachigen Bezeichnung versehen werden.

Von Wikidata aus können wir unterschiedlichste Informationen nachladen. Für uns interessant sind geographische Koordinaten (P625), sowie GeoNames-Kennung (P1566) und TGN-Kennung (P1667), falls für den Ort bei Wikidata keine Koordinaten hinterlegt sind.

Das Nachladen dieser Eigenschaften erfolgt wieder via “Wikidata" "Edit column" "Add columns from reconciled values…”.

Die geographischen Daten im WGS84 Format können wir anschließend in separate Spalten für den Breiten- und Längengrad aufteilen. Dafür verwenden wir das Spaltenmenü “geographische Koordinaten" "Edit column" "Split into several columns…” und verwenden im folgenden Dialog das Trennzeichen ,. Die erste Spalte beinhaltet dann die geographische Breite lat und die zweite Spalte die geographische Länge lng und sollten entsprechend umbenannt werden.

Getty TGN

Wie der Abgleich mit der Getty Reconciliation Service API funktioniert, haben wir bereits in einem anderen Tutorial besprochen:

Haben wir IDs von Getty TGN, dann sollten wir diese zuerst noch umformatieren, um dem Getty Service mitzuteilen, dass es sich um IDs aus dem TGN Katalog handelt. Dafür verwenden wir das Spaltenmenü “Getty TGN" "Edit cells" "Transform…” mit dem folgenden GREL Ausdruck:

if(isNull(value), "", "tgn/" + value)

Anschließend können wir wie gewohnt die Spalte für das Reconciling vorbereiten “Getty TGN" "Reconcile" "Use values as identifiers…”. In dem Dialog wählen wir den Service “Getty Vocabularies Reconciliation Service”. Sollte der nicht in der Auswahl stehen, dann nochmal in 17 Erweiterter Abgleich mit Getty nachlesen, wie man den Service hinzufügt.

Anschließend können wir die Spalte “Coordinates” nachladen via “Getty TGN" "Edit column" "Add columns from reconciled values…”

Bei Getty ist sowohl die Reihenfolge als auch das Format anders als gewohnt.

[7.733333,47.75]

Um die Koordinaten in Breiten- und Längengrad aufzuspalten verwenden wir daher “Coordinates" "Edit column" "Add column based on this column…” mit den folgenden GREL Ausdrücken.

Für die geographische Breite (lat) verwenden wir den folgenden GREL Ausdruck.

parseJson(value)[1]

Für die geographische Länge (lng) verwenden wir den folgenden GREL Ausdruck.

parseJson(value)[0]

Allgemeine API

Neben der speziellen Reconciliation Service API gibt es auch allgemeine Programmierschnittstellen (engl. abgekürzt API) um mit Diensten im Internet zu kommunizieren. Das Abrufen von Daten (Fachsprache GET-Request) lässt sich in OpenRefine mit dem Dialog zum Hinzufügen von Spalten über URLs umsetzen.

Bildschirmfoto des Dialogs zum Nachladen von URLs für GeoNames.
Bildschirmfoto des Dialogs zum Nachladen von URLs für GeoNames.

Wie in Abbildung 2 gezeigt, lässt sich in dem Dialog pro Zeile im Projekt eine URL zusammenbasteln, deren Inhalte dann von OpenRefine angefragt werden. Die Anfragen lassen sich meistens komplett über die URL steuern, manche Optionen sollten jedoch auch als so genannte HTTP header gesetzt werden.

Bei kostenpflichtigen APIs ist das häufig ein Zugangsschlüssel für das Authorization Feld. Kostenlose Dienste bitten häufig darum z.B. die E-Mailadresse im Feld User-Agent mit anzugeben. So haben die Betreiber eine Möglichkeit mit euch in Kontakt zu treten, falls ihr unbewusst oder ungewollt gegen die Benutzungsregeln verstoßt. Die Alternative ist, dass die IP-Adresse von euch bzw. euem Arbeitgeber bei groben Regelverstößen direkt gesperrt wird.

Im Feld Throttle delay könnt ihr eine automatische Pause zwischen einzelnen Anfragen definieren, um den Dienst nicht mit zu vielen Anfragen nacheinander zu überlasten.

Als Ergebnis dieser Anfragen erhält man häufig Daten im so genannten JSON Format.

Exkursion zu JSON

Bei JSON handelt es sich um ein leichtgewichtiges Textformat für den Datenaustausch. Für diesen Workshop benötigen wir lediglich ein paar einfache Grundlagen von JSON.

Ein Objekt wird in JSON geschweifte Klammern eingefasst:

{
  // Hier sind alle Informationen für ein Objekt zusammengefasst
}

Informationen in Objekten bestehen aus Schlüssel-Werte-Paaren:

{
  "name": "Kaltenbach (Malsburg-Marzell)",
  "lat": 47.75388981,
  "lon": 7.72699657
}

Informationen können geschachtelt werden:

{
  "name": "Kaltenbach (Malsburg-Marzell)",
  "coordinates": {
    "lat": 47.75388981,
    "lon": 7.72699657
  }
}

Informationen können mit eckigen Klammern in Listen gespeichert werden:

{
  "name": "Kaltenbach (Malsburg-Marzell)",
  "coordinates": [47.75388981, 7.72699657]
}

Wikipedia API

Je nach Projekt kann es sein, dass man eine Liste von Wikipedia URLs hat, für die man Normdaten nachladen möchte. Für Orte gibt es in Wikipedia auch direkt Geodaten, die man ebenfalls auslesen kann.

Wikipedia hat eine eigene API, welche unter Wikimedia REST API mit der OpenAPI Spezifikation dokumentiert wird. In der OpenAPI Spezifikation der Wikimedia REST API gibt es auch nochmal Hinweise auf die Benutzungsregeln, also z.B. dass doch bitte, wie weiter oben beschrieben, identifizierbare User-Agents verwendet werden.

Die API Dokumentation ist interaktiv, so dass Anfragen zu den einzelnen Endpunkten direkt ausprobiert und getestet werden können.

Um die Wikidata QID oder die Geokoordinaten zu einer Wikipedia Seite auszulesen, bietet sich der Endpunkt für die Seitenzusammenfassung an.

Bildschirmfoto der interaktiven OpenAPI Dokumentation der Wikimedia REST API.
Bildschirmfoto der interaktiven OpenAPI Dokumentation der Wikimedia REST API.

In Abbildung 3 ist das Ergebnis einer interaktiven Anfrage für die Wikipedia Seite für Kaltenbach gezeigt. Über der Anzeige der Rückgabedaten (Response) gibt es auch die Request URL, die wir in OpenRefine direkt weiterverwenden können.

Um die Daten zeilenweise mit OpenRefine über die Wikimedia REST API zu laden, filtern wir zuerst die Zeilen, die eine Wikipedia URL haben über via “Wikipedia" "Facet" "Customized facets" "Facet by blank (null or empty string)…”.

Anschließend verwenden wir das Spaltenmenü “Wikipedia" "Facet" "Customized facets" "Facet by blank (null or empty string)…” und erstellen eine neu Spalte “Wikipedia JSON” mit dem folgenden GREL Ausdruck (User-Agent… nicht vergessen).

"https://de.wikipedia.org/api/rest_v1/page/summary/"+escape(value.split("/")[-1], "url")+"?redirect=true"

Der GREL Ausdruck nimmt den letzten Teil der Wikipedia URL und setzt ihn als Titel an die entsprechende Stelle in der API Anfrage. Dabei verwenden wir eine URL spezifische Umwandlung von Sonderzeichen.

Als Resultat erhalten wir in jeder Zeile eine Antwort im so genannten JSON Format, welches hier gekürzt dargestellt wird.

{
  "type": "standard",
  "title": "Kaltenbach (Malsburg-Marzell)",
  "wikibase_item": "Q65183925",
  "lang": "de",
  "coordinates": {
    "lat": 47.75388981,
    "lon": 7.72699657
  },
  "extract": "Kaltenbach ist ein Wohnplatz auf 730 m ü. NN, der zur Gemeinde Malsburg-Marzell im Landkreis Lörrach gehört. Die Gemarkungsfläche umfasst 450 Hektar.",
  ...
}

Da wir nur relativ wenig Datenpunkte aus dem JSON extrahieren wollen, machen wir das spaltenweise über “Wikipedia JSON" "Edit column" "Add column based on this column…”.

Für die geographische Breite (lat) benötigen wir den folgenden GREL Ausdruck.

parseJson(value)["coordinates"]["lat"]

Für die geographische Breite (lng) benötigen wir den folgenden GREL Ausdruck.

parseJson(value)["coordinates"]["lon"]

Für die Wikidata QID benötigen wir den folgenden GREL Ausdruck.

parseJson(value)["wikibase_item"]

GeoNames API

Für GeoNames gibt es anders als für die Wikimedia REST API (noch) keine OpenAPI Spezifikation. Hier beschränken wir uns darauf basierend auf der GeoNames ID die zugehörigen Geokoordinaten von dem Service abzurufen. Hierfür benötigen wir einen Account bei GeoNames, da der Benutzername ein Bestandteil der Anfrage ist.

Zuerst filtern wir Zeilen mit GeoNames IDs via “GeoNames" "Facet" "Customized facets" "Facet by blank (null or empty string)…” . Anschließend fügend wir eine neue Spalte hinzu via “GeoNames" "Edit column" "Add column by fetching URLs…” , die wir “GeoNames JSON” nennen.

In dem GREL Ausdruck für die URL setzen wir dann die GeoNames ID an die vorgesehene Stelle ein. Diese brauchen wir nicht explizit für URLs aufzubereiten, da sie nur aus Zahlen besteht.

Nicht vergessen den Parameter USERNAME mit dem eigenen Benutzernamen zu ersetzen.

"http://api.geonames.org/getJSON?formatted=true&geonameId="+value+"&username=USERNAME&style=short"

Die JSON kodierte Antwort sieht gekürzt und umsortiert dann etwa wie folgt aus.

{
  "bbox": {
    "east": 7.737455174771594,
    "south": 47.74660464350438,
    "north": 47.76459194100723,
    "west": 7.710695459994031,
    "accuracyLevel": 0
  },
  "lat": "47.7556",
  "lng": "7.72408",
  "name": "Kaltenbach",
  "adminName4": "Malsburg-Marzell",
  "adminName3": "Landkreis Lörrach",
  "adminName2": "Freiburg Region",
  "adminName1": "Baden-Wurttemberg",
  "countryName": "Germany",
  "continentCode": "EU",
  ...
}

Neben dem Längen- und Breitengrad erhalten wir zusätzlich auch ein minimal umgebendes Rechteck (engl. “bounding box”) und weitere Informationen zu dem angefragten Ort.

Um die Längen- und Breitengrade in separate Spalten zu überführen, verwenden wir wieder das Spaltenmenü “GeoNames JSON" "Edit column" "Add column based on this column…”.

Für die geographische Breite (lat) benötigen wir den folgenden GREL Ausdruck.

parseJson(value)["lat"]

Für die geographische Breite (lng) benötigen wir den folgenden GREL Ausdruck.

parseJson(value)["lng"]

GeoNames bietet noch deutlich mehr WebServices an um zum Beispiel Orte via Postleitzahlen oder Geokoordinaten zu finden. Es sind jedoch nicht alle Services kostenfrei, es stehen teilweise Daten nur für einzelne Länder zur Verfügung und teilweise sind die Services auch nur aus den USA nutzbar.

OpenStreetMap API

Das Projekt OpenStreetMap sammelt mit Freiwilligen weltweit Geoinformationen, die unter freien Lizenzen weitergenutzt werden können. Hierfür gibt es unterschiedliche APIs um auf die Daten zuzugreifen.

Zwei dieser APIs verwenden wir in diesem Tutorial, um Geokoordinaten für Orte abzurufen, ohne sie davor mit Normdaten abzugleichen.

Da OpenStreetMap teilweise sehr genaue Daten hat, können wir darüber auch deutlich genauere Geokoordinaten z.B. auf Gebäude- oder Straßenebene abrufen. Das sind Informationen, die aus Relevanzgründen in den bisher besprochenen Datenquellen nicht erfasst sind.

Die Arbeit mit den Ergebnissen dieser Suchdienste in OpenRefine ist jedoch nicht ganz so komfortabel, wie mit der Reconciliation Service API.

Photon

Photon ist ein Service von der Outdoor-Routenplanung-Plattform Komoot. Der Service wird als OpenSource Projekt auf GitHub zur Verfügung gestellt. Das bedeutet, dass man sowohl die öffentliche API unter Photon nutzen, als auch diesen Dienst intern selbst betreiben kann. Das ist gerade für größere Projekte interessant, bei denen es schwer fällt die fair use Bedingungen von öffentlichen APIs einzuhalten.

Die Datenbasis ist hier die gleiche, wie für die Nominatim Schnittstelle, die wir uns im nächsten Abschnitt ansehen.

In Photon können wir die Daten semi-strukturiert eingeben und auf Basis von Layern einschränken. Dafür laden wir die Daten zeilenweise via “Ort" "Edit column" "Add column by fetching URLs…” in die neue Spalte “Photon JSON” mit dem folgenden GREL Ausdruck.

"https://photon.komoot.io/api?q="+escape(value+", Baden-Württemberg", "url")+"&layer=city&layer=district&lang=de&limit=3"

Zusätzlich zum Ortsnamen (value) ergänzen wir noch den Begriff “Baden-Württemberg”, um die Suchergebnisse besser einzuschränken. Hier könnten wir auch noch mehr Daten einbinden, wie Landkreise usw., wenn wir diese Daten denn vorliegen haben.

Die Rückgabedaten erhalten wir wieder im JSON Format, wie im folgenden Beispiel verkürzt dargestellt.

{
  "features":[
    {
      "geometry":{
        "coordinates":[
          8.239959,
          48.7610716
        ],
        "type":"Point"
      },
      "type":"Feature",
      "properties":{
        "name":"Baden-Baden",
        "state":"Baden-Württemberg",
        "country":"Deutschland",
        "type":"city",
        ...
      }
    },
    {
      "geometry":{
        "coordinates":[
          7.7255337,
          47.7549089
        ],
        "type":"Point"
      },
      "type":"Feature",
      "properties":{
        "name":"Kaltenbach",
        "city":"Malsburg-Marzell",
        "county":"Landkreis Lörrach",
        "state":"Baden-Württemberg",
        "country":"Deutschland",
        "type":"district",
        ...
      }
    },
  ...
  ],
  "type":"FeatureCollection"
}

Anders als bei dem Beispiel mit GeoNames erhalten wir mehrere Ergebnisse, wo wir uns zuerst noch für das passende Ergebnis entscheiden sollten. Dafür nutzen wir das erworbene Wissen aus 10 Arbeiten mit Records, so dass jedes Ergebnis in einer eigenen Zeile steht. Das funktioniert über das Spaltenmenü “Photon JSON" "Edit cells" "Transform…” mit dem folgenden GREL Ausdruck.

forEach(parseJson(value)["features"],v, v).join("||")

Anschließend erzeugen wir die Record-Struktur über “Photon JSON" "Edit cells" "Split multi-valued cells…” mit dem Trennzeichen ||.

Um die Ergebnisse im JSON Format besser lesbar zu machen, formatieren wir die Spalte noch via “Photon JSON" "Edit cells" "Transform…” und verwenden diesmal Python anstatt GREL um das JSON lesbar zu formatieren.

import json
return json.dumps(json.loads(value), indent=4)

Jetzt können wir die einzelnen Zeilen mit den passenden Ergebnissen zum Beispiel mit Sternchen markieren und anschließend filtern. Wir können uns bei der Auswahl aber auch von OpenRefine etwas helfen lassen.

Zum Beispiel können wir via “Photon JSON" "Edit column" "Add column based on this column…”. eine neue Spalte mit dem Namen “Photon Match” hinzufügen.

Im GREL Ausdruck greifen wir auf Funktionen zurück, die wir auch im Teil zu Clustering mit OpenRefine besprochen haben. Hier vergleichen wir konkret den Namen des Ortes mit der Eigenschaft name in JSON, mit den Methoden Cologne Phonetic und Fingerprint. Sollte der Vergleich einer der beiden Methoden übereinstimmen, wird true in der neuen Spalte gespeichert. Das können wir dann wieder zum Filtern von (möglicherweise) passenden Ergebnissen nutzen.

with(row.record.cells["Ort"][0].value, ort,
  with(parseJson(value)["properties"]["name"], json_name,
    or(
      phonetic(ort, "cologne") == phonetic(json_name, "cologne"),
      fingerprint(ort) == fingerprint(json_name))))

Die Geokoordinaten selbst extrahieren wir wieder über das Spaltenmenü “Photon JSON" "Edit column" "Add column based on this column…”.

Für die geographische Breite (lat) benötigen wir den folgenden GREL Ausdruck.

parseJson[value]["geometry"]["coordinates"](1)

Für die geographische Breite (lng) benötigen wir den folgenden GREL Ausdruck.

parseJson[value]["geometry"]["coordinates"](0)

Generell ist die Photon API durch die relativ freie Textsuche recht einfach zu benutzen. Es gibt aber auch schnell Falsch-Positive, weil z.B. der zusätzliche Suchbegriff “Baden-Württemberg” zu einem hohen Ranking der Orte “Baden-Baden” und “Badenweiler” führt.

Nominatim

Nominatim ist die offizielle OpenSource Suchmaschine zur Arbeit mit Daten in OpenStreetMap. Für unseren Anwendungsfall verwenden wir den Search Endpunkt der Nominatim API. Im Gegensatz zu der recht einfach gehaltenen Photon API, können wir die Daten bei Nominatim auch strukturiert übermitteln. Außerdem kann zwischen verschiedenen Rückgabeformaten gewählt werden.

Wir laden die Daten zeilenweise via “Ort" "Edit column" "Add column by fetching URLs…” in die neue Spalte “Nominatim JSON” mit dem folgenden GREL Ausdruck.

"https://nominatim.openstreetmap.org/search?city="+escape(value, "url")+"&state="+escape("Baden-Württemberg", "url")+"&countrycodes=de&featureType=city,settlement&format=jsonv2&limit=3&email=YOUR_MAIL_ADDRESS"

Hier nicht vergessen am Ende den Platzhalter durch die eigene E-Mailadresse zu ersetzen.

Das im folgenden gezeigte und gekürzte Ergebnis ist etwas weniger strukturiert wie das von Photon, was die Aufarbeitung jedoch vereinfacht.

[
  {
    "name":"Kaltenbach",
    "display_name":"Kaltenbach, Malsburg-Marzell, Verwaltungsgemeinschaft Kandern, Landkreis Lörrach, Baden-Württemberg, 79429, Deutschland",
    "type":"village",
    "lat":"47.7549089",
    "lon":"7.7255337",
    "licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright",
    ...
  },
  {
    "name":"Kaltenbach",
    "display_name":"Kaltenbach, Forbach, Landkreis Rastatt, Baden-Württemberg, Deutschland",
    "type":"isolated_dwelling",
    "lat":"48.6319441",
    "lon":"8.357038733877207",
    "licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright",
    ...
  },
  ...
]

Wie bei der Arbeit mit den Ergebnissen von Photon arbeiten wir wieder mit Records, so dass jedes Ergebnis in einer eigenen Zeile steht. Das funktioniert über das Spaltenmenü “Nominatim JSON" "Edit cells" "Transform…” mit dem folgenden GREL Ausdruck.

forEach(parseJson(value),v, v).join("||")

Anschließend erzeugen wir die Record-Struktur über “Nominatim JSON" "Edit cells" "Split multi-valued cells…” mit dem Trennzeichen ||.

Um die Ergebnisse im JSON Format besser lesbar zu machen, formatieren wir die Spalte wieder via “Nominatim JSON" "Edit cells" "Transform…” und verwenden wieder Python anstatt GREL um das JSON lesbar zu formatieren.

import json
return json.dumps(json.loads(value), indent=4)

Nachdem wir die passenden Ergebnisse ausgewählt und gefiltert haben, extrahieren wir die Geokoordinaten wieder über das Spaltenmenü “Nominatim JSON" "Edit column" "Add column based on this column…”.

Für die geographische Breite (lat) benötigen wir den folgenden GREL Ausdruck.

parseJson[value]("lat")

Für die geographische Breite (lng) benötigen wir den folgenden GREL Ausdruck.

parseJson[value]("lon")

Hat man die Daten schon einigermaßen strukturiert vorliegen, dann liefert die Nominatim API in unseren Experimenten passendere Ergebnisse als Photon. Wir haben hier aber auch nur einen kleinen Teil der API getestet und genutzt.

Fazit

Arbeitet man sowieso mit Normdaten, so lassen sich je nach Datenlage Geokoordinaten mit OpenRefine aus der genutzten oder einer verknüpften Normdatenquelle über die Reconciliation Service API nachladen. In einigen Fällen kann man die Ergebnisse mit einfachen API Anfragen an Wikipedia oder GeoNames vervollständigen.

Möchte man genauere Geokoordinaten zum Beispiel auf Adressebene, dann lohnt sich ein Blick auf die OpenStreepMap APIs.

Es gibt noch viele andere Geocoding APIs von hauptsächlich kommerziellen Anbietern mit einem bestimmten Freikontingent an Anfragen. Diese können mit OpenRefine ähnlich genutzt werden, wie bei den OpenStreetMap APIs beschrieben.

Ein ähnliches Projekt zur Arbeit mit OpenRefine und WebAPIs haben wir bei Entfernungen zu GPX Tracks mit OpenRefine ermitteln dokumentiert.

Lizenzhinweise

Dieser Artikel und die Bildschirmfotos enthalten Informationen aus der GeoNames geographische Datenbank, welche verfügbar ist unter der CC BY 4.0 Lizenz.

Dieser Artikel und die Bildschirmfotos enthalten Informationen aus OpenStreetMap, welche verfügbar sind unter der ODbL 1.0.

Dieser Artikel und die Bildschirmfotos enthalten Informationen aus dem Thesaurus of Geographic Names (TGN) ®, welcher verfügbar ist unter der ODC Attribution License v1.0.

Dieser Artikel und die Bildschirmfotos enthalten Informationen aus Wikipedia, welche verfügbar sind unter der CC BY SA 4.0 Lizenz.

Benjamin Rosemann
Benjamin Rosemann
Data Scientist

Ich evaluiere KI- und Software-Lösungen und integriere sie in den Archivalltag.

Ähnliches