ラストマイル配送はサプライチェーンの中で最もコストの高い部分だ。業界のベンチマークでは、ラストマイルコストが総配送コストの53%を占めると一貫して示されている。その中で最も制御可能な変数はルート効率だ。15の停車地を間違った順序で完了するドライバーは、燃料を消費し、車両を傷め、再配送料金を引き起こす配送時間帯を見逃しながら、必要以上に40%多くの距離を走行する可能性がある。
ルート最適化はコードで解決するのが難しい問題ではなくなった。かつては高価な専門ロジスティクスソフトウェアを必要としたものが、今やAPIコールになっている。このチュートリアルでは、MapAtlas Routing APIを使った完全な複数拠点ルートオプティマイザーを構築する。配送停車地のリストを送り、最適化されたシーケンスと総距離および時間を受け取り、時間帯制約を適用し、都市配送のEU低排出ゾーン制限を処理するPythonスクリプト。JavaScriptスニペットが結果をマップに表示する。
Pythonの実装は55行未満だ。JavaScriptのマップ表示は追加の30行だ。
ラストマイルコスト問題
最適化が実際に節約するものを理解するために、現実的な配送シナリオの数字を見てみよう:
- フリート:10台のバン
- 1日あたりの停車地数:18
- 現在の平均距離:210 km/バン/日
- 燃料費:0.38ユーロ/km(ディーゼル、EU平均)
- ドライバーコスト:22ユーロ/時間
- 現在の平均ルート時間:7.5時間/日
現在の1バンあたりの1日コスト:(210 × 0.38ユーロ) + (7.5 × 22ユーロ) = 79.80ユーロ + 165ユーロ = 244.80ユーロ/バン/日
30%の距離削減(密集した都市ネットワークでの良好な最適化で達成可能)と20%の時間節約は以下を生み出す:
- 最適化された距離:147 km → 燃料費:55.86ユーロ
- 最適化された時間:6時間 → ドライバーコスト:132ユーロ
- 最適化後の1バンあたりの1日コスト:187.86ユーロ/バン/日
1バンあたりの1日の節約:56.94ユーロ。10台のバンで250稼働日:142,350ユーロ/年、1つのAPI統合から。
上記のベンチマークはラストマイルロジスティクス研究から公開された実際の数字を反映している。あなたの具体的な数字は地理、車両タイプ、停車地密度によって異なる。密集した都市エリアは最大の利益を見る。なぜならナイーブな順次ルートは不必要な往復で最も多くの距離を無駄にするからだ。
ナイーブルートと最適化ルートの視覚的比較
ナイーブ(順次)ルートと最適化ルートの違いはマップ上で明白だ。
ナイーブなルーティングは、入力された順番に停車地を入力したとき、最初の注文をした顧客が地理に関係なく最初のルートになるときに起こる。アムステルダムやベルリンのような都市では、これは「スパゲッティルート」問題を引き起こす。ドライバーが常に自分のパスを横断する。
最適化は停車地セットの巡回セールスマン問題(TSP)を解く。15〜20の停車地ではミリ秒で計算可能だ。数百の停車地を持つ大規模フリートでは、車両ルーティング問題(VRP)ソルバーが複数の車両と容量制限の追加制約を処理する。
ステップ1:配送データを構造化する
各停車地には場所が必要で、時間帯配送の場合は配送が受け入れ可能な時間帯を指定するtime_windowが必要だ。
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" } }
]
ステップ2:ルート最適化エンドポイントを呼び出す
デポと停車地リストを最適化ルーティングエンドポイントにPOSTする。APIは総ルート距離と所要時間とともに最も効率的な訪問順序で停車地を返す。
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)
ステップ3:最適化ルートを解析して表示する
APIレスポンスには最適化された順序の停車地、各停車地の累積ETA、総距離、総所要時間が含まれる。
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)
上記5つの停車地のサンプル出力:
--- 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%)
ステップ4:EU低排出ゾーンの処理
アムステルダムのZTLゾーン、パリのCrit'Airシステム、ベルリンのUmweltzoneは特定の時間帯に特定の車両タイプの中心エリアへのアクセスを制限する。距離だけで見て効率的に見えるルートが、あなたの車両には無効かもしれない。
avoid_low_emission_zones: trueパラメーターとvehicle_profileを組み合わせると、非準拠車両の制限ゾーンを自動的に迂回する。電気と Euro 6 の車両は LEZ を通過可能であり、このパラメーターは影響を与えない。
# 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
ディーゼルから電気への移行を計画しているロジスティクス事業の場合、ルートごとにこれら2つの出力を比較することで、電気化によって利用可能な走行距離の改善を直接定量化できる。
ステップ5:最適化ルートをマップに表示する
APIレスポンスからルートのジオメトリを取得し、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);
実際の節約を計算する
APIレスポンスを手に入れたら、節約計算は簡単だ。レスポンスのsaving_vs_naive_kmフィールドが節約された距離を直接教えてくれる。そこからコスト節約を導き出す:
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)
時間帯最適化
午前6時にパン屋に配送し、午後2時にレストランに配送しながら、総ルート距離を最小化することは制約付き最適化問題だ。APIはこれを自動的に処理する。必要なのはウィンドウを指定するだけだ:
# 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" } }
]
デポの出発時間と現在の交通モデルを考慮してどの時間帯制約も満たせない場合、APIはどの停車地が時間どおりに到達できなかったかをリストしたconstraint_violations配列を返す。ディスパッチソフトウェアはその後、ドライバーに警告したり、より早い出発を提案したりできる。
この上に構築するもの
ルート最適化が基盤だ。実行中になったら、自然な拡張は次の通り:
- ライブドライバー追跡:最適化されたルート座標をライブドライバー追跡マップチュートリアルに供給し、顧客にリアルタイムETA更新を表示する。
- アイソクロンベースのカバレッジ計画:Travel Time APIを使用して、フリートが配送ウィンドウ内に到達できる郵便番号を可視化する。アイソクロンマップの説明記事でその方法を示している。
- 一括住所検証:最適化を実行する前に、ジオコーディングAPIですべての配送住所を検証してタイプミスと時代遅れの郵便番号をキャッチする。ジオコーディングAPIを使って1万件の住所を一括検証する方法を参照。
ロジスティクスと配送業界ページとフリート管理業界ページは、複数車両VRPとデポへの帰還最適化を含む、ディスパッチソフトウェアに関連する追加のMapAtlas機能をカバーしている。
はじめに
- MapAtlas APIキーの無料サインアップ。無料ティアにはルーティングと最適化コールが含まれており、クレジットカード不要
- ルートプロファイル、時間帯パラメーター、複数車両オプションの完全なリストについてはルーティングAPIドキュメントを確認する
- 製品概要についてはルート計画とナビゲーション機能ページを参照する
よくある質問
ルート最適化は配送コストをどのように削減しますか?
ルート最適化は複数拠点の配送シーケンスを並び替えて総距離と走行時間を最小化します。研究では、ナイーブな順次ルートと比べて走行距離が20〜35%削減されることが一貫して示されています。1日200km走行する車両で燃料費が0.35ユーロ/kmの場合、30%削減で1日あたり約21ユーロの節約、年間1台当たり約5,000ユーロになります。
ルート最適化における時間帯とは何ですか?
時間帯とは、停車地を特定の時間帯内に訪問することを必要とする配送制約です。例えば、午前9時から12時の間のみ配送を受け付けるビジネスなどです。オプティマイザーはすべての時間帯を守りながら、総ルート距離を最小化する必要があります。これは制約なしの最適化よりも計算的に大幅に難しい問題です。
MapAtlas Routing APIはEU低排出ゾーンに対応していますか?
はい。MapAtlas Routing APIには、アムステルダムのZTL、パリのCrit'Airゾーン、ベルリンのUmweltzoneなど、EU低排出ゾーンの道路制限データが含まれています。車両プロファイル(ディーゼルEuro 5、ガソリン、電気)をパラメーターとして渡すと、ルーターは非準拠車両の制限ゾーンを自動的に回避します。
よくある質問
ルート最適化は配送コストをどのように削減しますか?
ルート最適化は複数拠点の配送シーケンスを並び替えて総距離と走行時間を最小化します。研究では、ナイーブな順次ルートと比べて走行距離が20〜35%削減されることが一貫して示されています。1日200km走行する車両で燃料費が0.35ユーロ/kmの場合、30%削減で1日あたり約21ユーロの節約、年間1台当たり約5,000ユーロになります。
ルート最適化における時間帯とは何ですか?
時間帯とは、停車地を特定の時間帯内に訪問することを必要とする配送制約です。例えば、午前9時から12時の間のみ配送を受け付けるビジネスなどです。オプティマイザーはすべての時間帯を守りながら、総ルート距離を最小化する必要があります。これは制約なしの最適化よりも計算的に大幅に難しい問題です。
MapAtlas Routing APIはEU低排出ゾーンに対応していますか?
はい。MapAtlas Routing APIには、アムステルダムのZTL、パリのCrit'Airゾーン、ベルリンのUmweltzoneなど、EU低排出ゾーン(LEZ)の道路制限データが含まれています。車両プロファイル(ディーゼルEuro 5、ガソリン、電気)をパラメーターとして渡すと、ルーターは非準拠車両の制限ゾーンを自動的に回避します。

