Gemeindegrenzen aus Shapefile auf OSM-Karte darstellen

Shapefile auf OSM-Karte darstellen

In meinem ersten Tutorial auf diesem Blog will ich darstellen, wie man Gemeindegrenzen auf einer Auf Open Street Map basierenden Karte darstellen kann. Die Gemeindegrenzen (in diesem Fall NRW) selbst nehme ich dabei nicht direkt von OSM, sondern aus einem Shapefile, das ich in diesem Fall von der Seite zur Landtagswahl des Innenministeriums herunterlade. Als Grundlage für die OSM-Darstellung nehme ich die JavaScript-Library leaflet.js.

Shapefile in GeoJSON überführen

Die Hauptaufgabe ist nun, das Shapefile mit den Gemeindegrenzen in das GeoJSON-Format zu überführen, denn dies ist das Format, das leaflet erwartet. Lädt man die Geometrie der Gemeinden (direkter Downloadlink) herunter und entpackt das ZIP-File, so erhält man folgende laut zu einem Shapefile zugehörigen Dateien:

DVG1_Gemeinden_utm.dbf
DVG1_Gemeinden_utm.prj
DVG1_Gemeinden_utm.shp
DVG1_Gemeinden_utm.shx

Wichtig ist dabei im Prinzip nur die Datei mit Endung .shp, da diese die eigentlichen Geometrien enthält.

Um diese Datei in GeoJSON umzuwandeln gibt es zum Glück ein Tool, dass sich ogr2ogr nennt und im GDAL-Paket zu finden ist. Unter OSX kann man dies mit macports wie folgt installieren:

sudo port install gdal +geos

Bevor man die Konvertierung startet, muss man zwei Dinge beachten. Zunächst einmal die Art der Projektion. So gibt es verschiedene Verfahren, die Erdkugel auf eine rechteckige Karte zu projizieren. Das Shapefile benutzt UTM (wie im Dateinamen zu erkennen),  leaflet bzw. OSM mag aber lieber EPSG:3857. Hier muss man also entweder umrechnen oder in leaflet die Projektion umstellen, was mit dem Plugin Proj4Leaflet geht. Ich werde hier aber die Daten schon vorher auf die Ziel-Projektion umrechnen.

Das zweite Problem ist die Datenmenge. Die Definition der Gemeindegrenzen ist nämlich sehr genau, was bedeutet, das die Polygone sehr viele Punkte beinhalten. Dies bedeutet aber, dass die Karte eher behäbig daherkommt. Da es bei der Darstellung aber eher auf die Flächen ankommt und man sowieso nicht bis auf den letzten Stein herunter zoomen kann, können und sollten diese Polygone vereinfacht werden.

Beide Probleme lassen sich glücklicherweise direkt mit ogr2ogr lösen und der zugehörige Aufruf sieht wie folgt aus:

ogr2ogr -f geojson -simplify 20 -t_srs EPSG:4326 -a_srs EPSG:4326 gemeinden.json DVG1_Gemeinden_utm.shp

Die einzelnen Argumente bedeuten dabei folgendes:

  • -f geojson definiert das Zielformat, das wir wünschen
  • -simplify 20 vereinfacht die Polygone. Mit dem Faktor 20 erhält man dabei statt einer 43 MB-Datei eine nur 4,9 MB grosse Datei. Den Wert habe ich dabei durch Ausprobieren ermittelt, indem ich mehrere Werte angegeben habe und auf der Karte geschaut habe, wo die Polygone zu grob wurden.
  • – t_srs EPSG:4326 definiert die Ziel-Projektion
  • -a_srs EPSG:4326 definiert die Ausgabe-Projektion (was genau der Unterschied zu oben ist, ist mir leider nicht so klar, aber so funktioniert es)

Eine Besonderheit von ogr2ogr ist dabei, dass zunächst die Zieldatei angegeben wird und dann erst die Quelldatei. Also aufpassen!

Man beachte auch die Projektion, denn entgegen meiner vorherigen Behauptung mit EPSG:3857 arbeitet leaflet dann wohl doch lieber mit EPSG:4326 (wenn ich das richtig verstehe ist letztere der Koordinatensatz auf einer Kugel, während 3857 die projizierte Version ist. Ob leaflet das dann automatisch umrechnet, ist mir nicht so ganz klar, mit 4326 funktioniert es aber auf jeden Fall).

Hat man nun die Datei, muss man sie nur noch anzeigen. Dazu initialisiert man zunächst leaflet (und jquery gleich mit). Laden wir zunächst die notwendigen Dateien vom CDN:

    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" />
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script src="http://code.jquery.com/jquery-migrate-1.1.1.min.js"></script>
    <script src="http://cdn.leafletjs.com/leaflet-0.5/leaflet.js"></script>

Dann noch ein bisschen CSS für eine Fullscreen-Map:

<style>
     html, body, #map {
         height: 100%;
     }
</style>

Die Karte selbst ist nur ein DIV:

<div id="map"></div>

Dann brauchen wir noch Code zum Initialisieren der Karte grob auf NRW (ausserdem benötigt man noch einen API-Key von cloudmade.com, wobei man zum Experimentieren aber alternativ auf die Tiles von OSM nutzen kann. Die Nutzung ist dort allerdings eingeschränkt):

    <script>
        $(document).ready(function() {
            var map = L.map('map').setView([51.463, 7.18], 10);
            L.tileLayer('http://{s}.tile.cloudmade.com/API-KEY/{z}/{x}/{y}.png', {
                attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery &copy; <a href="http://cloudmade.com">CloudMade</a>',
                    maxZoom: 18
            }).addTo(map);
    </script>

Nun müssen wir nur noch das GeoJSON-Objekt laden und anzeigen. Dies geschieht per getJSON-Aufruf:

$.getJSON("/gemeinden/gemeinden.json", function(data) {
    L.geoJson(data, {
        style: function(feature) {
            switch (feature.properties.KN) {
                default:
                    return {
                        fillColor: '#00'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,2)+"00",
                        fillOpacity: 0.5,
                        weight: 2,
                        color: '#111',
                        opacity: 0.7,
                        dashArray: '4'
                    }
            }
        }
    }).addTo(map);
});

Wichtig ist dabei hauptsächlich der Aufruf von L.geoJSON und der Angabe der Daten. Mit der style-Funktion ist man zusätzlich noch in der Lage einzelne Flächen einzufärben (hier per random-Grün), die Grenzen zu stylen usw. Mehr Informationen erhält man hier.

Ein komplettes Demo gibt es auch!