A entrega na milha final é a parte mais cara de qualquer cadeia de suprimentos. Os benchmarks da indústria consistentemente colocam os custos da milha final em 53% do custo total de envio. Dentro disso, a variável controlável mais importante é a eficiência da rota. Um motorista completando 15 paradas na ordem errada pode dirigir 40% a mais de quilómetros do que o necessário, queimando combustível, desgastando o veículo e perdendo as janelas de tempo de entrega que desencadeiam taxas de reentrega.
A otimização de rotas não é mais um problema difícil de resolver em código. O que costumava exigir software de logística especializado caro agora é uma chamada de API. Este tutorial constrói um otimizador de rotas com múltiplas paradas usando a API de Roteamento MapAtlas: um script Python que envia uma lista de paradas de entrega, obtém uma sequência otimizada com distância total e tempo, aplica restrições de janelas de tempo e lida com restrições de Zonas de Baixa Emissão da UE para entregas urbanas. Um snippet de JavaScript então desenha o resultado em um mapa.
A implementação em Python tem menos de 55 linhas. A exibição do mapa em JavaScript são 30 linhas adicionais.
O Problema do Custo da Milha Final
Para entender o que a otimização realmente economiza, calcule os números para um cenário de entrega realista:
- Frota: 10 vans
- Paradas por van por dia: 18
- Distância média atual: 210 km/van/dia
- Custo de combustível: €0,38/km (diesel, média UE)
- Custo do motorista: €22/hora
- Tempo médio de rota atual: 7,5 horas/dia
Custo diário atual por van: (210 × €0,38) + (7,5 × €22) = €79,80 + €165 = €244,80/van/dia
Uma redução de 30% na distância (alcançável com boa otimização em uma rede urbana densa) e uma economia de 20% no tempo produz:
- Distância otimizada: 147 km → custo de combustível: €55,86
- Tempo otimizado: 6 horas → custo do motorista: €132
- Custo diário otimizado por van: €187,86/van/dia
Economia por van por dia: €56,94. Para 10 vans em 250 dias úteis: €142.350/ano, com uma integração de API.
Os benchmarks acima refletem números reais publicados em estudos de logística de milha final. Seus números específicos variarão por geografia, tipo de veículo e densidade de paradas. Áreas urbanas densas veem os maiores ganhos porque as rotas sequenciais ingênuas desperdiçam a maior distância em backtracking desnecessário.
Rotas Ingênuas vs Otimizadas: Uma Comparação Visual
A diferença entre uma rota ingênua (sequencial) e uma otimizada é marcante em um mapa.
O roteamento ingênuo ocorre quando você alimenta paradas na ordem em que foram inseridas, o primeiro cliente que fez um pedido é o primeiro na rota, independentemente da geografia. Em uma cidade como Amsterdã ou Berlim, isso cria o problema da "rota de espaguete": seu motorista constantemente cruzando seu próprio caminho.
A otimização resolve o Problema do Caixeiro Viajante (TSP) para seu conjunto de paradas. Para 15 a 20 paradas, isso é computacionalmente tratável em milissegundos. Para frotas maiores com centenas de paradas, solucionadores de problema de roteamento de veículos (VRP) lidam com as restrições adicionais de múltiplos veículos e limites de capacidade.
Passo 1: Estruture Seus Dados de Entrega
Cada parada precisa de uma localização e, para entregas com janela de tempo, um time_window especificando quando a entrega é aceitável.
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" } }
]
Passo 2: Chame o Endpoint de Otimização de Rota
Envie POST o depósito e a lista de paradas para o endpoint de roteamento otimizado. A API retorna as paradas na ordem de visita mais eficiente, juntamente com a distância total da rota e duração.
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)
Passo 3: Analisar e Exibir a Rota Otimizada
A resposta da API inclui as paradas em ordem otimizada, ETAs cumulativas para cada parada, distância total e duração total.
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)
Exemplo de saída para as cinco paradas acima:
--- Resumo da Rota Otimizada ---
Distância total : 38.4 km
Duração total : 94 min
Paradas : 5
INÍCIO Warehouse - Sloterdijk
1. Vondelpark Paviljoen ETA 07:48 (no horário)
2. Café De Jaren ETA 08:31 (no horário)
3. Albert Heijn Jordaan ETA 09:15 (no horário)
4. Hotel V Nesplein ETA 10:02
5. Westergasfabriek Events ETA 10:44 (no horário)
FIM Warehouse - Sloterdijk
Economia de combustível estimada vs sequencial: 14.2 km (27%)
Passo 4: Manipulação de Zonas de Baixa Emissão da UE
A zona ZTL de Amsterdã, o sistema Crit'Air de Paris e a Umweltzone de Berlim restringem certos tipos de veículos de áreas centrais em horários específicos. Uma rota que parece eficiente apenas em distância pode ser inválida para seu veículo.
O parâmetro avoid_low_emission_zones: true combinado com o vehicle_profile roteia automaticamente em torno de zonas restritas para veículos não conformes. Para veículos elétricos e Euro 6, as LEZs são transitáveis e o parâmetro não tem efeito.
# Exemplo: van diesel Euro 5, será roteada ao redor do ZTL de Amsterdã
result_euro5 = optimise_route(depot, stops, vehicle_profile="van-diesel-euro5")
# Exemplo: van elétrica, restrições LEZ não se aplicam
result_electric = optimise_route(depot, stops, vehicle_profile="electric-van")
print(f"Distância de rota Euro 5 : {result_euro5['route']['total_distance_km']:.1f} km")
print(f"Distância de rota elétrica: {result_electric['route']['total_distance_km']:.1f} km")
# A rota elétrica será tipicamente mais curta, pois pode usar estradas com restrição LEZ
Para operações de logística planejando uma transição de diesel para elétrico, comparar essas duas saídas por rota fornece uma quantificação direta da melhoria de autonomia disponível com a eletrificação.
Passo 5: Exiba a Rota Otimizada em um Mapa
Pegue a geometria da rota da resposta da API e processe-a como uma camada de linha em 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);
Calculando Suas Economias Reais
Depois de ter a resposta da API em mãos, o cálculo da economia é direto. O campo saving_vs_naive_km na resposta fornece a distância economizada diretamente. A partir daí, derive a economia de custo:
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)
Otimização de Janela de Tempo
Entregar em uma padaria às 06:00 e em um restaurante às 14:00 enquanto minimiza a distância total da rota é um problema de otimização restrita. A API lida com isso automaticamente, você só precisa fornecer as janelas:
# Paradas sensíveis ao tempo, a API agendará estas dentro de suas janelas
stops_with_windows = [
{ "lat": 52.3726, "lng": 4.8971, "name": "Padaria",
"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": "Restaurante",
"time_window": { "start": "13:00", "end": "15:00" } }
]
Se alguma restrição de janela de tempo não puder ser satisfeita dado o horário de partida do depósito e modelo de tráfego atual, a API retorna um array constraint_violations listando quais paradas não puderam ser alcançadas no horário. Seu software de despacho pode então alertar o motorista ou sugerir uma partida anterior.
O Que Construir Nesse Sentido
A otimização de rota é a base. Uma vez que esteja funcionando, as extensões naturais são:
- Rastreamento ao vivo do motorista: Alimente coordenadas de rota otimizadas para o tutorial de Mapa de Rastreamento ao Vivo do Motorista e mostre aos clientes atualizações de ETA em tempo real.
- Planejamento de cobertura baseado em isócrona: Use a API de Tempo de Viagem para visualizar quais códigos postais sua frota pode alcançar dentro de sua janela de entrega. O artigo Isochrone Maps Explained mostra como.
- Validação em massa de endereços: Antes de executar a otimização, valide todos os endereços de entrega com a API de Geocodificação para capturar erros de digitação e códigos postais desatualizados. Veja Como Usar a API de Geocodificação para Validar 10.000 Endereços em Massa.
A página de Indústria de Logística e Entrega e a página de Indústria de Gerenciamento de Frotas cobrem recursos MapAtlas adicionais relevantes para software de despacho, incluindo VRP multi-veículo e otimização de retorno ao depósito.
Começando
- Inscreva-se para uma chave API MapAtlas gratuita, o nível gratuito inclui chamadas de roteamento e otimização, sem cartão de crédito necessário
- Revise a documentação da API de Roteamento para a lista completa de perfis de veículo, parâmetros de janela de tempo e opções multi-veículo
- Explore a página de capacidades de Planejamento e Navegação de Rota para uma visão geral do produto
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.

