Newer
Older
quiz2023 / sukunai.js
/*******************************************************

 * 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();

});