Google reverse geocoding: Get addresses from coordinates

Reverse geocoding is the process of converting geographic coordinates (latitude and longitude) into a human readable address. In this blog post, I’ll show you how to use the Google Geocoding API to reverse geocode latitude and longitude coordinates into a clean, formatted address and display it on a map.

What we'll build - a reverse geocoding demo app that used the Google Geocoding API

Part 1: A practical guide to the Google Geocoding API
Part 2: Forward geocoding with the Google Geocoding API
Part 3: Google reverse geocoding: Get addresses from coordinates (this article)
Part 4: Google Address Validation API: Better than USPS?
Part 5: Google Maps Building Outlines and Entrances API

As with our other tutorials, we'll use what we've learned to build a simple demo app (https://google-reverse-geocoding.afi.dev/). Code (frontend / backend) for this project is available for free on GitHub.

Google Reverse Geocoding Demo
How to use the Google Geocoding API to convert geographic coordinates into addresses

What is reverse geocoding?

Reverse geocoding turns any latitude and longitude pair into a structured, human readable address or place name, known as a reverse geocode. It’s the inverse of forward geocoding, which transforms addresses into geographic coordinates.

When you send a pair of coordinates to the reverse geocoding endpoint of the Google Geocoding API, Google matches them to the closest known feature in its geospatial database and returns a complete, display-ready address string. It's a technology thats widely used on social media apps for geotagging photos, recommending content and grouping posts based on neighborhoods, cities, or landmarks.

Reverse geocoding used in Instagram to automatically location tag posts

Google Geocoding API example (reverse geocoding)

Here's a simple geocoding example that returns an address string for 49.2846966, -123.1119349 in downtown Vancouver.

EndpointGET

https://maps.googleapis.com/maps/api/geocode/json?
latlng={LATLNG}&
key={YOUR_API_KEY}

{LATLNG} is the comma separated latitude and longitude coordinates of the location you want to convert into the nearest human readable address.

{YOUR_API_KEY} is your Google Maps API key with the Google Geocoding API enabled.

Endpoint: GET

https://maps.googleapis.com/maps/api/geocode/json?latlng=49.2846966,-123.1119349&key={YOUR_API_KEY}

Response

{
    "plus_code": {
        "compound_code": "7VMQ+V6G Vancouver, BC, Canada",
        "global_code": "84XR7VMQ+V6G"
    },
    "results": [
        
        {
            "address_components": [
                //... address components array
            ],
            "formatted_address": "555 W Hastings St, Vancouver, BC V6B 4N4, Canada",
            "geometry": {
                "bounds": {
                    "northeast": {
                        "lat": 49.2851751,
                        "lng": -123.111479
                    },
                    "southwest": {
                        "lat": 49.28441489999999,
                        "lng": -123.1126507
                    }
                },
                "location": {
                    "lat": 49.2844998,
                    "lng": -123.1128788
                },
                "location_type": "ROOFTOP",
                "viewport": {
                    "northeast": {
                        "lat": 49.28614398029149,
                        "lng": -123.1107158697085
                    },
                    "southwest": {
                        "lat": 49.28344601970849,
                        "lng": -123.1134138302915
                    }
                }
            },
            "navigation_points": [
                {
                    "location": {
                        "latitude": 49.2845048,
                        "longitude": -123.1124829
                    }
                }
            ],
            "place_id": "ChIJUcKFZ3hxhlQREJGVU1foPaE",
            "types": [
                "premise",
                "street_address"
            ]
        },
        //... 14 more entries
    ],
    "status": "OK"
}

Just like with forward geocoding, the response to the reverse geocoding endpoint of the Google Geocoding API is an array, results. Unlike forward geocoding though, reverse geocoding typically returns multiple results, ranked from most precise to least precise - starting with the exact street address and followed by broader regions like neighborhoods, cities, counties, and states. In the response above, 15 locations were returned:

  1. street_address 1200-555 W Hastings St, Vancouver, BC V6B 4N6, Canada
  2. street_address premise 555 W Hastings St, Vancouver, BC V6B 4N4, Canada

...

  1. city political Vancouver, BC, Canada
  2. administrative_area_level_2 political Metro Vancouver, BC, Canada
  3. administrative_area_level_1 political British Columbia, Canada
  4. country political Canada

From the list above, you can see that the geocoded point may be denoted as a street_address, a city (Vancouver), a province administrative_level_1 (British Columbia) or as a country (Canada). All are "addresses" to the geocoder. Which one you use depends on your needs. For deliveries or field services, you'll want exact street level accuracy. But for tasks like geospatial analytics, a more general "address" - like a postal code or district, might be enough.

Reverse geocoded addresses are not necessarily at the input coordinates location

An interesting detail about reverse geocoding is that the returned address (reverse geocode) isn’t always located exactly at the input coordinates - it’s often the nearest known address instead. The easiest way to illustrate this is by reverse geocoding coordinates in the ocean (picture above) and notice how the returned address appears on the nearest shoreline.

In each result object:

The formatted_address field contains the complete, human readable version of the address - exactly how you’d write it for mailing purposes, such as on a letter or postcard.

The types field is an array that describes the categories associated with the geocoded result. Most addresses belong to multiple types. For instance, “Vancouver, Canada” is labeled as both locality and political, indicating that it functions as a city and also as a recognized political region.

The geometry object contains geographic details about the geocoded result, specifically:

  • location, which contains the exact location.lat and location.lng of the place,
{
    "location": {
        "lat": 49.2846966,
        "lng": -123.1119349
    }
}
  • location_type, which indicates the accuracy of the geocoded result.
    • "ROOFTOP" means the result is highly precise, accurate down to a specific street address.
    • "RANGE_INTERPOLATED" and "APPROXIMATE" suggest the location is estimated and less reliable.

The place_id (docs) is a unique identifier assigned by Google Maps to a specific place. You can use this ID to look up more detailed information about the location, such as photos, user ratings, and reviews.

Filtering reverse geocoding results by type

Because the reverse geocoding endpoint returns multiple addresses - ranging from a precise street address to broader areas like neighborhoods, cities, and states, it's important to filter the results by precision to fit your use case.

For example, if you are using reverse geocoding to show where a delivery occurred, you would want to use a result with the "street_address" type. If you are using it to tag photos, broader types like "neighborhood" or "locality" might be more appropriate.

To filter by type, add the result_type parameter to the Google Geocoding API call like so:

https://maps.googleapis.com/maps/api/geocode/json?latlng=49.2846966,-123.1119349&key={YOUR_API_KEY}&result_type=street_address

Including result_type=street_address in your request filters the response to only include results of the type "street_address". Note that result_type doesn't limit the search itself - instead, it acts as a post-processing filter. The API retrieves all possible results for the given coordinates, then removes any that don’t match the specified type.

Building a Google Maps reverse geocoding demo app

In this final section, we’ll build a web app (https://google-reverse-geocoding.afi.dev/) using the reverse geocoding endpoint of the Google Geocoding API to better understand how reverse geocoding works. Users can drag and drop a marker anywhere on a Google Map to retrieve the formatted_address of the nearest reverse geocode. A blue circle icon will indicate the location of that address. This app highlights how the input coordinates and the returned reverse geocode location can sometimes differ - often significantly.

How our code is structured

google_reverse_geocoding is a single page React app that uses Vite as the build tool and development server. The app works together with google_geocoding_backend, which retrieves reverse geocode data from the Google Geocoding API. As with our other tutorials, we'll be using the super useful Google Maps React component library from @vis.gl/react-google-maps to quickly scaffold our app and manage map interactions and UI in a way that is consistent with React. The <Map/>, <DraggableMarker/> and  <LocationMarker/> components are examples of this.

App.jsx

App.jsx is the main entry point of the app. It manages the overall layout, routing, structure, and handles the call to the backend to perform reverse geocoding using the Google Geocoding API.

The structure of our reverse geocoding demo app

App.jsx has four main components:

  1. <SearchBox/>, which holds the Place Autocomplete text box (<PlaceAutocompleteInput/>) that is used to switch the map view to any location worldwide.
  2. <GoogleMap/>, a React wrapper around the 2D Maps JavaScript API provided by the @vis.gl/react-google-maps library.
  3. <DraggableMarker/> and <LocationMarker/>, the marker components for the draggable red balloon pin and its corresponding reverse geocoded location (displayed on the map as a blue dot), and
  4. <InfoWindow/>, a temporary overlay that shows up when <DraggableMarker/> is clicked. This pop up shows the formatted_address of the reverse geocoded pin location, together with the actual latitude and longitude of the pin marker.
/*** App.jsx ***/
import { APIProvider } from "@vis.gl/react-google-maps";
import { useState, useEffect } from "react";
import axios from "axios";

import GoogleMap from "~/components/GoogleMap";
import SearchBox from "~/components/SearchBox";

import "./App.scss";

function App() {
  const [autocompletePlace, setAutocompletePlace] = useState(null);
  const [reversePlace, setReversePlace] = useState(null);

  const handleSearchPlace = async ({ lat, lng }) => {
    const res = await axios.get(
      `${import.meta.env.VITE_API_URL}/reverse-geocode`,
      {
        params: { lat, lng },
      },
    );

    if (res.data && res.data.results) {
      const nearestPlace = res.data.results?.find((place) => !place.plus_code);
      setReversePlace(nearestPlace);
    }
  };

  const handleDragEnd = (location) => {
    handleSearchPlace(location);
  };
  // Reset reverse place
  useEffect(() => {
    if (autocompletePlace) {
      setReversePlace(autocompletePlace);
    }
  }, [autocompletePlace]);

  return (
    <APIProvider apiKey={import.meta.env.VITE_GOOGLE_API_KEY}>
      <div className="App">
        <SearchBox onSelectAutocompletePlace={setAutocompletePlace} />

        <GoogleMap
          autocompletePlace={autocompletePlace}
          reversePlace={reversePlace}
          onDragEnd={handleDragEnd}
        />
      </div>
    </APIProvider>
  );
}

export default App;

SearchBox.jsx

SearchBox.jsx is a basic presentational component that contains a text input field, implemented using the <PlaceAutoCompleteInput/> child component. The input is powered by Google Place Autocomplete, which allows users to quickly find and select addresses or place names.

/*** components/SearchBox.jsx ***
import { useMap, useMapsLibrary } from "@vis.gl/react-google-maps";
import { useCallback, useEffect, useState } from "react";

import Icon from "~/components/Icon";
import PlaceAutocompleteInput from "~/components/PlaceAutocompleteInput";
import "./SearchBox.scss";

const SearchBox = ({ onSelectAutocompletePlace }) => {
  const map = useMap();
  const googleMaps = useMapsLibrary("places");

  const [placeService, setPlaceService] = useState(null);

  useEffect(() => {
    if (googleMaps && map) {
      setPlaceService(new googleMaps.PlacesService(map));
    }
  }, [map, googleMaps]);

  const handlePlaceSelect = useCallback(
    (autocompletePlace) => {
      onSelectAutocompletePlace(autocompletePlace);
    },
    [placeService],
  );

  return (
    <div className="SearchBox">
      <label className="label">Location</label>

      <div className="input-group">
        <PlaceAutocompleteInput onPlaceSelect={handlePlaceSelect} />
        <Icon type="search" size={24} />
      </div>
    </div>
  );
};

export default SearchBox;

PlaceAutocompleteInput.jsx

The <PlaceAutocompleteInput/> component, as the name suggests, contains a text input field that uses Google Places Autocomplete to suggest places dynamically as the user types. When the user selects a place from the dropdown menu, Google triggers a place_changed event on the Autocomplete instance.

PlaceAutocompleteInput.jsx listens for this event and uses getPlace() to retrieve the selected place's details (place_idgeometryname, and formatted_address), and saves it to an autocompletePlace object. This data is passed on to its parent component via the onPlaceSelect() prop and eventually passed to App.jsx where it is saved in state.

/*** components/PlaceAutocomepleteInput.jsx ***
import { useMapsLibrary } from "@vis.gl/react-google-maps";
import { useEffect, useRef, useState } from "react";

const PlaceAutocompleteInput = ({ onPlaceSelect }) => {
  const [placeAutocomplete, setPlaceAutocomplete] = useState(null);
  const inputRef = useRef(null);
  const places = useMapsLibrary("places");

  useEffect(() => {
    if (!places || !inputRef.current) return;

    const options = {
      fields: ["place_id", "geometry", "name", "formatted_address"],
    };

    setPlaceAutocomplete(new places.Autocomplete(inputRef.current, options));
  }, [places]);

  useEffect(() => {
    if (!placeAutocomplete) return;

    placeAutocomplete.addListener("place_changed", () => {
      onPlaceSelect(placeAutocomplete.getPlace());
    });
  }, [onPlaceSelect, placeAutocomplete]);

  return (
    <div className="PlaceAutocompleteInput">
      <input ref={inputRef} />
    </div>
  );
};

export default PlaceAutocompleteInput;

GoogleMap.jsx

GoogleMap.jsx, found in components/GoogleMap/index.jsx, is a React component that displays a full screen Google Map using the @vis.gl/react-google-maps library. It handles three main tasks:

  1. Centers the map at a new location whenever autocompletePlace is updated.
  2. Displays a draggable red pin marker and a blue circle icon at the location of reversePlace.
  3. Calls the handleDragEnd() function (passed through the onDragEnd prop) when the user finishes dragging the red pin marker.
/*** components/GoogleMap/index.jsx ***
import { Map } from "@vis.gl/react-google-maps";

import MapHandler from "./MapHandler";
import LocationMarker from "./LocationMarker";
import DraggableMarker from "./DraggableMarker";

const DEFAULT_CENTER = { lat: 49.25307278849622, lng: -123.12095840000302 };

const GoogleMap = ({ autocompletePlace, reversePlace, onDragEnd }) => {
  return (
    <div className="GoogleMap">
      <Map
        style={{
          height: "100dvh",
          width: "100dvw",
        }}
        defaultZoom={12}
        mapId={import.meta.env.VITE_GOOGLE_MAP_ID}
        defaultCenter={DEFAULT_CENTER}
        gestureHandling="greedy"
        disableDefaultUI
        reuseMaps
      >
        <MapHandler place={autocompletePlace} />

        {reversePlace && (
          <LocationMarker key={reversePlace.place_id} place={reversePlace} />
        )}

        {autocompletePlace && (
          <DraggableMarker
            place={autocompletePlace}
            reversePlace={reversePlace}
            onDragEnd={onDragEnd}
          />
        )}
      </Map>
    </div>
  );
};

export default GoogleMap;

When autocompletePlace is not null (meaning the user has selected a location from the address autocomplete input), the red pin <DraggableMarker/> is displayed on the map. When a reverse geocoded location is available (i.e., reversePlace is not null), the blue circle icon from <LocationMarker/> is also shown.

DraggableMarker.jsx

Drag and drop marker used to reverse geocoded any point on a Google Map

<DraggableMarker/> uses the <AdvancedMarker/> component from the @vis.gl/react-google-maps library as a wrapper for Google's AdvancedMarkerElement. It works seamlessly with React state, and re-renders intelligently when props like position, zIndex, or children change.

/*** components/GoogleMap/DraggableMarker.jsx ***/
<AdvancedMarker
  ref={markerRef}
  draggable={true}
  zIndex={2}
  position={position}
  onDragEnd={handleDragEnd}
  onClick={() => setInfoOpen((prev) => !prev)}
>
  <Pin />
</AdvancedMarker>;

In the return statement of DraggableMarker.jsx, we render an <AdvancedMarker/> with draggable={true}. This allows users to click and drag the marker on the map just as if you had set marker.setDraggable(true) in plain Javascript. Google Maps also automatically adds an “X” icon at the base of the marker on hover to indicate that the marker can be dragged, which is the default behavior from the Google Maps Javascript API’s Web Components library.

When the user finishes dragging the marker - that is, when they drop it anywhere on the map - the onDragEnd event fires and handleDragEnd() is called. This function receives e, the event object from Google Maps which contains the marker’s new position. The state of <DraggableMarker/> is then updated with this new location using setPosition(location), which sets it in place.

/*** components/GoogleMap/DraggableMarker.jsx ***/
const handleDragEnd = (e) => {
  const location = { lat: e.latLng.lat(), lng: e.latLng.lng() };
  setPosition(location);
  onDragEnd(location);
};

Finally, the location object is passed to onDragEnd(), a callback function provided via props from App.jsx. In App.jsx, onDragEnd points to handleDragEnd which in turn calls the handleSearchPlace() method, which is responsible for implementing an API loading wrapper design pattern to reverse geocode the coordinates in location.

/*** App.jsx ***/
const handleSearchPlace = async ({ lat, lng }) => {
  const res = await axios.get(
    `${import.meta.env.VITE_API_URL}/reverse-geocode`,
    {
      params: { lat, lng },
    },
  );

  if (res.data && res.data.results) {
    const nearestPlace = res.data.results?.find((place) => !place.plus_code);
    setReversePlace(nearestPlace);
  }
};

const handleDragEnd = (location) => {
  handleSearchPlace(location);
};

After receiving a response from the /reverse-geocode endpoint of google_geocoding_backend, we loop through the results and select the first one that doesn’t include a plus_code property with const nearestPlace = res.data.results?.find((place) => !place.plus_code);. Since Plus Codes are used for locations without traditional street addresses - and we’re looking for actual addresses - we skip those. Because reverse geocoding results are sorted by precision, this ensures we get the nearest and most relevant address for our marker’s location. The result is used to update the reversePlace state with the value of nearestPlace.

nearestPlace is the closest human readable address to our marker coordinates. It's a Geocoding Response Object with the following structure (we've seen it before in our earlier reverse geocoding example):

{
    "address_components": [
        //... address components array
    ],
    "formatted_address": "555 W Hastings St, Vancouver, BC V6G 3J4, Canada",
    "geometry": {
        "location": {
            "lat": 49.2844998,
            "lng": -123.1128788
        },
        "location_type": "ROOFTOP",
        // ... javascript object
    },
    "navigation_points": [
        //... javascript object
    ],
    "place_id": "ChIJw6AQfXhxhlQRSgv78ppPKXc",
    "types": [
        "street_address",
        "subpremise"
    ]
}

The formatted_address is used as the heading of the <InfoWindow/> component while its geographic coordinates (geometry.location.lat and geometry.location.lng) are used to position the blue circle <LocationMarker/> . In the example above, the reverse geocoded nearestPlace is actually across the street from the location of the draggable marker.

Data returned by the reverse geocoding endpoint of the Google Geocoding API

LocationMarker.jsx

Like <DraggableMarker />, <LocationMarker /> is a wrapper around Google Map's AdvancedMarkerElement class. However, instead of using the default red balloon pin like <DraggableMarker />, <LocationMarker/> displays a custom SVG icon styled as a blue circle.

/*** components/GoogleMap/LocationMarker.jsx ***/
import { AdvancedMarker, Pin } from "@vis.gl/react-google-maps";
import { useEffect, useState } from "react";

import Icon from "~/components/Icon";

import { getLatLng } from "~/utils";
import "./Marker.scss";

const LocationMarker = ({ place }) => {
  const [position, setPosition] = useState(() =>
    getLatLng(place.geometry.location),
  );

  return (
    <AdvancedMarker
      draggable={false}
      zIndex={1}
      position={getLatLng(place.geometry.location)}
    >
      <Icon type="marker" />
    </AdvancedMarker>
  );
};

export default LocationMarker;

Here's a detailed explanation of how this works:

  1. The <Icon/> component returns an SVG element, and when nested inside an <AdvancedMarker/> element from @vis.gl/react-google-maps, React allows you to insert that SVG directly in to the map as a custom marker with your own design and styling.
  2. <Icon/> is imported from /components/Icon.jsx, which returns an <ImageSVG/> taken from /assets/index.js.
  3. <Icon/> was given a type prop of "marker". In Icon.jsx, the line const ImageSVG = icons[type] means that when type is "marker", <ImageSVG/> gets its data from icons["marker"], which in /assets/index.js points to assets/marker.svg.
  4. marker.svg was added to our app with the line import marker from '~/assets/marker.svg?react'. The ?react appended at the end tells the build system to load the SVG as a React component, instead of a URL or an image file.
  5. When rendered as an SVG, the marker is styled as a blue circle with a white border (below).
SVG file imported into React used to create custom markers

When the red <DraggableMarker/> is moved to a new position, the app calls the reverse geocoding endpoint of the Google Geocoding API again, and updates reversePlace with the new location.

See reverse geocoding in action

To see reverse geocoding at work, head over to https://google-reverse-geocoding.afi.dev/ and enter an address or place name e.g. "Vancouver, BC" in the text field at the top left. Drag the marker around the map to see the reverse geocoded address and location details update in real time.

If you prefer to run the app locally:

  1. Fork both google_reverse_geocoding and google_geocoding_backend  repositories on GitHub.
  2. In google_geocoding_backend, run npm install to install the dependencies followed by npm start.
  3. In google_reverse_geocoding, run npm install followed by vite run dev to start the app. Open your browser to http://localhost:3000/ to view the app.

Remember to update the .env files in both folders with a Google Maps API key that has the Google Geocoding API enabled.

Coming up

In the next section, we'll touch on the Google Address Validation API, which is used to validate, correct, and standardize postal addresses according to official data sources. Unlike the Google Geocoding API, which tells you where an address is (or in reverse geocoding, what approximate address matches a set of coordinates), the Address Validation API checks whether an address is real, complete, and deliverable - in other words, if you mailed a letter to that address, it would actually arrive.

Next: Part 4: Google Address Validation API: Better than USPS?