Need to use the Routes API in your app or website? 👋 Say Hello! to start working with Afi Labs.

Plan a route with multiple stops using the Routes API

Using the Google Maps Routes API to create routes, get travel times, find alternate routes, avoid tolls and draw routes between multiple points.

Plan a route with multiple stops using the Routes API

In this blog post, I'll show you how to use the Google Routes API to build a traffic aware navigation app (demo / source code) that displays traffic conditions using color coded polylines. I’ll also provide an in depth look at the Compute Routes endpoint of the Routes API, explaining how you can use it to not only get the most efficient route between two points, but also retrieve traffic aware polylines, alternative route options, turn by turn directions, toll details, and must have metrics such as trip distances and estimated travel times.

The Google Routes API with traffic and avoid toll information
The Google Routes API with traffic and avoid toll information

Part 1: A developer's guide to the Google Routes API
Part 2: Plan a route with multiple stops using the Routes API (this article)
Part 3: Using the Google Distance Matrix API for taxi dispatch
Part 4: Google Maps Search Along Route
Part 5: How to draw a route on Google Maps for Android with the Routes API

Routes API Demo
Use the Google Routes API to find the shortest route between an origin and destination

What is the Compute Routes endpoint of the Routes API?

The Compute Routes endpoint of the Routes API is the Google Maps Get Route API. It receives as input an origin and a destination, and returns the ideal route between these two locations.

The components of the route polyline returned by the Google Routes API
The components of the route polyline returned by the Google Routes API

The route returned is expressed as an encoded polyline that looks like this:

iggkHlrynVr@j@h@L~C`@b@ERKRYt@iBR{@Hw@FgBHcCRkCt@qNAuAe@mG{BoWW{FEkFDsp@@aTIcHU{Gi@uJESW}C[oCuFag@gBwPe@oCi@eBi@wAsA_Cq@y@o@m@}

When properly decoded using a polyline decoder or the Google Maps Geometry Library, the polyline converts into a list of latitude and longitude coordinate pairs, which can then be used to draw the route on a map (more on this later).

Routes API Field Masks

In addition to the route polyline, the Routes API can return all kinds of useful information about the route. What's included in the response depend on the fields specified in the X-Goog-FieldMask header in your request.

Response field masks used in the Google Routes API
Response field masks used in the Google Routes API

The response field mask is a comma-separated list of strings, where each string specifies a unique field in the response message. The list must not include any spaces.

routes.duration is the total time in seconds needed to complete the route (drive time). You can add this value to the departureTime from the request body to get the ETA, or Estimated Time of Arrival.

routes.distanceMeters is the total length of the route in meters.

routes.polyline is the route's encoded polyline string.

routes.legs.steps provides a detailed breakdown of the route into individual navigation steps each with a startLocation, endLocation, polyline and turn by turn directions,.

routes.travelAdvisory.tollInfo gives you an estimate of toll fees and other one-off charges incurred when using the route.

routes.travelAdvisory.speedReadingIntervals retrieves real time traffic speed data for different segments of the route.

💡
You can learn more about field masks and how they ensure you only access and pay for data that you need here: Choosing what fields to return.

Routes API example - how to make a route in Google Maps

Here's a basic example of how a Compute Routes call in the Routes API is structured. We'll set 7 Leedon Heights, Singapore as the starting point and Singapore Changi Airport as the destination.

Google Routes API example API call
Google Routes API example API call

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters,routes.polyline, routes.legs

Request Body

{
    "origin": {
        "location": {
            "latLng": {
                "latitude": 1.3144075,
                "longitude": 103.8013863
            }
        }
    },
    "destination": {
        "location": {
            "latLng": {
                "latitude": 1.3512884,
                "longitude": 103.9762515
            }
        }
    },
    "travelMode": "DRIVE",
    "polylineQuality": "OVERVIEW",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T15:00:00Z"
}

origin.location.latLng are the coordinates of the route's start location.

destination.location.latLng are the coordinates of the route's end location.

Besides using coordinates, you can also specify locations using Google Place IDs or an address string, like this:

{
    "origin": {
        "address": "7 Leedon Heights, Singapore 267953"
    },
    "destination": {
        "address": "Singapore Changi Airport"
    }
}

travelMode refers to the mode of transport ("DRIVE", "WALK", "TRANSIT" or "BICYCLE"). Right now, truck routing is not supported by the Routes API but this might change in the future.

The polylineQuality setting lets you choose how detailed the route looks on a map. If you go with "HIGH_QUALITY", you'll get a super detailed polyline with lots of points. On the other hand, "OVERVIEW" uses fewer points, making it load much faster but with less precision. As a result, it returns fewer route segments, which means real-time traffic congestion details are less accurate.

The routingPreference parameter has three options: "TRAFFIC_UNAWARE", "TRAFFIC_AWARE", and "TRAFFIC_AWARE_OPTIMAL".

  • Choosing "TRAFFIC_AWARE" enables the routing algorithm to account for real-time traffic conditions when calculating routes. This is ideal for urban areas, where traffic congestion during peak hours can lead to significant delays.
  • Selecting "TRAFFIC_UNAWARE" means the Routes API will base its calculations solely on posted speed limits, without considering live traffic data.

For the most accurate and efficient routes in traffic-heavy areas, "TRAFFIC_AWARE" is the recommended option.

The departureTime field, when used with "routingPreference" : "TRAFFIC_AWARE", is arguably the most critical input parameter. It allows you to specify the start time of the route in RFC3339 UTC "Zulu" format (e.g., "2025-02-24T15:00:00Z"), ensuring that the API accounts for predicted real-time traffic conditions when calculating travel time or drive time. The "Z" at the end of the string signifies Zulu time (GMT+0). To get the correct ETA and travel time for your location, you’ll need to convert it to your local timezone.

If departureTime is left empty, it defaults to the time that the request was made. If you you use a date time value in the past, then the request fails.

The above API call returns the following:

Response

{
    "routes": [
        {
            "legs": [
                {
                    "steps": [
                        {
                            "distanceMeters": 20,
                            "staticDuration": "7s",
                            "polyline": {
                                "encodedPolyline": "_w_G_cqxRJXAF"
                            },
                            "startLocation": {
                                "latLng": {
                                    "latitude": 1.3145563999999998,
                                    "longitude": 103.8035189
                                }
                            },
                            "endLocation": {
                                "latLng": {
                                    "latitude": 1.3145083,
                                    "longitude": 103.8033518
                                }
                            },
                            "navigationInstruction": {
                                "maneuver": "DEPART",
                                "instructions": "Head southwest\nRestricted usage road"
                            },
                            "localizedValues": {
                                "distance": {
                                    "text": "20 m"
                                },
                                "staticDuration": {
                                    "text": "1 min"
                                }
                            },
                            "travelMode": "DRIVE"
                        },
                        // ... 5 more entries
                        {
                            "distanceMeters": 632,
                            "staticDuration": "37s",
                            "polyline": {
                                "encodedPolyline": "ygbGqcsxR}BMmASyAc@kBy@eDqB}Ay@eDyAqDmB"
                            },
                            "startLocation": {
                                "latLng": {
                                    "latitude": 1.3274864,
                                    "longitude": 103.8138518
                                }
                            },
                            "endLocation": {
                                "latLng": {
                                    "latitude": 1.3325178,
                                    "longitude": 103.81634620000001
                                }
                            },
                            "navigationInstruction": {
                                "maneuver": "NAME_CHANGE",
                                "instructions": "Continue onto Adam Rd"
                            },
                            "localizedValues": {
                                "distance": {
                                    "text": "0.6 km"
                                },
                                "staticDuration": {
                                    "text": "1 min"
                                }
                            },
                            "travelMode": "DRIVE"
                        },
                        {
                            "distanceMeters": 19870,
                            "staticDuration": "1010s",
                            "polyline": {
                                "encodedPolyline": "ggcGessxR]CwAm@a@Gg@?YFYXIPE\\BPDRTV^PV@^GXO^e@|C_HrAoC|@aBVKfDkH\\aAf@}ApBoHd@uAhAaCvAuBbBiB`DwCh@y@\\u@XcANmA?oA[oD]yDCiAFy@Ry@n@yBvA{CZeAJy@BoAC}@YaB{EuT_AsEm@eBgA}BiEqIm@gB[qAYoBKwAEiA]wTEgDJuFLkCr@mRDyA@gBPgC\\kKAkBKuAqBcJmCgLKaAEgADsAV}BPy@h@cBlCiH`@aBR{@X_B~@uE\\oAz@}BbA}BbAcBhBaC|CaD~AoAp@a@pLcIzAiAtHoFdAaAn@y@^q@h@}@Zw@h@mBNsAFyFAyAKuASyAK]Q[uCcJ_EgNa@mBOcAMmBM_K@SE_B[wD[wCuDqVkAsI{Fmg@yA{Lk@yCw@wD]oBiC{Lc@eBk@yAMQcCeFcBgDc@eAgE_L{KuZ{BsGeImTcAaDY}@oAuF[gBYmBYmCOoBKwBKcD?mEJaB|@a]NsHHyB@kBIgBIo@Oe@u@gBi@w@eM}OaFgG_GqHiS_W}CwD{@mAsLkO_JuK}AwBwAiCSa@{AmDy@{BWkAS_BQ{BEyC@mAtAmIRaAf@mBv@uBdAuBhBcD`CeCtAiAnBqAvDwBTQnE_CnJmFjDuBxAgAr@o@v@y@vAeBjBqC"
                            },
                            "startLocation": {
                                "latLng": {
                                    "latitude": 1.3325178,
                                    "longitude": 103.81634620000001
                                }
                            },
                            "endLocation": {
                                "latLng": {
                                    "latitude": 1.3405361999999998,
                                    "longitude": 103.9721112
                                }
                            },
                            "navigationInstruction": {
                                "maneuver": "RAMP_LEFT",
                                "instructions": "Merge onto PIE via the ramp to Changi Airport\nToll road"
                            },
                            "localizedValues": {
                                "distance": {
                                    "text": "19.9 km"
                                },
                                "staticDuration": {
                                    "text": "17 mins"
                                }
                            },
                            "travelMode": "DRIVE"
                        },
                        {
                            "distanceMeters": 34,
                            "staticDuration": "2s",
                            "polyline": {
                                "encodedPolyline": "kydGu`ryRFWLa@"
                            },
                            "startLocation": {
                                "latLng": {
                                    "latitude": 1.3405361999999998,
                                    "longitude": 103.9721112
                                }
                            },
                            "endLocation": {
                                "latLng": {
                                    "latitude": 1.3404269,
                                    "longitude": 103.9723952
                                }
                            },
                            "navigationInstruction": {
                                "maneuver": "RAMP_LEFT",
                                "instructions": "Take exit 1"
                            },
                            "localizedValues": {
                                "distance": {
                                    "text": "34 m"
                                },
                                "staticDuration": {
                                    "text": "1 min"
                                }
                            },
                            "travelMode": "DRIVE"
                        }
                    ]
                }
            ],
            "distanceMeters": 22843,
            "duration": "1168s",
            "polyline": {
                "encodedPolyline": "_w_G_cqxRJXAFKFWH@jB|@JXkAlA{FBUeCo@uAm@gAy@}CgDq@{@kCwCcCiCmFkGo@eAk@sAkBgF_@y@_AkAwDiD_@]eAo@{@SaC[}Fq@kDc@_COkK_@}BMmASyAc@kBy@eDqB}Ay@eDyAqDmB]CwAm@a@Gg@?YFYXIPE\\BPDRTV^PV@^GXO^e@|C_HrAoC|@aBVKfDkH\\aAf@}ApBoHd@uAhAaCvAuBbBiB`DwCh@y@\\u@XcANmA?oA[oD]yDCiAFy@Ry@n@yBvA{CZeAJy@BoAC}@YaB{EuT_AsEm@eBgA}BiEqIm@gB[qAYoBKwAEiA]wTEgDJuFLkCr@mRDyA@gBPgC\\kKAkBKuAqBcJmCgLKaAEgADsAV}BPy@h@cBlCiH`@aBR{@X_B~@uE\\oAz@}BbA}BbAcBhBaC|CaD~AoAp@a@pLcIzAiAtHoFdAaAn@y@^q@h@}@Zw@h@mBNsAFyFAyAKuASyAK]Q[uCcJ_EgNa@mBOcAMmBM_K@SE_B[wD[wCuDqVkAsI{Fmg@yA{Lk@yCw@wD]oBiC{Lc@eBk@yAMQcCeFcBgDc@eAgE_L{KuZ{BsGeImTcAaDY}@oAuF[gBYmBYmCOoBKwBKcD?mEJaB|@a]NsHHyB@kBIgBIo@Oe@u@gBi@w@eM}OaFgG_GqHiS_W}CwD{@mAsLkO_JuK}AwBwAiCSa@{AmDy@{BWkAS_BQ{BEyC@mAtAmIRaAf@mBv@uBdAuBhBcD`CeCtAiAnBqAvDwBTQnE_CnJmFjDuBxAgAr@o@v@y@vAeBjBqCFWLa@"
            }
        }
    ]
}

The response from the Routes API is straightforward. The routes object contains one or more route objects, each with a distanceMeters field, representing the total distance traveled, and duration, which is the total time in seconds to complete the route. To determine the ETA, add the duration to the departureTime. For example, if departureTime is "2025-02-24T15:00:00Z" (23:00:00 Singapore time) and the duration is 1,168 seconds, the ETA would be 23:19:28 Singapore time.

Routes, legs and steps returned by the Google Routes API
Routes, legs and steps returned by the Google Routes API

Each route consists of one or more legs (if there are multiple waypoints, there will be multiple legs). Each leg contains steps (numbered in the diagram above), which break down the route into manageable segments, usually corresponding to a specific turn, street, or maneuver.

Routes API Pricing

The Google Maps Routes API uses a pay as you go pricing model, with costs determined by the specific features utilized in your request. The Compute Routes endpoint has three pricing tiers  - Basic ($5), Advanced ($10 CPM) and Preferred ($15 CPM). 

Basic: This tier functions similarly to the Directions API, offering fundamental routing from a starting point to a destination. It supports up to 10 intermediate waypoints.

Advanced: Expanding on the basic version, this tier allows routing with 11 to 25 waypoints. It also integrates real-time traffic data and enables additional customization with modifiers such as Side of the roadHeading and Vehicle stopover.

Preferred: The most comprehensive option, this tier includes all features from the previous levels while adding support for Two wheeled vehicle routingToll calculation and Traffic information on polylines.

If you work with a Google Maps Partner, you can access discounted Routes API pricing based on the following price schedule.

0 - 100k 100k - 500k 500k - 1M 1M - 5M 5M - 10M 10M - 20M 20M +
Compute Routes (Basic) $5.00 $4.00 $3.00 $1.50 $0.38 $0.11 $0.04
Compute Routes (Advanced) $10.00 $8.00 $6.00 $3.00 $0.75 $0.23 $0.08
Compute Routes (Preferred) $15.00 $12.00 $9.00 $4.50 $1.14 $0.33 $0.12

Draw a route with real time traffic using the Google Routes API

Hands down, the biggest reason to use the Routes API is its ability to generate traffic aware polylines. The colors on these polylines dynamically adjust based on traffic data, allowing developers to display real time congestion levels on a map.

How to draw a real time traffic polyline with the Google Routes API
How to draw a real time traffic polyline with the Google Routes API

To get the Routes API to return real time traffic information, you need to first, include routes.polyline and routes.travelAdvisory.speedReadingIntervals in the response field mask.

Second, add the following fields to the request body:

{
    "polylineQuality": "HIGH_QUALITY",
    "extraComputations": "TRAFFIC_ON_POLYLINE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T01:00:00Z"
}

I previously explained the effects of adding polylineQuality, routingPreference, and departureTime to a Routes API request. Including extraComputations: "TRAFFIC_ON_POLYLINE" means that the response will also include traffic-related information, such as congestion levels along different segments of the route.

Traffic conditions are expressed in terms of speed categories ("NORMAL", "SLOW" and "TRAFFIC_JAM") applicable on a given interval of the response polyline. These intervals are defined by the indexes of their starting (inclusive) and ending (exclusive) polyline points.

Here's an example of a 10 coordinate polyline with three speedReadingIntervals:

{
    "travelAdvisory": {
        "speedReadingIntervals": [
            {
                "startPolylinePointIndex": 0,
                "endPolylinePointIndex": 2,
                "speed": "SLOW"
            },
            {
                "startPolylinePointIndex": 2,
                "endPolylinePointIndex": 4,
                "speed": "NORMAL"
            },
            {
                "startPolylinePointIndex": 4,
                "endPolylinePointIndex": 9,
                "speed": "TRAFFIC_JAM"
            }
        ]
    }
}

The JSON above indicates the following traffic conditions along the route:

  • Coordinates 0 to 2 experience "SLOW" traffic (represented in orange).
  • Coordinates 2 to 4 have "NORMAL" traffic (displayed in blue).
  • Coordinates 4 to 9 face "TRAFFIC_JAM" conditions (marked in red).
Example of a traffic aware route polyline returned by the Routes API
Example of a traffic aware route polyline returned by the Routes API

But the encoded polyline returned by the Routes API looks something like this:

l@|@JXkA`@mB^eBJg@BU]Ic@Kc@MUGICECGAe@SWMIEe@]a@[[]OOa@c@y@}@UWq@{@s@w@]_@W[KMUUs@u@OQWWg@i@gAqAiAoAu@{@SWQUQYGIOUEKQ]Yu@{eCISQe@Sg@_@y@Yu@Kc@MUGI

How do we convert this into color coded polylines that we can display on a map? First, decode the polyline provided by the Routes API using the Google Maps Geometry Library.

/* Polyline.jsx */
const Polyline = ({ route, routeIndex, onClick }) => {
  const [polylines, setPolylines] = useState([]);
  const geometryLibrary = useMapsLibrary("geometry");

  useEffect(() => {
    if (!route?.polyline?.encodedPolyline || !geometryLibrary) return;

    const decodePath = (encodedPath) => {
      return geometryLibrary.encoding.decodePath(encodedPath).map((point) => ({
        lat: point.lat(),
        lng: point.lng(),
      }));
    };

    const polylinePoints = decodePath(route.polyline.encodedPolyline);
  });
};

This converts the polyline into an array of individual latitude and longitude coordinates (below).

Array of coordinate pairs retrieved from the encoded route polyline
Array of coordinate pairs retrieved from the encoded route polyline

Next, use the speedReadingIntervals array to determine the startPolylinePointIndex and endPolylinePointIndex for each colored segment. Then extract the corresponding coordinates, group them into segments, and encode each segment into a shorter polyline.

/* Polyline.jsx */

let zIndex = route.travelAdvisory.speedReadingIntervals.length + 2;
const newPolylines = route.travelAdvisory.speedReadingIntervals.map(
  (interval) => {
    const newPoints = polylinePoints.slice(
      interval.startPolylinePointIndex,
      interval.endPolylinePointIndex + 1,
    );

    return {
      path: geometryLibrary.encoding.encodePath(newPoints),
      options: {
        zIndex: 100 - routeIndex + zIndex--,
        strokeColor: SPEED_COLORS[interval.speed] || SPEED_COLORS.NORMAL,
        strokeOpacity: routeIndex === 0 ? 1.0 : 0.5,
        strokeWeight: 6,
      },
    };
  },
);

For each segment, store this polyline in a new array (let's call it polylines) and pair it with an options object that defines its color (based on speed category), opacity, and thickness.

Finally, for each polyline object in polylines, pass the polyline.path and polyline.options object as props into a react-google-maps <Polyline/> component and render it inside a <Map/> component.

/* Polyline.jsx */
import { Polyline as EPolyline } from './external/Polyline';

return (
  <>
    {polylines.map((polyline, index) => (
      <EPolyline
        key={`p-${index}`}
        onClick={(e) => onClick(e, route)}
        encodedPath={polyline.path}
        {...polyline.options}
      />
    ))}
  </>
);

The final output is a series of connected polylines displayed on a map, where each segment represents a different speedReadingInterval.

Traffic aware route polyline returned by the Routes API
Traffic aware route polyline returned by the Routes API

The complete source code for the code snippets above is available at the routes_api_demo GitHub repository.

Using the Routes API to plan a route with multiple stops

You can also use the Routes API to add multiple waypoints (intermediate stops) between the origin and the final destination. This is especially useful for planning routes that include scheduled stops, or for avoiding specific areas along the way.

{
    "intermediates": [
        {
            "address": "Marina Bay Sands Singapore, 10 Bayfront Ave, Singapore 018956"
        },
        {
            "address": "Jurong East, Singapore"
        }
    ]
}

Here's a simple example that uses the intermediates array property to add two intermediate waypoints to a Routes API request.

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters, routes.legs

Request

{
    "origin": {
        "address": "7 Leedon Heights, Singapore 267953"
    },
    "destination": {
        "address": "Jewel Changi Airport, Singapore"
    },
    "intermediates": [
        {
            "address": "Marina Bay Sands Singapore, 10 Bayfront Ave, Singapore 018956"
        },
        {
            "address": "Jurong East, Singapore"
        }
    ],
    "travelMode": "DRIVE",
    "polylineQuality": "HIGH_QUALITY",
    "extraComputations": "TRAFFIC_ON_POLYLINE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T01:00:00Z"
}

Response

{
    "routes": [
        {
            "legs": [
                {
                    // Origin to waypoint 1

                    "distanceMeters": 16972,
                    "duration": "1449s",
                    "staticDuration": "1371s",
                    "polyline": {
                        // ... polyline string
                    },
                    "startLocation": {
                        "latLng": {
                            "latitude": 1.3145563999999998,
                            "longitude": 103.8035189
                        }
                    },
                    "endLocation": {
                        "latLng": {
                            "latitude": 1.2842133,
                            "longitude": 103.8604276
                        }
                    },
                    "steps": [
                        // ... steps array
                    ]
                },
                {
                    // Waypoint 1 to waypoint 2

                    "distanceMeters": 18908,
                    "duration": "1608s",
                    "staticDuration": "1470s",
                    "polyline": {
                        // ... polyline string
                    },
                    "startLocation": {
                        "latLng": {
                            "latitude": 1.2842133,
                            "longitude": 103.8604276
                        }
                    },
                    "endLocation": {
                        "latLng": {
                            "latitude": 1.3327898999999999,
                            "longitude": 103.7437308
                        }
                    },
                    "steps": [
                        // ... steps array
                    ]
                },
                {
                    // Waypoint 2 to destination

                    "distanceMeters": 33931,
                    "duration": "2317s",
                    "staticDuration": "1947s",
                    "polyline": {
                        // ... polyline string
                    },
                    "startLocation": {
                        "latLng": {
                            "latitude": 1.3327898999999999,
                            "longitude": 103.7437308
                        }
                    },
                    "endLocation": {
                        "latLng": {
                            "latitude": 1.3609805,
                            "longitude": 103.99002589999999
                        }
                    },
                    "steps": [
                        // ... steps array
                    ]
                }
            ],
            "distanceMeters": 69811,
            "duration": "4787s"
        }
    ]
}

Each route contains a separate leg for the path from each waypoint to the next. For each additional waypoint added to the route after the origin and destination, the Routes API adds a separate leg.

Waypoints, legs and steps included in each route returned by the Routes API
Waypoints, legs and steps included in each route returned by the Routes API

Metrics for the route as a whole can be found at the end of the response body. In the example above, the total driving distance, distanceMeters, is 69811 m (69.8 km) and the total trip duration, or drive time, is 4787 sec (79 min 47 sec). These values closely match the numbers returned by the Google Maps app.

Trip durations, distances in a multi waypoint route returned by the Routes API
Trip durations, distances in a multi waypoint route returned by the Routes API

Optimize waypoints with the Routes API

In the example above, the route sent the driver in a loop around Singapore. By default, the Compute Routes endpoint of the Routes API follows the exact order of the waypoints provided. To optimize the route and arrange stops in the most efficient order, you can include "optimizeWaypointOrder": "true" in the request body.

⚠️
Requests using waypoint optimization are billed at a higher rate ($15 CPM) on the ComputeRoutes-Advanced SKU.

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters, routes

Request

{
    "origin": {
        "address": "7 Leedon Heights, Singapore 267953"
    },
    "destination": {
        "address": "Jewel Changi Airport, Singapore"
    },
    "intermediates": [
        {
            "address": "Marina Bay Sands Singapore, 10 Bayfront Ave, Singapore 018956"
        },
        {
            "address": "Jurong East, Singapore"
        }
    ],
    "travelMode": "DRIVE",
    "polylineQuality": "HIGH_QUALITY",
    "extraComputations": "TRAFFIC_ON_POLYLINE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T01:00:00Z",
    "optimizeWaypointOrder": "true"
}

Response

{
    "routes": [
        {
            "legs": [
                {
                    "distanceMeters": 13710,
                    "duration": "1322s",
                    "staticDuration": "1247s",
                    "polyline": {
                        "encodedPolyline": "some_polyline"
                    },
                    "startLocation": {
                        "latLng": {
                            "latitude": 1.3145563999999998,
                            "longitude": 103.8035189
                        }
                    },
                    "endLocation": {
                        "latLng": {
                            "latitude": 1.3327525,
                            "longitude": 103.7437052
                        }
                    },
                    "steps": [
                        // ... steps array
                    ],
                    "travelAdvisory": {
                        "speedReadingIntervals": [
                            // ... speedReadingIntervals array
                        ]
                    },
                },
                {
                    "distanceMeters": 18358,
                    "duration": "1700s",
                    "staticDuration": "1331s",
                    "polyline": {
                        some_polylines"
                    },
                    "startLocation": {
                        "latLng": {
                            "latitude": 1.3327525,
                            "longitude": 103.7437052
                        }
                    },
                    "endLocation": {
                        "latLng": {
                            "latitude": 1.2842133,
                            "longitude": 103.8604276
                        }
                    },
                    "steps": [
                        // ... steps array
                    ],
                    "travelAdvisory": {
                        "speedReadingIntervals": [
                            // ... speedReadingIntervals aray
                        ]
                    }
                },
                {
                    "distanceMeters": 19497,
                    "duration": "1204s",
                    "staticDuration": "1125s",
                    "polyline": {
                        "another polyline"
                    },
                    "startLocation": {
                        "latLng": {
                            "latitude": 1.2842133,
                            "longitude": 103.8604276
                        }
                    },
                    "endLocation": {
                        "latLng": {
                            "latitude": 1.3609805,
                            "longitude": 103.99002589999999
                        }
                    },
                    "steps": [
                        // ... steps array
                    ],
                    "travelAdvisory": {
                        "speedReadingIntervals": [
                            // ... speedReadingIntervals array
                        ]
                    },
                }
            ],
            "distanceMeters": 51565,
            "duration": "4227s",
            "staticDuration": "3705s",
            "polyline": {
                "encodedPolyline": "yet_another polyline"
            },
            "description": "PIE",
            "warnings": [
                "This route includes a highway.",
                "This route has tolls."
            ],
            "viewport": {
                "low": {
                    "latitude": 1.2705161999999999,
                    "longitude": 103.7423905
                },
                "high": {
                    "latitude": 1.3613139,
                    "longitude": 103.99001758752786
                }
            },
            "travelAdvisory": {
                "speedReadingIntervals": [
                    ... // speedReadingIntervals array
                ]
            },
            "optimizedIntermediateWaypointIndex": [
                1,
                0
            ],
            "routeToken": "a_route_token",
            "routeLabels": [
                "DEFAULT_ROUTE"
            ],
            "polylineDetails": {}
        }
    ]
}

Using the index numbers for the two waypoints provided in the request, the service then returns the optimized order:

{
    "optimizedIntermediateWaypointIndex": [
        1,
        0
    ]
}

With the waypoint order optimized, the new route now takes 4227 sec (down from 4787 sec) and 51565 m (down from 69811 m).

Optimized multi waypoint route returned by the Routes API
Optimized multi waypoint route returned by the Routes API

Google Maps two wheeler routes

The default travel mode in the Routes API is set to "DRIVE", which optimizes routes for passenger cars. However, if you operate a quick commerce business like Swiggy or Getir, where deliveries are made using scooters and motorbikes, you should use the "TWO_WHEELER" option. This ensures that your routes and estimated arrival times (ETAs) are tailored specifically for two wheeled vehicles.

This is especially important if you operate in cities such as Bangkok, Kuala Lumpur or Jakarta, where motor cycles and scooters can weave through traffic and take shortcuts and roads specific to two wheelers.

💡
Two wheeler routing is not supported in every country. To see the latest coverage, refer to the Countries and Regions Supported for two wheeled vehicles.

To enable two wheeler mode, add "travelMode" : "TWO_WHEELER" to the request body:

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters

Request

{
    "origin": {
        "address": "Petronas Twin Towers, Kuala Lumpur, Malaysia"
    },
    "destination": {
        "address": "KL Sentral, Kuala Lumpur, Malaysia"
    },
    "travelMode": "TWO_WHEELER",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T01:00:00Z"
}

Response

{
    "routes": [
        {
            "distanceMeters": 7098,
            "duration": "924s"
        }
    ]
}

Using "TWO WHEELER" mode, a trip from Petronas Twin Towers to KL Sentral in Kuala Lumpur takes 924 sec (15m 24s). The same trip on "DRIVE" mode take 1070 sec (17m 50).

Routes API two wheeler routes compared to standard vehicle routes
Routes API two wheeler routes compared to standard vehicle routes

When comparing both routes, you'll notice that the "TWO_WHEELER" mode provides a significantly faster travel time. This is not only due to the higher average speeds of two-wheelers but also because, in this specific case, the Routes API prioritizes surface streets. By doing so, it avoids the long detour needed to reach the nearest highway, resulting in a more efficient route.

How to avoid tolls on Google Maps

In last-mile delivery, every cent counts. With tight or nonexistent profit margins, minimizing the cost per delivery is crucial. If you want your drivers to take the most direct route but avoid tolls, you can use the avoidTolls parameter within the routeModifiers object in your request body, like this:

{
    "routeModifiers": {
        "avoidTolls": true
    }
}

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters

Request

{
    "origin": {
        "address": "7 Leedon Heights, Singapore 267953"
    },
    "destination": {
        "address": "Marina Bay Sands Singapore, 10 Bayfront Ave, Singapore 018956"
    },
    "travelMode": "DRIVE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T00:00:00Z",
    "routeModifiers": {
        "avoidTolls": true
    }
}

Response

{
    "routes": [
        {
            "distanceMeters": 10439,
            "duration": "1683s"
        }
    ]
}

With avoidTolls enabled, the Routes API returns a route that is longer and encounters heavy traffic (28m 3s) compared to one that uses an expressway (23m 5s).

Avoid tolls vs allow tolls in the Routes API
Avoid tolls vs allow tolls in the Routes API

To check the estimated toll cost, add routes.travelAdvisory.tollInfo to the request field mask together with the following extraComputations and routeModifiers (this feature is only available in select cities, check coverage for TollPass).

{
    "extraComputations": [
        "TOLLS"
    ],
    "routeModifiers": {
        "tollPasses": [
            "US_MA_EZPASSMA",
            "US_WA_GOOD_TO_GO"
        ]
    }
}

Show multiple routes / alternative routes in Google Maps

By default, the Routes API returns a single route, which is typically the fastest route from the origin to the destination. But just like in the Google Maps app, the Routes API can also return alternative routes so that your users can choose one that best fits their requirements. All you need to do is add "computeAlternativeRoutes": true to the request body:

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters,routes.warnings

Request

{
    "origin": {
        "address": "7 Leedon Heights, Singapore 267953"
    },
    "destination": {
        "address": "Marina Bay Sands Singapore, 10 Bayfront Ave, Singapore 018956"
    },
    "computeAlternativeRoutes": true,
    "travelMode": "DRIVE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T00:00:00Z"
}

Response

{
    "routes": [
        {
            "distanceMeters": 16972,
            "duration": "1377s",
            "warnings": [
                "This route has tolls.",
                "This route includes a highway."
            ]
        },
        {
            "distanceMeters": 10436,
            "duration": "1551s",
            "warnings": [
                "This route has tolls."
            ]
        },
        {
            "distanceMeters": 14351,
            "duration": "1591s",
            "warnings": [
                "This route has tolls.",
                "This route includes a highway."
            ]
        }
    ]
}

In the response, the routes array will include up to three alternative routes, along with polylines, travel distance, trip duration, and any toll or highway usage information.

Multiple routes returned by the Routes API
Multiple routes returned by the Routes API

Vehicle heading and side of road preference

If your chosen origin is next to a two way road, the Routes API will select the fastest route to the destination. This may involve making a right or left turn across the road to optimize travel time.

Unwanted left or right hand turns when vehicle heading / side of road is not specified
Unwanted left or right hand turns when vehicle heading / side of road is not specified

In certain situations, such as when a taxi driver receives a pickup notification, making a turn across the road may be risky and unsafe, so it's best to avoid it. If you call the Routes API directly, it will return a travel time that is unrealistic because it doesn't take into account the U-turns and additional maneuvers needed to safely get to the other side of the road.

Correct route taken when vehicle heading / side of road is specified
Correct route taken when vehicle heading / side of road is specified

Side of road

To resolve this, add "sideOfRoad": true to the origin object in your Routes API request. This will prompt the routing engine to assume the driver will continue driving until he can safely make a U-turn or take an alternative route to reach their destination. Here's a concrete example of how vehicle headings and side of road preferences work.

Consider the route starting from Hua Yu Wee Seafood Restaurant, 462 Upper E Coast Rd, Singapore 466508 to Villa Haji Kahar, 3 Bedok Ave, Singapore 469898.

An example of an incorrect route when vehicle side of road isn't specified
An example of an incorrect route when vehicle side of road isn't specified

In Singapore, cars drive on the left side of the road. If you start from Hua Yu Wee, the default route from the Routes API requires a risky right turn into traffic. To get a safer and more realistic route, specify that the driver should continue on the same side of the road he is already on.

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.duration,routes.distanceMeters,routes.polyline

Request

{
    "origin": {
        "address": "Hua Yu Wee Seafood Restaurant, 462 Upper E Coast Rd, Singapore 466508",
        "sideOfRoad": true
        
    },
    "destination": {
        "address": "Villa Haji Kahar, 3 Bedok Ave, Singapore 469898"
    },
    "computeAlternativeRoutes": true,
    "travelMode": "DRIVE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T00:00:00Z"
}

Response

{
    "routes": [
        {
            "distanceMeters": 2506,
            "duration": "494s",
            "polyline": {
                "encodedPolyline": "az_GcalyRb@lFoKfA[ASO}@}B]eAa@m@{@cACK?wEj@CNGVs@LiAPIjAG~FQ`@EESy@wAGSIIa@wAg@aCYiBoBeHq@mCk@_FaBiIWcAu@gEKm@]mBUw@iA}Cg@NkBlAoAkBiAfA"
            }
        }
    ]
}

With sideOfRoad set to true, the new route (which involves a small detour) takes 8m 14s compared to the 4m 27s for the default route provided by the Routes API.

The correct route when vehicle side of road is specified
The correct route when vehicle side of road is specified

Vehicle heading

Alternatively, if you know the driver’s heading, for example, by retrieving it from the location listener in the Google Navigation SDK or by calculating it from stored GPS traces using the Compute Heading method in the Google Geometry library - you can include the heading field in the origin object to achieve a similar result.

This is especially useful when using the driver's real-time GPS location as the origin, as it may not always align with the correct side of the road they are driving on.

Vehicle heading used in a Routes API calculation
Vehicle heading used in a Routes API calculation

Heading values are whole numbers that align with the compass directions, and therefore range from zero to 359. For example, a value of 0 indicates a heading direction of due North. In the example above, the heading of a vehicle driving on the left side of the road and picking someone ip from Hua Yu Wee restaurant is approximately 261.18.

{
    "origin": {
        "location": {
            "latLng": {
                "latitude": 1.315003,
                "longitude": 103.941463
            },
            "heading": 261
        }
    },
    ...
}

Get a route token

The Routes API plays well with other Google Maps APIs. For example, you can use it to generate a route token that can be passed on to the Google Navigation SDK for in-app turn by turn navigation. This encrypted route token includes both the route polyline and objectives.

Routes API route token used in the Google Navigation SDK
Routes API route token used in the Google Navigation SDK

To get the Routes API to return a route token:

  1. Set travelMode to "DRIVE",
  2. Set routingPreference to "TRAFFIC_AWARE" ,
  3. Specify the routes.routeToken field mask

Endpoint POST https://routes.googleapis.com/directions/v2:computeRoutes

Headers
Content-Type: application/json
X-Goog-Api-Key: YOUR_API_KEY
X-Goog-FieldMask: routes.routeToken

Request

{
    "origin": {
        "address": "7 Leedon Heights, Singapore 267953"
    },
    "destination": {
        "address": "Marina Bay Sands Singapore, 10 Bayfront Ave, Singapore 018956"
    },
    "computeAlternativeRoutes": true,
    "travelMode": "DRIVE",
    "routingPreference": "TRAFFIC_AWARE",
    "departureTime": "2025-02-24T00:00:00Z"
}

Response

{
    "routes": [
        {
            "routeToken": "CrsCCsYBMsMBGqgBCjMCFg5VIHzyBkYMyyDoxO4BupvyBrmKswimAaqCI9HwI8awgQTJwFns2ecBjWGWYoy9AgASNLxgT3G8NgpsHPKF6m0sNRfhAk6wZGpTqZ68pfO2ElrN05jTYWHVqTAgFb69oOxpRefYWrAaFAAbtAKABgACJUGGBgIBA_____8PKg1SX3cOAAAICgYKABV3MgEGPaaqBz9FUIKYPUiekcyEo5TgodkBIhZxeGlzWl9GTDFyM0wxZ19hazgtaEN3EAUaVwpVChgKDQoCCAERAAAAAACAZkARppvEIBCEt0ASGggAEAMQBhATEBIYAkIMCgYQgO3uvQYaAggFIhsKF3FoaXNaN3pMUE5hOXk5WVAycFBQb1FzcAEoASIVANIYyLeEYHP-97XdsVZCQt-xVdHt"
        }
    ]
}

You can then pass the route token to the Navigation SDK using the Navigator.setDestinations() method like so:

/*** MainActivity.kt ***/
CustomRoutesOptions customRoutesOptions =
      CustomRoutesOptions.builder()
      .setRouteToken(routeToken)
      .setTravelMode(TravelMode.DRIVING)
      .build();

ListenableResultFuture<RouteStatus> routeStatusFuture =
        navigator.setDestinations(destinations, customRoutesOptions, displayOptions);

Routes API alternatives

There are three main alternatives to the Routes API. The Google Directions API, the OSRM Route API, and competing navigation and location based services such as MapBox, HERE Maps and TomTom.

Google Directions API The Google Directions API is the OG point to point routing API. It's less expensive (pricing starts at $5 CPM) but does not include traffic on polylines, toll information or two wheeler routes. It's also going to be discontinued in Q2 2025, and will not be eligible for volume discounts and bulk pricing.

OSRM Route API The Open Source Routing Machine (OSRM) Route API uses user contributed OpenStreetMap data to offer a free alternative to the Google Maps Routes API. Its main drawback is that it does not account for real time traffic, which can lead to inaccurate ETAs and travel times. It also requires some engineering expertise to install and set up correctly.

MapBox, HERE Maps and TomTom MapBox, HERE Maps and TomTom are geospatial technology and mapping companies that primarily serve governments, auto manufacturers, and large enterprises. Their APIs are generally more affordable than Google Maps but can be more complex to use. HERE Maps and TomTom stand out for their built in truck routing features, making them a strong choice for companies operating large delivery trucks or heavy vehicles.

Using the Routes API to build a traffic aware navigation app

In this final section, we'll create a traffic aware navigation app. The app will allow users to select a start and end location and will retrieve one or more routes for both cars and two wheelers using the Routes API. The app will also show real time traffic conditions using color coded polylines, making it easy to see traffic conditions along the route.

Traffic aware navigation app that uses the Routes API
Traffic aware navigation app that uses the Routes API

How our code is structured

routes_api_demo is a single page react app built on three main components:

  • <SearchBox/>, a container for the <PlaceAutocompleteInput/> input fields where we'll enter the origin and destination for our route,
  • <Map/>, which renders and manages a Google Map instance for us to draw traffic aware route polylines on.
  • The Redux state management library.
The structure of our traffic aware navigation app used in the Routes API
The structure of our traffic aware navigation app used in the Routes API

Like our other tutorials, we'll be using @vis.gl/react-google-maps  to quickly set up our app. This library simplifies integrating Google Maps into a React app by offering prebuilt components that make it easy to handle map interactions and UI in a React-friendly way. The <Map/>, <AdvancedMarker/>, <Polyline/> and <InfoWindow/> components are all examples of this. You can find the complete source code and installation instructions for running the app locally in our GitHub repository.

App.jsx

App.jsx functions as the primary entry point to our app. It features a streamlined, map-focused interface with a search box (<SearchBoxContainer/>) on the left, where users can enter their origin and destination to plan a route. The Google Maps base map (<MapContainer/>) serves as the background on which the route polyline is displayed.

/*** App.jsx ***/
import React from 'react';
import { APIProvider } from '@vis.gl/react-google-maps';
import MapContainer from './containers/MapContainer';
import SearchBoxContainer from './containers/SearchBoxContainer';

import './App.scss';

const App = () => {
  return (
    <div className="App">
      <APIProvider apiKey={process.env.REACT_APP_GOOGLE_API_KEY}>
        <div className="control-pannel">
          <SearchBoxContainer />
        </div>
        <MapContainer />
      </APIProvider>
    </div>
  );
};

export default App;

The <APIProvider/> wrapper component supplies the Google Maps API context to it's <MapContainer/> child component. This setup allows <MapContainer/> to access the Google Maps API using the REACT_APP_GOOGLE_API_KEY.

SearchBoxContainer.jsx

SearchBoxContainer.jsx is a container component that takes care of state management for its child, <SearchBox/>. It also handles API calls to the Google Routes API, fetching route data when a valid origin and destination are provided.

When the user selects a new origin or destination in the address autocomplete search box (or chooses a different travel mode), the useEffect hook is triggered. This hook then calls the getDirections() method from /services/google.js, which subsequently makes an API request to the Routes API to get the best route.

/*** SearchBoxContainer.jsx ***/
import { getDirections } from '../services/google';

useEffect(() => {
    const fetchDirections = async () => {
      if (origin && destination && Object.keys(origin).length && Object.keys(destination).length) {
        try {
          console.log('Fetching directions:', { origin, destination });
          const newRoutes = {};
          for (const mode of TRAVEL_MODES) {
            const res = await getDirections({
              origin: { address: origin.formattedAddress },
              destination: { address: destination.formattedAddress },
              travelMode: mode,
              routingPreference: 'traffic_aware',
              extraComputations: ['TRAFFIC_ON_POLYLINE'],
            });
            newRoutes[mode] = res?.routes || [];
          }
          dispatch(setData({ routes: newRoutes }));
        } catch (error) {
          console.error('Failed to fetch directions:', error);
          dispatch(setData({ routes: [], error: 'Failed to fetch directions' }));
        }
      }
    };

Google.js

services/google.js fetches data from the Routes API. When the getDirections() method is called, we use the axios.post() method to make an HTTP POST request to the Compute Routes endpoint of the Google Routes API: https://routes.googleapis.com/directions/v2:computeRoutes. The request is built incrementally.

💡
Axios is a promise-based HTTP client for Javascript that allows you to make requests to APIs from both the browser and Node.js.

First the headers (x-goog-api-key, x-goog-fieldmask) followed by the body ( {origin, destination, travelMode, routingPreference, extracomputations, polylineQuality, computeAlternativeRoutes}), wrapped in a axios.post() call.

/*** google.js ***/
import axios from 'axios';

const DIRECTION_API_URL = 'https://routes.googleapis.com/directions/v2:computeRoutes';
const API_KEY = process.env.REACT_APP_GOOGLE_API_KEY;

const getHeaders = () => ({
  'x-goog-api-key': API_KEY,
  'x-goog-fieldmask': '*',
});

const buildRequestBody = (body) => ({
  ...body,
  polylineQuality: 'high_quality',
  computeAlternativeRoutes: true,
});

const getDirections = async (body) => {
  try {
    const response = await axios.post(DIRECTION_API_URL, buildRequestBody(body), {
      headers: getHeaders(),
    });
    return response.data;
  } catch (error) {
    console.error('Error fetching directions:', error);
    throw error;
  }
};

export { getDirections };

The response payload is dispatched to Redux and used to update the global state in SearchBoxContainer.jsx.

SearchBox.jsx

Google places address autocomplete search box used to get origin and destinations
Google places address autocomplete search box used to get origin and destinations

SearchBox.jsx is a presentational component that wraps both <PlaceAutocompleteInput/> and the travel mode selections buttons. When a user selects a new address or switches travel mode, this information is passed to the parent <SearchBoxContainer/> via the onOriginAddressChange, onDestinationAddressChange and onTravelModeChange props.

/*** SearchBox.jsx ***/
import React from 'react';
import PlaceAutocompleteInput from './PlaceAutocompleteInput';
import { TRAVEL_MODES } from '../constants';
import { formatDuration } from '../services/time';
import Image from '../images';

import './SearchBox.scss';

const SearchBox = ({ routes, travelMode, onTravelModeChange, onOriginAddressChange, onDestinationAddressChange }) => {
  return (
    <div className="search-box">
      <div className="travel-mode-filter-btns">
        {TRAVEL_MODES.map((mode) => {
          const duration = formatDuration(routes[mode]?.[0]?.duration);
          return (
            <button key={mode} className={travelMode === mode ? 'active' : ''} onClick={() => onTravelModeChange(mode)}>
              <Image type={mode} />
              {duration && <span>{duration}</span>}
            </button>
          );
        })}
      </div>
      <div className="input-address-group">
        <Image type="ThreeDot" />
        <PlaceAutocompleteInput onPlaceSelect={onOriginAddressChange} />
        <PlaceAutocompleteInput onPlaceSelect={onDestinationAddressChange} />
      </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.

Google places address autocomplete calling the Google Places API
Google places address autocomplete calling the Google Places API

To do this, we first add a text input field in the return statement of PlaceAutocompleteInput.jsx and give it the ref="inputRef".

Second, in a useEffect hook, we create a new Google Places Autocomplete instance with the line: new places.Autocomplete(inputRef.current, options) and bind it to the text input field.

Third, we set up an event listener on the placeAutocomplete object which listens for the "place_changed" event that is triggered whenever the user selects a place from the list of autocomplete suggestions. The result is passed to the onPlaceSelect prop, which eventually bubbles up to SearchBoxContainer.jsx.

/*** PlaceAutocompleteInput.jsx ***/
import React, { useRef, useEffect, useState } from 'react';
import { useMapsLibrary } from '@vis.gl/react-google-maps';
import './PlaceAutocompleteInput.scss';

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="autocomplete-container">
      <input ref={inputRef} />
    </div>
  );
};

export default PlaceAutocompleteInput;

The placeAutocomplete.getPlace() object returned by Google Places Autocomplete and passed up to SearchBoxContainer.jsx, has the following structure:

{
    "formatted_address": "11 Leedon Heights, D'Leedon, Singapore 267955",
    "geometry": {
        "location": {
            "lat": 1.3162837,
            "lng": 103.8045578
        },
        "viewport": {
            "south": 1.315023719708498,
            "west": 103.8031722197085,
            "north": 1.317721680291502,
            "east": 103.8058701802915
        }
    },
    "name": "D'Leedon",
    "place_id": "ChIJk2oGxxMa2jERBMEx943Jyyo",
    "html_attributions": []
}

In SearchBoxContainer.jsx, this address data is sent to the handleAddressChange() method where it is used to update the application's state using a Redux action (setData).

/*** SearchBoxContainer.jsx ***/
dispatch(
  setData({
    [type]: {
      name: address.name,
      formattedAddress: address.formatted_address,
      position: {
        lat: address.geometry.location.lat(),
        lng: address.geometry.location.lng(),
      },
      place_id: address.place_id,
    },
  }),
);

If type was "origin", then the JSON blob saved to redux would look like this:

{
  "origin": {
    "name": "D'Leedon",
    "formattedAddress": "11 Leedon Heights, D'Leedon, Singapore 267955",
    "position": {
      "lat": 1.3162837,
      "lng": 103.8045578
    },
    "place_id": "ChIJk2oGxxMa2jERBMEx943Jyyo"
  }
}

MapContainer.jsx

MapContainer.jsx is a container component for <Map/>. Whenever state.map.data in the Redux store changes i.e. whenever the origin, destination, or route is updated, const { origin, destination, routes, travelMode } = useSelector((state) => state.map.data); is run and the new values for travelMode, routes, origin and destination are passed as props to <Map/>.

/*** containers/MapContainer.jsx ***/
import React from 'react';
import { useSelector } from 'react-redux';

import Map from '../components/Map';

const MapContainer = () => {
  const { origin, destination, routes, travelMode } = useSelector((state) => state.map.data);

  return <Map origin={origin} destination={destination} travelMode={travelMode} routes={routes[travelMode] || []} />;
};

export default MapContainer;

Map.jsx

Map.jsx (/Map/index.jsx) initializes a <Map/> component centered on Vancouver, BC. When a user enters an origin and destination to retrieve a route, the map automatically recenters on that route, which is displayed using a <Polyline/>.

InfoWindow, Polyline and Marker components displayed on a Google Map
InfoWindow, Polyline and Marker components displayed on a Google Map

Alongside <Polyline/>, the <Map/> component includes three key child components:

  • <MapHandler/>: Adjusts the map's viewport as needed.
  • <Marker/>: Adds icons to indicate the starting and ending points of the route.
  • <InfoWindow/>: Popup window that displays the travel mode, trip distance and duration when you click on a route polyline.
/*** components/Map/index.jsx ***/
import React, { useMemo, useState } from 'react';
import { useMap, Map as Gmap, InfoWindow } from '@vis.gl/react-google-maps';
import { DEFAULT_MAP_CENTER, DEFAULT_MAP_ZOOM } from '../../constants';
import Marker from './Marker';
import Polyline, { InfoWindow as PolylineInfoWindow } from './Polyline';
import MapHandler from './MapHandler';

const Map = ({ origin, destination, travelMode, routes }) => {
  const map = useMap();
  const [infoWindow, setInfoWindow] = useState(null);
  const [activeRoute, setActiveRoute] = useState(0);

  const stops = useMemo(() => {
    const stopsArray = [];
    if (origin?.position) stopsArray.push(origin);
    if (destination?.position) stopsArray.push(destination);
    return stopsArray;
  }, [origin, destination]);

  const handlePolylineClick = (event, route, routeIndex) => {
    const position = {
      lat: event.latLng.lat(),
      lng: event.latLng.lng(),
    };
    setActiveRoute(routeIndex);
    setInfoWindow({
      position,
      content: <PolylineInfoWindow route={route} travelMode={travelMode} />,
    });
  };

  return (
    <Gmap
      mapId={process.env.REACT_APP_GOOGLE_MAP_ID}
      defaultZoom={DEFAULT_MAP_ZOOM}
      defaultCenter={DEFAULT_MAP_CENTER}
      gestureHandling="greedy"
      disableDefaultUI={true}
    >
      <MapHandler map={map} stops={stops} />
      {origin && <Marker type="OriginMarker" position={origin.position} />}
      {destination && <Marker type="DestinationMarker" position={destination.position} />}
      {routes.map((route, routeIndex) => (
        <Polyline
          key={`polyline-${routeIndex}`}
          route={route}
          activeRoute={activeRoute}
          routeIndex={routeIndex}
          onClick={handlePolylineClick}
        />
      ))}
      {infoWindow && (
        <InfoWindow
          key={infoWindow.position.lat + ',' + infoWindow.position.lng}
          position={infoWindow.position}
          onCloseClick={() => setInfoWindow(null)}
        >
          {infoWindow.content}
        </InfoWindow>
      )}
    </Gmap>
  );
};

export default React.memo(Map);

MapHandler.jsx

Map viewport used to center the route returned by the Routes API
Map viewport used to center the route returned by the Routes API

MapHandler.jsx is responsible for centering <Map/> squarely on the route polyline. It does this in a two step process:

  1. Iterating over the stops array (the start and end location of each route) and use the LatLngBounds.extend() method determine the "corner" southwest and northeast bounds (a LatLngBounds object in Google Maps represents a rectangular geographic area defined by its northeast and southwest corners).
  2. Calling the Maps.fitBounds() method to contain the viewport within the given bounds.
/*** components/Map/MapHandler.jsx ***/
import React, { useEffect } from 'react';

const MapHandler = ({ map, stops }) => {
  useEffect(() => {
    if (!map) return;

    if (stops.length > 0) {
      const bounds = new window.google.maps.LatLngBounds();
      stops.forEach((stop) => {
        bounds.extend(new window.google.maps.LatLng(stop.position.lat, stop.position.lng));
      });
      map.fitBounds(bounds);
    }

    if (stops.length === 1) {
      map.setZoom(14);
    }
  }, [map, stops]);

  return null;
};

export default React.memo(MapHandler);

The final result is a <Map/> that is properly centered on the route's polyline.

Marker.jsx

<Marker/> is a React wrapper for Google's AdvancedMarkerElement, enabling the creation of customizable, interactive markers on Google Maps using SVG files. In our application, we use these markers to indicate the origin and destination of the route, which are selected through the Places Autocomplete text field.

SVG images used to represent the origin and destination of a route
SVG images used to represent the origin and destination of a route

Using the origin marker as an example, here's how <Image/> in Marker.jsx is used to replace the default red marker with an SVG file:

/*** components/Map/Marker.jsx ***/
import React from 'react';
import { AdvancedMarker } from '@vis.gl/react-google-maps';
import Image from '../../images';
import './Marker.scss';

const Marker = ({ style, position, active, onToggle, type }) => {
  return (
    <AdvancedMarker
      position={position}
      className={`marker ${active ? 'active' : ''}`}
      zIndex={active ? 2 : 1}
      onClick={onToggle}
    >
      <Image type={type} style={style} />
    </AdvancedMarker>
  );
};

export default Marker;
  1. In <Map/>, the origin marker is added with the line: <Marker type="OriginMarker" position={origin.position}/>.
  2. In Marker.jsx, we next import images/index.jsx with the line import Image from '../../images'. This gives us access to the <Image/> component, which we include as a child component of <AdvancedMarker/>.
  3. In images/index.jsx, the code below assigns OriginMarker to the SVGComponent variable. In the return statement, {SVG}, now becomes <OriginMarker/>.
/*** images/index.jsx ***/
import { ReactComponent as ThreeDot } from './three-dot.svg';
import { ReactComponent as DRIVE } from './drive.svg';
import { ReactComponent as TWO_WHEELER } from './two-wheeler.svg';
import { ReactComponent as OriginMarker } from './origin-marker.svg';
import { ReactComponent as DestinationMarker } from './destination-marker.svg';

const images = {
  ThreeDot,
  DRIVE,
  TWO_WHEELER,
  OriginMarker,
  DestinationMarker,
};

const SVGComponent = images[type];
return SVGComponent ? (
  <SVGComponent
    style={{ fill: color || undefined, stroke: strokeColor || undefined }}
  />
) : null;
  1. Because we already imported origin-marker.svg as a React component and linked it to <OriginMarker/>, the SVG file is rendered as an inline SVG and can be used like any other JSX element.

Polyline.jsx

Each route returned by the Routes API is rendered as a <Polyline/> component on <Map/>. <Polyline/> (/Map/polyline.jsx) is actually a thin wrapper that allows us to combine multiple polylines together into a single color coded polyline. The actual rendering and event handling code can be found in a separate file, /Map/external/polyline.jsx, which was taken from the Geometry Example of the react-google-maps library. <Polyline/> accepts the following props:

key: a unique identifier for the polyline (not important)
route: route data that includes polyline.encodedPolyline
activeRoute: the index of the selected route (highlighted)
routeIndex: the index of the route in the order it was returned by the Routes API
onClick: an event handle for the <InfoWindow/> component

/*** components/Map/index.jsx ***/
{
  routes.map((route, routeIndex) => (
    <Polyline
      key={`polyline-${routeIndex}`}
      route={route}
      activeRoute={activeRoute}
      routeIndex={routeIndex}
      onClick={handlePolylineClick}
    />
  ));
}

When route is passed as a prop into <Polyline/>, it is decoded into an array of coordinate pairs, segmented by traffic speed, and redrawn as a color coded traffic-aware polyline. This process is described near the start of this blog post in the section on drawing a route with real time traffic.

Color coded, traffic aware polyline returned by the Routes API
Color coded, traffic aware polyline returned by the Routes API

Alternative routes (if any) are given a strokeOpacity of 0.5. When a certain route is clicked on, its routeIndex is designated as the activeRoute and its polyline is given a strokeOpacity of 1.0 (a solid line).

/*** components/Map/polyline.jsx ***/
return {
  path: geometryLibrary.encoding.encodePath(newPoints),
  options: {
    zIndex: 100 - routeIndex + zIndex--,
    strokeColor: SPEED_COLORS[interval.speed] || SPEED_COLORS.NORMAL,
    strokeOpacity: routeIndex === activeRoute ? 1.0 : 0.5,
    strokeWeight: 6,
  },
};

InfoWindow.jsx

InfoWindow component used to display route travel times and distances
InfoWindow component used to display route travel times and distances

The final component we'll cover is <InfoWindow/>, which displays the trip's distance, duration, and travel mode in a temporary popup when you click on the route polyline.

When <Polyline/> is clicked, the handlePolylineClick() method in Map.jsx gets called. The info window is positioned at the click location and the route information (which includes distance and duration), together with the travelMode is passed in to <PolylineInfoWindow/>.

/*** components/Map/index.jsx ***/
const handlePolylineClick = (event, route, routeIndex) => {
  const position = {
    lat: event.latLng.lat(),
    lng: event.latLng.lng(),
  };
  setActiveRoute(routeIndex);
  setInfoWindow({
    position,
    content: <PolylineInfoWindow route={route} travelMode={travelMode} />,
  });
};

<PolylineInfoWindow/> is a named export React functional component of Polyline.jsx. It receives a route (containing distanceMeters and duration) and travelMode as props and displays them in a nicely formatted way.

/*** components/Map/Polyline.jsx ***/
export const InfoWindow = ({ route, travelMode }) => (
  <div className="polyline-info-window">
    <div className="content">
      <div className="title">
        <Image type={travelMode} color="#212121" />
        <b>{formatDuration(route.duration)}</b>
      </div>
      <div className="distanceMeters">
        <p>{(route.distanceMeters / 1000).toFixed(2)} km</p>
      </div>
    </div>
  </div>
);

Just like the icon used in the origin and destination markers, we use inline SVGs (<Image type={travelMode}/>} to switch between the car or two wheeler icon to indicate the travelMode.

To test the app, visit https://routes-api-demo.afi.dev/ and enter an origin and destination, just like you would on Google Maps.

To run the app locally:

  1. Fork the routes_api_demo repository from GitHub.
  2. Run npm install to install dependencies.
  3. Start the app with npm start.
  4. Update the .env file with your Google Maps API key before running the app.

Coming up next in the Routes API tutorial series

In this blog post, I showed you how to use the Compute Routes endpoint of the Routes API to get the fastest route between two points, as well as distances, travel times, and driving directions. In our next post, we'll explore the Route API's Compute Route Matrix endpoint, which calculates travel times and distances for multiple origin-destination pairs, and use this to build a basic taxi dispatch system that assigns passengers to the nearest available vehicle.

Part 3: Using the Google Distance Matrix API for taxi dispatch