SQL Server Spatial Data und Mapnik

coverAlastair Aitchison, Autor des Buchs Beginning Spatial with SQL Server 2008, hat im letzten Jahr in seinem Blog eine kleine Blog-Serie gestartet, in der er erklärt, wie man Mapnik zusammen mit dem SQL Server verwenden kann. Mapnik ist ein Open Source Renderer der unter anderem vom freien Geodaten Projekt OpenStreetMap verwendet wird.

Leider gab es zum Zeitpunkt seines ersten Beitrags noch einige Probleme mit der damaligen Mapnik Version unter Windows, wodurch Alastair zu MapServer gewechselt ist.

Da ich den Einstieg äußerst interessant fand und Mapnik in der Version 2.0 eine bessere Unterstützung für Windows und den SQL Server bietet, möchte ich in diesem Blog-Beitrag das Rendern mit der aktuellen Mapnik Version 2.0.1 RC0 dann doch noch einmal detaillierter beschreiben. Der Beitrag ist jedoch auch mehr als Einstieg zu verstehen, viele Tutorials und detaillierte Informationen sind im Mapnik Wiki sehr gut erklärt und würden den Rahmen eines Beitrages erheblich sprengen.

Installation

Die Installation von Mapnik unter Windows ist in der aktuellen Version recht einfach und sehr gut auf der Seite Windows binaries (Release Candidate 0) erklärt. Neben den Windows binaries wird Python in der Version 2.7 (32-Bit) benötigt.

Nachdem beides heruntergeladen und installiert bzw. in das Verzeichnis C:mapnik-2.0.1rc0 entpackt wurde, müssen noch die folgenden Pfad-Angaben hinzugefügt werden:

set PATH=%PATH%;c:mapnik-2.0.1rc0lib
set PYTHONPATH=%PYTHONPATH%;c:mapnik-2.0.1rc0python2.7site-packages
set PATH=%PATH%;c:Python27

Danach sollten sowohl alle Mapnik Bibliotheken sowie die Python Bibliotheken zur Verfügung stehen. Um die Installation zu prüfen, kann am besten die Python Demo rundemo.py aus dem Verzeichnis C:mapnik-2.0.1rc0demopython ausgeführt werden.

Das Python Script sollte dann folgende Grafik in den verschiedensten Versionen generieren:

demo

 

Vorbereiten der Daten

180px-Openstreetmap_logo_svgUm eigene Karten zu Rendern werden natürlich zuerst auch eigene Daten benötigt. Mapnik liefert bereits Demo Daten mit, die in dem Verzeichnis C:mapnik-2.0.1rc0demodata liegen und auf denen auch die entsprechenden Demos basieren.

Ich möchte für diese Demo allerdings OSM Karten verwenden und habe mir dafür die OSM Karten von Köln als ShapeFile heruntergeladen. Die Karten werden täglich neu von der Geofabrik angeboten. In den zur Verfügung gestellt Daten stehen für unterschiedliche Objekte der Karte einzelne ShapeFiles zur Verfügung. So existieren die ShapeFiles Buildings, Landuse, Natural, Places, Points, Railways, Roads und Waterways.

Um die ShapeFiles in den SQL Server zu laden, können entweder die SQL Server 2008 Spatial Tools von Morten Nielsen verwendet werden, oder meine SSIS ShapeFileSource von Codeplex. Die Spatial Tools sind normalerweise die einfacher Lösung, da hier die Daten ohne weitere Schritte in den SQL Server importiert werden können. Aus einem mir derzeit nicht bekannten Grund funktionieren die Tools aber nicht immer mit alle ShapeFiles und/oder Zielen, weswegen ich in diesem Fall auf meinen SSIS Task zurückgreife…was mir im Endeffekt dann doch auch mehr Spaß macht!

 

image

Ein Beispiel SSIS Paket kann hier heruntergeladen werden: Cologne.dtsx

 

Das Python-Scripts

python-logoUm auf den SQL Server zuzugreifen werden in Mapnik sogenannte Layer verwendet, die über OGR Library und den XML basierten Treiber OGR Virtual Format dargestellt werden. Für jede Abfrage muss ein eigener Layer in Form einer  Datei (OVF) erstellt werden. Für die Demo-Karte werde ich mich hier nur auf Strassen und Buildings konzentrieren.

Neben dem Connection String wird in einer OVF Datei auch der SQL Befehl sowie verschiedene Zuordnung definiert. Zum einen muss die Spalte mit den Geodaten als GeometryField angegeben werden, zum anderen müssen die Felder, die später im Python Script verwendet werden definiert werden. Da Mapnik nicht direkt mit den Geometry Daten des SQL Server umgehen kann, werden die Daten als Text geladen und entsprechend als WKT definiert.

   1: <OGRVRTDataSource>

   2:

   3:     <OGRVRTLayer name="Roads">

   4:

   5:         <SrcDataSource>MSSQL:server=localhost;Initial Catalog=Cologne;Integrated Security=SSPI;</SrcDataSource>

   6:         <SrcLayer>Roads</SrcLayer>

   7:         <SrcSQL>SELECT [name], [type], [ref], [points].STAsText() AS geom FROM [Cologne].[dbo].[Roads]</SrcSQL>

   8:         <GeometryField encoding="WKT" field="geom"/>

   9:         <Field name="name" type="String" src="name" />

  10:         <Field name="type" type="String" src="type" />

  11:         <Field name="ref" type="String" src="ref" />

  12:

  13:     </OGRVRTLayer>

  14:

  15: </OGRVRTDataSource>

 

Nachdem die einzelnen OVF-Dateien für alle zu verwendeten Layer erstellt worden sind, kann das eigentliche Python Script erstellt werden.

Ein sehr gutes Tutorial für die ersten Gehversuche ist Getting started in Python aus dem Mapnik Wiki. Anstatt jedoch wie in dem Tutorial beschrieben direkt auf das ShapeFile zuzugreifen

ds = mapnik.Shapefile(file=’ne_110m_admin_0_countries.shp’)

muss die Datenquelle hier auf die OVF Datei verweisen

ds = mapnik.Ogr(file=’roads.ovf’,layer=”roads”)

Wobei roads.ovf die entsprechende Datei ist und roads der darin definierte Layer.

Das Zeichnen der einzelnen Objekte ist eigentlich nicht sonderlich schwierig, eine genaue Erläuterung sprengt dann aber doch wie bereits erwähnt diesen Blog-Beitrag. Wer sich intensiver mit dem Zeichnen einzelner Objekte beschäftigen möchte, sollte sich unbedingt auch die Beispiel-Dateien aus dem Mapnik Installations-Verzeichnis anschauen.

Ich möchte allerdings noch auf 2 Punkte hinweisen, die mir beim erstellen einer Karte als wichtig erschienen sind.

Layer

Mapnik arbeitet grundlegend mit Layern. Das heißt alle Objekte werden als Layer über einander gelegt und “überzeichnen” damit auch den drunter liegenden Layer. Hierdurch muss man sich die Reihenfolge der Layer recht genau überlegen.

Liegen zum Beispiel kleinere Straßen über größere Straßen, werden diese durch die größere Straße durchgezeichnet, dies sieht in einer Karte recht unschön aus.

Möchte man eine Straße zweifarbig bzw. mit einem Rahmen darstellen, wie es bei größeren Straßen häufig der Fall ist, so macht man sich dieses Verhalten jedoch zu nutzen. Hier wird zuerst z.B. eine dickere Straße in grau gezeichnet und danach die gleiche Straße dünner in Gelb. Diese Darstellung lässt sich in der obigen Beispiel Karte bei dem gelben Hauptstraßen recht gut erkennen.

Filter

In der OVF Datei wird eine Tabelle oder Abfrage definiert um die Daten zu Laden, die in der Karte dargestellt werden sollen. In den OSM Daten haben Straßen einen Typ, der definiert ob es sich um eine Autobahn, eine Hauptstraße, einen Fußgängerweg usw. handelt. Sollen diese Straßen in unterschiedlichen Farben in der Karte dargestellt werden, so sollte man jedoch nicht für jeden Straßentyp eine eigene OVF Datei erstellen. Innerhalb des Python Scripts kann ein Filter für einen einzelnen Layer definiert werden, der an dieser Stelle besser verwendet werden sollte.

roads1_rule_1.filter = mapnik.Expression(“[type] = ‘primary'”)

Durch diesen Filter werden z.B. nur die Daten im Layer dargestellt, die dem Filter [type] = ‘primary’ entsprechen. So können Layer für alle verschiedenen Straßentypen auf Basis einer Datenquelle erstellt werden. Wichtig, das Feld, auf das durch den Filter zugegriffen werden soll, muss zuvor in der OVF Datei über den Parameter Field definiert werden.

 

image

 

Wie man an der obigen Karte sieht, lassen sich so sehr nette Karten erzeugen. Die genaue Farbgestaltung, die Filterung der einzelnen Straßen, Gebäude, Plätze usw. ist jedoch eine ziemliche Detailarbeit. Das Zeichnen einer Karte mit vielen Layern kann schon ein paar Minuten dauern; bei einer zu großen Karte (Abmessung und Anzahl der Layer) kam es auf meinem Notebook auch zu Speicherproblemen.

Leider konnte ich in der OVF Datei in den Abfragen keine < oder > Zeichen verwenden. Um nur Daten zu selektieren die in einem definierten Umkreis eines Objektes, hier das Odysseum in Köln, liegen, musste ich also jeweils eine Sicht für Roads und Buildings erstellen.

   1: SELECT     dbo.Buildings.*

   2: FROM         dbo.Buildings

   3: WHERE Points.STDistance(

   4:     (SELECT POINTS FROM Buildings WHERE [Name] LIKE '%Odysseum%')

   5:     ) < 0.005

 

Anstatt der bisherigen Tabelle Roads greife ich über die OVF Datei dann auf die entsprechende Sicht zu. So entsteht zum Beispiel die folgende – noch nicht wirklich hübsche – Karte.

 

image