Leaflet Geocoding plugin: search addresses on your Leaflet map

Exciting news! We are happy to announce that we released a Geoapify Geocoding Plugin for Leaflet. This plugin lets you integrate the Geoapify Geocoding service with a Leaflet map in just a few steps. We hope that this handy tool will help your users find addresses and desired locations faster!

We made the Geocoding control lightweight, easy-to-use, flexible and customizable. It is open source and has a permissive license. It has no dependencies, so it doesn't add bulk or complexity to your project. You can find the source code and the npm library by following the links below:

This article briefly introduces the Leaflet Geocoding plugin, its use and customization.

Install Leaflet Geocoding control plugin

As you'd expect, you can also install the Leaflet Geocoding plugin with the npm command:

npm i @geoapify/leaflet-address-search-plugin

To use the plugin, you must also add its CSS file to your project. You can do this in the same way and the same place as you've added the Leaflet CSS files:

"node_modules/leaflet/dist/leaflet.css",
"node_modules/@geoapify/leaflet-address-search-plugin/dist/L.Control.GeoapifyAddressSearch.min.css"

Then you can include the plugin in your code, for example, like this:

import * as L from 'leaflet';
import '@geoapify/leaflet-address-search-plugin';

For small map projects, the most straightforward option is to add the Geocoding Control to your HTML files. The plugin is available on Unpkg CDN servers, so you can add the JS and CSS files links to the <head> section of your HTML file:

<link rel="stylesheet" href="https://unpkg.com/@geoapify/leaflet-[email protected]^1/dist/L.Control.GeoapifyAddressSearch.min.css" />
<script src="https://unpkg.com/@geoapify/[email protected]^1/dist/L.Control.GeoapifyAddressSearch.min.js"></script>

Once you've included the plugin in your code, you can start using it right away.

Add Geocoding control to your Leaflet map

Here's a code sample that shows you how to add geocoding functionality to your Leaflet map:

var map = L.map('my-map').setView([48.1500327, 11.5753989], 6);

var myAPIKey = "YOUR_API_KEY"; // Get an API Key on https://myprojects.geoapify.com
var mapURL = L.Browser.retina
  ? `https://maps.geoapify.com/v1/tile/{mapStyle}/{z}/{x}/{y}.png?apiKey={apiKey}`
  : `https://maps.geoapify.com/v1/tile/{mapStyle}/{z}/{x}/{y}@2x.png?apiKey={apiKey}`;

// Add map tiles layer. Set 20 as the maximal zoom and provide map data attribution.
L.tileLayer(mapURL, {
  attribution: 'Powered by Geoapify | © OpenMapTiles © OpenStreetMap contributors',
  apiKey: myAPIKey,
  mapStyle: "osm-bright-smooth", // More map styles on https://apidocs.geoapify.com/docs/maps/map-tiles/
  maxZoom: 20
}).addTo(map);

// Add Geoapify Address Search control
const addressSearchControl = L.control.addressSearch(myAPIKey, {
  position: 'topleft',
  resultCallback: (selectedAddress) => {
    console.log(selectedAddress);
  },
  suggestionsCallback: (suggestions) => {
    console.log(suggestions);
  }
});
map.addControl(addressSearchControl);
L.control.zoom({ position: 'bottomright' }).addTo(map);

To use the Leaflet Geocoding plugin, you'll need a Geoapify API key. Get your API key by registering and creating a project on myprojects.geoapify.com if you don't have one already.

As you can see from the code sample above, you can use the same API key for a Leaflet baselayer or map tiles and geocoding.

Here is a live demo that demonstrates Leaflet Geocoding control:

Display addresses on a map

Geoapify's new Geocoding Control for Leaflet makes searching addresses a breeze and allows you to get notifications when the control shows new address suggestions or when a user has chosen an address.

The control doesn't alter the map state or your map view. It doesn't add or delete markers. Instead, it allows you to react to changes and decide what and how to show as a result of those changes.

The code sample below shows how to display addresses entered by a user as markers. The example uses the Map Marker API to create markers on the map:

let marker = null;
let suggestionMarkers = [];

const suggestionsMarkerIcon = L.icon({
  iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&shadowColor=%23fafafa&color=%23fff351&size=small&scaleFactor=2&apiKey=${myAPIKey}` /* get an API Key on https://myprojects.geoapify.com */,
  iconSize: [25, 37], // size of the icon
  iconAnchor: [12.5, 34], // the point on the icon which will correspond to the marker's location, substruct the shadow
  popupAnchor: [0, -36] // the point from which the popup should open relative to the iconAnchor
});

const resultMarkerIcon = L.icon({
  iconUrl: `https://api.geoapify.com/v1/icon/?type=awesome&shadowColor=%23fafafa&color=%2336d867&scaleFactor=2&apiKey=${myAPIKey}` /* get an API Key on https://myprojects.geoapify.com */,
  iconSize: [31, 46],
  iconAnchor: [15.5, 42],
  popupAnchor: [0, -45] 
}); 

const addressSearchControl = L.control.addressSearch(myAPIKey /* get an API Key on https://myprojects.geoapify.com */, {
  position: 'topleft',
  resultCallback: (address) => {
    removeMarkers();

    if (!address) {
      return;
    }

    marker = L.marker([address.lat, address.lon], {icon: resultMarkerIcon}).addTo(map);
    if (address.bbox && address.bbox.lat1 !== address.bbox.lat2 && address.bbox.lon1 !== address.bbox.lon2) {
      map.fitBounds([[address.bbox.lat1, address.bbox.lon1], [address.bbox.lat2, address.bbox.lon2]], { padding: [100, 100] })
    } else {
      map.setView([address.lat, address.lon], 15);
    }
  },
  suggestionsCallback: (suggestions) => {    
    removeMarkers();

    if (!suggestions || !suggestions.length) {
      return;
    }

    const bbox = L.latLngBounds();
    suggestions.forEach(suggestion => {
      bbox.extend([suggestion.lat, suggestion.lon]);
      suggestionMarkers.push(L.marker([suggestion.lat, suggestion.lon], {icon: suggestionsMarkerIcon}).bindPopup(suggestion.formatted).addTo(map));
    });

    if (bbox.isValid) {
      map.fitBounds(bbox, { padding: [100, 100] })
    } else {
      map.setView([suggestions[0].lat, suggestions[0].lon], 15);
    }
  }
});

function removeMarkers() {
    if (marker) {
      marker.remove();
    }

    if (suggestionMarkers) {
      suggestionMarkers.forEach(marker => marker.remove());
      suggestionMarkers = [];
    }
}

The Leaflet Geocoding plugin returns Geoapify Geocodong objects. You can find information about the object structure on the Geocoding documentation page.

Sometimes, the geocoding result is not a single point but a region instead. For example, when you receive information for a city or a country. The bbox property of an address lets you adjust the map's view.

Here is a demo of a visualizing address suggestions and user selections as markers:

Here are some helpful Leaflet methods for setting a map view:

MethodDescriptionExample
setView(center, zoom, options?)Set a map view to the concrete center and zoom values.map.setView([48.1500327, 11.5753989], 6)
panTo(center, options?)Zooms the map to a given center.map.panTo([47.125641, 5.521431])
flyTo(center, zoom, options?)Smoothly pan and zoom the map to set the view.map.flyTo([48.1500327, 11.5753989], 6)
panInside(point, options?)Pans the map so that the point is visible.map.panInside([48.1500327, 11.5753989])
fitBounds(bounds, options?)Set the map view to zoom and center to show the bounds.map.fitBounds([[47.125641, 5.521431], [46.878355, 6.370686]], {padding: 50})
flyToBounds(bounds, options?)Set the map view to zoom and center to show the bounds with an animation.map.flyToBounds([[47.125641, 5.521431], [46.878355, 6.370686]], {padding: 50})
panInsideBounds(bounds, options?)Pans the map so that the bounds are visible.map.panInsideBounds([[47.125641, 5.521431], [46.878355, 6.370686]])

On the Leaflet documentation page, you can find detailed descriptions of all the methods for changing a map's state.

Customize Geocoding control

We've made the Geocoding Control fit with Leaflet's existing styling and use default style and font settings. However, you can easily make the style look different - change colors, borders, size, and font.

Provide a custom class name when creating the Geocoding control:

const addressSearchControl = L.control.addressSearch(myAPIKey /* get an API Key on https://myprojects.geoapify.com */, {
  position: 'topleft',
  className: 'custom-address-field', // add a custom class name
  resultCallback: (address) => {
    ...
  },
  suggestionsCallback: (suggestions) => {
    ...
  }
});
map.addControl(addressSearchControl);
L.control.zoom({ position: 'bottomright' }).addTo(map);

Then add custom CSS in your styles file:

.leaflet-bar.custom-address-field {
  border: 5px solid #ff5722;
  border-radius: 10px;
}

.custom-address-field .geoapify-address-input {
  line-height: 36px;
  height: 36px;
  font-size: 16px;
}

.custom-address-field .geoapify-clear-button {
  height: 36px;
}

.custom-address-field .address {
  font-size: 14px;
}

Here is a live demo of the example above:

What's next