Shapefiles – von Lawinen und Steinböcken

  • Pascal Thormeier

Shapefiles bieten eine hervorragende Möglichkeit, um raumbezogene Geodaten wie GrundstĂŒcke, Gemeindegrenzen, FlĂŒsse, Seen oder Strassen zusammen mit Metadaten wie Namen, IDs, GrĂŒndungsjahre usw. zu speichern.

Mehr zu unserem Open Data service.

Bei Liip haben wir vor Kurzem eine Anwendung fĂŒr Architekturstudent*innen fertiggestellt, die Shapefiles als Datengrundlage verwendet, um Informationen zu BaugrundstĂŒcken in der Stadt ZĂŒrich zu liefern. Die Informationen werden auf einer Karte angezeigt und können von den Benutzer*innen eingesehen werden. Ein Ă€hnliches Projekt wurde von einem studentischen Team, zu dem ich gehörte, an der Hochschule fĂŒr Technik der FHNW durchgefĂŒhrt: eine lustige, benutzerfreundliche Webanwendung, mit der Primarschulkinder etwas ĂŒber ihre Gemeinde lernen können.

Shapefiles tragen nicht nur dazu bei, die User Experience zu verbessern, sondern beinhalten auch Daten, die, wenn man sie miteinander in Zusammenhang bringt, interessante Erkenntnisse liefern. Schauen wir also mal, was ich herausfinde, wenn ich eine kleine App entwickle, um damit ein paar Shapefiles genauer unter die Lupe zu nehmen.
In diesem Blogbeitrag stelle ich einen praktischen Ansatz zur Arbeit mit Shapefiles vor und zeige euch, wie ihr auch selbst damit arbeiten könnt!

Alpenkonvention

Das erste Hindernis bei der Arbeit mit Shapefiles besteht darin, zu verstehen, was Shapefiles eigentlich sind, und sie ĂŒberhaupt erst zu erwerben.

Shapefiles sind BinĂ€rdateien, die Geodaten enthalten. Sie bestehen in der Regel aus Gruppen von mindestens drei verschiedenen Dateien, die alle denselben Namen haben und sich nur durch ihre Dateiendung unterscheiden: .shp, .shx und .dbf. Ein vollstĂ€ndiges Datenset kann jedoch viel mehr Dateien enthalten. Das Datenformat wurde von Esri entwickelt und in den frĂŒhen 1990er Jahren eingefĂŒhrt. Shapefiles enthalten sogenannte Features – eine geometrische Form mit den dazugehörigen Metadaten. Ein Feature könnte ein Punkt (einzelne XY-Koordinaten) und ein Text sein, der diesen Punkt beschreibt. Oder ein Polygon, das aus mehreren XY-Punkten besteht, eine einzelne Linie, eine Multi-Linie usw.

Um an einige Shapefiles fĂŒr die Arbeit zu kommen, werfe ich einen Blick auf OpenData.swiss. OpenData stellt insgesamt 102 Shapefile-Datenssets (Stand: November 2017) zur VerfĂŒgung. Ich muss sie nur herunterladen und kann sofort anfangen, mit ihnen zu arbeiten. FĂŒr dieses Beispiel habe ich ein eher kleines Shapefile ausgewĂ€hlt: Alpenkonvention enthĂ€lt die Perimeter der Alpenkonvention in der Schweiz.

Da es sich hierbei um BinÀrdateien handelt, die ich mir nicht in einem Texteditor ansehen kann, brauche ich ein Tool, das mir zeigt, was ich gerade heruntergeladen habe.
QGIS – ein freies Open-Source-Geoinformationssystem
QGIS ist eine umfassende Lösung fĂŒr alle Arten von Geoinformationssystemen (GIS). Es kann gratis heruntergeladen und installiert werden und verfĂŒgt ausserdem ĂŒber zahlreiche Plugins sowie eine aktive Community. Ich werde es nun nutzen, um einen Blick auf mein zuvor heruntergeladenes Shapefile zu werfen.

FĂŒr dieses Beispiel habe ich QGIS mit dem OpenLayers-Plugin installiert. So sieht es aus, nachdem es gestartet wurde:

Um zu sehen, wo ich gerade bin und wo meine Daten hingehören, fĂŒge ich OpenStreetMap als Layer hinzu. Ausserdem verschiebe und zoome ich die Karte, damit ich die Schweiz im Blick habe.

Der nĂ€chste Schritt besteht darin, das Shapefile zu öffnen, damit QGIS es ĂŒber der Karte anzeigen kann. Dazu reicht ein Doppelklick auf die .shp-Datei im Dateibrowser auf der linken Seite.

In dieser Ansicht erhalte ich bereits einige Informationen ĂŒber das verwendete Shapefile, ĂŒber das Gebiet, das es abdeckt, und ĂŒber die Anzahl der darin enthaltenen Features. Das Shapefile der Alpenkonvention scheint aus lediglich einem einzigen Feature – einem grossen Polygon – zu bestehen, was ausreichend ist, da es nur den Perimeter der Alpenkonvention in der Schweiz abbildet.

Um zu sehen, welche Bereiche und/oder Einzelheiten es abdeckt, kann ich den Stil des Layers Àndern und auf transparent umstellen. Ausserdem zoome ich nÀher heran, um zu sehen, wie prÀzis die Form definiert ist. Seine Kanten sollten sich genau mit den Schweizer Grenzen decken.

Wunderbar. Nachdem ich mir die Form des Features angesehen habe, wende ich mich nun den Metadaten des Shapefiles zu. Diese können in der Attributtabelle des Shapefiles im Browser unten links eingesehen werden.

Auch diese Datei enthÀlt nicht viele Metadaten, aber jetzt sehe ich, dass es darin zwei Features gibt. Wie dem auch sein, es gibt wohl noch mehr interessante Shapefiles, mit denen man arbeiten kann. Aber ich werde beim Thema Alpen bleiben.

Von Lawinen und Steinböcken

Beim weiteren Durchstöbern der Shapefiles von OpenData bin ich auf zwei weitere Shapefiles gestossen, die möglicherweise interessante Daten liefern könnten. Die Verbreitung der Steinbockkolonien und der Stand der Naturgefahrenkartierung in den Gemeinden bezüglich Lawinen.

So genial QGIS fĂŒr die Analyse der vorliegenden Daten auch ist, ich möchte meinen eigenen Explorer erstellen und das vielleicht sogar auf Basis mehrerer Shapefiles und mit meinem eigenen Design. Dazu gibt es verschiedene Bibliotheken in den unterschiedlichsten Sprachen. Im Bereich Webentwicklung wĂ€ren die drei interessantesten: PHP, https://pypi.python.org/pypi/pyshp teext: Python und JavaScript.

FĂŒr mein Beispiel werde ich eine kleine Frontend-Anwendung in JavaScript entwickeln, um so meine drei Shapefiles anzuzeigen: die Alpenkonvention, die Steinbockkolonien und die Lawinenkartierung. DafĂŒr verwende ich ein JavaScript-Paket, das ganz einfach Shapefile heisst, sowie Leafletjs, um die Polygone aus den Features anzuzeigen. Aber immer eins nach dem anderen.

Auslesen der Features

Disclaimer: Die folgenden Codebeispiele verwenden ES6 und setzen ein Webpack/Babel-Setup voraus. Man kann sie nicht einfach «copy-pasten», aber sie zeigen auf, wie man mit den zur VerfĂŒgung stehenden Tools arbeiten kann.

Zuerst versuche ich das Shapefile der Alpenkonvention zu laden und in die Konsole zu importieren. Das oben erwÀhnte Shapefile-Paket enthÀlt zwei Beispiele in der README-Datei, weshalb ich einfachheitshalber folgenden Ansatz anwende:

import { open } from 'shapefile'

open('assets/shapefiles/alpine-convention/shapefile.shp').then(source => {
  source.read().then(function apply (result) { // Read feature from the shapefile
    if (result.done) { // If there's no features left, abort
      return
    }

    console.log(result)

    return source.read().then(apply) // Iterate over result
  })
})

Klappt wunderbar! Das Ergebnis sind die beiden Features des Shapefiles in der Konsole. Was ich hier bereits sehen kann, ist, dass die Eigenschaften jedes Features, die normalerweise in separaten Dateien gespeichert sind, hier bereits in den Features enthalten sind. Das kommt daher, dass die Bibliothek beide Dateien abruft und zusammenfĂŒhrt:

Ich werfe jetzt einen Blick auf die Koordinaten, die dem ersten Feature zugeordnet sind. Als erstes fÀllt mir auf, dass es zwei verschiedene Datenfelder gibt, was auf zwei unterschiedliche KoordinatensÀtze hindeutet. Ich werde den zweiten aber vorerst ignorieren.

Und hier kommt der erste Fallstrick. Diese Koordinaten sehen ĂŒberhaupt nicht wie WGS84-Koordinaten (geografische Breite / geografische LĂ€nge) aus. WGS84-Koordinaten sollten als Gleitkommazahl abgebildet sein und idealerweise mit 47 und 8 fĂŒr die Schweiz beginnen. Bei diesem Shapefile sehe ich mich mit einem anderen Koordinatenreferenzsystem (CRS) konfrontiert. Um die Form auf einer Karte korrekt darzustellen oder sie mit anderen Daten zu verwenden, mĂŒsste ich diese Koordinaten zunĂ€chst in WGS84-Koordinaten umwandeln, aber dazu muss ich erst herausfinden, welches CRS in diesem Shapefile verwendet wird. Da dieses Shapefile vom Bundesamt fĂŒr Raumentwicklung ARE stammt, ist das verwendete CRS höchstwahrscheinlich CH1903, mit anderen Worten das Schweizer Koordinatensystem.

Um das umzurechnen, braucht es also erst etwas Mathematik. Nach etwas Recherche im Internet habe ich eine JavaScript-Lösung gefunden, mit der ich Umrechnungen zwischen CH1903 und WGS84 machen kann. Ich brauche aber nur ein paar Teile davon, weshalb ich den Code kopiere und etwas umĂ€ndere.

// Inspired by https://raw.githubusercontent.com/ValentinMinder/Swisstopo-WGS84-LV03/master/scripts/js/wgs84_ch1903.js

/**
 * Converts CH1903(+) to Latitude
 * @param y
 * @param x
 * @return {number}
 * @constructor
 */
const CHtoWGSlat = (y, x) => {
  // Converts military to civil and to unit = 1000km
  // Auxiliary values (% Bern)
  const yAux = (y - 600000) / 1000000
  const xAux = (x - 200000) / 1000000

  // Process lat
  const lat = 16.9023892 +
    (3.238272 * xAux) -
    (0.270978 * Math.pow(yAux, 2)) -
    (0.002528 * Math.pow(xAux, 2)) -
    (0.0447 * Math.pow(yAux, 2) * xAux) -
    (0.0140 * Math.pow(xAux, 3))

  // Unit 10000" to 1" and converts seconds to degrees (dec)
  return lat * 100 / 36
}

/**
 * Converts CH1903(+) to Longitude
 * @param y
 * @param x
 * @return {number}
 * @constructor
 */
const CHtoWGSlng = (y, x) => {
  // Auxiliary values (% Bern)
  const yAux = (y - 600000) / 1000000
  const xAux = (x - 200000) / 1000000

  // Process lng
  const lng = 2.6779094 +
    (4.728982 * yAux) +
    (0.791484 * yAux * xAux) +
    (0.1306 * yAux * Math.pow(xAux, 2)) -
    (0.0436 * Math.pow(yAux, 3))

  // Unit 10000" to 1 " and converts seconds to degrees (dec)
  return lng * 100 / 36
}

/**
 * Convert CH1903(+) to WGS84 (Latitude/Longitude)
 * @param y
 * @param x
 */
export default (y, x) => [
  CHtoWGSlat(y, x),
  CHtoWGSlng(y, x)
]

Das Resultat kann ich nun fĂŒr meine Hauptanwendung verwenden:

import { open } from 'shapefile'
import ch1903ToWgs from './js/ch1903ToWgs'

open('assets/shapefiles/alpine-convention/shapefile.shp').then(source => {
  source.read().then(function apply (result) { // Read feature from the shapefile
    if (result.done) { // If there's no features left, abort
      return
    }

    // Convert CH1903 to WGS84
    const coords = result.value.geometry.coordinates[0].map(coordPair => {
      return ch1903ToWgs(coordPair[0], coordPair[1])
    })

    console.log(coords)

    return source.read().then(apply) // Iterate over result
  })
})

Daraus ergeben sich die folgenden angepassten Koordinaten:

Das ist viel besser. Mit diesen Koordinaten ĂŒberlagere ich nun die Karte, damit ich auch tatsĂ€chlich etwas sehe.

Dinge sichtbar machen

Dazu fĂŒge ich der App Leaflet und die Positron-Lite-Kacheln aus CartoDB hinzu. Das Positron-Lite-Design ist in Hellgrau/Weiss gehalten, was einen guten Kontrast zu den Features bildet, die ich darĂŒbergelagert anzeigen werde. OpenStreetMap ist grossartig, aber zu bunt, weshalb die Polygone nicht deutlich genug sichtbar sind.

import leaflet from 'leaflet'
import { open } from 'shapefile'
import ch1903ToWgs from './js/ch1903ToWgs'

/**
 * Add the map
 */
const map = new leaflet.Map(document.querySelector('#map'), {
  zoomControl: false, // Disable default one to re-add custom one
}).setView([46.8182, 8.2275], 9) // Show Switzerland by default

// Move zoom control to bottom right corner
map.addControl(leaflet.control.zoom({position: 'bottomright'}))

// Add the tiles
const tileLayer = new leaflet.TileLayer('//cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
  minZoom: 9,
  maxZoom: 20,
  attribution: '© CartoDB basemaps'
}).addTo(map)

map.addLayer(tileLayer)

/**
 * Process the shapefile
 */
open('assets/shapefiles/alpine-convention/shapefile.shp').then(source => {
  source.read().then(function apply (result) { // Read feature from the shapefile
    if (result.done) { // If there's no features left, abort
      return
    }

    // Convert CH1903 to WGS84
    const coords = result.value.geometry.coordinates[0].map(coordPair => {
      return ch1903ToWgs(coordPair[0], coordPair[1])
    })

    console.log(coords)

    return source.read().then(apply) // Iterate over result
  })
})

Daraus ergibt sich bereits eine schöne Karte, mit der ich arbeiten kann:

Der nÀchste Schritt besteht darin, die Karte mit dem Tool zum Laden von Shapefiles zu verbinden. Zu diesem Zweck erstelle ich aus den Koordinaten, die ich vorhin in WGS84-Koordinaten umgewandelt habe, Leaflet-Polygone.

// ...

// Convert CH1903 to WGS84
const coords = result.value.geometry.coordinates[0].map(coordPair => {
  return ch1903ToWgs(coordPair[0], coordPair[1])
})

const leafletPolygon = new leaflet.Polygon(coords, {
  weight: 0.5,
  color: '#757575',
  fillOpacity: 0.3,
  opcacity: 0.3,
})

leafletPolygon.addTo(map)

// ...

Jetzt kann ich mir das Ergebnis ansehen:

Schön!

Das Endergebnis

Durch ein leicht erweitertes User Interface und das HinzufĂŒgen eines Umschalters fĂŒr die verschiedenen Shapefiles, die ich vorhin heruntergeladen habe, kann ich eine kleine App entwickeln, die mir sowohl die Gefahrenzonen fĂŒr Lawinen als auch die LebensrĂ€ume der Steinböcke in der Schweiz zeigt:

Hier sehen wir die beiden Shapefiles im Einsatz: Die hellgrĂŒnen, grĂŒnen, gelben und roten Polygone sind Gemeinden, die einer gewissen Lawinengefahr ausgesetzt sind (hellgrĂŒn = geringe Gefahr, grĂŒn = mittel bis geringe Gefahr, gelb = mittlere Gefahr, rot = hohe Gefahr), die darĂŒbergelegten dunkleren Polygone zeigen die Steinbockkolonien.

Diese Karte liefert bereits gute Erkenntnisse ĂŒber das Verhalten von Steinböcken: Offenbar leben sie nur in Zonen mit geringer bis mittlerer LawinengefĂ€hrdung. Sie neigen auch dazu, mittel- und hochgefĂ€hrdete Gebiete zu meiden. Da Steinböcke alpines GelĂ€nde bevorzugen und dort in der Regel eine gewisse Lawinengefahr besteht, ist dies plausibel. Aber jetzt habe ich einige handfeste Daten, die diese Behauptung tatsĂ€chlich untermauern!

Die fertige Anwendung prÀsentiert sich hier in Aktion. Den Quellcode findet man in diesem Archiv.

Die Fallstricke

Obwohl Shapefiles eine gute Möglichkeit darstellen, um GIS-Daten zu bearbeiten, sollte man doch einige Fallstricke vermeiden. Einer, den ich bereits erwĂ€hnt habe, ist ein unĂŒbliches CRS. In den meisten FĂ€llen erkennt man das bei der Anwendung, aber es empfiehlt sich zu prĂŒfen, welches CRS im Shapefile verwendet wird. Der zweite grosse Fallstrick ist die Grösse der Shapefiles. Wenn Shapefiles direkt im Browser mit JavaScript verwendet werden, kann es vorkommen, dass der Browser abstĂŒrzt, weil er die riesige Menge an Polygonen nicht verarbeiten kann. Um das zu vermeiden, gibt es aber verschiedene Lösungen: Man kann das Shapefile entweder vereinfachen, indem man unnötige Polygone entfernt, oder es vorbearbeiten und die Polygone in einer Art Datenbank speichern, wĂ€hrend man nur die aktuell sichtbaren Polygone abfragt.

Denkanstösse

Ich persönlich liebe es, mit Shapefiles zu arbeiten. Es gibt viele sinnvolle AnwendungsfĂ€lle fĂŒr Shapefiles und es ist Ă€usserst spannend, sich mit den darin enthaltenen Daten auseinanderzusetzen. Auch wenn es den einen oder anderen Fallstrick zu beachten gibt, ist es im Allgemeinen sehr praktisch mit Shapefiles zu arbeiten, da man mit wenig Aufwand etwas erfolgreich entwickeln und zum Laufen bringen kann. Die OpenData-Community verwendet sehr hĂ€ufig Shapefiles, weshalb sie eine etablierte Norm sind. Ausserdem gibt es zahlreiche Bibliotheken und Tools, die die Arbeit mit Shapefiles so fantastisch machen, wie sie ist.


Sag uns was du denkst