By the end of this lesson, you will:
Navigation transforms maps from static displays into interactive guides. Apps like Uber, Waze, and Google Maps use sophisticated routing to connect users with destinations. Whether you're building a delivery app, a fitness tracker, or a social meetup platform, navigation makes your app actionable.
In this lesson, we'll implement routing features that help users get from point A to point B efficiently, with clear visual feedback and accurate time estimates.
The Directions API calculates routes between locations:
const getDirections = async (origin, destination) => {
const apiKey = 'YOUR_API_KEY';
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=${apiKey}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.routes.length > 0) {
const route = data.routes[0];
return {
distance: route.legs[0].distance.text,
duration: route.legs[0].duration.text,
polyline: route.overview_polyline.points,
};
}
} catch (error) {
console.error('Directions error:', error);
return null;
}
};
// Usage
const route = await getDirections(
{ latitude: 37.78825, longitude: -122.4324 },
{ latitude: 37.7749, longitude: -122.4194 }
);
console.log(`Distance: ${route.distance}`);
console.log(`Duration: ${route.duration}`);
API Response Structure:
routes[]: Array of possible routeslegs[]: Segments of the route (origin to destination)distance: Total distance in meters and textduration: Total time in seconds and textoverview_polyline: Encoded route coordinatesGoogle returns encoded polylines to reduce response size. Decode them to display routes:
// Install: npm install @mapbox/polyline
import polyline from '@mapbox/polyline';
const decodePolyline = (encoded) => {
return polyline.decode(encoded).map(([latitude, longitude]) => ({
latitude,
longitude,
}));
};
// Usage
const encodedPolyline = 'u{~vFvyys@fS~n@';
const coordinates = decodePolyline(encodedPolyline);
console.log(coordinates);
// [
// { latitude: 37.78825, longitude: -122.4324 },
// { latitude: 37.7849, longitude: -122.4294 },
// ...
// ]
💡 Tip: Always decode polylines on the client side. The encoded format is much smaller for API responses.
Render directions as polylines:
import { useState } from 'react';
import MapView, { Polyline, Marker } from 'react-native-maps';
import polyline from '@mapbox/polyline';
import { View, Button, StyleSheet } from 'react-native';
export default function RouteMapScreen() {
const [routeCoords, setRouteCoords] = useState([]);
const [distance, setDistance] = useState('');
const [duration, setDuration] = useState('');
const origin = { latitude: 37.78825, longitude: -122.4324 };
const destination = { latitude: 37.7749, longitude: -122.4194 };
const fetchRoute = async () => {
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
if (data.routes.length > 0) {
const route = data.routes[0];
const coords = polyline
.decode(route.overview_polyline.points)
.map(([lat, lng]) => ({ latitude: lat, longitude: lng }));
setRouteCoords(coords);
setDistance(route.legs[0].distance.text);
setDuration(route.legs[0].duration.text);
}
};
return (
<View style={styles.container}>
<MapView
style={styles.map}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}}
>
<Marker coordinate={origin} title="Start" pinColor="green" />
<Marker coordinate={destination} title="End" pinColor="red" />
{routeCoords.length > 0 && (
<Polyline
coordinates={routeCoords}
strokeColor="#4285F4"
strokeWidth={5}
/>
)}
</MapView>
<View style={styles.controls}>
<Button title="Get Directions" onPress={fetchRoute} />
{distance && (
<View style={styles.info}>
<Text style={styles.infoText}>Distance: {distance}</Text>
<Text style={styles.infoText}>Duration: {duration}</Text>
</View>
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
flex: 1,
},
controls: {
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
},
info: {
backgroundColor: '#fff',
padding: 12,
borderRadius: 8,
marginTop: 10,
},
infoText: {
fontSize: 14,
marginBottom: 4,
},
});
Support different transportation methods:
const getDirections = async (origin, destination, mode = 'driving') => {
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&mode=${mode}&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
return data;
};
// Usage
const drivingRoute = await getDirections(origin, destination, 'driving');
const walkingRoute = await getDirections(origin, destination, 'walking');
const transitRoute = await getDirections(origin, destination, 'transit');
const bicyclingRoute = await getDirections(origin, destination, 'bicycling');
Available Travel Modes:
driving: Default, car routeswalking: Pedestrian pathsbicycling: Bike-friendly routestransit: Public transportationMode-Specific Features:
Route through intermediate stops:
const getRouteWithWaypoints = async (origin, destination, waypoints) => {
const waypointsParam = waypoints
.map(wp => `${wp.latitude},${wp.longitude}`)
.join('|');
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&waypoints=${waypointsParam}&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
return data;
};
// Usage: Delivery route with multiple stops
const deliveryRoute = await getRouteWithWaypoints(
{ latitude: 37.78825, longitude: -122.4324 }, // Warehouse
{ latitude: 37.7649, longitude: -122.4094 }, // Final destination
[
{ latitude: 37.7749, longitude: -122.4194 }, // Stop 1
{ latitude: 37.7699, longitude: -122.4144 }, // Stop 2
]
);
Waypoint Options:
optimize:true: Reorder waypoints for fastest routeExtract step-by-step navigation:
const getTurnByTurn = async (origin, destination) => {
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
if (data.routes.length > 0) {
const steps = data.routes[0].legs[0].steps.map(step => ({
instruction: step.html_instructions.replace(/<[^>]*>/g, ''), // Strip HTML
distance: step.distance.text,
duration: step.duration.text,
maneuver: step.maneuver, // turn-left, turn-right, etc.
}));
return steps;
}
};
// Usage
const instructions = await getTurnByTurn(origin, destination);
instructions.forEach((step, index) => {
console.log(`${index + 1}. ${step.instruction} (${step.distance})`);
});
// Output:
// 1. Head north on Main St (0.2 mi)
// 2. Turn right onto Oak Ave (0.5 mi)
// 3. Turn left onto Maple Dr (1.1 mi)
Available Maneuvers:
turn-left, turn-rightturn-slight-left, turn-slight-rightturn-sharp-left, turn-sharp-rightramp-left, ramp-rightmerge, fork-left, fork-rightroundabout-left, roundabout-rightInclude traffic data for accurate ETAs:
const getDirectionsWithTraffic = async (origin, destination) => {
const departureTime = Math.floor(Date.now() / 1000); // Current time in seconds
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&departure_time=${departureTime}&traffic_model=best_guess&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
if (data.routes.length > 0) {
const leg = data.routes[0].legs[0];
return {
distance: leg.distance.text,
normalDuration: leg.duration.text,
trafficDuration: leg.duration_in_traffic?.text || leg.duration.text,
};
}
};
// Usage
const route = await getDirectionsWithTraffic(origin, destination);
console.log(`Normal: ${route.normalDuration}`);
console.log(`With traffic: ${route.trafficDuration}`);
Traffic Parameters:
departure_time: Unix timestamp (seconds)traffic_model: best_guess, pessimistic, optimisticduration_in_traffic: Real-time estimate with traffic💡 Tip: Use
departure_time=nowfor real-time traffic. For future trips, calculate the Unix timestamp.
Offer users multiple route options:
const getAlternativeRoutes = async (origin, destination) => {
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&alternatives=true&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
return data.routes.map((route, index) => ({
id: index,
distance: route.legs[0].distance.text,
duration: route.legs[0].duration.text,
summary: route.summary,
polyline: route.overview_polyline.points,
}));
};
// Usage
const routes = await getAlternativeRoutes(origin, destination);
routes.forEach((route, index) => {
console.log(`Route ${index + 1}: ${route.summary} - ${route.distance}, ${route.duration}`);
});
Complete navigation screen with route comparison:
import { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
import MapView, { Polyline, Marker } from 'react-native-maps';
import polyline from '@mapbox/polyline';
export default function NavigationScreen() {
const [routes, setRoutes] = useState([]);
const [selectedRoute, setSelectedRoute] = useState(0);
const origin = { latitude: 37.78825, longitude: -122.4324 };
const destination = { latitude: 37.7749, longitude: -122.4194 };
const fetchRoutes = async () => {
const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&alternatives=true&departure_time=now&key=YOUR_API_KEY`;
const response = await fetch(url);
const data = await response.json();
const parsedRoutes = data.routes.map((route, index) => ({
id: index,
distance: route.legs[0].distance.text,
duration: route.legs[0].duration_in_traffic?.text || route.legs[0].duration.text,
summary: route.summary,
coordinates: polyline
.decode(route.overview_polyline.points)
.map(([lat, lng]) => ({ latitude: lat, longitude: lng })),
}));
setRoutes(parsedRoutes);
};
return (
<View style={styles.container}>
<MapView
style={styles.map}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}}
>
<Marker coordinate={origin} title="Start" pinColor="green" />
<Marker coordinate={destination} title="End" pinColor="red" />
{routes.map((route, index) => (
<Polyline
key={route.id}
coordinates={route.coordinates}
strokeColor={index === selectedRoute ? '#4285F4' : '#999'}
strokeWidth={index === selectedRoute ? 6 : 3}
onPress={() => setSelectedRoute(index)}
/>
))}
</MapView>
<View style={styles.controls}>
<TouchableOpacity style={styles.button} onPress={fetchRoutes}>
<Text style={styles.buttonText}>Get Directions</Text>
</TouchableOpacity>
{routes.length > 0 && (
<ScrollView horizontal style={styles.routeOptions}>
{routes.map((route, index) => (
<TouchableOpacity
key={route.id}
style={[
styles.routeCard,
index === selectedRoute && styles.routeCardSelected,
]}
onPress={() => setSelectedRoute(index)}
>
<Text style={styles.routeSummary}>{route.summary}</Text>
<Text style={styles.routeDuration}>{route.duration}</Text>
<Text style={styles.routeDistance}>{route.distance}</Text>
</TouchableOpacity>
))}
</ScrollView>
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
flex: 1,
},
controls: {
position: 'absolute',
bottom: 20,
left: 0,
right: 0,
},
button: {
backgroundColor: '#4285F4',
padding: 16,
marginHorizontal: 20,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
routeOptions: {
marginTop: 12,
paddingHorizontal: 20,
},
routeCard: {
backgroundColor: '#fff',
padding: 12,
borderRadius: 8,
marginRight: 12,
minWidth: 140,
borderWidth: 2,
borderColor: '#ddd',
},
routeCardSelected: {
borderColor: '#4285F4',
},
routeSummary: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 4,
},
routeDuration: {
fontSize: 16,
color: '#4285F4',
fontWeight: 'bold',
marginBottom: 2,
},
routeDistance: {
fontSize: 12,
color: '#666',
},
});
| Pitfall | Solution |
|---|---|
| Polyline not rendering | Ensure coordinates are decoded properly as {latitude, longitude} |
| Inaccurate ETAs | Include departure_time and traffic_model parameters |
| Route not following roads | Check origin/destination coordinates are valid locations |
| API quota exceeded | Cache routes, use session tokens, optimize requests |
| Missing transit routes | Verify transit mode is available in the region |
In the next lesson, we'll explore Geofencing and Location Triggers, learning how to detect when users enter or exit geographic zones, enabling location-based notifications and automation.