最后一公里配送是任何供应链中成本最高的部分。行业基准表明最后一公里成本占总运费的53%。其中,最可控的变量是路线效率。驾驶员以错误的顺序完成15个停靠点可能会多行驶40%以上的公里数,这会消耗燃料、磨损车辆,并可能错过触发重新配送费的时间窗口。
路线优化在代码中已经不是一个困难的问题。曾经需要昂贵的专业物流软件的工作现在可以通过API调用完成。本教程使用MapAtlas Routing API构建了一个完整的多停靠点路线优化器:一个Python脚本发送配送停靠点列表,获取带有总距离和时间的优化序列,应用时间窗口约束,并处理城市配送的欧盟低排放区限制。JavaScript代码片段随后在地图上绘制结果。
Python实现不到55行代码。JavaScript地图显示额外30行。
最后一公里成本问题
为了理解优化实际节省的成本,请对现实的配送场景运行数字计算:
- 车队:10辆面包车
- 每辆车每天停靠点数:18
- 当前平均距离:210公里/车/天
- 燃油成本:0.38欧元/公里(柴油,欧盟平均)
- 驾驶员成本:22欧元/小时
- 当前平均路线时间:7.5小时/天
每辆车当前日成本:(210 × 0.38欧元) + (7.5 × 22欧元) = 79.80欧元 + 165欧元 = 244.80欧元/车/天
实现30%的距离削减(在密集城市网络上可通过良好的优化实现)和20%的时间节省产生:
- 优化距离:147公里,燃油成本:55.86欧元
- 优化时间:6小时,驾驶员成本:132欧元
- 每辆车优化日成本:187.86欧元/车/天
每辆车每天节省:56.94欧元。对于10辆车,250个工作日:每年142,350欧元,来自一个API集成。
上述基准反映来自最后一公里物流研究的真实公开数据。您的具体数字会因地理位置、车辆类型和停靠点密度而异。密集城市地区获得最大收益,因为天真的顺序路由浪费了最多的距离在不必要的反复折返上。
天真路由与优化路由:视觉对比
天真(顺序)路由和优化路由之间的差异在地图上非常明显。
天真路由发生在按输入顺序提供停靠点时,首先下单的客户是路线上的第一个,不考虑地理位置。在阿姆斯特丹或柏林这样的城市,这会产生「意大利面路由」问题:您的驾驶员不断穿越自己的路线。
优化解决了您的停靠点集的旅行商问题(TSP)。对于15-20个停靠点,这在毫秒内计算上是可处理的。对于有数百个停靠点的较大车队,车辆路由问题(VRP)求解器处理多车辆和容量限制的额外约束。
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.
在此基础上可以构建的内容
路线优化是基础。一旦运行,自然的扩展是:
- 实时驾驶员追踪:将优化的路线坐标输入实时驾驶员追踪地图教程,向客户显示实时ETA更新。
- 基于等时线的覆盖规划:使用旅行时间API来可视化您的车队在配送时间窗口内可以到达哪些邮编区域。等时线地图详解文章展示了如何操作。
- 批量地址验证:在运行优化之前,使用地理编码API验证所有配送地址,以捕捉打字错误和过期邮编。请参阅如何使用地理编码API批量验证10,000个地址。
物流和配送行业页面和车队管理行业页面涵盖与调度软件相关的MapAtlas其他功能,包括多车辆VRP和返回配送中心优化。
开始使用
- 注册免费MapAtlas API密钥,免费层级包括路由和优化调用,无需信用卡
- 查阅Routing API文档获取车辆配置、时间窗口参数和多车辆选项的完整列表
- 浏览路线规划和导航功能页面获取产品概览
常见问题
路线优化如何降低配送成本?
路线优化通过重新排列多站点配送顺序来最小化总距离和行驶时间。研究表明与天真的顺序路由相比,行驶距离减少20-35%。对于每天行驶200公里、燃油成本为0.35欧元/公里的车辆,30%的削减每辆车每天节省约21欧元,每年约5000欧元。
路线优化中的时间窗口是什么?
时间窗口是配送约束条件,要求停靠点在指定的时间范围内被访问,例如只接受09:00至12:00之间配送的企业。优化器必须在最小化总路线距离的同时尊重所有时间窗口,这是一个计算上比无约束优化更复杂的问题。
MapAtlas Routing API是否处理欧盟低排放区?
是的。MapAtlas Routing API包含欧盟低排放区(包括阿姆斯特丹的ZTL、巴黎的Crit'Air区域和柏林的Umweltzone)的道路限制数据。传递车辆配置(柴油Euro 5、汽油、电动)作为参数,路由器将自动为不符合要求的车辆避开受限区域。
常见问题
路线优化如何降低配送成本?
路线优化通过重新排列多站点配送顺序来最小化总距离和行驶时间。与天真的顺序路由相比,研究表明距离行驶可减少20-35%。对于每天行驶200公里、燃油成本为0.35欧元/公里的车辆,减少30%意味着每辆车每天节省约21欧元,每年约5000欧元。
路线优化中的时间窗口是什么?
时间窗口是配送约束条件,要求停靠点在指定的时间范围内被访问,例如只接受09:00至12:00之间的配送。优化器必须在最小化总路线距离的同时尊重所有时间窗口,这是一个计算上比无约束优化更复杂的问题。
MapAtlas Routing API是否处理欧盟低排放区?
是的。MapAtlas Routing API包含欧盟低排放区(LEZ)的道路限制数据,包括阿姆斯特丹的ZTL、巴黎的Crit'Air区域和柏林的Umweltzone。传递车辆配置(柴油Euro 5、汽油、电动)作为参数,路由器将自动为不符合要求的车辆避开受限区域。

