Skip to main content
APIs 11 min read

Smart Route Planning: Crafting 8-Hour Depot-to-Depot Trips with HERE APIs

Smart Route Planning

In the world of logistics, fleet management, and field services, planning efficient multi-stop routes is a daily puzzle. Drivers often have fixed work durations, typically an 8-hour shift, and vehicles usually need to start and end their day at a central depot. Manually planning these routes, especially with dozens or hundreds of stops, is incredibly time-consuming and rarely yields the most optimal path. Greedy algorithms, while simple, can lead to inefficient long-term plans and excessive API calls.

This blog will guide you through a robust solution to tackle this challenge. We'll build a web application that:

  1. Takes a list of stops, a start depot, and an end depot.
  2. Uses the HERE Waypoints Sequence API (v8) to determine the globally optimal order to visit all stops.
  3. Intelligently identifies the maximum number of these optimally sequenced stops that can be covered in a primary 8-hour work shift, ensuring the vehicle starts at the depot and returns to the depot within this time limit.
  4. Lists any remaining stops as "for review," effectively identifying waypoints that would be "dropped" from this primary 8-hour round trip.
  5. Visualizes the primary route and reviewable stops on a map using HERE Maps API for JavaScript and the HERE Routing API (v8).

 

Let's dive into how we can build this solution!

The Core Challenge: Beyond Simple A-to-B

The problem we're solving is a variation of the Traveling Salesperson Problem (TSP) with added real-world constraints:

  • Fixed Work Duration: A driver has a maximum work time (e.g., 8 hours), which includes driving time and service time at each stop.
  • Depot-to-Depot Round Trips: The primary operational leg must start and end at a designated depot.
  • Optimal Stop Inclusion: We want to include as many stops as possible from an optimally sequenced list within this primary depot-to-depot leg.
  • Identifying "Droppable" Stops: Stops that cannot be accommodated in the primary leg need to be clearly identified for rescheduling or alternative planning.

 

A naive approach might involve making many small API calls, which is slow and costly. Our strategy is more efficient: optimize globally first, then apply constraints.

Our Solution: A Hybrid Approach with HERE APIs

We'll leverage two powerful HERE APIs:

  1. HERE Waypoints Sequence API v8 (findsequence2): This API is designed to solve the TSP for a given set of waypoints. It takes your start, end, and all intermediate destinations and returns the most time or distance-efficient order to visit them. We'll use this to get our globally optimal sequence.
  2. HERE Routing API v8 (/routes): This API calculates routes between specified waypoints, considering various transport modes, road features, and optionally traffic. We'll use it for two purposes:
    • To get the detailed route geometry (polylines) for each leg to display on the map.
    • Crucially, as a helper to quickly calculate the travel time and distance from a potential last stop of the day back to our end depot. This is key for ensuring our 8-hour leg is a true round trip.
  3. HERE Maps API for JavaScript (v3.1): For displaying the base map, routes, and markers in our web application.

The High-Level Workflow:

  1. Input: Define start depot, end depot, and a list of intermediate stops with their locations and estimated service times.
  2. Global Optimization: Send all these points to the Waypoints Sequence API v8 to get the single best sequence.
  3. Primary Leg Construction: Iterate through the globally optimized sequence. For each stop, calculate if adding it (plus its service time and the travel time to it) AND then traveling from it back to the end depot would exceed the 8-hour work limit.
  4. Identify Reviewable Stops: Any stops from the global sequence not included in this primary depot-to-depot leg are flagged for review.
  5. Visualization: Draw the primary leg's route and markers. Optionally, mark the reviewable stops differently.

Diving into the Code

Let's break down the key JavaScript snippets.

1. Configuration (CONFIG section)

Copied
        const APIKEY = 'YOUR_HERE_API_KEY'; // Replace with your actual key

const SEQUENCE_API_URL = 'https://wps.hereapi.com/v8/findsequence2';
const ROUTING_API_URL  = 'https://router.hereapi.com/v8/routes';

const MAX_WORK_SEC = 8 * 60 * 60; // 8 hours for (Driving + Service Time)
const ROUTING_API_MAX_WAYPOINTS_PER_REQUEST = 50; // For GET request chunking
const DEFAULT_REST_TIMES_MODEL = 'default'; // For Waypoints Sequence API
const PROCESS_REVIEWABLE_STOPS_INTO_FURTHER_LEGS = false; // Our focus
  

Explanation:

  • APIKEY: Your HERE API key is essential for authenticating requests.
  • SEQUENCE_API_URL & ROUTING_API_URL: Endpoints for the HERE services.
  • MAX_WORK_SEC: Defines our 8-hour work limit in seconds. This includes both driving and service time.
  • ROUTING_API_MAX_WAYPOINTS_PER_REQUEST: A practical limit for the number of waypoints in a single GET request to the Routing API (helps avoid overly long URLs).
  • DEFAULT_REST_TIMES_MODEL: Tells the Waypoints Sequence API to consider standard driver rest times (e.g., European rules) when optimizing the initial global sequence. This makes the initial sequence more realistic.
  • PROCESS_REVIEWABLE_STOPS_INTO_FURTHER_LEGS: A flag we've set to false to focus on our primary objective: identify the first 8-hour depot-to-depot leg and the stops that don't fit. If set to true, it could trigger logic to plan subsequent, non-depot-returning legs.

2. Map Initialization & Sample Data

The map is initialized using the standard HERE Maps API for JavaScript. The sample data section defines startLocation, endLocation (your depots), and stopsToVisit (your list of customer stops). Each stop should have an id, lat, lng, and serviceTime (in seconds).

Copied
        /******************* SAMPLE DATA ********************************************/
const startLocation = {id:'BER-Start', lat:52.365638, lng:13.533344, serviceTime: 0};
const endLocation   = {id:'BER-End',   lat:52.368222, lng:13.520706, serviceTime: 0}; 

const stopsToVisit = [
  // Your list of 96+ stops here...
  { "id": "Stop_1", "lat": 52.54198, "lng": 13.40372, "serviceTime": 1200 },
  // ... more stops
];

const stopDataById = new Map();
// Populate stopDataById for easy lookup
if (startLocation) stopDataById.set(startLocation.id, { ...startLocation, serviceTime: startLocation.serviceTime || 0 });
stopsToVisit.forEach(s => {
    if (s) stopDataById.set(s.id, { ...s, serviceTime: s.serviceTime || 0 });
});
if (endLocation) stopDataById.set(endLocation.id, { ...endLocation, serviceTime: endLocation.serviceTime || 0 });


  

3. The Main Asynchronous Flow (main function)

This is where the orchestration happens:

Copied
        (async function main() {
  // ... (API key check, panel setup) ...

  try {
    // 1. Prepare parameters for Waypoints Sequence API
    const sequenceApiParams = new URLSearchParams();
    if (!startLocation) throw new Error("startLocation is not defined.");
    sequenceApiParams.append('start', `${startLocation.id};${startLocation.lat},${startLocation.lng}`);
    sequenceApiParams.append('mode', 'fastest;car;traffic:enabled'); 
    sequenceApiParams.append('departure', 'now');
    sequenceApiParams.append('improveFor', 'time');
    sequenceApiParams.append('restTimes', DEFAULT_REST_TIMES_MODEL);
    if (!endLocation) throw new Error("endLocation is not defined.");
    sequenceApiParams.append('end', `${endLocation.id};${endLocation.lat},${endLocation.lng};st:${endLocation.serviceTime || 0}`);
    stopsToVisit.forEach((s,i) => {
       sequenceApiParams.append(`destination${i}`, `${s.id};${s.lat},${s.lng};st:${s.serviceTime || 0}`);
    });

    // 2. Call Waypoints Sequence API (POST request)
    console.log("Sequence API Body Params:", sequenceApiParams.toString());
    const sequenceResponse = await fetch(`${SEQUENCE_API_URL}?apiKey=${APIKEY}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: sequenceApiParams.toString()
    });
    // ... (Error handling for sequenceResponse) ...
    const sequenceData = await sequenceResponse.json();
    // ... (Error handling for sequenceData content) ...

    const sequencedWaypointsFull = sequenceData.results[0].waypoints.map(wp => ({
        ...wp,
        ...(stopDataById.get(wp.id) || {serviceTime: 0, id: wp.id, lat: wp.lat, lng: wp.lng })
    }));
    const interconnectionsFull = sequenceData.results[0].interconnections;

    // 3. Determine the primary depot-to-depot leg and reviewable stops
    statsPanel.textContent = 'Determining primary depot-to-depot leg...';
    const { primaryLeg, reviewableStops } = await determinePrimaryDepotToDepotLeg(
        sequencedWaypointsFull, 
        interconnectionsFull, 
        startLocation, 
        endLocation
    );

    // 4. Display results
    statsPanel.innerHTML = '<h3>Optimized Route Plan</h3>'; 
    const legColors = ['#0070ff', /* ... more colors ... */];
    
    if (primaryLeg && primaryLeg.waypoints.length > 1) {
        // ... (Draw primaryLeg, log its stats) ...
    } else {
        // ... (Handle case where no primary leg could be formed) ...
    }
    
    if (reviewableStops.length > 0) {
        // ... (List reviewableStops, mark them on map) ...
    } else if (primaryLeg && primaryLeg.waypoints.length > 1) {
        // ... (Indicate all stops fit) ...
    }

    // ... (Optionally process reviewableStops into further legs if PROCESS_REVIEWABLE_STOPS_INTO_FURTHER_LEGS is true) ...
    
    // ... (Adjust map view) ...

  } catch (error) {
    // ... (Global error handling) ...
  }
})();
  

Explanation:

  • Parameters for Waypoints Sequence: We build parameters including start, end (both depots), all destinationN (our stopsToVisit), the mode (e.g., fastest;car;traffic:enabled), departure time, and importantly, restTimes.
  • API Call: A POST request is made. The apiKey is in the URL's query string, and other parameters are in the application/x-www-form-urlencoded body.
  • Processing Response: The optimal sequence and interconnections (travel details between sequenced stops) are extracted.
  • determinePrimaryDepotToDepotLeg: This crucial function (explained next) is called.
  • Display: The primary leg is drawn, and reviewable stops are listed.

4. Helper: getRouteTimeAndDistance

This small utility function is vital for our depot-to-depot logic. It makes a quick call to the HERE Routing API v8 to find the travel time and distance between any two points. We use it to calculate the time needed to return to the endLocation (depot) from a potential last customer stop.

Copied
        async function getRouteTimeAndDistance(originWp, destinationWp) {
    if (!originWp || !destinationWp) return { time: Infinity, distance: Infinity };

    const routingApiParams = new URLSearchParams();
    routingApiParams.append('apiKey', APIKEY);
   routingApiParams.append('transportMode', 'car');
   routingApiParams.append('routingMode', 'fastest'); // Or 'fast'
    routingApiParams.append('return', 'summary'); // Only need time & distance
    routingApiParams.append('origin', `${originWp.lat},${originWp.lng}`);
   routingApiParams.append('destination', `${destinationWp.lat},${destinationWp.lng}`);

    try {
        const response = await fetch(`${ROUTING_API_URL}?${routingApiParams.toString()}`);
        // ... (Error handling and response parsing) ...
        if (/* successful response with summary */) {
            return { time: summary.duration || 0, distance: summary.length || 0 };
        }
        return { time: Infinity, distance: Infinity }; // Indicate failure
    } catch (error) {
        // ... (Error handling) ...
        return { time: Infinity, distance: Infinity };
    }
}
  

5. The Core Logic: determinePrimaryDepotToDepotLeg

This function is the heart of achieving our specific objective.

Copied
        async function determinePrimaryDepotToDepotLeg(allSequencedWaypoints, allInterconnections, startDepot, endDepot) {
    let primaryLeg = { /* ... initial structure ... */ };
    const reviewableStops = [];

    if (!allSequencedWaypoints || /* ... basic checks ... */ ) {
        return { primaryLeg: null, reviewableStops: allSequencedWaypoints || [] };
    }

    // Initialize primaryLeg with startDepot
   primaryLeg.waypoints.push(actualStartDepot);
    primaryLeg.serviceTimeSeconds += actualStartDepot.serviceTime || 0;

    // Iterate through the globally sequenced stops
    for (let i = 0; i < allInterconnections.length; i++) {
        const interCon = allInterconnections[i]; 
        const toWpInSeq = allSequencedWaypoints[i+1]; // Waypoint we are considering adding

        // Stop if 'toWpInSeq' is the final depot of the entire tour
        if (toWpInSeq.id === endDepot.id && /* ... check sequence number ... */) {
            break; 
        }
        
        const enrichedToWp = { /* ... get full details for toWpInSeq ... */ };
        const drivingTimeToNext = interCon.time || 0;
        const serviceAtNext = enrichedToWp.serviceTime || 0;
        // ... (other interCon details) ...

        // Calculate work time IF this 'toWpInSeq' is added
        const workTimeWithNextStop = primaryLeg.drivingTimeSeconds + drivingTimeToNext +
                                    primaryLeg.serviceTimeSeconds + serviceAtNext;
        
        // CRITICAL STEP: Get time to return to endDepot FROM this 'toWpInSeq'
        const { time: returnToDepotTime } = await getRouteTimeAndDistance(enrichedToWp, endDepot);

        if (returnToDepotTime === Infinity) { // Cannot route back to depot
            // ... (Add remaining stops to reviewableStops, then break) ...
        }

        // Check if (work to this stop + travel back to depot) fits 8-hour limit
        if (workTimeWithNextStop + returnToDepotTime <= MAX_WORK_SEC) {
            // Add 'toWpInSeq' to primaryLeg
            primaryLeg.drivingTimeSeconds += drivingTimeToNext;
     &nbsp;      primaryLeg.serviceTimeSeconds += serviceAtNext;
            // ... (accumulate rest, distance) ...
           primaryLeg.waypoints.push(enrichedToWp);
        } else {
            // This stop (and subsequent ones) won't fit the depot-to-depot 8-hour leg.
            // Add 'toWpInSeq' and all following (except final depot) to reviewableStops.
            for (let j = i + 1; /* ... loop through remaining ... */ ) {
                if (/* not the final depot of the whole tour */) {
                    reviewableStops.push(allSequencedWaypoints[j]);
                }
            }
            break; // Stop building the primary leg
        }
    }

    // Finalize primary leg: add actual travel from last customer stop back to endDepot
    if (primaryLeg.waypoints.length > 1) { 
        const lastCustomerStop = primaryLeg.waypoints[primaryLeg.waypoints.length - 1];
        const { time: finalReturnTime, distance: finalReturnDistance } = await getRouteTimeAndDistance(lastCustomerStop, endDepot);
        if (finalReturnTime !== Infinity) {
            primaryLeg.drivingTimeSeconds += finalReturnTime;
            primaryLeg.distanceMeters += finalReturnDistance;
           primaryLeg.waypoints.push(actualEndDepot); // Add endDepot to complete the leg
        }
    } else if (/* ... handle case where no customer stops fit ... */) {
        // ... (Make primaryLeg a direct depot-to-depot or null) ...
    }
    return { primaryLeg, reviewableStops };
}
  

Explanation:

  • It starts with the startDepot.
  • It iterates through the allInterconnections (which link the allSequencedWaypoints).
  • For each potential next stop (toWpInSeq), it calculates the cumulative work time (driving to it + service at all stops so far).
  • Crucially, it then calls getRouteTimeAndDistance to find out how long it would take to drive from toWpInSeq directly back to the endDepot.
  • If (workTimeWithNextStop + returnToDepotTime) is within MAX_WORK_SEC, toWpInSeq is added to the primaryLeg.
  • If it exceeds the limit, toWpInSeq and all subsequent stops (except the final endDepot of the overall tour) are added to the reviewableStops list, and the construction of the primaryLeg stops.
  • Finally, the actual travel time and distance from the last included customer stop back to the endDepot are added to primaryLeg's totals, and the endDepot itself is added as the last waypoint of this leg.

6. Drawing the Leg (drawLegOnMap) and Other Utilities

The drawLegOnMap function takes the waypoints of a leg and uses the HERE Routing API (via GET requests, chunked if necessary for many via points) to fetch the route polylines. Markers are also added. The createNumberIcon function allows for different styling for regular vs. reviewable stops, and logLegStatistics displays the details in the side panel.

Understanding the Output

When you run the application:

  • Primary Leg: The map will show the route for the first 8-hour depot-to-depot leg (e.g., in blue). The panel will detail its statistics.
  • Waypoints for Review: The panel will list any waypoints that couldn't be included in this primary leg. These are your "droppable" stops for this specific 8-hour shift. They will also be marked on the map (e.g., in grey) to distinguish them.
32

         A blue primary leg, and a list of 88 "Waypoints for Review" with grey markers.

Full code snippet:

You can see the full code in action here!

Benefits and Next Steps

This approach provides a powerful and efficient way to:

  • Get a globally optimal sequence for all your stops.
  • Strictly adhere to an 8-hour work limit for a primary leg that starts and ends at your depot.
  • Clearly identify which stops cannot be serviced within this primary constraint, allowing for better decision-making (reschedule, reassign, or truly drop).

Potential Enhancements:

  • UI for Managing Reviewable Stops: Allow users to select reviewable stops and perhaps re-run the optimization for a "next day" leg (which might itself be another depot-to-depot leg or a simpler point-to-point sequence).
  • Multiple Vehicles/Drivers: Extend the logic to plan for multiple vehicles.

 

By combining the strengths of the HERE Waypoints Sequence API v8 for strategic ordering and the HERE Routing API v8 for tactical pathfinding and time calculations, you can build sophisticated logistics solutions tailored to complex operational needs. Happy routing!

Sachin Jonda

Sachin Jonda

Principal Support Engineer

Sign up for our newsletter

Why sign up:

  • Latest offers and discounts
  • Tailored content delivered weekly
  • Exclusive events
  • One click to unsubscribe