# Web: Directions API Patterns Coordinates are always `longitude,latitude` order. Default to the `driving-traffic` profile — it factors in live traffic, congestion, and incidents. Use `driving` only when you need `arrive_by` (not supported by `driving-traffic`); both profiles support `depart_at`. ## Basic Route Display **Use when:** Show driving directions on a web map ```javascript import mapboxgl from 'mapbox-gl'; mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN'; const map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/standard', center: [-122.4194, 37.7749], zoom: 12 }); async function getRoute(start, end) { // start/end are [lon, lat] const query = await fetch( `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${start[0]},${start[1]};${end[0]},${end[1]}?` + `steps=true&geometries=geojson&access_token=${mapboxgl.accessToken}`, { method: 'GET' } ); const json = await query.json(); const route = json.routes[0]; // Display route on map if (map.getSource('route')) { map.getSource('route').setData(route.geometry); } else { map.addSource('route', { type: 'geojson', data: { type: 'Feature', geometry: route.geometry } }); map.addLayer({ id: 'route', type: 'line', source: 'route', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': '#3b9ddd', 'line-width': 8, 'line-opacity': 0.8 } }); } // Add start and end markers new mapboxgl.Marker({ color: '#3FB1CE' }).setLngLat(start).addTo(map); new mapboxgl.Marker({ color: '#FF0000' }).setLngLat(end).addTo(map); // Fit map to route const bounds = new mapboxgl.LngLatBounds(); route.geometry.coordinates.forEach((coord) => bounds.extend(coord)); map.fitBounds(bounds, { padding: 50 }); return route; } // Example usage const start = [-122.4194, 37.7749]; // San Francisco const end = [-122.2711, 37.8044]; // Oakland getRoute(start, end); ``` > **Geometry format:** `geometries=geojson` is used here because the response is fed straight into a GL JS source. It's the largest of the three geometry formats over the wire — when you don't need to render immediately (backend processing, caching, mobile clients), request `geometries=polyline6` instead and decode client-side. See [Performance Optimization](best-practices.md#performance-optimization). ## Turn-by-Turn Instructions Display ```javascript function displayInstructions(route) { const steps = route.legs[0].steps; const instructionsHTML = steps .map((step, index) => { const instruction = step.maneuver.instruction; const distance = (step.distance * 0.000621371).toFixed(1); // Convert to miles const duration = Math.round(step.duration / 60); // Convert to minutes return `
${index + 1}
${instruction}
${distance} mi · ${duration} min
`; }) .join(''); document.getElementById('instructions').innerHTML = `

Directions

Distance: ${(route.distance * 0.000621371).toFixed(1)} miles
Duration: ${Math.round(route.duration / 60)} minutes
${instructionsHTML}
`; } ``` ## Alternative Routes ```javascript async function getRouteWithAlternatives(start, end) { const query = await fetch( `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${start[0]},${start[1]};${end[0]},${end[1]}?` + `alternatives=true&` + `geometries=geojson&` + `steps=true&` + `access_token=${mapboxgl.accessToken}` ); const json = await query.json(); const routes = json.routes; // Display all alternative routes routes.forEach((route, index) => { const routeId = `route-${index}`; const isMainRoute = index === 0; map.addSource(routeId, { type: 'geojson', data: { type: 'Feature', geometry: route.geometry } }); map.addLayer({ id: routeId, type: 'line', source: routeId, layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': isMainRoute ? '#3b9ddd' : '#cccccc', 'line-width': isMainRoute ? 8 : 6, 'line-opacity': isMainRoute ? 0.8 : 0.5 } }); // Make routes clickable to select alternative map.on('click', routeId, () => { selectRoute(index); }); map.on('mouseenter', routeId, () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', routeId, () => { map.getCanvas().style.cursor = ''; }); }); return routes; } function selectRoute(routeIndex) { // Update styling to highlight selected route routes.forEach((route, index) => { map.setPaintProperty(`route-${index}`, 'line-color', index === routeIndex ? '#3b9ddd' : '#cccccc'); map.setPaintProperty(`route-${index}`, 'line-width', index === routeIndex ? 8 : 6); map.setPaintProperty(`route-${index}`, 'line-opacity', index === routeIndex ? 0.8 : 0.5); }); // Update instructions for selected route displayInstructions(routes[routeIndex]); } ``` ## Multi-Stop Routing ```javascript async function getMultiStopRoute(waypoints) { // waypoints: array of [lng, lat] coordinates // Maximum 25 waypoints including start and end const coordinates = waypoints.map((wp) => `${wp[0]},${wp[1]}`).join(';'); const query = await fetch( `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${coordinates}?` + `steps=true&` + `geometries=geojson&` + `access_token=${mapboxgl.accessToken}` ); const json = await query.json(); const route = json.routes[0]; // Display route displayRoute(route); // Add numbered markers for each waypoint waypoints.forEach((waypoint, index) => { const el = document.createElement('div'); el.className = 'waypoint-marker'; el.textContent = index + 1; new mapboxgl.Marker(el).setLngLat(waypoint).addTo(map); }); // Display total distance and duration console.log(`Total distance: ${(route.distance * 0.000621371).toFixed(1)} miles`); console.log(`Total duration: ${Math.round(route.duration / 60)} minutes`); return route; } // Example: Delivery route with 5 stops const deliveryStops = [ [-122.4194, 37.7749], // Start: San Francisco [-122.4089, 37.7849], // Stop 1 [-122.3922, 37.7911], // Stop 2 [-122.3844, 37.8044], // Stop 3 [-122.2711, 37.8044] // End: Oakland ]; getMultiStopRoute(deliveryStops); ``` ## Route Optimization **Use when:** Need to optimize the order of waypoints (traveling salesman problem) **Hard limit: 12 coordinates per request** (Optimization v1 API). `source` and `destination` only accept the string values `first`/`any` and `last`/`any` respectively — not numeric indices. If you need more than 12 stops, or time windows/vehicle capacities/driver shifts, see the **Optimization API v2** note below instead of trying to work around the v1 limit. ```javascript async function getOptimizedRoute(waypoints, { startAtFirst = true, endAtLast = false } = {}) { // Hard limit: 12 coordinates max for the v1 endpoint. if (waypoints.length > 12) { throw new Error('Optimization v1 API supports a maximum of 12 coordinates per request'); } const coordinates = waypoints.map((wp) => `${wp[0]},${wp[1]}`).join(';'); // source/destination only accept 'first'/'any' and 'last'/'any' — no numeric indices const source = startAtFirst ? 'first' : 'any'; const destination = endAtLast ? 'last' : 'any'; const query = await fetch( `https://api.mapbox.com/optimized-trips/v1/mapbox/driving-traffic/${coordinates}?` + `source=${source}&` + `destination=${destination}&` + `roundtrip=true&` + `steps=true&` + `geometries=geojson&` + `access_token=${mapboxgl.accessToken}` ); const json = await query.json(); const optimizedRoute = json.trips[0]; // Get the optimized order of waypoints const waypointOrder = json.waypoints.map((wp) => wp.waypoint_index); console.log('Optimized waypoint order:', waypointOrder); console.log(`Optimized distance: ${(optimizedRoute.distance * 0.000621371).toFixed(1)} miles`); console.log(`Optimized duration: ${Math.round(optimizedRoute.duration / 60)} minutes`); return { route: optimizedRoute, order: waypointOrder }; } ``` **Note:** For more than 12 locations, or time windows, vehicle capacities, and driver shifts, see the [Optimization API v2](https://docs.mapbox.com/api/navigation/optimization/) — a separate, async, job-submission API (`POST` a routing problem, then poll for the solution) currently in **Public Beta** (requires signing up for early access), supporting up to 1,000 locations per routing problem. It is not a drop-in replacement for the v1 endpoint above; it uses a different request/response shape entirely. ## Congestion-Based Route Coloring **Use when:** Visualize traffic severity along a route (the `driving-traffic` profile above already includes live traffic in ETAs — this adds a `congestion` annotation for per-segment styling) `annotations` must always be paired with `overview=full` — without it, the API returns a simplified geometry that doesn't line up point-for-point with the per-segment annotation array. ```javascript async function getTrafficRoute(start, end) { const query = await fetch( `https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${start[0]},${start[1]};${end[0]},${end[1]}?` + `steps=true&` + `geometries=geojson&` + `overview=full&` + `annotations=duration,distance,speed,congestion&` + `access_token=${mapboxgl.accessToken}` ); const json = await query.json(); const route = json.routes[0]; // Color code route by congestion const congestion = route.legs[0].annotation.congestion; const coordinates = route.geometry.coordinates; // Create segments with congestion-based colors const segments = []; for (let i = 0; i < congestion.length; i++) { segments.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: [coordinates[i], coordinates[i + 1]] }, properties: { congestion: congestion[i] } }); } map.addSource('route-traffic', { type: 'geojson', data: { type: 'FeatureCollection', features: segments } }); map.addLayer({ id: 'route-traffic', type: 'line', source: 'route-traffic', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': [ 'match', ['get', 'congestion'], 'low', '#4CAF50', // Green - free flow 'moderate', '#FFC107', // Yellow - moderate traffic 'heavy', '#FF5722', // Orange - heavy traffic 'severe', '#F44336', // Red - severe congestion 'unknown', '#3b9ddd', // Blue - unknown congestion '#3b9ddd' // Default blue ], 'line-width': 8 } }); return route; } ```