/******************************************************* * 1. 初期設定 *******************************************************/ const GH_KEY = "YOUR_API_KEY"; // GraphHopper の API キーを入れて下さい const MAX_POINTS = 3; // クリックで指定できる地点数 // Leaflet マップ const map = L.map("map").setView([35.681, 139.767], 13); L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors" }) .addTo(map); // クリック地点・マーカー・ルート線を保持 let points = []; // [[lat, lng], ...] let markers = []; let routeLine = null; /******************************************************* * 2. 地点クリックのハンドリング *******************************************************/ map.on("click", e => { if (points.length >= MAX_POINTS) { alert(`指定できるのは最大 ${MAX_POINTS} 地点までです`); return; } const pt = [e.latlng.lat, e.latlng.lng]; points.push(pt); const marker = L.marker(e.latlng) .addTo(map) .bindPopup(`地点 ${points.length}`) .openPopup(); markers.push(marker); // 2 点以上になったらルート計算 if (points.length >= 2) { drawRoute(); } }); /******************************************************* * 3. GraphHopper で「登りの少ない」ルートを取得して描画 *******************************************************/ function drawRoute() { const startDrawing = Date.now(); // ---- 3-1. クエリ文字列の組み立て ------------------ // クリック地点 → &point=lat,lng&point=lat,lng ... const pointParams = points.map(p => `point=${p[0]},${p[1]}`).join("&"); // 代替経路を最大 3 本取得(points_encoded=false で GeoJSON 相当の座標をもらう) const url = `https://graphhopper.com/api/1/route?` + `vehicle=foot&locale=ja&points_encoded=false` + `&elevation=true` + `&alternative_route.max_paths=3` + `&${pointParams}` + `&key=${GH_KEY}`; // ---- 3-2. ルーティング実行 ------------------------ fetch(url) .then(res => res.json()) .then(json => { // 何かしら API エラー if (!json.paths || json.paths.length === 0) { throw new Error("ルートが取得できませんでした"); } // ---- 3-3. 一番「登り」の少ない経路を選ぶ ---------- const best = json.paths.reduce((a, b) => (a.ascend < b.ascend ? a : b)); // ---- 3-4. 既存ルートを消して新ルートを描画 ------- if (routeLine) map.removeLayer(routeLine); // best.points.coordinates = [[lng,lat,z], ...] const lineLatLngs = best.points.coordinates.map(c => [c[1], c[0]]); routeLine = L.polyline(lineLatLngs, { color: "blue" }).addTo(map); map.fitBounds(routeLine.getBounds()); // ---- 3-5. 情報パネル更新 ------------------------ const distKm = (best.distance / 1000).toFixed(2); const timeMin = (best.time / 60000).toFixed(1); // ms → 分 const ascend = best.ascend.toFixed(0); const descend = best.descend.toFixed(0); document.getElementById("info").innerHTML = `<h3>ルート情報(坂道少なめ)</h3> <p> 距離:${distKm} km<br> 所要時間:${timeMin} 分<br> 登り合計:${ascend} m<br> 下り合計:${descend} m </p> <small>計算時間:${(Date.now() - startDrawing)} ms</small>`; }) .catch(err => { console.error(err); alert("ルート取得に失敗しました"); }); } /******************************************************* * 4. 便利なリセット(F5 でもよいが、一応) *******************************************************/ function reset() { points = []; markers.forEach(m => map.removeLayer(m)); markers = []; if (routeLine) { map.removeLayer(routeLine); routeLine = null; } document.getElementById("info").innerHTML = ""; } // Esc キーでリセット document.addEventListener("keydown", e => { if (e.key === "Escape") reset(); });