Geocoding in Verbindung mit einer Leaflet-App

Gestern schrieb ich in einem Artikel, wie Adressen aus Koordinaten mittels des Dienstes Nominatim, unter Zuhilfenahme von OpenStreetMap-Daten, ermittelt werden können. Mithilfe der freien Bibliothek Leafleat, kann dies nun in eine kleine Webapplikation gegossen werden.

Anhand der Geo-Koordinate wird die Adresse ermittelt und dargestellt.

Ziel ist es eine Applikation zu bauen, welche eine Karte darstellt und bei einem Klick auf diese Karte den Geocoding-Dienst anfragt. Sobald die Antwort vom Dienst eingetroffen ist, soll in einem Pop-up die Adresse dargestellt werden. Nachdem ein Grundgerüst in Form einer HTML-Datei erstellt wurde, werden dort die benötigten Bibliotheken (Leaflet und jQuery) eingebunden. Anschließend kann die Karte erzeugt werden. Damit diese im Vollbild dargestellt wird, findet sich eine entsprechende CSS-Definition in der Datei:

<style type="text/css">
	body {
		padding: 0;
		margin: 0;
	}

	html, body, #map {
		height: 100%;
	}
</style>

Anschließend kann der TileLayer angelegt werden und der Map zugewiesen werden:

// Options
var centerLatitude=53.49577;
var centerLongitude=13.30873;
var zoomLevel=15;

// Define layer
var openStreetMapLayer = new L.TileLayer(
		'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
		{attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>'});

// Define map
var map = L.map('map', {
	center: new L.LatLng(centerLatitude, centerLongitude),
	zoom: zoomLevel,
	layers: [openStreetMapLayer]
});

Für die Karte muss im nächsten Schritt ein Click-Event definiert werden, welches den AJAX-Request gegen den Geocoding-Dienst auslöst und den Marker erstellt. Der jeweilige Marker wird in eine separate Ebene gepackt und beim nächsten Klick wieder gelöscht und anschließend ein neuer Marker erstellt:

// Marker managment
var markerLayer;
map.on('click', function(e){

	// Console debug
	var coord = e.latlng;
	var lat = coord.lat;
	var lng = coord.lng;
	console.log("You clicked the map at latitude: " + lat + " and longitude: " + lng);

	// Marker managment		
	if(markerLayer != undefined) {
		map.removeLayer(markerLayer);
	}
	
	markerLayer = new L.Marker(e.latlng);
	map.addLayer(markerLayer);

	// Call geocode api
	var request =	$.ajax({
		method: "GET",
		url: "https://nominatim.openstreetmap.org/reverse?lat=" + lat + "&lon=" + lng + "&format=json"
	});

	request.done(function( json ) {
		console.log(json);
		markerLayer.bindPopup(json.display_name).openPopup();
	});
});

Am Stück sieht die Applikation wie folgt aus:

<!DOCTYPE html>
<html>
<head>
	<title>Leaflet address encoder</title>
	<meta charset="utf-8" />

	<meta name="viewport" content="width=device-width, initial-scale=1.0">

	<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css"
   integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
   crossorigin=""/>
	
	<style type="text/css">
		body {
			padding: 0;
			margin: 0;
		}

		html, body, #map {
			height: 100%;
		}
	</style>
	
	<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"
   integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
   crossorigin=""></script>
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body>
	<div id="map"></div>
	
	<script>
	        // Options
		var centerLatitude=53.49577;
		var centerLongitude=13.30873;
		var zoomLevel=15;
		
		// Define layer
		var openStreetMapLayer = new L.TileLayer(
				'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
				{attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>'});
		
		// Define map
		var map = L.map('map', {
		    center: new L.LatLng(centerLatitude, centerLongitude),
		    zoom: zoomLevel,
		    layers: [openStreetMapLayer]
		});
		
		// Marker managment
		var markerLayer;
		map.on('click', function(e){

			// Console debug
			var coord = e.latlng;
			var lat = coord.lat;
			var lng = coord.lng;
			console.log("You clicked the map at latitude: " + lat + " and longitude: " + lng);

			// Marker managment		
			if(markerLayer != undefined) {
				map.removeLayer(markerLayer);
			}
			
			markerLayer = new L.Marker(e.latlng);
       		map.addLayer(markerLayer);

			// Call geocode api
			var request =	$.ajax({
				method: "GET",
				url: "https://nominatim.openstreetmap.org/reverse?lat=" + lat + "&lon=" + lng + "&format=json"
			});

			request.done(function( json ) {
				console.log(json);
				markerLayer.bindPopup(json.display_name).openPopup();
			});
  		});
	</script>
</body>
</html>

Alternativ kann der Quelltext heruntergeladen bzw. ausprobiert werden.

Adresse anhand einer Koordinate ermitteln

Im Umfeld von OpenStreetMap existieren eine Reihe von Diensten und APIs. Einer dieser Dienste ist Nominatim. Dabei handelt es sich um einen Geocoding-Dienst auf Basis der OpenStreetMap-Daten. Mithilfe der API kann anhand einer Koordinate in Form von Latitude und Longitude, eine Adresse ermittelt werden. Ein Beispielaufruf könnte wie folgt aussehen:

GET https://nominatim.openstreetmap.org/reverse?lat=53.49567958129127&lon=13.302898406982424&format=json

Gewöhnungsbedürftig an diesem Aufruf ist der Parameter format, über welchen das Format des Response definiert wird. Im Sinne einer sinnvollen REST-API, hätte dies besser über den Header-Parameter Accept gelöst werden sollen. Als Ergebnis erhält der Response die ermittelte Adresse:

{
    "place_id": 239463758,
    "licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
    "osm_type": "way",
    "osm_id": 626862936,
    "lat": "53.4957878878448",
    "lon": "13.3026220240811",
    "display_name": "Weinbergsweg, Burg Stargard, Stargarder Land, Mecklenburgische Seenplatte, Mecklenburg-Vorpommern, 17094, Deutschland",
    "address": {
        "road": "Weinbergsweg",
        "town": "Burg Stargard",
        "county": "Stargarder Land",
        "state": "Mecklenburg-Vorpommern",
        "postcode": "17094",
        "country": "Deutschland",
        "country_code": "de"
    },
    "boundingbox": [
        "53.4949976",
        "53.4960744",
        "13.3023076",
        "13.3027343"
    ]
}

Neben der Geocoding-Funktionalität enthält der Dienst eine Reihe weiterer Funktionen, welche ihn zu einer Art Suchmaschine für OpenStreetMap-Daten machen. Die offizielle Projektseite des Dienstes ist unter nominatim.org zu finden. Der Quelltext des Dienstes ist auf GitHub zu finden. Das Projekt ist unter der GPL2 lizenziert und damit freie Software.

JavaScript-Animationen in 140 Zeichen

Damals, als die Welt noch in Ordnung war, erlaubte Twitter 140 Zeichen pro Tweet. Diese Grenze haben die Menschen hinter dem Projekt Dwitter genommen und in eine Herausforderung umgewandelt. Bei Dwitter geht es darum in maximal 140 Zeichen eine JavaScript-Animation zu erstellen. Die Ergebnisse können sich sehen lassen; mittlerweile befinden sich etliche solcher Animationen auf der Seite und zeigen was in 140 Zeichen alles möglich ist.

In 140 Zeichen zu einer JavaScript-Animation

Zu finden ist das Projekt unter dwitter.net. Die Webseite, ein in Python geschriebenes Projekt, ist freie Software und unter Apache License in der Version 2 lizenziert. Der Quelltext kann über GitHub bezogen werden.

Daten unter Java in ein Byte-Array umwandeln

Für ein Projekt wollte ich eine Liste von long-Werten unter Java in ein einzelnes Byte-Array umwandeln. Dafür kann unter Java die Klasse ByteBuffer genutzt werden. In diesem Beispiel wird eine Liste namens dataStream definiert:

List dataStream = new ArrayList<>();

In dieser Liste befinden sich eine unbestimmte Anzahl von Werten. Mit der Klasse ByteBuffer kann diese Liste bzw. die Werte in der Liste in ein Byte-Array überführt werden:

ByteBuffer byteBuffer = ByteBuffer.allocate(dataStream.size() * 8);

for(long value: dataStream) {
	byteBuffer.putLong(value);
}

byte[] array = byteBuffer.array();

Im ersten Schritt wird der ByteBuffer mit einer Kapazität angelegt, welcher der Größe der long-Werte in Byte entspricht. Anschließend werden die long-Werte in den ByteBuffer geschrieben. Ist dies geschehen kann ein Byte-Array aus dem ByteBuffer erstellt werden. Damit ist die Umwandlung abgeschlossen.

curl zur Abfrage von REST-APIs benutzen

Die Aufgabe des freien Kommandozeilentools curl ist einfach beschrieben: Datentransfer. So unterstützt curl unterschiedlichste Protokolle wie FTP, HTTP, HTTPS, IMAP, SCP, SMB und viele mehr. Ein einfacher Download einer Datei über HTTP bzw. HTTPS würde mit curl wie folgt aussehen:

curl -O https://example.com/file.zip

Auch ein Transfer z.B. per FTP ist kein Problem:

ftp://example.com/file.zip

Allerdings beherrscht curl wesentlich mehr Operationen als nur das Herunterladen von Dateien. So kann curl genutzt werden, um REST-APIs zu benutzen. Diese APIs arbeiten nicht nur mit dem HTTP-Verb GET, sondern auch mit anderen Verben wie POST und PUT. Ein einfacher GET-Request wurde mittels curl wie folgt aussehen:

curl -X GET https://example.com/

Ein POST-Request wird auf die gleiche Art durchgeführt:

curl -X POST https://example.com/

Sollen zusätzlich Daten übertragen werden, so geschieht dies mit dem Parameter -d:

curl -X POST https://example.com/  -d '{
	field: "data",
	field2: "data",
	field3: "data"
}'

Damit werden die Daten im Body des Requests mitgesendet. Auch die Übergabe von Headern ist mittels curl möglich:

curl -X POST https://example.com/ 
 -H 'HeaderField: headerValue'
 -d '{
	field: "data",
	field2: "data",
	field3: "data"
}'

Manche APIs und andere Services blockieren Abrufe über curl manchmal. Dabei wird der Useragent von curl ausgesperrt. Allerdings kann dieser einfach geändert werden:

curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0" -X GET https://example.com/

Damit können solche fragwürdigen Maßnahmen, welche zum Ausschluss von curl führen, umgangen werden. Daneben verfügt curl über viele weitere Operationen bzw. Optionen. So kann z.B. mittels des Parameters -I nur der Header des Response bezogen werden:

curl -I -X GET https://example.com

Somit bietet curl die entsprechende Funktionalität um REST-APIs für Tests und ähnliches flexibel abzufragen.