Andreas Bruns

Softwareentwicklung für Oldenburg und Bremen

Markers für OpenStreetMap-Karten mit OpenLayers

Google hat die Lizenzbedingungen für die Verwendung der Maps-API angepasst und damit muss man jetzt eventuell für den Dienst zahlen (siehe auch Heise FAQ). Zum Glück gibt es ja das OpenStreetMap-Projekt (OSM), das eine kostenfreie Alternative bietet und jetzt sicherlich noch größere Verbreitung findet.

Viele Freiwillige sammeln für OSM weltweit Daten (z.B.: Straßenverläufe, Fahrradwege, Restaurants, Postkästen) und die erstellten OSM-Karten kann jeder für eigene Zwecke nutzen. OpenStreetMap liefert beispielsweise unter der Adresse http://a.tile.openstreetmap.org/8/134/83.png die folgende vorgefertigte, statische Karte aus:
Wenn wir allerdings wie mit Google Maps innerhalb einer Karte navigieren wollen, benötigen wir für unsere Webseite eine spezielle JavaScript-Bibliothek. Diese muss allerhand Aufgaben erledigen: Navigationselemente anzeigen, passende Karten-Bilder anfordern, diese Karten korrekt anzeigen und zusätzliche vom Benutzer festzulegende Elemente, wie Marker, darstellen. OpenLayers ist momentan die Standard-Bibliothek für diesen Einsatzzweck und wer OpenLayers mal einsetzen möchte, sollte sich einfach mal dessen zahlreichen Beispiele ansehen.

Das folgende komplette Beispiel zeigt eine OpenStreetMap-Karte als Vollbild innerhalb eines Browser-Fensters an. Auf dieser Karte sollen anschließend ein paar Marker mit Popups angezeigt werden.
Der folgende Code ist ähnlich auch bei OpenStreetMap zu finden: OSM-Example

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Markers für OpenStreetMap-Karten mit OpenLayers</title>

<script type="text/javascript"
  src="http://dev.openlayers.org/releases/OpenLayers-2.11/OpenLayers.js"></script>

<script type="text/javascript">
  PROJECTION_4326 = new OpenLayers.Projection("EPSG:4326");
  PROJECTION_MERC = new OpenLayers.Projection("EPSG:900913");

  function init() {
     map = new OpenLayers.Map("map", {
      controls : [ new OpenLayers.Control.PanZoomBar(),
        new OpenLayers.Control.Navigation(),
        new OpenLayers.Control.LayerSwitcher(),
        new OpenLayers.Control.MousePosition(),
        new OpenLayers.Control.Attribution(),
        new OpenLayers.Control.OverviewMap() ],
      maxExtent : new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
      numZoomLevels : 18,
      maxResolution : 156543,
      units : 'm',
      projection : PROJECTION_MERC,
      displayProjection : PROJECTION_4326
    });

    // OSM Layer
    var osmLayer = new OpenLayers.Layer.OSM("OpenStreetMap");
    map.addLayer(osmLayer);

    var center = new OpenLayers.LonLat(8.807357, 53.075813);
    var centerAsMerc = center.transform(PROJECTION_4326, PROJECTION_MERC);
    map.setCenter(centerAsMerc, 8);
  }
</script>

<style type="text/css">
html,body,#map {
  width: 100%;
  height: 100%;
  margin: 0;
}
</style>

</head>
<body onload="init()">
  <div id="map"></div>
</body>
</html>

Der gesamte HTML-Code des Beispiels besteht grob aus folgenden Teilen:

  • JavaScript-Einbindung von Openlayers
  • JavaScript-Code, um die angezeigte OSM-Karte zu konfigurieren
  • CSS-Code, damit die Karte als Vollbild angezeigt wird
  • HTML-Code, wobei die OSM-Kartendaten das div-Element befüllen, sobald das Dokument geladen wurde

Den Hauptteil nimmt unser eigener JavaScript-Code ein und davon die Transformation der Longitude/Latitude-Koordinaten-Projektion „EPSG:4326“ in die Spherical Mercator Projection „EPSG:900913“ von OpenStreetMap (OpenLayers-Doku: Spherical Mercator):

  • Definition der beiden Projektionen
  • Definition der init()-Methode, die nach dem Laden des HTML-Dokuments aufgerufen wird
  • Erzeugung eines Map-Objekts mit einer Referenz ‚map‘ auf das div-Element
  • Map-Objekt wird mit einem Hash konfiguriert: anzuzeigende Controls und Angaben zu den Maßstäben der Karte
  • die Map um den Base Layer von OpenStreetMap ergänzen
  • das Zentrum der Karte mit dem Zoom-Faktor definieren, wobei für die Angabe des Zentrums eine Umrechnung der Koordinaten notwendig ist

In OpenLayers wird zwischen Base Layer und Non Base Layer unterschieden (OpenLayers-Doku: Layers). Es kann immer nur ein Base Layer aktiv sein und dieser bestimmt das aktuelle Koordinatensystem (Projektion) und den Zoom-Level. Zu dem aktuellen Base Layer können mehrere Non Base Layer aktiviert werden. Unabhängig davon wird außerdem zwischen den beiden Layer-Arten Raster-Layer und Overlay-Layer unterschieden. Raster-Layers bestehen aus Kartenbildern und bilden zumeist den Base Layer (z.B.: OpenLayers.Layer.OSM, OpenLayers.Layer.Bing, OpenLayers.Layer.Google). Die Overlay-Layers sind meistens Non Base Layer und zeigen Zusatzinformationen zu der angezeigten Karte an. Die beiden wichtigsten Overlay-Layers OpenLayers.Layer.Markers und OpenLayers.Layer.Vector werde ich nachfolgend nutzen, um Marker auf der oben genutzten OpenStreetMap-Karte anzuzeigen.

Die OpenLayer-Entwickler versuchen übrigens die Anzahl der Layers zu verringern, indem sie die Funktionalität der Klasse OpenLayers.Layer.Vector erweitern. Wenn man also Beispiele aus dem Internet als Vorbild für seine eigene Entwicklung verwendet, sollte stets die Dokumentation zu den verwendeten Klassen geprüft werden, ob sie nicht als deprecated markiert sind. So wird beispielsweise die Verwendung der Klasse OpenLayers.Layer.GML nicht mehr empfohlen http://dev.openlayers.org/apidocs/files/deprecated-js.html#OpenLayers.Layer.GML, obwohl sie in so mancher Dokumentation noch benutzt wird.

Wenn eine Karte mit Overlays, z.B.: (Markern) ausgestattet werden soll, muss man sich zwischen Marker-Overlays und Vector-Overlays entscheiden. Marker-Overlays sind die klassische einfache Möglichkeit, während OpenLayers heutzutage Vector-Overlays empfiehlt und auch gleich eine Anleitung für den Umstieg anbietet.

Ein Marker-Overlay mit der Klasse OpenLayers.Layer.Markers lässt sich folgendermaßen erstellen:

  • Definition eines Icons mit Größe und Offset
  • einen Marker mit dem Icon erstellen, dessen Koordinate umgerechnet werden muss
  • Event an dem Marker registrieren und den auszuführenden JavaScript-Code übergeben
  • mit OpenLayers.Layer.Markers einen Layer definieren und der Karte hinzufügen
  • zu dem Layer den erstellten Marker hinzufügen
var size = new OpenLayers.Size(32,37);
var offset = new OpenLayers.Pixel(-16,-35);
var icon = new OpenLayers.Icon('powerplant.png',size,offset);
markerAkwBrunsbuettel = new OpenLayers.Marker(new OpenLayers.LonLat(9.201667, 53.891667).transform(PROJECTION_4326, PROJECTION_MERC),icon)
markerAkwBrunsbuettel.events.register('mousedown', markerAkwBrunsbuettel, function(evt) { alert(this.icon.url); OpenLayers.Event.stop(evt); });

var akwMarkersLayer = new OpenLayers.Layer.Markers("AKW Marker Layer");
map.addLayer(akwMarkersLayer);
akwMarkersLayer.addMarker(markerAkwBrunsbuettel);

Eine spezielle, sehr bequeme Variante der Klasse OpenLayers.Layer.Markers bietet die Klasse OpenLayers.Layer.Text. Dieser Klasse muss man lediglich eine Textdatei vorlegen, dessen Werte mit Tabulatoren getrennt sind, und dann wird für jede Zeile ein entsprechender Marker erstellt:

var akwTextLayer = new OpenLayers.Layer.Text("AKW Text Layer", {
  location : "./akws.txt",
  projection : PROJECTION_4326
});
map.addLayer(akwTextLayer);

Die zugehörige Textdatei ‚akws.txt‘ sieht folgendermaßen aus. Zwischen den Werten muss jeweils ein Tabulator-Zeichen vorhanden sein, sodass man nach dem Kopieren der Zeilen die Leerzeichen durch Tabulatoren ersetzen muss.

lat    lon    title    description    icon    iconSize    iconOffset
53.4277    8.480197    Unterweser    AKW Unterweser<br><a href="http://de.wikipedia.org/wiki/AKW_Unterweser">Wikipedia</a>    powerplant.png    32,37    -16,-35
52.474231    7.317858    Emsland    AKW Emsland    powerplant.png    32,37    -16,-35

Mit den Marker-Overlays lassen sich also schon sehr einfach Marker auf Karten anordnen (OpenLayers-Beispiel mit Marker). Für spezielle Wünsche muss man mit den Marker-Overlays allerdings einige Verrenkungen machen. Flexiblere Möglichkeiten bietet der von OpenLayers empfohlene Vector-Overlay, auf dem sich geometrische Figuren anordnen lassen. Wenn man solch eine Figur dann noch mit einem Bild ausstattet und um etwas JavaScript erweitert, ergibt sich ein sehr hübscher und gut anzupassender Marker mit Popup.

Wir müssen zunächst einmal der Karte einen Layer der Klasse OpenLayers.Layer.Vector hinzufügen. Auf diesem Layer können wir dann sogenannte Features ergänzen, die bei uns von der Klasse OpenLayers.Feature.Vector sind. Der Konstruktor benötigt ein Objekt der Klasse OpenLayers.Geometry  (hier OpenLayers.Geometry.Point). Zusätzlich kann man dem Konstruktor zwei optionale Hashes übergeben, die zusätzliche Attribute enthalten oder den Style des Features beeinflussen. Der Attribute-Hash enthält zu dem Feature beschreibende Daten, die wir im Popup des Markers nutzen werden. Ohne Style-Hash wird standardmäßig ein Kreis gezeichnet (1. Feature) und wenn wir einen passenden Style mit einem Bild übergeben, erhalten wir einen korrekt aussehenden Marker.

var akwVectorLayer = new OpenLayers.Layer.Vector("AKW Vector Layer");
map.addLayer(akwVectorLayer);

var brokdorfPoint = new OpenLayers.Geometry.Point(9.344722, 53.850833).transform(PROJECTION_4326, PROJECTION_MERC);
var brokdorfAttributes = {title: "AKW Brokdorf", description: "Weitere Informationen: <a href='http://de.wikipedia.org/wiki/AKW_Brokdorf'>Wikipedia</a>"};
var stylesAkwImage = {externalGraphic: 'powerplant.png', graphicHeight: 37, graphicWidth: 32, graphicYOffset: -35, graphicXOffset: -16 };
var brokdorfFeature = new OpenLayers.Feature.Vector(brokdorfPoint, brokdorfAttributes, stylesAkwImage);
akwVectorLayer.addFeatures(brokdorfFeature);

Jetzt fehlt noch der Code für das Anzeigen eines Popups, den ich größtenteils aus der OpenLayers-Dokumentation genommen habe (hier). Der Karte müssen wir ein OpenLayers.Control.SelectFeature mit unserem Vector-Overlay hinzufügen und dort geben wir an, welche Methoden bei welchen Events auszuführen sind (hier onFeatureSelect() und onFeatureUnselect()). Die jeweiligen Methoden liste ich hier nicht auf, weil man sie direkt aus der angegebenen Dokumentation kopieren kann. Solch ein Marker mit einem Popup sieht dann folgendermaßen aus:

selectControl = new OpenLayers.Control.SelectFeature(akwVectorLayer);
map.addControl(selectControl);
selectControl.activate();

akwVectorLayer.events.on({
'featureselected' : onFeatureSelect,
'featureunselected' : onFeatureUnselect
});

Ich hoffe, mit den Beispielen konnte ich einen kleinen Überblick zu der Verwendung von Markern mit OpenLayers und OpenStreetMap geben. Sobald man eine etwas größere Anwendung mit OpenLayers erstellen möchte, sollte man sich wegen der Flexibilität und der gesicherten weiteren Entwicklung lieber für Vector-Overlays (noch ein tolles Beispiel: Popup-Matrix) entscheiden. Die gemeinsame Verwendung beider Overlays führt in meinem Beispiel übrigens dazu, dass die Marker-Overlays nur reagieren, wenn der Vector-Layer deaktiviert ist. Mein Beispiel kann man sich hier ansehen oder von Github herunterladen. Tolle, frei verwendbare Bilder für Marker gibt es übrigens auf der Seite von Nicolas Mollet.

7 Kommentare

  1. Danke für die ausführliche Anleitung! Eine Möglichkeit, OSM Maps in sein WordPress-Blog einzufügen, ohne mit Quellcode hantieren zu müssen, gibts übrigens seit kurzem auch – und zwar mit meinem Plugin „Leaflet Maps Marker“ – dieses bietet neben Marker & Layer maps mit Icons, Popuptexten auch die Möglichkeit, WMS Layer drüberzublenden, Export als KML zur Anzeige in Google Earth/Maps, GeoJSON-/GeoRSS-API, Wikitude Augmented-Reality-API und noch einges mehr 😉
    Demo gibts auf der Plugin-Webseite unter http://www.mapsmarker.com – das Plugin selbst kann im WordPress-Repository kostenlos downgeloaded werden 🙂

  2. Wow – sieht echt spannend aus. Dann muss ich wohl mal WordPress selber installieren – bei wordpress.com kann ich das ja so leider noch nicht nutzen

  3. Hallo,

    erstmal herzlichen Dank für das schoene Tutorial. Bei deinem Beispiel sitzt aber der Punkt in der Vektorebene an der falschen Stelle. an der richtigen Position sehe ich einen gelben runden Punkt. Weisswt du, was da schief gelaufen ist?

    Beste Grüße,

    Stefan

  4. Hallo Stefan,

    ich verstehe nicht ganz, was du meinst. Bei der Verwendung von Icons kann man einen Offset angeben, damit beispielsweise die Spitze des Icons auf die richtige Stelle verweist.
    In meinem Live-Beispiel habe ich den Eindruck, dass die Marker schon richtig funktionieren. Bei höchster Zoomstufe sieht man allerdings, dass die Koordinaten mancher AKWs nicht genau genug angegeben sind. Die Spitze zeigt in den verschiedenen Zoomstufen jedoch immer auf den gleichen Punkt.

    Ich hoffe das hilft dir – schöne Grüße
    Andreas

  5. Gibt es auch eine Möglichkeit solche Karten (die mit OpenLayer erstellt wurden) auszudrucken, so das man dann noch die Straßennamen lesen kann ?
    Ps. Ich meine nicht einen Screenshot

    Gruß
    Andreas