Testing Nominatim Geocoder with Leaflet.js

street sign showing directions to various places

I recently wrote a post about the Esri Geocoding plugin for the Leaflet.js web mapping library.  In my spare time, I have been working on developing a version of an existing web map that does not use any  Esri-Leaflet plugins.  One of these features of the map is geocoding or searching by street address or place name.

In researching alternatives to the Esri World Geocoding service, I discovered Open Street Map’s Nominatim and the Leaflet-Control-OSM-Geocoder plugin for Leaflet.  I wanted to share how I’ve tested implementing this, as well as provide my honest thoughts about Nominatim.  I have a working demo on CodePen.

Using Leaflet-Control-OSM-Geocoder

Let me start by sharing how I’ve been testing the plugin to use Nominatim in Leaflet.  There is a stylesheet and javascript file required for this plugin.  So in the index.html file:

<!doctype html>
<html lang="en">
    <head>
        <!-- standard elements -->
        <!-- various stylesheets -->
        <!-- Leaflet OSM Geocoder -->
        		<link rel="stylesheet" type="text/css" href="[path][to]/leaflet-geocoder-osm/Control.OSMGeocoder.css" />    </head>
    <body>
       <!-- standard elements -->
       <!-- container for map -->
<div id="map"></div>
<!-- various scripts -->
       <!-- Leaflet OSM Geocoder -->
       <script src="[path]/[to]/leaflet-geocoder-osm/Control.OSMGeocoder.js"></script>
    </body>
</html>

 

The next part is creating the L.Control.OSMGeocoder object in your app’s appropriate JavaScript file.  Below is a screenshot of the options you can pass into the object.  You can use the bounds property to restrict search results within a coordinate bounding box.  This can be useful if you want to limit results to a state or country.

options for the L.Control.OSMGeocoder control for Leaflet.js web mapping library
options to pass into the geocoding control object

I have created a different callback function then the default.  This is where you can define what happens when a user searches for address/place.  I handle both cases for results and no results.  To handle no results (you could call this an error, I suppose),  I created some DOM elements outside of the callback to contain the “error message.”

If there are no results, I add a message containing the search string to a DOM element, and display it on the map.  If there are results, I add a marker for the first result object, add it to the map, open a pop-up, and center the map on the result.  Below is my working code:

// these DOM elements are created in the script due to the map being
// embedded on a Drupal site, and wanting to limit HTML in the editor contenxt
// create geocode error box element
var geocodeErEl = document.createElement('div');
// set id for element
geocodeErEl.setAttribute('id', 'geocodeError');

// create close button for error box
var geocoderErCloseEl = document.createElement('input');
// set id for element
geocoderErCloseEl.setAttribute('id','errorCloseBtn');
// set type of input element
geocoderErCloseEl.setAttribute('type','button');
// set value for input element
geocoderErCloseEl.setAttribute('value', 'X');

// create paragraph element to contain error text
var geocodeErTextEl = document.createElement('p');
// set id for element
geocodeErTextEl.setAttribute('id','errorText');

// append elements to geocode error div
// as this is test code, you notice a mix of Vanilla JS and jQuery
jQuery(geocodeErEl).append(geocoderErCloseEl);
jQuery(geocodeErEl).append(geocodeErTextEl);

// append error message elememnt to map
jQuery('#map').append(geocodeErEl);

// container for address search results
var addressSearchResults = new L.LayerGroup().addTo(map);
// limit search to coordinate bounding box
var searchBounds = L.latLngBounds([36.548091,-82.576010],[43.603546,-73.268137]); 

/*** Geocoder ***/
// OSM Geocoder
var osmGeocoder = new L.Control.OSMGeocoder({
    collapsed: false,
    position: 'topright',
    text: 'Search',
    placeholder: 'Enter address',
    bounds: searchBounds,
    callback: function(results) {
            // error box element (div)
            var geocodeErrorBox = jQuery('#geocodeError');
            // paragraph element containing message
            var geocodeErrorText = jQuery('#errorText');

            // close error box if it is open
            if (!geocodeErrorBox.css('display','none')) {
                geocodeErrorBox.hide();
            }

            // If no results are found, add a message to the screen
            if (results.length == 0) {
	        // placeholder="[placeholder text]" is key to selecting DOM element
                // search string entered by user
                var searchText = jQuery('.leaflet-control-geocoder-form input[placeholder="Enter address"]').val();
                // get search text or result text and put that in box
                geocodeErrorText.html('No results found for ' + searchText);
                geocodeErrorBox.show();
                return;
	    }

            // clear previous geocode results
            addressSearchResults.clearLayers();

            // create icon for result
            // using Leaflet Awesome Markers plugin in this example
            var addressSearchIcon = L.AwesomeMarkers.icon({
                icon: 'map',
                prefix: 'fa',
                markerColor: 'orange',
                iconColor: '#fff'
             });

            // get coordinates for first result object
            var coords = L.latLng(results[0].lat,results[0].lon);

            // create a marker for result
            var marker = L.marker(coords, {
                icon: addressSearchIcon
            });

            // add result object to map and zoom to
            addressSearchResults.addLayer(marker);
            this._map.addLayer(marker).setView(coords,17);  

            // open pop-up for location
            var popup = L.popup({closeOnClick: true}).setLatLng(coords).setContent(results[0].display_name).openOn(map);
        }
    }).addTo(map);

// add event listener to click event for error message close button
jQuery('#errorCloseBtn').click(function() {
   jQuery('#geocodeError').hide();
});

Esri Versus Nominatim

My personal opinion is that the Esri World Geocoding Service is a superior to Nominatim.  There are several addresses that exist that map with Esri but not with Nominatim.  And when I looked into editing Open Street Map (OSM) to improve Nominatim, I learned road segments do not have fields for address ranges.  Working with my County’s Public Safety department, where we dispatch 911 calls based upon address ranges, this just seems absurd.

If you’re looking to build a web mapping application without the use of commercial products, then I guess Nominatim will do.  But I find it lacking, primarily because of the underlying data scheme.  I have no idea why Open Street Map does not include address ranges in the roads.  While I could find time to edit the roads in Open Street Map, I will never have the time to edit individual address points.  I should mention Leaflet lists multiple geocoding plugins, in case you want to see other options.  And as a reminder, I have a demo of this on CodePen.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s