Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions examples/directions/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# Google Maps Directions API Example
# Google Maps Routes API Example

![image](https://raw.githubusercontent.com/visgl/react-google-maps/main/website/static/images/examples/directions.jpg)

This is an example which shows how to use `useMapsLibrary` to load the `routes` library, and then use `DirectionsService` and `DirectionsRenderer` to find and display a route on a map.
This is an example which shows how to use `useMapsLibrary` to load the `routes` library, and then use the modern `Route` class to compute and render routes on a map.

It allows the user to choose alternative routes, updating the route being rendered on the map.

Users can also drag the markers around the map to change the route. The route is updated in real-time.
It utilizes the modern client-side `Route.computeRoutes()` method combined with custom-styled React `<Polyline>` rendering, completely avoiding legacy services and CORS restrictions.

> [!IMPORTANT]
>
> This example is only compatible with the
> Directions API Legacy Service. Using this Services requires enabling the
> API on your Google Cloud project by following the direct links:
> [Directions API (Legacy)][gcp-directions-api]
> This example uses the new [Routes API (Recommended)][gcp-routes-api] which is the modern and current way to calculate directions. If you are using the [Directions API (Legacy)][gcp-directions-api] Service, consider switching to this implementation of Routes API.

## Google Maps Platform API Key

Expand Down Expand Up @@ -47,3 +42,4 @@ The regular `npm start` task is only used for the standalone versions of the exa

[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key
[gcp-directions-api]: https://console.cloud.google.com/apis/library/directions-backend.googleapis.com
[gcp-routes-api]: https://console.cloud.google.com/apis/library/routes.googleapis.com
181 changes: 100 additions & 81 deletions examples/directions/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, {useEffect, useState} from 'react';
import React, { useEffect, useState, useRef } from 'react';
import {createRoot} from 'react-dom/client';

import {
APIProvider,
Map,
useMapsLibrary,
useMap
useMap,
AdvancedMarker
} from '@vis.gl/react-google-maps';
import ControlPanel from './control-panel';

Expand All @@ -15,104 +16,122 @@ const API_KEY =
const App = () => (
<APIProvider apiKey={API_KEY}>
<Map
defaultCenter={{lat: 43.65, lng: -79.38}}
defaultZoom={9}
defaultCenter={{ lat: -23.588363, lng: -46.658475 }}
defaultZoom={15}
gestureHandling={'greedy'}
fullscreenControl={false}>
<Directions />
<RouteDisplay
origin="R. Dr. Diogo de Faria, 946, São Paulo"
destination="R. Domingos Fernandes, 588, São Paulo"
travelMode="DRIVING"
/>
</Map>
<ControlPanel />
</APIProvider>
);

function Directions() {
interface RouteDisplayProps {
origin: string;
destination: string;
travelMode: 'DRIVING' | 'WALKING' | 'BICYCLING' | 'TWO_WHEELER' | 'TRANSIT';
}

interface RouteDetails {
distanceMeters: number;
durationMillis: number;
startCoords?: google.maps.LatLngLiteral;
endCoords?: google.maps.LatLngLiteral;
steps: Array<{
instructions: string;
distanceMeters: number;
durationMillis: number;
maneuver?: string;
}>;
}

export function RouteDisplay({
origin,
destination,
travelMode,
}: RouteDisplayProps) {
const map = useMap();
const routesLibrary = useMapsLibrary('routes');
const [directionsService, setDirectionsService] =
useState<google.maps.DirectionsService>();
const [directionsRenderer, setDirectionsRenderer] =
useState<google.maps.DirectionsRenderer>();
const [routes, setRoutes] = useState<google.maps.DirectionsRoute[]>([]);
const [routeIndex, setRouteIndex] = useState(0);
const selected = routes[routeIndex];
const leg = selected?.legs[0];

// Initialize directions service and renderer
useEffect(() => {
if (!routesLibrary || !map) return;
setDirectionsService(new routesLibrary.DirectionsService());
setDirectionsRenderer(
new routesLibrary.DirectionsRenderer({
draggable: true, // Only necessary for draggable markers
map
})
);
}, [routesLibrary, map]);
const routesLib = useMapsLibrary('routes');
const polylinesRef = useRef<google.maps.Polyline[]>([]);
const [routeDetails, setRouteDetails] = useState<RouteDetails | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

// Add the following useEffect to make markers draggable
useEffect(() => {
if (!directionsRenderer) return;

// Add the listener to update routes when directions change
const listener = directionsRenderer.addListener(
'directions_changed',
() => {
const result = directionsRenderer.getDirections();
if (result) {
setRoutes(result.routes);
if (!routesLib || !map || !origin || !destination) return;

// Clean up previous route lines
polylinesRef.current.forEach(p => p.setMap(null));
polylinesRef.current = [];
setError(null);
setLoading(true);

const request = {
origin: origin,
destination: destination,
travelMode: travelMode,
fields: ['path', 'distanceMeters', 'durationMillis', 'viewport', 'legs'],
};

// Access the modern client-side Route.computeRoutes class service
(routesLib.Route as any).computeRoutes(request)
.then(({ routes }: { routes: any[] }) => {
setLoading(false);
if (!routes || routes.length === 0) {
setError('No route found.');
return;
}
}
);

return () => google.maps.event.removeListener(listener);
}, [directionsRenderer]);
const primaryRoute = routes[0];

// Render polylines dynamically using modern Route.createPolylines()
const newPolylines = primaryRoute.createPolylines();
newPolylines.forEach((polyline: google.maps.Polyline) => {
polyline.setOptions({
strokeColor: '#3b82f6', // Stunning visual Tailwind Blue 500
strokeOpacity: 0.85,
strokeWeight: 6,
});
polyline.setMap(map);
});
polylinesRef.current = newPolylines;

if (primaryRoute.viewport) {
map.fitBounds(primaryRoute.viewport);
}

// Use directions service
useEffect(() => {
if (!directionsService || !directionsRenderer) return;

directionsService
.route({
origin: '100 Front St, Toronto ON',
destination: '500 College St, Toronto ON',
travelMode: google.maps.TravelMode.DRIVING,
provideRouteAlternatives: true
const details: RouteDetails = {
distanceMeters: primaryRoute.distanceMeters ?? 0,
durationMillis: primaryRoute.durationMillis ?? 0,
steps: [],
};
setRouteDetails(details);
})
.then(response => {
directionsRenderer.setDirections(response);
setRoutes(response.routes);
.catch((err: any) => {
setLoading(false);
console.error('Error computing routes:', err);
setError(err.message || 'Failed to compute route.');
});

return () => directionsRenderer.setMap(null);
}, [directionsService, directionsRenderer]);

// Update direction route
useEffect(() => {
if (!directionsRenderer) return;
directionsRenderer.setRouteIndex(routeIndex);
}, [routeIndex, directionsRenderer]);
return () => {
polylinesRef.current.forEach(p => p.setMap(null));
polylinesRef.current = [];
};
}, [routesLib, map, origin, destination, travelMode]);

if (!leg) return null;
if (loading) return <div className="route-panel">Calculating route...</div>;
if (error) return <div className="route-panel error">{error}</div>;
if (!routeDetails) return null;

return (
<div className="directions">
<h2>{selected.summary}</h2>
<p>
{leg.start_address.split(',')[0]} to {leg.end_address.split(',')[0]}
</p>
<p>Distance: {leg.distance?.text}</p>
<p>Duration: {leg.duration?.text}</p>

<h2>Other Routes</h2>
<ul>
{routes.map((route, index) => (
<li key={route.summary}>
<button onClick={() => setRouteIndex(index)}>
{route.summary}
</button>
</li>
))}
</ul>
<div className="route-panel">
<h2>Toronto Route Summary</h2>
<p>Distance: {(routeDetails.distanceMeters / 1000).toFixed(2)} km</p>
<p>Duration: {(routeDetails.durationMillis / 60000).toFixed(0)} mins</p>
</div>
);
}
Expand Down
21 changes: 13 additions & 8 deletions examples/directions/src/control-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,28 @@ import './control-panel.css';
const GCP_DIRECTIONS_API =
'https://console.cloud.google.com/apis/library/directions-backend.googleapis.com';

const GCP_ROUTES_API =
'https://console.cloud.google.com/apis/library/routes.googleapis.com';

function ControlPanel() {
return (
<div className="control-panel">
<h3>Directions</h3>
<h3>Routes API</h3>
<p>
Loading the routes library to render directions on the map using
<code>DirectionsService</code> and <code>DirectionsRenderer</code>.
Loading the routes library to compute and render routes on the map using
the modern <code>Route.computeRoutes</code> service.
</p>

<p className={'note'}>
<strong>Important:</strong> This example is only compatible with the
Directions API Legacy Service. Using this Services requires enabling the
API on your Google Cloud project by following the direct links:{' '}
<strong>Important:</strong> This example uses the new {' '}
<a target={'_new'} href={GCP_ROUTES_API}>
Routes API (Recommended)
</a>{' '},
the modern and current way to calculate directions. If you are using the{' '}
<a target={'_new'} href={GCP_DIRECTIONS_API}>
Directions API (Legacy)
</a>
.
</a>{' '}
Service, switch to this implementation of Routes API.
</p>

<div className="links">
Expand Down
Binary file modified website/static/images/examples/directions.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.