はじめに
業務で「OD間の自動車最短経路(最短距離or最短時間)を一括で調べ、csvに出力したい」という場面に遭遇しました。
Googleマップで一件一件調べるのは非効率なので、座標CSVを投げると距離・所要時間・有料道路料金をまとめて返してくれるWebアプリを作りました。
地図表示・ターンバイターンのナビ案内まで確認できます。
ソースコード:https://github.com/masuda171/zidousya_keisan.git
作ったもの
- CSVで複数の座標ペア(出発地・目的地)をアップロード
- HERE Routing API v8 で自動車ルートを一括計算
- 結果(距離・所要時間・有料道路料金)をテーブル表示+CSVエクスポート
- OpenStreetMap上にルートを色分け表示
- 料金所名・入口・出口を含む有料道路料金の内訳を表示
- ターンバイターンの日本語ナビ案内をタイムライン形式で表示
技術スタック
| 分類 | 採用技術 |
|---|---|
| フレームワーク | Next.js 15 (App Router) |
| UIライブラリ | React 19 |
| 地図 | Leaflet + react-leaflet |
| ルートAPI | HERE Routing API v8 |
| ポリライン | @here/flexpolyline |
HERE Routing API とは
HERE が提供するルーティングAPIです。
無料枠(月250,000リクエスト)があり、日本の有料道路料金にも対応しています。
APIキーは HERE Developer Portal で取得できます。
アプリの処理フロー
ユーザーがHERE APIキーを入力
座標ペアを含むCSVをアップロード
HERE Routing API v8へのリクエストを200ms間隔で順次実行
結果を表示・CSV出力
実装のポイント
1. HERE Routing API v8 の呼び出し
const url = new URL("https://router.hereapi.com/v8/routes");
url.searchParams.set("transportMode", "car");
url.searchParams.set("routingMode", "fast"); // 最短時間
url.searchParams.set("departureTime", "any"); // 渋滞なし(標準速度)
url.searchParams.set("return", "summary,tolls,polyline,actions,instructions");
url.searchParams.set("lang", "ja"); // 日本語ナビ案内
url.searchParams.set("apikey", apiKey);
return パラメータで取得する情報を指定します。tolls で料金情報、polyline で地図描画用のルート形状、instructions でターンバイターンの日本語案内が取得できます。
2. 有料道路料金の取り出し(現金料金優先)
function extractTollCost(data) {
let total = 0;
data.routes[0].sections.forEach((section) => {
(section.tolls || []).forEach((toll) => {
const fares = toll.fares || [];
// 現金料金を優先、なければ最初のfareを使用
const cashFare = fares.find((f) =>
(f.paymentMethods || []).includes("cash")
);
const fare = cashFare ?? fares[0];
if (fare) total += fare.price?.value ?? 0;
});
});
return total;
}
paymentMethods に "cash" が含まれる fare を優先して取得します。ETCと現金で料金が異なる場合があるためです。
3. Leaflet の SSR 問題を回避
LeafletはブラウザのWindow APIに依存するため、Next.jsのSSRと相性が悪いです。
// dynamic importでSSRを無効化
const MapView = dynamic(() => import("./MapView"), { ssr: false });
4. HERE Flexible Polyline のデコード
HERE APIのルート形状は独自形式の「Flexible Polyline」で返ってきます。
@here/flexpolyline パッケージでデコードします。
import { decode } from "@here/flexpolyline";
// polyline文字列 → [[lat, lng], ...] の配列に変換
const positions = decode(polylineString).polyline;
5. CSV出力のExcel対応
日本語を含むCSVをExcelで開くと文字化けする問題への対処です。
// UTF-8 BOMを先頭に付与
const blob = new Blob(["\uFEFF" + csvContent], {
type: "text/csv;charset=utf-8;",
});
6. レート制限対策
HERE APIへのリクエストを200ms間隔で順次実行します。
for (let i = 0; i < inputRows.length; i++) {
// ... API呼び出し ...
if (i < inputRows.length - 1) {
await new Promise((r) => setTimeout(r, 200));
}
}
入力CSVのフォーマット
id,origin_lat,origin_lng,dest_lat,dest_lng
1,35.6812,139.7671,34.6937,135.5023
2,35.6812,139.7671,35.0116,135.7681
出力CSVのフォーマット
id,出発地緯度,出発地経度,目的地緯度,目的地経度,距離_km,所要時間_分,有料道路料金_円,ステータス
1,35.6812,139.7671,34.6937,135.5023,519.32,287,6050,成功
ハマったところ
HERE APIのセクション構造
HERE Routing API v8では、ルートは複数のセクション(区間)で構成されています。
sections[0] だけを参照すると、フェリー区間などを含む長距離ルートで距離・時間が欠落することがありました。
// NG: セクション0のみ
const dist = data.routes[0].sections[0].summary.length;
// OK: 全セクションを合算
const dist = sections.reduce((sum, s) => sum + (s.summary?.length ?? 0), 0);
ターンバイターンには return=instructions が必要
return=actions だけだと案内テキストが含まれません。
instructions も明示的に指定する必要があります。
// instructionsを含めないと instruction フィールドが空になる
url.searchParams.set("return", "summary,tolls,polyline,actions,instructions");
おわりに
HERE Routing APIは日本の有料道路料金まで取得できるのが便利です。
業務で複数地点間の移動コスト計算が必要な方はぜひ試してみてください。
ソースコード: [https://github.com/masuda171/zidousya_keisan.git]

