Beliebige Karten mit OpenLayers darstellen

Heute will ich zeigen, wie einfach man mit Hilfe von OpenLayers polygonbasierte Karten im Browser darstellen kann. Die Möglichkeit mit diesen zu interagieren bekommt man bei OpenLayers zusätzlich geschenkt. Die Karten, die ich darstellen möchte, habe ich nach dem im vorigen Post beschriebenen Verfahren erzeugt.

Die erzeugte Karte exportiere ich im KML-Format. OpenLayers ermöglicht es, Repräsentierungen in KML direkt als Layer einzufügen und einzelne Elemente zu selektieren. Ergänzt durch die Möglichkeit die Darstellung der Kartenelemente anpassen zu können bringt OpenLayers bereits alles mit, um Karten nach eigenem belieben zu gestalten.

Beginnen wir also mit dem Export der Karte im KML-Format. KML beschreibt geometrische Daten in XML-Syntax und wird unter anderem von Google Earth verwendet. Für KML gibt es praktischerweise Java-Bibliotheken, die einem viel Arbeit beim Exportieren abnehmen. Ich habe mich für die “Java API for KML” entschieden. Diese ist praktischerweise in den Maven-Repositories enthalten und problemlos verwendbar.

Der KML-Export erhält die Karte, die durch die Datenstruktur Map repräsentiert wird und aus einer Menge von Field-Objekten besteht und exportiert die enthaltenen Polygone im KML-Format:

public class KmlExport {
 
    public static void export(Map map, File file) throws FileNotFoundException {
        KmlExport kmlExport = new KmlExport(map);
        kmlExport.export(file);
    }
 
    private final Map map;
    private Kml kml;
    private Folder folder;
 
    public KmlExport(Map map) {
        this.map = map;
        kml = new Kml();
        folder = kml.createAndSetFolder();
        initializeKml();
    }
 
    private void initializeKml() {
        for (Field field : map.getFields()) {
            addField(field);
        }
    }
 
    private void addField(Field field) {
        Poly polygon = field.getPolygon();
        Placemark placemark = folder.createAndAddPlacemark();
        createPolygon(placemark, polygon);
        placemark.withId(field.getId());
    }
 
    private Polygon createPolygon(Placemark placemark, Poly polygon) {
        Polygon kmlPolygon = placemark.createAndSetPolygon();
        kmlPolygon.createAndSetOuterBoundaryIs().createAndSetLinearRing().
        	withCoordinates(getCoordinateList(polygon));
        return kmlPolygon;
    }
 
    private List<Coordinate> getCoordinateList(Poly polygon) {
        List<Coordinate> coordinates = Lists.newLinkedList();
        for (int i = 0; i < polygon.getNumPoints(); i++) {
            coordinates.add(new Coordinate(polygon.getX(i), polygon.getY(i)));
        }
        return coordinates;
    }
 
    private void export(File file) throws FileNotFoundException {
        kml.marshal(file);
    }
}

Erzeugt wird dabei eine KML-Datei, bestehend aus einer Placemark für jedes Feld.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<kml xmlns="http://www.opengis.net/kml/2.2"
 xmlns:gx="http://www.google.com/kml/ext/2.2"
 xmlns:atom="http://www.w3.org/2005/Atom"
 xmlns:xal="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0">
    <Folder>
        <Placemark id="field-20:58">
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>
                        15.712625560320168,65.93689948581041
                        17.33884357493316,50.63783991486605
                        26.27898632591434,53.348566036849846
                        25.456421744205493,65.6200372838336
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </Placemark>
        ...
    </Folder>
</kml>

Damit sind die Vorarbeiten auch schon abgeschlossen. Weiter geht es mit OpenLayers. Unter http://trac.osgeo.org/openlayers/wiki/HowToDownload kann man sich die letzte stabile Version herunterladen. In der Zip-Datei von OpenLayers finden sich Beispiele, Dokumentation und die Implementierung. Wir benötigen die Verzeichnisse img, lib und theme, die wir in ein Verzeichnis auf einem beliebigen Webserver kopieren. Die exportierte KML-Datei sollte ebenfalls dort abgelegt werden. Nun erstellen wir im selben Verzeichnis die Datei index.html und füllen Sie mit dem folgenden Inhalt:

<html>
	<head>
		<script src="lib/OpenLayers.js"></script>
		<script type="text/javascript">
		var map;
 
		function init(){
			map = new OpenLayers.Map('map');
			var kmlLayer = new OpenLayers.Layer.Vector("KML", {
				isBaseLayer: true,
				strategies: [new OpenLayers.Strategy.Fixed()],
				protocol: new OpenLayers.Protocol.HTTP({
					url: "map.kml",
					format: new OpenLayers.Format.KML({
						extractStyles: true,
						extractAttributes: true,
						maxDepth: 2
					})
				})
			});
 
			map.addLayers([kmlLayer]);
			map.addControl(new OpenLayers.Control.MousePosition());
			map.setCenter(new OpenLayers.LonLat(50, 50), 2);
		}
		</script>
	</head>
	<body onload="init()">
		<div id="map" style="width:300px; height:300px;
			border: 1px solid black;"></div>
	</body>
</html>

Schauen wir zunächst in den HTML-Teil: Hier wird ein div mit der id “map” definiert. Dieses Element dient als Platzhalter für die Karte. In der Definition des body-Elements legen wir fest, dass die JavaScript-Funktion init() aufgerufen werden soll, sobald der Browser die Seite geladen hat. In der Funktion init() erzeugen wir ein OpenLayers.Map Objekt und übergeben die Id des Platzhalters. Anschließend wird ein Layer definiert und per addLayers Aufruf zur Karte hinzugefügt. In der Definition des Layers geben wir an, dass er als Baselayer fungieren soll. OpenLayers erfordert immer exakt einen BaseLayer, der verantwortlich für Projektion, Koordinatensystem und Zoom der Karte ist. Außerdem geben wir an, dass die Daten per HTTP zu laden sind und wo diese zu finden sind. Zu beachten ist hierbei, dass eine HTTP-Anfrage per JavaScript von den meisten Browsern nur dann gestattet wird, wenn der Aufruf als Ziel den selben Server hat, von dem auch die Hauptseite geladen wurde. Deshalb sollten alle Dateien auf einem Webserver liegen und von diesem geladen werden. Ein direkter Zugriff über das lokale Dateisystem hat bei mir nicht funktioniert, da der Browser die JavaScript-Anfrage zum Laden der KML-Datei vom Dateisystem unterbindet. Nachdem der Layer hinzugefügt wurde wird noch ein Control hinzugefügt, das die Koordinaten der aktuellen Mausposition ausgibt. Dieses ist nicht notwendig, aber für die Fehlersuche recht brauchbar. Als letzten Schritt positionieren wir noch den Kartenausschnitt.

Als Ergebnis sollte nun eine Karte wie neben im Bild angezeigt werden. Der dargestellte Ausschnitt kann dank OpenLayers ohne weiteres Zutun verschoben werden und auch die Zoomstufe kann angepasst werden.

 

Selektion von einzelnen Feldern

Als nächstes möchte ich zeigen, wie wir einzelne Felder selektieren können. Dazu fügen wir ein weiteres Control zur Map hinzu:

var selectControl = new OpenLayers.Control.SelectFeature(kmlLayer, {
		hover: false});
selectControl.handlers.feature.stopDown = false;
map.addControl(selectControl);
selectControl.activate();

Nun sind einzelne Felder bereits selektierbar. Das Deaktivieren des Flags stopDown=false ist notwendig, um den Kartenausschnitt noch verschieben zu können, da andernfalls die Mouseevents nicht weitergegeben werden. Um etwas mehr Kontrolle über Selektionen zu erhalten übergeben wir dem SelectFeature zusätzlich noch zwei Callback-Funktionen:

var selectControl = new OpenLayers.Control.SelectFeature(kmlLayer, {
	hover: false,
	onSelect: onSelectFeature,
	onUnselect: onUnselectFeature});
//...
 
function onSelectFeature(feature) {
	//...
}
 
function onUnselectFeature(feature) {
	//...
}

Durch die beiden Callbacks können wir auf Selektionen reagieren und eigene Routinen ansprechen. Dies kann zum Beispiel das hervorheben benachbarter Felder sein.

 

Identifizierung von benachbarten Feldern

Um benachbarte Felder zu einem selektierten Feld zu identifizieren kann man alle Felder auf übereinstimmende Eckpunkte hin überprüfen. Dies sind allerdings sehr viele Berechnungen die bei jeder Selektion ausgeführt werden müssen. Eine Alternative ist, die Information über benachbarte Felder direkt zu jedem Feld abzuspeichern. So ist die Berechnung nur einmal beim Erstellen der Karte notwendig. Im Post zur Erstellung polygonbasierter Karten wurde die Nachbarschaftsbeziehung von Feldern bereits mit erfasst. Diese Information muss allerdings noch mit in die KML-Datei exportiert werden. KML ermöglicht einen Bereich für beliebige Daten, den wir nutzen können, um für jedes Feld die Ids der benachbarten Felder zu speichern. Dazu ergänzen wir im Export die Methode addField um den Aufruf

placemark.createAndSetExtendedData().createAndAddData(
	getNeighborString(field)).withName("neighbors");

und die Methodendefinition:

private String getNeighborString(Field field) {
    Set<Field> neighbors = field.getNeighbors();
    StringBuilder builder = new StringBuilder();
    for (Field neighbor : neighbors) {
        builder.append(neighbor.getId());
        builder.append(" ");
    }
    return builder.toString().trim();
}

Als Ergebnis erhalten wir in der erzeugten KML-Datei einen zusätzlichen Eintrag:

<Placemark id="field-20.59559517983287:58.5135086149343">
	<ExtendedData>
		<Data name="neighbors">
			<value>
			field-12.499276737294759:57.65290807656499
			field-24.421019924908876:45.89702072013858
			field-31.22221212521951:59.22581755872216
			field-21.06757660005685:73.02735953357276
			</value>
		</Data>
	</ExtendedData>
	<Polygon>
	...

Diese Daten können wir bei z.B. bei einer Selektion auslesen und allen benachbarten Feldern einen bestimmten Style zuweisen:

function onSelectFeature(feature) {
	var neighbors = feature.data.neighbors.value.split(" ");
 
	for(var i=0; i < feature.layer.features.length; i++) {
		var element = feature.layer.features[i];
		for(var j=0; j < neighbors.length; j++) {
			if(element.fid == neighbors[j]) {
				element.style = neighborStyle.defaultStyle;
			}
		}
	}
	feature.layer.redraw();
}

Den verwendeten Style neighborStyle haben wir bisher allerdings noch nicht definiert.

 

Eigene Styles für die Darstellung der Karte

OpenLayers erlaubt es in einer CSS ähnlichen Art Styles zu definieren und an bestimmte Objekte zu binden. Hier findet sich eine Übersicht möglicher Eigenschaften, die definiert werden können. Im folgenden definieren wir drei Styles, einen Standard, einen für selektierte Felder und einen für zum selektierten Feld benachbarte Felder.

var defaultStyle = new OpenLayers.Style({
  'fillColor': '#CCCCCC'
});
 
var selectStyle = new OpenLayers.Style({
  'fillColor': '#5555DD'
});
 
var neighborStyle = new OpenLayers.Style({
	'fillColor': '#44FF44'
});
 
var styleMap = new OpenLayers.StyleMap({'default': defaultStyle,
	 'select': selectStyle, "neighbor": neighborStyle});

Die definierten Styles werden in einem StyleMap Objekt zusammengefasst und an den Layer übergeben, in dem sie verwendet werden sollen:

var kmlLayer = new OpenLayers.Layer.Vector("KML", {
	styleMap: styleMap,
	//...

Dies erlaubt uns im Selektions-Callback den Style der benachbarten Felder zu ändern. Um ein Feld zurück auf den Standard zu setzen (z.B. im onUnselectFeature Callback) wird element.style auf null gesetzt.

Das erzielte Ergebnis sollte wie rechts im Bild aussehen. Ich hoffe ich konnte mit der kurzen Einführung in OpenLayers ein paar Startschwierigkeiten aufheben. Für mich selbst ist OpenLayers noch neu und ich suche z.B. noch eine Lösung, um auf bestimmte Felder direkt zugreifen zu können ohne über alle Elemente eines Layers iterieren zu müssen. Falls ihr hier eine Lösung kennt, lasst es mich wissen.

Dieser Beitrag wurde unter Tutorials veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Hinterlasse eine Antwort