Daniel Hundt - Erfahrung Technologien im Backend

zurück zur Technologie-Übersicht

Offene Geodaten nutzen - OpenStreetMap

Inhalt

Für einige andere Projekte habe ich mich bereits mit Geodaten beschäftig. Anbieter der Daten (Karte, Straßenverzeichnis, PLZ-Verzeichnis, POIs) waren dabei meist kommerzielle Anbieter, die (wenn überhaupt) nur innerhalb eines engen Rahmens kostenlos nutzbar waren und dann aber schnell sehr teuer werden (Google, Bing, Nokia, DataFactory usw.).

Immer wieder tauchte die Alternative OpenStreetMap auf, was dazu führte, dass ich mir das ganze mal etwas näher angesehen habe.

OpenStreetMap.org ist ein im Jahre 2004 gegründetes internationales Projekt mit dem Ziel, eine freie Weltkarte zu erschaffen. Dafür sammeln wir weltweit Daten über Straßen, Eisenbahnen, Flüsse, Wälder, Häuser und vieles mehr.

http://www.openstreetmap.de/

Datenquelle: Planet-File

OpenStreetMap selbst biete nur einen kompletten Datenbank-Dump des gesamten Planeten an. Das globale "Planet-File" hat derzeit im komprimierten (!) XML-Format eine Größe von 43 GB.
Da mir das für den Anfang etwas zu groß war, hab ich erstmal nur mit dem Datenbank-Ausschnitt von Deutschland gearbeitet. Die entsprechende XML-Datei kann z.B. auf GeoFabrik.de heruntergeladen werden. Die Dateigröße des komprimierten XMLs ist mit "überblickbaren" 2,5 GB angegeben. Ausgepackt ergibt dies jedoch eine Dateigröße von doch schon ansehnlichen 46,2 GB!

XML-Struktur

Im XML selbst liegt folgende relevante Struktur vor:

<osm ...>
	<!-- Node ohne Tags -->
	<node id="[long]" lat="[double]" lon="[double]" version="[int]" timestamp="[datetime]" ... />
	<!-- Node mit Tags -->
	<node id="[long]" lat="[double]" lon="[double]" version="[int]" timestamp="[datetime]" ... >
		<tag k="[string]" v="[string]" />
		<tag k="[string]" v="[string]" />
		...
	</node>
	
	<way id="[long]" version="[int]" timestamp="[datetime]" ... >
		<nd ref="[long]" />
		<nd ref="[long]" />
		...
		<tag k="[string]" v="[string]" />
		<tag k="[string]" v="[string]" />
		...
	</way>
	
	<relation id="[long]" version="[int]" timestamp="[datetime]" ... >
		<member type="node" ref="[long]" />
		<member type="way" ref="[long]" />
		...
		<tag k="[string]" v="[string]" />
		<tag k="[string]" v="[string]" />
		...
	</way>
</osm>

Beispiel-XML

Hier die Struktur mit ein paar Beispieldaten:

<node id="298884269" lat="54.0901746" lon="12.2482632" version="1" timestamp="2008-09-21T21:37:45Z" ... />
<node id="1831881213" version="1" lat="54.0900666" lon="12.2539381" timestamp="2012-07-20T09:43:19Z" ...>
	<tag k="name" v="Neu Broderstorf"/>
	<tag k="traffic_sign" v="city_limit"/>
</node>

<way id="26659127" version="5" timestamp="2010-03-16T11:47:08Z">
	<nd ref="298884269"/>
	<nd ref="1831881213"/>
	<tag k="highway" v="unclassified"/>
	<tag k="name" v="Pastower Straße"/>
</way>

<relation id="56688" version="28" timestamp="2011-01-12T14:23:49Z">
	<member type="node" ref="298884269" role=""/>
	<member type="node" ref="1831881213" role=""/>
	<member type="way" ref="26659127" role=""/>

	<tag k="name" v="Küstenbus Linie 123"/>
	<tag k="network" v="VVW"/>
	<tag k="operator" v="Regionalverkehr Küste"/>
	<tag k="ref" v="123"/>
	<tag k="route" v="bus"/>
	<tag k="type" v="route"/>
</relation>
Neben den genannten Attribute gibt es noch weitere Attribute wie user, uid, visible und changeset, die ich allerdings für meine Anwendungsfälle erstmal gekonnt ignoriert habe ;-)
version und timestamp sind nur enthalten, damit später eine einfacheres Update der Datensätze möglich ist.
Die Reihenfolge in "way" und "relation" ist entscheidend um ein Polygon zu zeichnen oder die Streckenführung nachzuvollziehen.

Import mit Java

Zum Import der Daten in die SQL-Datenbank habe ich ein kleines Java-Programm mit dem SAX-Parser und dem Insert via "Batch Prepared Statement" geschrieben.

Warum SAX?

Da eine Datenmenge von über 46 GB nicht komplett in den Arbeitsspeicher passt (zumindest auf meinem Rechner derzeit hier), konnte nur ein Tag-Weises parsen des XMLs ("nach und nach") erfolgen. Es gibt sicherlich noch andere (vielleicht auch bessere?) Bibliotheken um das zu lösen, Google hat allerdings (neben den OSM-eigenen) SAX empfolen.

Warum Insert via "Batch Prepared Statement"?

Der MySQL-Connector von Java erlaubt das Einfügen via Prepared Statement. Das hat den Vorteil, dass das DBMS die (interne) Insert-Strategie nur einmal ermitteln muss und anschließend cachen kann. Bei Milliarden (!) an Insert-Operationen sicherlich nicht verkehrt.
Die erste Programm-Version hat noch jedes XML-Tag einzeln in die Datenbank eingefügt. Der Durchsatz und die Auslastung war dabei sehr gering. In der nachfolgenden, weiter optimierten Version ist es so, dass erst einige Datensätze gesammelt werden (10.000 - 100.000) und diese dann in Blöcken in die Datenbank geschrieben werden. Dadurch war ein direkter Geschwindigkeitsvorteil festzustellen, wenn auch die Auslastung von Java und MySQL nicht 100% beträgt und das Ganze eine Ewigkeit dauert.

Mehrere Threads für höheren Durchsatz

Beim Ausführen der oben genannten Version fiel auf, dass abwechselnd Java (XML parsen und Datensätze sammeln) und MySQL (Daten einfügen) CPU-Last erzeugt haben - allerdings weiterhin zusammen gerade einmal ca. 30%. Da sowohl die Daten im XML als auch die Tabellen in der Datenbank relativ unabhängig voneinander sind, können "nodes", "ways" und "relations" auch unabhängig importiert werden. In der aktuellen Version werden daher 3 Threads erzeugt, die das XML parallel abarbeiten (höhere Belastung der Festplatte).

Das aktuelle Import-Programm kann hier runtergeladen werden.

Datenbank Struktur

Datenbankschema - vom XML
Import-Datenbankschema - vom XML direkt in die DB

Das Datenbank-Schema ist im ersten Schritt erstmal sehr stark an das XML-Format angelehnt. In den nachfolgenden Schritten können dann weitere Optimierungen (Normalisierung) stattfinden und es kann auf Strukturen umgeformt werden, die auf die Anwendungsfälle zugeschnitten sind. Außerdem können Daten gelöscht werden, die nicht benötigt werden.

Wie gehts weiter?

Wenn die Daten erstmal vollständig in der Datenbank sind (dauert leider noch...) können diese (hoffentlich) auch einfacher ausgewertet werden. Dabei schwebt mir u.a. noch folgendes vor:

  • Zuordnung von PLZ zu Ort (und umgedreht)
  • Straßenverzeichnis für Ort/PLZ
  • Hausnummern-genaue Verortung (Koordinaten ermitteln)
  • Kategorisierter POI-Katalog (Touristen-Attraktionen, Bauwerke, Supermärkte, Sportstätten, Tankstellen usw.)
  • schnelle Bereitstellung der Daten für Kartenausschnitt (z.B. via Solr-Index)

Stay tuned!