Skip to content
Reverse Geocoding on Click

Get an Address from a Clicked Point in Maptoolkit Maps JS

Reverse geocoding turns a coordinate into an address. It is what a click-anywhere map, a drag-a-pin confirmation step and a “use my location” button all need, because none of them start from something the user typed. This example listens for a click, calls the Geocoding API reverse endpoint and shows the resulting address in a popup at that point.

const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [16.3722, 48.2082],
        zoom: 15,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    const popup = new maptoolkit.Popup({ closeButton: true, maxWidth: '260px' });
    let pending = 0;

    map.on('click', (e) => {
        const { lng, lat } = e.lngLat;
        const request = ++pending;

        popup.setLngLat([lng, lat]).setHTML('<div class="mtk-address">Looking up...</div>').addTo(map);

        const url = new URL('https://geocoder.maptoolkit.net/reverse');
        url.searchParams.set('lat', lat);
        url.searchParams.set('lon', lng);
        url.searchParams.set('language', 'en');
        url.searchParams.set('api_key', API_KEY);

        fetch(url)
            .then(r => r.json())
            .then(result => {
                // A later click already went out, so this answer is stale.
                if (request !== pending) return;

                const a = result.address || {};
                const street = [a.road, a.house_number].filter(Boolean).join(' ');
                const place = [a.postcode, a.city || a.town || a.village].filter(Boolean).join(' ');

                // Not every coordinate sits on an addressed feature. Fall back through the
                // fields that are present rather than showing an empty line.
                const headline = street || result.name || a.suburb || a.neighbourhood ||
                                 (result.display_name || '').split(',')[0] || 'This location';

                popup.setHTML(`
                    <div class="mtk-address">
                      <strong>${headline}</strong>
                      <span>${[place, a.country].filter(Boolean).join(', ')}</span>
                    </div>`);
            })
            .catch(() => {
                if (request !== pending) return;
                popup.setHTML('<div class="mtk-address">No address found here.</div>');
            });
    });
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Reverse Geocoding on Click - Maptoolkit Maps JS</title>
    <meta property="og:description" content="Turn a clicked coordinate into a street address." />
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
    <style>
        html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
        #map { width: 100%; height: 100%; }
        #hint {
            position: absolute; top: 10px; left: 10px; z-index: 999;
            background: #fff; border-radius: 6px; box-shadow: 0 0 15px #68686880;
            font: 13px/1.4 system-ui, sans-serif; padding: 8px 12px;
        }
        .mtk-address { font: 13px/1.5 system-ui, sans-serif; max-width: 220px; }
        .mtk-address strong { display: block; margin-bottom: 2px; }
        .mtk-address span { color: #666; }
    </style>
</head>
<body>
<div id="map"></div>
<div id="hint">Click anywhere on the map</div>
<script>
    const API_KEY = 'YOUR_API_KEY';

    const map = new maptoolkit.Map({
        container: 'map',
        apiKey: API_KEY,
        style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
        center: [16.3722, 48.2082],
        zoom: 15,
        attributionControl: { compact: false }
    });

    map.addControl(new maptoolkit.NavigationControl(), 'top-right');

    const popup = new maptoolkit.Popup({ closeButton: true, maxWidth: '260px' });
    let pending = 0;

    map.on('click', (e) => {
        const { lng, lat } = e.lngLat;
        const request = ++pending;

        popup.setLngLat([lng, lat]).setHTML('<div class="mtk-address">Looking up...</div>').addTo(map);

        const url = new URL('https://geocoder.maptoolkit.net/reverse');
        url.searchParams.set('lat', lat);
        url.searchParams.set('lon', lng);
        url.searchParams.set('language', 'en');
        url.searchParams.set('api_key', API_KEY);

        fetch(url)
            .then(r => r.json())
            .then(result => {
                // A later click already went out, so this answer is stale.
                if (request !== pending) return;

                const a = result.address || {};
                const street = [a.road, a.house_number].filter(Boolean).join(' ');
                const place = [a.postcode, a.city || a.town || a.village].filter(Boolean).join(' ');

                // Not every coordinate sits on an addressed feature. Fall back through the
                // fields that are present rather than showing an empty line.
                const headline = street || result.name || a.suburb || a.neighbourhood ||
                                 (result.display_name || '').split(',')[0] || 'This location';

                popup.setHTML(`
                    <div class="mtk-address">
                      <strong>${headline}</strong>
                      <span>${[place, a.country].filter(Boolean).join(', ')}</span>
                    </div>`);
            })
            .catch(() => {
                if (request !== pending) return;
                popup.setHTML('<div class="mtk-address">No address found here.</div>');
            });
    });
</script>
</body>
</html>

Use the prompt below with any LLM to get the same result. Make sure the Maptoolkit MCP server is connected first — check out AI Integration & MCP to get started.

Use the Maptoolkit Connector. Create an interactive map of Vienna. When the user clicks the map, call the Geocoding API reverse endpoint for that coordinate and show the address in a popup.

How it works

The parameter is lon, not lng. The reverse endpoint takes lat and lon, while the Weather API forecast endpoint next door takes lat and lng. Sending lng here is not rejected, it is ignored, and the request falls back to a default that has nothing to do with where the user clicked.

language defaults to de. A map with an English interface returns “Wien” and “Österreich” unless the parameter is set, which is the kind of thing that ships and then gets reported as a bug by a customer.

Reverse geocoding returns the nearest feature, and that is often a shop or a building rather than a street address. Worse, some coordinates have no addressed feature near them at all and come back with a postcode and a country and nothing else. This example reads address.road and address.house_number and then falls back through name, suburb and the first segment of display_name, because a single field chosen up front will be empty somewhere. The structured address object is what you want whenever the output goes anywhere other than straight onto the screen, since display_name can come back as a shop name followed by a street.

pending is a request counter, and it matters more on this endpoint than it looks. Clicks arrive faster than the network answers, and responses do not come back in the order they were sent. Without the counter, a slow answer for an earlier click overwrites the address for the one the user is looking at. Comparing the counter before writing to the popup is the smallest fix that works.

boundingbox in the response is [minLat, maxLat, minLng, maxLng], which is neither the order fitBounds takes nor GeoJSON order. Reorder it explicitly if you frame the map on a result.

Next steps

A confirmed address is usually the start of something. Letting the user drag the pin and looking the address up again on release is the standard delivery-address flow, and the same call covers it with no changes.

From a coordinate the user has chosen, the Routing API gives directions to it and the Isochrone API gives the area around it, both on the key already in the page. If the address needs to be typed rather than clicked, the forward direction and the parameters that keep results inside one country are in the Geocoding API reference.