Address lookups with Leaflet and Nominatim
I recently wrote a patch for joind.in to add a map of an event's location to the event detail page. With the same patch, I also replaced the location part of the event edit page with a solution that uses JQuery, Leaflet as map API, OpenStreetMap tiles and Nominatim for doing address lookups. This article forms a small tutorial on how to use this same set-up yourself.
The Basics
To start, we create a new directory for our project:
mkdir addresses cd addresses
Then I downloaded the Leaflet and jQuery libraries and extracted them in the js directory of the project:
mkdir js curl -L https://github.com/CloudMade/Leaflet/zipball/v0.4.5 -o leaflet.zip unzip leaflet.zip mv CloudMade-Leaflet-*/dist/* js rm -rf CloudMade-Leaflet-* rm leaflet.zip curl http://code.jquery.com/jquery-1.8.2.min.js -o js/jquery-1.8.2.min.js
As first step, we are simply going to show a map on a web page. The map is going to be full screen, and will not have any bells and whistles. The code to embed a map is small, but we will separate it into three files for clarity: a CSS file for our styles (site.css), an HTML file for the structure (index.html) and a JS file for all our JavaScript functions (js/map.js).
Let's start with the HTML file:
<html>
<head>
<title>Leaflet and Nominatim example</title>
<link rel="stylesheet" href="js/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="js/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" type="text/css" href="site.css">
<script src="js/leaflet.js"></script>
<script src="js/jquery-1.8.2.min.js"></script>
</head>
<body>
<div id="map"/>
<script src="js/map.js"></script>
</body>
</html>
This HTML file includes the Leaflet and jQuery libraries, as well as the default CSS file that Leaflet needs. We are also including our own CSS file (site.css):
body {
margin: 0;
}
div#map {
width: 100%;
height: 100%;
}
In the body of the HTML file, we place a <div> as contained for the map, and then include a JavaScript file that is responsible for embedding them map:
var map;
function load_map() {
map = new L.Map('map', {zoomControl: false});
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttribution = 'Map data © 2012 <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
osm = new L.TileLayer(osmUrl, {maxZoom: 18, attribution: osmAttribution});
map.setView(new L.LatLng(51.538594, -0.198075), 12).addLayer(osm);
}
window.onload = load_map;
If you request the index.html page now through the browser, you will see something like:
Adding the Address Search
In order to add an address lookup form, we need to add more HTML. Our HTML will feature an input box (for the address), a submit button, and a place holder to show our results in. We add this code between the <div id="map"/> and the <script... tags:
<div id="search"> <input type="text" name="addr" value="" id="addr" size="10" /> <button type="button" onclick="addr_search();">Search</button> <div id="results"/> </div>
To style this, we add the following at the end of our CSS file:
div#search {
background-color: white;
position: absolute;
bottom: 40px;
left: 40px;
width: auto;
height: auto;
padding: 10px;
}
div#search input {
width: 200px;
}
div#results {
font-style: sans-serif;
color: black;
font-size: 75%;
}
If we reload the page in our browser, we will see something like:
Now the only thing left to do is to implement the addr_search function. In our JS file (js/map.js) we add before window.onload = load_map; the following lines (split over multiple sections in this tutorial):
function addr_search() {
var inp = document.getElementById("addr");
$.getJSON('http://nominatim.openstreetmap.org/search?format=json&limit=5&q=' + inp.value, function(data) {
This above line uses jQuery's AJAX capabilities to request a URL, parse the JSON result and issue a callback if it worked. We query Nominatim here, with as format json and limiting the result to 5 items. Nominatim also supports other parameters, which are documented here.
var items = [];
$.each(data, function(key, val) {
items.push(
"<li><a href='#' onclick='chooseAddr(" +
val.lat + ", " + val.lon + ");return false;'>" + val.display_name +
'</a></li>'
);
});
For each of the items in our result, we create an <li> element which has an <a href containing a call to a JavaScript function (chooseAddr). This function is responsible for re-centering the map according to the picked latitude and longitude.
$('#results').empty();
if (items.length != 0) {
$('<p>', { html: "Search results:" }).appendTo('#results');
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('#results');
} else {
$('<p>', { html: "No results found" }).appendTo('#results');
}
});
}
This processes the results that came back from Nominatim. If there are results, we shows those including a Search results: header, and if there are no results, we show No results found.
Then we need to add one more function, the chooseAddr function which looks like:
function chooseAddr(lat, lng, type) {
var location = new L.LatLng(lat, lng);
map.panTo(location);
if (type == 'city' || type == 'administrative') {
map.setZoom(11);
} else {
map.setZoom(13);
}
}
We simply use the latitude and longitude from the function invocation, and in order to make things slightly nicer we zoom in a bit less if the item type is either a city or an administrative border. As each of the returned results actually includes a full bounding box, we probably can use that to zoom in better, but I will leave that for your own experiments - you'd want the panInsideBounds() method of Leaflet's Map class for that.
In the end, if we click on the Search button, a list is presented of all our search results:
And after clicking one of the links, we see the map centered on Paris:
The code for this example is available on github in my osm-tools repository at https://github.com/derickr/osm-tools/tree/master/leaflet-nominatim-example
Life Line
Updated 3 restaurants
I walked 3.1km in 29m25s
I walked 4.4km in 45m01s
I walked 5.4km in 55m28s
Updated a restaurant; Confirmed a hotel
I walked 6.3km in 1h12m59s
Paraphrasing opening keynote speaker at ConFoo: "Should we go back to the waterfall method of writing massive specs upfront to feed to AI coding agents?"
I walked 1.6km in 17m29s
I walked 2.1km in 17m44s
Updated a pub
I walked 2.6km in 26m41s
Merged pull request #1065
Comparison whether class is userland or internal used the wrong macro
PHP 8.6: zend_enum.h now mixes code with declarations
PHP 8.6: Argument names are now stored as zend_strings
Updated a bench and a waste_basket
I walked 8.3km in 1h25m37s
Created a recycling
I walked 10.5km in 1h46m57s
An interesting journey in story form, showing how English changed over time.
https://www.deadlanguagesociety.com/p/how-far-back-in-time-understand-english
A much better writer than I is summing up perfectly why I have such disdain for Generative AI/LLMs.
https://jonn.substack.com/p/so-why-do-i-feel-so-angry-about-this
Created a waste_basket; Updated a waste_basket; Deleted a bench
Created a bench; Updated 7 benches and a gate; Deleted 2 benches and a gate
Created 10 benches and 2 waste_baskets; Updated an information


Shortlink
This article has a short URL available: https://drck.me/addrlookup-9mg