Geodaten und Application Express: OpenStreetMap und OpenLayers
von Bruno Unberath, Nürnberger Versicherungsgruppe

Das OpenStreetMap-Projekt beschäftigt sich mit der Sammlung, Pflege und Bereitstellung von Geodaten unter einer OpenSource-Lizenz. Der Anfang wurde 2004 in England gemacht. Inzwischen haben die freiwilligen Kartographen sehr umfangreiche Datenbestände erfasst - Es sind mehrere Basis-Layer entstanden und einige Parallel-Projekte beschäftigen sich mit der Entwicklung von Techniken, die man bei geographischen Anwendungen mittlerweile nicht missen will - z.B. Routing.

Darstellen wollen wir die Karte mit OpenLayers. Diese JavaScript-Bibliothek ist ebenfalls dem OpenSource-Bereich zuzuordnen - mit Ihrer Hilfe kann man neben den OpenStreetMap-Daten auch andere (bspw. NASA WorldWind) darstellen.

Die frei verfügbare Karte von OpenStreetMap (Mapnik-Layer) in Application Express

Abbildung 1: Die frei verfügbare Karte von OpenStreetMap (Mapnik-Layer) in Application Express

Dieser Tipp ist als Ergänzung zu der 5-teiligen Reihe Geodaten und Application Express gedacht. Im Gegensatz dazu werden die Kartendaten hier nicht in der Datenbank gespeichert; vielmehr wird die Karte komplett vom OpenStreetMap-Server abgerufen. Die Karte kann also lediglich dargestellt werden. In einem weiteren HowTo-Dokument werden wir vorstellen, wie Sie Daten von OpenStreetMap herunterladen, in die Oracle-Datenbank laden und dort weiter damit arbeiten können.

Dieser Tipp stellt in wesentlichen die Vorgehensweise vor, wie Sie eine OpenStreetMap-Karte in Ihre Application Express-Anwendung integrieren und Daten aus lokalen Tabellen mit dieser kombinieren. Hintergrundinformationen dazu finden Sie im in der bereits vorhandenen Tippreihe zum Thema Geodaten und Application Express.

Vorbereitungen

Zunächst benötigen Sie eine Tabelle KUNDEN mit einigen Beispieldatensätzen, die im weiteren Verlauf auf die Karte platziert werden.

Für dieses Beispiel werden wir die Koordinaten der Kunden per Zufallsgenerator erzeugen. Grundsätzlich wird das Ermitteln einer Geokoordinate aus einer postalischen Adresse als Geocoding bezeichnet. Auch Geocoding ist mit der Oracle-Datenbank möglich, die Beschreibung würde jedoch den Rahmen dieses Tutorials sprengen. Mehr Informationen zum Thema Geocoding in der Oracle-Datenbank finden Sie in der Oracle-Dokumentation.

Lassen Sie zur Erzeugung der Beispieldaten das folgende SQL-Skript laufen. Nehmen Sie dazu jedoch nach Möglichkeit SQL*Plus oder den SQL Developer, nicht jedoch den Application Express SQL Workshop. Loggen Sie sich unter dem Parsing Schema (in welchem auch die anderen Geodaten liegen) ein.

create table kunden (
  id number(10),
  name varchar2(50),
  lokation sdo_geometry,
  constraint pk_kunden primary key (id)
)
/

insert into user_sdo_geom_metadata (table_name, column_name, diminfo, srid)
values (
  'KUNDEN', 
  'LOKATION', 
  SDO_DIM_ARRAY(
    SDO_DIM_ELEMENT('X', -180, 180, 1), 
    SDO_DIM_ELEMENT('Y', -90, 90, 1)
  ), 
  8307
)
/

begin
  for i in 1..250 loop
    insert into kunden (id, name, lokation)
    values (
      i,
      'Kunde #'||i,
      sdo_geometry(
        2001,
        8307,
        sdo_point_type(
          dbms_random.value(8,12),
          dbms_random.value(48,52),
          null

        ),
        null,
        null
      )
    );
  end loop;
end;
/

create index kunden_sx on kunden (lokation)
indextype is mdsys.spatial_index
/

Darstellung der OpenStreetMap-Karte

Erstellen Sie eine Application Express-Anwendung mit einer Seite und einer HTML-Region auf derselben. Hinterlegen Sie folgenden HTML-Quelltext:

<div id="map" style="left:0px; top:0px; width:800px; height: 500px;">
</div>

Im Seiten-Header hinterlegen Sie anschließend den JavaScript-Code, welcher die Karte vom OpenStreetMap-Server abruft und darstellt.

<script type="text/javascript" src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript" src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
<script type="text/javascript">
  var map;
  var layer_mapnik;
  
  function drawmap() {
    OpenLayers.Lang.setCode('de');
    // Position und Zoomstufe der Karte
    var lon = 11;
    var lat = 49.49;
    var zoom = 7;
    // Kartendefinition
    map = new OpenLayers.Map('map', {
        projection: new OpenLayers.Projection("EPSG:900913"),
        displayProjection: new OpenLayers.Projection("EPSG:4326"),
        controls: [
            new OpenLayers.Control.Attribution(),
            new OpenLayers.Control.MouseDefaults(),
            new OpenLayers.Control.LayerSwitcher(),
            new OpenLayers.Control.PanZoomBar(),
            new OpenLayers.Control.MousePosition()],
        maxExtent:
            new OpenLayers.Bounds(-20037508.34,-20037508.34,
                                    20037508.34, 20037508.34),
        numZoomLevels: 18,
        maxResolution: 156543,
        units: 'meters'
    });
 
    // Hier wird festgelegt, dass der Layer "Mapnik" verwendet wird;
    // testweise kann hier auch "Osmarender" oder "Cycle Map" eingesetzt werden
    layer_mapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
    map.addLayers([layer_mapnik]);
    setMyCenter(lon,lat,zoom);
    // ende function drawmap
  }

  function setMyCenter(lo,la,zo) {
    var lonLat = new OpenLayers.LonLat(lo, la).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
    map.setCenter (lonLat, zo);
    // ende function setMyCenter
  }
</script>

Der JavaScript-Code definiert zwei Funktionen: drawmap() konfiguriert die darzustellende Karte und setMyCenter() zentriert sie auf einem bestimmten Punkt. Letztere Funktion wird später nochmals benötigt. Wenn Sie sich den JavaScript-Code genau ansehen, stellen Sie (unten) fest, dass der Kartenlayer Mapnik verwendet wird. Daneben stehen Layer wie Osmarender oder eine Fahradwegkarte (Cycle Map) zur Verfügung. Probieren Sie sie einfach mal aus ...

Tragen Sie zum Abschluß noch folgenden Text ins die HTML-Body-Attribut ein, um die JavaScript-Funktionen beim Starten der Seite auch wirklich auszuführen.

onload="drawmap();"

An dieser Stelle kann es passieren, dass beim Speichern der Seite ein Fehler auftritt (Abbildung 2).

Fehler nach dem Eintragen des "HTML-Body-Attributs"

Abbildung 2: Fehler nach dem Eintragen des "HTML-Body-Attributs"

Das Problem lässt sich einfach beheben: Stellen Sie in den Anzeigeattributen der Seite den Cursor-Fokus auf Cursor nicht als Fokus um. Speichern Sie die Seite anschließend und starten Sie sie - das Ergebnis sollte dann wie in Abbildung 3 aussehen.

Ein erstes Ergebnis: Die OpenStreetMap-Karte in Application Express

Abbildung 3: Ein erstes Ergebnis: Die OpenStreetMap-Karte in Application Express

Eine "Overview Map" hinzufügen

Wenn Sie sich den JavaScript-Code genauer ansehen, erkennen Sie den Bereich controls: [ ... ]. Dort werden die einzelnen Steuerelemente für die Karte konfiguriert. Beispiele sind PanZoomBar für die Leiste links, an der Sie das Zoom-Level verstellen können oder LayerSwitcher - dieser ist für das Plus-Symbol (+) am rechten Kartenrand verantwortlich. Wenn Sie darauf klicken, können Sie den Kartenlayer umschalten - Probieren Sie es!

Nun wollen wir ein weiteres Steuerelement, die Überblickskarte, hinzufügen. Tauschen Sie dazu den JavaScript-Code im Seiten-Header gegen den folgenden aus - die geänderten Code-Abschnitte sind markiert.

<script type="text/javascript" src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript" src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
<script type="text/javascript">

var map;
var layer_mapnik;
var layer_ov;

function drawmap() {
    OpenLayers.Lang.setCode('de');
    // Position und Zoomstufe der Karte
    var lon = 11;
    var lat = 49.49;
    var zoom = 7;
    // Kartendefinition
    map = new OpenLayers.Map('map', {
        projection: new OpenLayers.Projection("EPSG:900913"),
        displayProjection: new OpenLayers.Projection("EPSG:4326"),
        controls: [
            new OpenLayers.Control.Attribution(),
            new OpenLayers.Control.MouseDefaults(),
            new OpenLayers.Control.LayerSwitcher(),
            new OpenLayers.Control.PanZoomBar(),
            new OpenLayers.Control.MousePosition()],
        maxExtent:
            new OpenLayers.Bounds(-20037508.34,-20037508.34,
                                    20037508.34, 20037508.34),
        numZoomLevels: 18,
        maxResolution: 156543,
        units: 'meters'
    });

    layer_mapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
    map.addLayers([layer_mapnik]);
    setMyCenter(lon,lat,zoom);
    // overview erzeugen
    layer_ov = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
    var options = {layers: [layer_ov]};
    map.addControl(new OpenLayers.Control.OverviewMap(options));

// ende function drawmap
}

function setMyCenter(lo,la,zo) {
    var lonLat = new OpenLayers.LonLat(lo, la).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
    map.setCenter (lonLat, zo);
// ende function setMyCenter
}
</script>

Starten Sie die Seite nochmals neu. Sie sehen nun unten rechts noch ein Plus-Symbol - wenn Sie es anklicken, erscheint eine kleinere Überblickskarte.

Karte mit Überblickskarte

Abbildung 4: Karte mit Überblickskarte

Karte mit Daten aus lokalen Tabellen anreichern

Im folgenden werden Sie Daten aus der lokalen Tabelle KUNDEN zur Karte hinzufügen. Allerdings werden Sie schon am verwendeten JavaScript-Code erkennen, dass dies eine recht aufwändige Operation und nur eingeschränkt möglich ist. In diesem Beispiel werden 50 Kunden aus der Datenbank abgerufen und auf der Karte platziert. Im Tipp 4. Intuitive Overfläche: Oracle MAPS wird dies mit wesentlich weniger Code erreicht - einfach weil alle Daten in der Datenbank liegen und mit Oracle MAPS gearbeitet wird.

Zunächst benötigen Sie einen Anwendungsprozeß, der die Kunden auf Anfrage bereitstellt - navigieren Sie dazu zu den Gemeinsamen Komponenten, dort zu den Anwendungsprozessen und erstellen Sie den Prozeß LOAD_KUNDE. Achten Sie darauf, dass er Bedarfsgesteuert (On Demand) ausgeführt wird. Hinterlegen Sie den folgenden PL/SQL-Code:

declare
 ret varchar2(200);
begin
 select 
  replace(k.lokation.sdo_point.x, ',', '.') || ';' || 
  replace(k.lokation.sdo_point.y, ',', '.') || ';' ||
  k.name  
  into ret
 from kunden k
 where id = :K_ID;
 htp.prn(ret);
exception 
  when others then
    ret := 'Problem' || sqlerrm;
    htp.prn(ret);
end;

Navigieren Sie anschließend zu den Anwendungselementen und erstellen Sie das Element K_ID (Abbildung 5).

Anwendungselement "K_ID"

Abbildung 5: Anwendungselement "K_ID"

Nun muss der JavaScript-Code im Seiten-Header nochmals angepasst werden; der Karte wird ein neuer Layer KUNDEN_LAYER hinzugefügt; dieser wird mit 50 aus der Datenbank geladenen Kunden gefüllt. Hier ist sehr gut erkennbar, dass diese Integration zwischen lokalen Daten aus der Datenbank und dem freien Kartenserver nur eingeschränkt möglich ist - bei sehr großen Datenmengen würde man mit Sicherheit an Performancegrenzen stoßen. Navigieren Sie also zum Seiten-Header und tauschen Sie den JavaScript-Code gegen den folgenden aus.

<script type="text/javascript" src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript" src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
<script type="text/javascript">

var map;
var layer_mapnik;
var layer_ov;
var layer_kunden;

function drawmap() {
    OpenLayers.Lang.setCode('de');
    // Position und Zoomstufe der Karte
    var lon = 11;
    var lat = 49.49;
    var zoom = 7;
    // Kartendefinition
    map = new OpenLayers.Map('map', {
        projection: new OpenLayers.Projection("EPSG:900913"),
        displayProjection: new OpenLayers.Projection("EPSG:4326"),
        controls: [
            new OpenLayers.Control.Attribution(),
            new OpenLayers.Control.MouseDefaults(),
            new OpenLayers.Control.LayerSwitcher(),
            new OpenLayers.Control.PanZoomBar(),
            new OpenLayers.Control.MousePosition()],
        maxExtent:
            new OpenLayers.Bounds(-20037508.34,-20037508.34,
                                    20037508.34, 20037508.34),
        numZoomLevels: 18,
        maxResolution: 156543,
        units: 'meters'
    });

    layer_mapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
    layer_kunden = new OpenLayers.Layer.Markers("Kunden", { projection: new OpenLayers.Projection("EPSG:4326"), visibility: false, displayInLayerSwitcher: true });
    map.addLayers([layer_mapnik, layer_kunden]);
    setMyCenter(lon,lat,zoom);
    // overview erzeugen
    layer_ov = new OpenLayers.Layer.OSM.Mapnik("Mapnik");
    var options = {layers: [layer_ov]};
    map.addControl(new OpenLayers.Control.OverviewMap(options));

    // layer_kunden aufbauen
    loadKunde();

// ende function drawmap
}

function setMyCenter(lo,la,zo) {
    var lonLat = new OpenLayers.LonLat(lo, la).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
    map.setCenter (lonLat, zo);
// ende function setMyCenter
}


function loadKunde() {
  var retval;
  var posKunde = new Array;
  var lo;
  var la;
  var ptext;
  var get = new htmldb_Get(
    null,
    html_GetElement('pFlowId').value,
    'APPLICATION_PROCESS=LOAD_KUNDE',
    0
  );
  for (var i=1;i<=50;i++){
    get.add('K_ID', i);
    retval = get.get();
    posKunde = retval.split(";");
    lo = posKunde[0];
    la = posKunde[1];
    ptext = posKunde[2];
    addMarker(layer_kunden, lo, la, ptext);
  } // ende loop
// ende function loadKunde
}


function addMarker(layer, lon, lat, popupContentHTML) {

    var ll = new OpenLayers.LonLat(lon, lat).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
    var feature = new OpenLayers.Feature(layer, ll);
    feature.closeBox = true;
    feature.popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud, { minSize: new OpenLayers.Size(200, 100) });
    feature.data.popupContentHTML = popupContentHTML;
    feature.data.overflow = "hidden";

    var marker = new OpenLayers.Marker(ll);
    marker.feature = feature;

    var markerClick = function(evt) {
        if (this.popup == null) {
            this.popup = this.createPopup(this.closeBox);
            map.addPopup(this.popup);
            this.popup.show();
        } else {
            this.popup.toggle();
        }
        OpenLayers.Event.stop(evt);
    };
    marker.events.register("mousedown", feature, markerClick);

    layer.addMarker(marker);
}
</script>

Starten Sie die Seite nun - das Ergebnis sollte wie in Abbildung 6 aussehen ...

Das Ergebnis: Karte mit Datenlayer aus der lokalen Tabelle "KUNDEN"

Abbildung 6: Das Ergebnis: Karte mit Datenlayer aus der lokalen Tabelle KUNDEN

Bericht mit Zentrierfunktion

Zum Abschluß soll der Seite nun noch ein Bericht auf die Tabelle KUNDEN hinzugefügt werden - klickt man in diesem Bericht auf einen der Kunden, so soll die Karte auf diesen Kunden fokussieren. Erzeugen Sie dazu zunächst einen einfachen SQL-Bericht neben der Karte und verwenden Sie folgende Abfrage.

select 
  k.id,
  k.name,
  k.lokation.sdo_point.x x,
  k.lokation.sdo_point.y y
from kunden k

Das vorläufige Ergebnis sieht dann wie in Abbildung 7 aus ...

Karte mit Bericht auf die Tabelle "KUNDEN"

Abbildung 7: Karte mit Bericht auf die Tabelle KUNDEN

Navigieren Sie nun zu den Berichtseigenschaften, dort zur Spalte NAME und dann in den Abschnitt Link. Machen Sie, wie in Abbildung 8 dargestellt, aus der Spalte einen Link mit dem Ziel javascript:setMyCenter(#X#,#Y#,10);.

Erstellen eines Links auf die Berichtsspalte "NAME"

Abbildung 8: Erstellen eines Links auf die Berichtsspalte NAME

Starten Sie die Seite nun nochmals und klicken Sie auf einen der Kunden; die Karte sollte sofort auf diesen zentrieren (Abbildung 9).

Zentrieren der Karte nach Klick auf einen Kunden

Abbildung 9: Zentrieren der Karte nach Klick auf einen Kunden

Fazit

Sie haben nun ohne weitere Infrastruktur, also nur mit Application Express, geografische Daten eines "Drittanbieters" (OpenStreetMap) als Karte in Ihre Applikation aufgenommen. Wie Sie allerdings sicherlich bemerkt haben, ist diese Integration recht aufwändig - so muss die Integration der Daten aus einer lokalen Tabelle vollständig mit PL/SQL und JavaScript programmiert werden.

Des weiteren verlassen Sie sich auf eine fremde Infrastruktur. Wenn der OpenLayers-Server nicht verfügbar ist, haben Sie keine Karte. Es ist nicht Ihre eigene Infrastruktur - von Sicherheitsaspekten gar nicht zu sprechen.

Aus diesem Grund wäre es überlegenswert, die freien OpenStreetMap-Daten in die lokale Datenbank zu laden und dann mit dem bereits früher vorgestellten Oracle MapViewer und Oracle Maps darzustellen - wie Sie das erreichen können, werden wir in einem späteren Tipp vorstellen.

Zurück zur Community-Seite

Bruno Unberath ist seit 1991 Anwendungsentwickler bei der Nürnberger Versicherungsgruppe. Nach etlichen Jahren Großrechnererfahrung, verlagerte sich sein Tätigkeitsschwerpunkt in das J2EE- und Oracle-Umfeld. Zur Zeit betreut er die Oracle-Belange in einem großen Projekt; er beschäftigt sich auch mit Datenbank-basierten Anwendungen - insbesondere APEX."