Skip to main content
Route-optimalisatie API: verlaag bezorgkosten met 30 procent in 50
Tutorials

Route-optimalisatie API: verlaag bezorgkosten met 30 procent in 50

Gebruik de MapAtlas Routing API om multi-stop bezorgroutes te optimaliseren, tijdvensters af te dwingen, EU Low Emission Zones te vermijden en het resultaat op een kaart

Brent van der Heiden10 min read
#route optimization#delivery route#routing api#last mile delivery#multi-stop route#logistics api

Last-mile bezorging is het duurste onderdeel van elke toeleveringsketen. Branchebenchmarks plaatsen last-mile-kosten consistent op 53% van de totale verzendkosten. Daarbinnen is de grootste controleerbare variabele routeefficiëntie. Een chauffeur die 15 stops in de verkeerde volgorde completeert, kan 40% meer kilometer rijden dan nodig, brandstof verbranden, het voertuig slijten en bezorgtijdvensters missen die omlevering veroorzaken.

Route optimisation is not a hard problem to solve in code anymore. What used to require expensive specialist logistics software is now an API call. This tutorial builds a complete multi-stop route optimiser using the MapAtlas Routing API: a Python script that sends a list of delivery stops, gets back an optimised sequence with total distance and time, applies time window constraints, and handles EU Low Emission Zone restrictions for urban deliveries. A JavaScript snippet then draws the result on a map.

The Python implementation is under 55 lines. The JavaScript map display is an additional 30.

The Last-Mile Cost Problem

To understand what optimisation actually saves, run the numbers for a realistic delivery scenario:

  • Fleet: 10 vans
  • Stops per van per day: 18
  • Current average distance: 210 km/van/day
  • Fuel cost: €0.38/km (diesel, EU average)
  • Driver cost: €22/hour
  • Average current route time: 7.5 hours/day

Current daily cost per van: (210 × €0.38) + (7.5 × €22) = €79.80 + €165 = €244.80/van/day

A 30% distance reduction (achievable with good optimisation on a dense urban network) and a 20% time saving produces:

  • Optimised distance: 147 km → fuel cost: €55.86
  • Optimised time: 6 hours → driver cost: €132
  • Optimised daily cost per van: €187.86/van/day

Saving per van per day: €56.94. For 10 vans over 250 working days: €142,350/year, from one API integration.

The benchmarks above reflect real published figures from last-mile logistics studies. Your specific numbers will vary by geography, vehicle type, and stop density. Dense urban areas see the largest gains because naive sequential routes waste the most distance on unnecessary backtracking.

Naive vs Optimised Routes: A Visual Comparison

The difference between a naive (sequential) route and an optimised one is stark on a map.

Naive routing happens when you feed stops in the order they were entered, first customer who placed an order is first on the route, regardless of geography. In a city like Amsterdam or Berlin, this creates the "spaghetti route" problem: your driver constantly crossing their own path.

Optimisation solves the Travelling Salesman Problem (TSP) for your stop set. For 15–20 stops this is computationally tractable in milliseconds. For larger fleets with hundreds of stops, vehicle routing problem (VRP) solvers handle the additional constraints of multiple vehicles and capacity limits.

Step 1: Structure Your Delivery Data

Each stop needs a location and, for time-windowed deliveries, a time_window specifying when the delivery is acceptable.

import requests
import json

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.mapatlas.eu/v1"

# Depot (start and end point)
depot = {
    "lat": 52.3402,
    "lng": 4.8952,
    "name": "Warehouse - Sloterdijk"
}

# Delivery stops with optional time windows
stops = [
    { "lat": 52.3726, "lng": 4.8971, "name": "Albert Heijn Jordaan",
      "time_window": { "start": "09:00", "end": "12:00" } },
    { "lat": 52.3601, "lng": 4.9123, "name": "Café De Jaren",
      "time_window": { "start": "08:00", "end": "11:00" } },
    { "lat": 52.3780, "lng": 4.8801, "name": "Westergasfabriek Events",
      "time_window": { "start": "10:00", "end": "14:00" } },
    { "lat": 52.3545, "lng": 4.9041, "name": "Hotel V Nesplein",
      "time_window": None },
    { "lat": 52.3620, "lng": 4.8820, "name": "Vondelpark Paviljoen",
      "time_window": { "start": "07:00", "end": "10:00" } }
]

Step 2: Call the Route Optimisation Endpoint

POST the depot and stop list to the optimised routing endpoint. The API returns the stops in the most efficient visit order along with the total route distance and duration.

def optimise_route(depot, stops, vehicle_profile="van-euro6"):
    """
    Request an optimised multi-stop route from the MapAtlas Routing API.
    vehicle_profile options: van-euro6, van-diesel-euro5, electric-van, bike
    """
    waypoints = [
        {
            "lat": s["lat"],
            "lng": s["lng"],
            "name": s["name"],
            **({"time_window": s["time_window"]} if s.get("time_window") else {})
        }
        for s in stops
    ]

    payload = {
        "origin": { "lat": depot["lat"], "lng": depot["lng"] },
        "destination": { "lat": depot["lat"], "lng": depot["lng"] },  # return to depot
        "waypoints": waypoints,
        "optimise": True,
        "vehicle_profile": vehicle_profile,
        "avoid_low_emission_zones": True  # auto-avoids LEZs for non-compliant profiles
    }

    response = requests.post(
        f"{BASE_URL}/routing/optimise",
        json=payload,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json"
        }
    )
    response.raise_for_status()
    return response.json()

result = optimise_route(depot, stops)

Step 3: Parse and Display the Optimised Route

The API response includes the stops in optimised order, cumulative ETAs for each stop, total distance, and total duration.

def display_route_summary(result):
    route = result["route"]
    print(f"\n--- Optimised Route Summary ---")
    print(f"Total distance : {route['total_distance_km']:.1f} km")
    print(f"Total duration : {route['total_duration_min']:.0f} min")
    print(f"Stops          : {len(route['waypoints'])}\n")

    print(f"  START  {depot['name']}")
    for i, stop in enumerate(route["waypoints"], 1):
        eta     = stop["eta"]
        tw      = stop.get("time_window")
        on_time = "(on time)" if tw and tw["start"] <= eta <= tw["end"] else ""
        print(f"  {i:>2}.   {stop['name']:<35} ETA {eta}  {on_time}")
    print(f"  END    {depot['name']}")

    print(f"\nEstimated fuel saving vs sequential: "
          f"{result.get('saving_vs_naive_km', 0):.1f} km "
          f"({result.get('saving_pct', 0):.0f}%)")

display_route_summary(result)

Sample output for the five stops above:

--- Optimised Route Summary ---
Total distance : 38.4 km
Total duration : 94 min
Stops          : 5

  START  Warehouse - Sloterdijk
   1.   Vondelpark Paviljoen               ETA 07:48  (on time)
   2.   Café De Jaren                      ETA 08:31  (on time)
   3.   Albert Heijn Jordaan               ETA 09:15  (on time)
   4.   Hotel V Nesplein                   ETA 10:02
   5.   Westergasfabriek Events            ETA 10:44  (on time)
  END    Warehouse - Sloterdijk

Estimated fuel saving vs sequential: 14.2 km (27%)

Step 4: EU Low Emission Zone Handling

Amsterdam's ZTL zone, Paris's Crit'Air system, and Berlin's Umweltzone restrict certain vehicle types from central areas at specified times. A route that looks efficient on distance alone may be invalid for your vehicle.

The avoid_low_emission_zones: true parameter combined with the vehicle_profile automatically routes around restricted zones for non-compliant vehicles. For electric and Euro 6 vehicles, LEZs are passable and the parameter has no effect.

# Example: diesel Euro 5 van, will be re-routed around Amsterdam ZTL
result_euro5 = optimise_route(depot, stops, vehicle_profile="van-diesel-euro5")

# Example: electric van, LEZ restrictions do not apply
result_electric = optimise_route(depot, stops, vehicle_profile="electric-van")

print(f"Euro 5 route distance  : {result_euro5['route']['total_distance_km']:.1f} km")
print(f"Electric route distance: {result_electric['route']['total_distance_km']:.1f} km")
# Electric route will typically be shorter as it can use LEZ-restricted roads

For logistics operations planning a transition from diesel to electric, comparing these two outputs per route provides a direct quantification of the range improvement available from electrification.

Step 5: Display the Optimised Route on a Map

Take the route geometry from the API response and render it as a line layer in JavaScript.

import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

// routeResult is the parsed API JSON response passed to the frontend
function renderOptimisedRoute(map, routeResult) {
  const { waypoints, geometry, total_distance_km, total_duration_min } = routeResult.route;

  map.on('load', () => {
    // Route line
    map.addSource('optimised-route', { type: 'geojson', data: { type: 'Feature', geometry } });
    map.addLayer({
      id: 'route-line',
      type: 'line',
      source: 'optimised-route',
      layout: { 'line-join': 'round', 'line-cap': 'round' },
      paint: { 'line-color': '#2563EB', 'line-width': 4 }
    });

    // Stop markers with sequence numbers
    waypoints.forEach((stop, i) => {
      const el = document.createElement('div');
      el.textContent = i + 1;
      el.style.cssText = `
        width:28px;height:28px;border-radius:50%;background:#2563EB;color:#fff;
        display:flex;align-items:center;justify-content:center;font-weight:700;
        font-size:13px;border:2px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,0.3)
      `;

      new mapmetricsgl.Marker({ element: el })
        .setLngLat([stop.lng, stop.lat])
        .setPopup(
          new mapmetricsgl.Popup().setHTML(`
            <strong>${i + 1}. ${stop.name}</strong>
            <p>ETA: ${stop.eta}</p>
          `)
        )
        .addTo(map);
    });

    // Fit map to route bounds
    const coords = geometry.coordinates;
    const bounds = coords.reduce(
      (b, c) => b.extend(c),
      new mapmetricsgl.LngLatBounds(coords[0], coords[0])
    );
    map.fitBounds(bounds, { padding: 48 });

    // Summary panel
    document.getElementById('route-summary').innerHTML = `
      <strong>${total_distance_km.toFixed(1)} km</strong> ·
      <strong>${total_duration_min.toFixed(0)} min</strong> ·
      ${waypoints.length} stops
    `;
  });
}

const map = new mapmetricsgl.Map({
  container: 'route-map',
  style: 'https://tiles.mapatlas.eu/styles/basic/style.json?key=YOUR_API_KEY',
  center: [4.9041, 52.3676],
  zoom: 12
});

renderOptimisedRoute(map, routeResult);

Calculating Your Real Savings

Once you have the API response in hand, the saving calculation is straightforward. The saving_vs_naive_km field in the response gives you distance saved directly. From that, derive cost savings:

def calculate_savings(result, fuel_cost_per_km=0.38, driver_cost_per_hour=22.0,
                       days_per_year=250, fleet_size=10):
    saving_km    = result.get("saving_vs_naive_km", 0)
    saving_hours = saving_km / 50  # assume 50 km/h average

    daily_fuel_saving   = saving_km * fuel_cost_per_km
    daily_driver_saving = saving_hours * driver_cost_per_hour
    daily_total         = daily_fuel_saving + daily_driver_saving

    annual_fleet_saving = daily_total * days_per_year * fleet_size

    print(f"Distance saved per route : {saving_km:.1f} km")
    print(f"Time saved per route     : {saving_hours * 60:.0f} min")
    print(f"Daily saving (1 vehicle) : €{daily_total:.2f}")
    print(f"Annual saving ({fleet_size} vehicles): €{annual_fleet_saving:,.0f}")

calculate_savings(result)

Time Window Optimisation

Delivering to a bakery at 06:00 and a restaurant at 14:00 while minimising total route distance is a constrained optimisation problem. The API handles this automatically, you only need to provide the windows:

# Time-sensitive stops, the API will schedule these within their windows
stops_with_windows = [
    { "lat": 52.3726, "lng": 4.8971, "name": "Bakery",
      "time_window": { "start": "05:30", "end": "07:00" } },
    { "lat": 52.3620, "lng": 4.8820, "name": "Café",
      "time_window": { "start": "07:00", "end": "09:00" } },
    { "lat": 52.3545, "lng": 4.9041, "name": "Restaurant",
      "time_window": { "start": "13:00", "end": "15:00" } }
]

If any time window constraint cannot be satisfied given the depot departure time and current traffic model, the API returns a constraint_violations array listing which stops could not be reached on time. Your dispatch software can then alert the driver or suggest an earlier departure.

What to Build on Top of This

Route optimisation is the foundation. Once it is running, the natural extensions are:

The Logistics and Delivery industry page and the Fleet Management industry page cover additional MapAtlas features relevant to dispatch software, including multi-vehicle VRP and return-to-depot optimisation.

Getting Started

Frequently Asked Questions

How does route optimisation reduce delivery costs?

Route optimisation reorders multi-stop delivery sequences to minimise total distance and drive time. Studies consistently show 20–35% reductions in distance driven versus a naive sequential route. For a vehicle driving 200 km/day at €0.35/km fuel cost, a 30% reduction saves around €21 per vehicle per day, roughly €5,000 per year per vehicle.

What are time windows in route optimisation?

Time windows are delivery constraints that require a stop to be visited within a specified time range, for example, a business that accepts deliveries only between 09:00 and 12:00. The optimiser must respect all time windows while still minimising total route distance, which is a significantly harder computational problem than unconstrained optimisation.

Does the MapAtlas Routing API handle EU Low Emission Zones?

Yes. The MapAtlas Routing API includes road restriction data for EU Low Emission Zones including Amsterdam's ZTL, the Paris Crit'Air zone, and Berlin's Umweltzone. Pass the vehicle profile (diesel Euro 5, petrol, electric) as a parameter and the router will automatically avoid restricted zones for non-compliant vehicles.

Veelgestelde vragen

Hoe verlaagt route-optimalisatie de bezorgkosten?

Route-optimalisatie herordent de volgorde van multi-stop bezorgingen om de totale afstand en rijtijd te minimaliseren. Studies laten consistent 20 tot 35 procent minder gereden kilometers zien ten opzichte van een naïeve sequentiële route. Voor een voertuig dat 200 km/dag rijdt bij 0,35 EUR/km brandstofkosten levert een reductie van 30 procent een besparing van zo'n 21 EUR per voertuig per dag op, ruwweg 5.000 EUR per jaar per voertuig.

Wat zijn tijdvensters bij route-optimalisatie?

Tijdvensters zijn bezorgbeperkingen die vereisen dat een stop binnen een bepaald tijdsbestek wordt bezocht, bijvoorbeeld een bedrijf dat alleen leveringen accepteert tussen 09:00 en 12:00. De optimizer moet alle tijdvensters respecteren en tegelijkertijd de totale routeafstand minimaliseren, wat een aanzienlijk moeilijker rekenprobleem is dan onbeperkte optimalisatie.

Verwerkt de MapAtlas Routing API EU Low Emission Zones?

Ja. De MapAtlas Routing API bevat wegrestrictiedata voor EU Low Emission Zones (LEZ), inclusief de Amsterdamse ZTL, de Parijse Crit'Air-zone en de Berlijnse Umweltzone. Geef het voertuigprofiel (diesel Euro 5, benzine, elektrisch) mee als parameter en de router mijdt automatisch beperkte zones voor niet-conforme voertuigen.

Was dit nuttig? Deel het.

Over de auteur

Brent van der Heiden

Geschreven door

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. He writes about geospatial infrastructure, AI search visibility, and how location data powers the products people rely on every day.

Alle artikelen bekijken
Terug naar blog