Eigene Bilder in OpenStreetMap anzeigen

Die Kunsthalle Rostock hat nach einer Möglichkeit gesucht, verschiedene Open-Air Werke digital auf einer Webseite zu präsentieren. Gefragt getan… Mit OpenStreetMap geht das total super!

Hier ist ein Beispiel, bei dem ich das Logo von CODE AHOI in den Rostocker Stadthafen projiziert habe:

Dies ist ein Platzhalter

Um die Karte anzeigen zu können muss dein Browser Inhalte von OpenStreetMap nachladen. Die Datenschutzerklärung von OpenStreetMap findest du hier: wiki.osmfoundation.org/wiki/Privacy_Policy

Ist das nicht cool :)

Selber Machen!

Mit dem OpenLayers-Projekt ist das vergleichsweise einfach. Zunächst ist ein bisschen HTML-Grundgerüst notwendig: Wir …

Ein Template könnte etwa wie folgt aussehen:

<body>

  <div id="map" style="width:100%; height: 50vh">
    <!-- hier wird die Karte angezeigt -->
  </div>
  
  <div id="popup" class="ol-popup">
    <a href="#" id="popup-closer" class="ol-popup-closer">
      <!-- kleines x um das Pop-Up zu schließen -->
    </a>
    <div id="popup-content">
      <!-- Inhalte im Pop-Up -->
    </div>
  </div>

  <!-- Bibliotheken des OpenLayers' Projekt herunterladen -->
  <link rel="stylesheet" href="/path/to/openlayers/ol.css">
  <script src="/path/to/openlayers/ol.js"></script>
  
</body>

Ein Icon-Feature pro Icon

Jetzt können wir mit der Magie beginnen. Für jedes Icon, dass in die Map soll, muss ein Icon-Feature erstellt werden. Für das Beispiel oben sieht das ungefähr so aus:

// erstelle ein Feature, dass später als Icon angezeigt wird
const iconFeature = new ol.Feature({
  geometry: new ol.geom.Point(ol.proj.fromLonLat([12.1110,54.0966])),
  info: {
    headline: "OpenStreetMap AHOI",
    body: "Das CODE AHOI Papierschiffchen schwimmt im Rostocker Stadthafen. [...]"
  },
});

// style das Feature: hier wird ein Icon-Bild gesetzt
iconFeature.setStyle(new ol.style.Style({
  image: new ol.style.Icon({
    src: "https://codeahoi.de/s/articles/2020/openlayers-showcase.png"
  })
}));

// alles zu einem Layer zusammenbauen
var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector ({
    features: [iconFeature]
  })
});

Im ersten Block werden ein paar Eigenschaften für das Icon gesetzt: Wo soll das Icon angezeigt werden? Welche Überschrift und welcher Text soll im Pop-up angezeigt werden? Der zweite Block definiert dann, wie das Icon aussehen soll. Hier laden wir ein Bild vom CODE AHOI Webserver, dass dann in der Karte angezeigt werden soll.. Im dritten Block erzeugen wir eine neue Vektor-Ebene, die später über die Karte gelegt werden soll – die Spezialität von OpenLayers :)
Das sieht in diesem Fall vielleicht ein bisschen kompliziert aus für ein einzelnes Logo, lässt sich bei vielen Icons aber gut wegautomatisieren.

Karte laden

Im nächsten Schritt können wir die Karte laden. Als unterste Ebene wollen wir die Kacheln (Tiles) von OpenStreetMap nehmen, auf die wir dann unsere neue Ebene mit dem Icon legen. Die Map soll dann auf Level 15 gezoomt und im Rostocker Stadthafen zentriert werden. Koordinaten und Zoomlevel kann man aus der Karte auf openstreetmap.org entnehmen. Wenn, wie in diesem Fall, die Koordinaten des Icons mit dem Zentrum der Karte übereinstimmen, wird das Icon zentriert in der Karte angezeigt ;-)

var map = new ol.Map({
  // die karte soll im container mit der ID 'map' angezeigt werden
  target: 'map',
  
  // die ebenen, die in der Karte angezeigt werden sollen
  layers: [
    new ol.layer.Tile({
      // erstes Layer sind die Tiles von OpenStreetMap
      source: new ol.source.OSM()
    }),
    // zweites Layer wird unser Icon Layer
    vectorLayer
  ],
  
  view: new ol.View({
    // zentriere initial auf die Koordinaten [12.1110,54.0966] (Stadthafen Rostock)
    center: ol.proj.fromLonLat([12.1110,54.0966]),
    // und zoome auf level 15
    zoom: 15
  })
});

Die Karte wird jetzt ordentlich geladen und das Icon wird an der richtigen Stelle angezeigt.

Pop-Up implementieren

Damit sind wir also fast fertig, aber wir wollen ja noch ein kleines Pop-Up anzeigen, wenn ein Nutzer auf das Icon klickt! Dafür müssen wir natürlich auch noch ein bisschen Code schreiben:

var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');


// Das Pop-Up wird eine neue Ebene in OpenLayers
var popup = new ol.Overlay({
  element: container,
  autoPan: true,
  autoPanAnimation: {
    duration: 250
  },
  positioning: 'bottom-center',
  stopEvent: false,
  offset: [0, -25]
});
map.addOverlay(popup);


// Event-Listener für den Klick auf das x
closer.onclick = function() {
  popup.setPosition(undefined);
  return false;
};


// Event-Listener für einen Klick in die Karte: wird eines unserer Icons angeklickt -> Pop-Up anzeigen
map.on('click', function(evt) {
  // wurde eins unserer Features angeklickt?
  var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) { return feature; });
  // wenn ja, fülle das Pop-Up mit den Informationen aus dem Feature
  if (feature) {
    // wo soll das Pop-Up angezeigt werden?
    var coordinates = feature.getGeometry().getCoordinates();
    // was soll in dem Pop-Up stehen?
    content.innerHTML = '<h2>' + feature["values_"]["info"]["headline"] + '</h2><p>' + feature["values_"]["info"]["body"] + "</p>";
    
    // Pop-Up an der richtigen stelle anzeigen
    popup.setPosition(coordinates);
  } else {
    // es wurde etwas anderes angeklickt -> Pop-Up schließen
    popup.setPosition(undefined);
  }
});

Das Pop-Up selbst können wir mit OpenLayers direkt als weitere Ebene (siehe zweiten Block) in der Karte realisieren – damit fühlt sich das Pop-Up nicht so aufdringlich an. Die nötigen Informationen für das Pop-Up haben wir oben direkt an unser Icon-Feature gespeichert. Diese können wir jetzt auslesen und anzeigen, wenn ein Nutzer das Icon anklickt (siehe letzter Block).

Styling

Die Logik ist fertig. Aber für das Makeup brauchen wir jetzt noch ein ordentliches Finishing ;-)
Das Styling ist natürlich sehr vom Layout drumherum und vielen subjektiven Faktoren abhängig. Generell könnte man aber ein Schließen-Symbol in dem popup-closer Container anzeigen und die Pop-Up-Blase ein bisschen anpassen. Hier ist ein Beispiel:

/* x zum Schließen */
.ol-popup-closer {
  text-decoration: none;
  position: absolute;
  top: 2px;
  right: 8px;
}
.ol-popup-closer:after {
  content: "✖";
}

/* Pop-Up-Blase mit kleinem Dreieck, das auf das Icon zeigt */
.ol-popup {
  position: absolute;
  background-color: white;
  box-shadow: 0 1px 4px rgba(0,0,0,0.2);
  padding: 15px;
  border-radius: 10px;
  border: 1px solid #cccccc;
  bottom: 12px;
  left: -50px;
  min-width: 280px;
  display: none;
}
.ol-popup:after, .ol-popup:before {
  top: 100%;
  border: solid transparent;
  content: " ";
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}
.ol-popup:after {
  border-top-color: white;
  border-width: 10px;
  left: 48px;
  margin-left: -10px;
}
.ol-popup:before {
  border-top-color: #cccccc;
  border-width: 11px;
  left: 48px;
  margin-left: -11px;
}

Komplette Lösung

Meinen Code für das Beispiel oben gibt es unverschleiert zum Download:

Das HTML-Gerüst ist mit dem Artikel drumherum in dem Fall ein bisschen komplexer, aber das könnt ihr euch ja selbstständig im Quellcode ansehen :)

Auf Geht's!

Licht aus & Spot an: CODE AHOI startet!
Nach einigen Vorbereitungen setz’ ich mal die Segel und probiere los :)

Ich habe lange an Universitäten gelernt und gearbeitet. Der Drang, etwas Angewandteres zu machen, wurde zunehmend größer. Ich habe aber leider keine Alternative gefunden, die mir 100%ig zusagt. Ich bin gut ausgebildet, kann interdisziplinär Denken und mich schnell in neue Themen einarbeiten. Mit diesen Fähigkeiten würde ich gern Organisationen und lokale Wirtschaft bei digitalen Projekten unterstützen.
CODE AHOI ist mein Versuch mit freiberuflicher Projektarbeit breite Erfahrung in möglichst vielen Domainen zu sammeln, um dann hoffentlich herauszufinden, wohin die Reise weiter gehen kann. Vielleicht vergleichbar mit bezahlten Praktika oder der Walz, wie es die Zimmermänner vor einiger Zeit pflegten.

Ein erstes kleines Aushängeschild ist diese Webseite. Ich habe mich für eine statische Webseite mit Jekyll entschieden: Die Seiten werden einmal kompiliert und dann nur noch ausgeliefert. Kein komplexes CMS und keine ausführbaren Dateien auf dem Server. So eine Webseite ist wesentlich performanter und umweltschonender, es fallen typischerweise weniger personenbezogene Daten an, und da nichts ausgeführt wird, ist das Sicherheitsrisiko wesentlich geringer. Außerdem wird die Webseite 100% von grüner Energie betrieben!

Das initiale Erstellen der Seite ist eventuell etwas komplizierter, aber wenn man ein bisschen technischen Sachverstand mitbringt geht es sogar mehrsprachig (beachtet die Fahne oben im Menü)! :)

Ich habe mich auch gegen jegliches Tracking entschieden: Lest oder lest nicht und klickt wo ihr wollt — mir egal. Wenn ihr möchtet, dass ich auf euch aufmerksam werde, dann hinterlasst einfach eine Nachricht über das Kontaktformular ganz unten.

Ich bin motiviert und gespannt!