/
CATEGORY
JavaScript
/
用 Geolocation API 和 Leaflet Routing Machine 實作叫車導航功能

用 Geolocation API 和 Leaflet Routing Machine 實作叫車導航功能

MUKI AI Summary

使用 Geolocation API 和 Leaflet Routing Machine 可以實現叫車導航功能。Geolocation API 提供使用者的經緯度,而 Leaflet 是輕量級地圖工具,用於顯示地圖和標記。Leaflet Routing Machine 則專門用於路線規劃和導航,能計算兩點間的路徑,並提供距離和時間資訊。這些工具的結合可實現從位置追蹤到路線導航的完整流程,適用於叫車服務中,Geolocation API 提供即時位置,Leaflet 負責地圖呈現,Leaflet Routing Machine 負責路線計算與導航視覺化。

在實作中,使用者輸入地址後,系統會自動計算司機的路線並顯示在地圖上,預估距離和時間。模擬啟動後,司機標記沿路線移動,顯示即時距離。此功能不僅適用於叫車服務,也適合物流追蹤、旅遊規劃和遊戲開發。完整程式碼可參考範例連結,並可深入了解 Leaflet 和 Routing Machine 的其他功能。...

Web API 系列文章

這系列文章是我參加 2024 iThome 鐵人賽期間撰寫的作品。完賽後,我對文章進行潤稿與修訂,並重新發佈在部落格上。
為了聚焦在 Web API 上,文章內的所有範例都使用原生 HTML + CSS + JavaScript 撰寫,並提供完整的線上範例,如有任何問題,歡迎留言討論。

在開發與地理位置相關的應用時,可以使用 Geolocation API 取得使用者當前的位置,但是 Geolocation API 僅能提供使用者的經緯度,並沒有辦法完成像路線規劃、距離計算或視覺化呈現等功能。因此,我們需要借助其他工具來補足這些功能,像是 LeafletLeaflet Routing Machine

以下是他們分別扮演的角色:

  • Geolocation API:用於取得即時的經緯度資訊,例如司機和使用者的當前位置。
  • Leaflet:輕量級的地圖繪製工具,用來顯示地圖並繪製地圖上的標記與其他視覺元素。
  • Leaflet Routing Machine:專為 Leaflet 設計的套件,用於路線規劃與導航。它可以計算兩點之間的路線,並提供距離和時間的資訊,同時也支援多種路線服務(如 OpenStreetMap)。

這三者的搭配讓我們可以實現從位置追蹤到路線導航的完整流程。例如,在叫車服務中,Geolocation API 提供即時位置,Leaflet 負責地圖呈現,而 Leaflet Routing Machine 則負責路線計算與導航視覺化。

用 Leaflet 與 Routing Machine 建構叫車服務

今天要做的是一個簡單的叫車服務,流程與功能如下:

  • 使用者輸入上車地址後,系統會自動計算司機從當前位置到目的地的路線,並顯示在地圖上。
  • 同時,系統會預估距離和時間。
  • 啟動模擬後,司機標記會沿著規劃的路線移動,並顯示距離目的地的即時距離。

▼ 本次實作的畫面範例,後面會提供範例連結

範例畫面

載入套件

首先載入 Leaflet 和 Leaflet Routing Machine 的 JavaScript 和 CSS 檔案:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet-routing-machine@latest/dist/leaflet-routing-machine.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-routing-machine/3.2.12/leaflet-routing-machine.min.js"></script>

宣告變數

// 地圖實例,在頁面上顯示地圖
let map;

// 使用者位置的標記,顯示使用者的當前位置
let userMarker;

// 司機位置的標記,顯示司機的當前位置
let driverMarker;

// 路線控制器,用於計算並在地圖上繪製路線
let routeControl;

// 模擬路線移動的計時器,用於定時更新司機位置
let simulationInterval;

// 儲存從起點到終點的路徑點座標
let routePoints = [];

// 當前路線點的索引,用於追蹤司機移動到的路線點位置
let currentPointIndex = 0;

初始化地圖

我們先初始化地圖並設定司機與使用者的標記位置:

  • 預設司機位置為台北市中心
  • 為司機設置一個黃色車子的圖示,圖片來源請點我

程式碼:

function initMap() {
  map = L.map('map').setView([25.0330, 121.5654], 13); // 台北市中心
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors',
  }).addTo(map);

  // 使用者位置
  userMarker = L.marker([25.0330, 121.5654]).addTo(map);

  // 司機位置
  driverMarker = L.marker([25.0330, 121.5654], {
    icon: L.icon({
      // <a href="<https://www.flaticon.com/free-icons/car>" title="car icons">Car icons created by Konkapp - Flaticon</a>
      iconUrl: 'car.png',
      iconSize: [30, 30],
      iconAnchor: [15, 15],
    }),
  }).addTo(map);
}

搜尋地址並計算路徑

使用 OpenStreetMap API 搜尋地址,可以使用 encodeURIComponent 來處理地址中的特殊字元,讓他能正常轉換解析(ex. 空白,逗號,引號 … 等),取得經緯度後完成以下事件:

  • 更新使用者標記的位置:userMarker.setLatLng([lat, lon])
  • 將地圖中心移動到使用者位置:map.setView([lat, lon], 13)
  • 使用 Leaflet Routing Machine 計算司機到使用者位置的路徑:calculateRoute(lat, lon)
async function searchAddress() {
  const address = document.getElementById('addressInput').value;
  const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`);
  const data = await response.json();

  if (data.length > 0) {
    const { lat, lon } = data[0];
    userMarker.setLatLng([lat, lon]); // 更新使用者位置
    map.setView([lat, lon], 13); // 重設地圖的中心點
    calculateRoute(lat, lon); // 計算司機到使用者位置的路徑
  } else {
    document.getElementById('status').textContent = '找不到地址,請重新輸入。';
  }
}

計算司機到使用者位置的路徑

使用 Leaflet Routing Machine 套件來計算路徑,他的官方文件有更詳細的介紹,歡迎閱讀參考:https://www.liedman.net/leaflet-routing-machine/

我們用到的主要功能如下:

  • 取得司機當前位置:使用 driverMarker.getLatLng()取得司機當前的經緯度;在模擬移動的時候,會不斷取得司機當前的經緯度以重新渲染畫面,這部分的程式碼後面會提到。
  • 設定路線:L.Routing.control 可以幫我們建立新的路線,並渲染到地圖上,參數介紹我會直接寫在程式碼的註解裡。
  • 事件監聽:找到路徑後會觸發 routesfound 事件,我們透過他取得路徑的距離和時間,並顯示到畫面上。
function calculateRoute(destLat, destLon) {
  if (routeControl) {
    map.removeControl(routeControl);
  }

  const driverStart = driverMarker.getLatLng();
  routeControl = L.Routing.control({
    waypoints: [
      L.latLng(driverStart.lat, driverStart.lng),
      L.latLng(destLat, destLon)
    ],
    routeWhileDragging: false,  // 禁止在拖動時重新計算路徑
    addWaypoints: false,  // 禁止添加新的路徑點
    draggableWaypoints: false,  // 禁止拖動路徑點
    fitSelectedRoutes: true,  // 自動調整地圖視角以適應選中的路徑
    show: true,  // 顯示路線指示面板
    lineOptions: {
      styles: [{ color: 'blue', opacity: 0.6, weight: 4 }]  // 設定路線的樣式
    }
  }).addTo(map);

  // 當找到路徑時,觸發事件
  routeControl.on('routesfound', function (e) {
    routePoints = e.routes[0].coordinates;
    const distance = e.routes[0].summary.totalDistance / 1000;  // 獲取總距離並轉換為公里
    const duration = Math.round(e.routes[0].summary.totalTime / 60);  // 獲取總時間並轉換為分鐘
    document.getElementById('status').textContent = `預計距離: ${distance.toFixed(2)} 公里, 時間: ${duration} 分鐘`;
  });
}

開始模擬:模擬司機移動的過程

我準備了兩個按鈕,分別是「開始模擬」與「停止模擬」,用來模擬司機沿著規劃路線移動的過程,並更新司機距離目的地的實時距離。

點選「開始模擬」按鈕後,會模擬司機沿著路線移動的過程。這個過程透過更新地圖上的標記位置以及實時顯示距離資訊來模擬司機的移動。

startSimulation() 主要功能:

初始化路線點索引:

使用變數 currentPointIndex 將當前路線的索引點初始化為 0,表示司機的起點位置。我們會利用這個索引與路線終點進行比對,判斷司機是否到達目的地。

設置定時器:

使用 setInterval 每隔一秒執行一次 moveDriver() 函數,模擬司機在路線上的移動過程,前面提到的「我們在模擬移動的時候,會不斷取得司機當前的經緯度以重新渲染畫面」,就是 moveDriver() 做的事情之一,定時器會確保司機的標記位置和地圖上的距離資訊隨著時間更新。


moveDriver() 主要功能:

檢查是否到達目的地

  • 將當前路線索引 (currentPointIndex) 與路線點數量 (routePoints.length) 進行比對,確保司機尚未到達終點。
  • 若索引超過或等於路線點數量,則停止模擬並顯示「模擬完成」訊息。

更新司機位置:

  • routePoints[currentPointIndex] 取得當前路線點的經緯度,並使用 driverMarker.setLatLng() 更新司機標記在地圖上的位置。
  • currentPointIndex 加 1,準備下一次移動至下一個路線點。

計算並更新距離資訊:

  • 使用 userMarker.getLatLng()driverMarker.getLatLng() 分別取得乘客與司機的當前經緯度。
  • 使用 distanceTo() 計算兩點之間的距離,並將距離資訊更新到畫面上,提供使用者即時的位置資訊。
function startSimulation() {
  if (routePoints.length === 0) {
    document.getElementById('status').textContent = "請先搜索地址並計算路線";
    return;
  }
  currentPointIndex = 0;
  simulationInterval = setInterval(moveDriver, 1000);
}

function moveDriver() {
  if (currentPointIndex >= routePoints.length) {
    stopSimulation();
    document.getElementById('status').textContent = "模擬完成,司機已到達目的地";
    return;
  }
  const point = routePoints[currentPointIndex];
  driverMarker.setLatLng([point.lat, point.lng]);
  currentPointIndex++;

  const userPos = userMarker.getLatLng();
  const driverPos = driverMarker.getLatLng();
  const distance = userPos.distanceTo(driverPos) / 1000;
  document.getElementById('status').textContent = `司機距離您還有 ${distance.toFixed(2)} 公里`;
}

停止模擬

「停止模擬」的功能相較之下簡單許多,停止 simulationInterval 計時器,並將之設為 null,防止再次誤用

function stopSimulation() {
  clearInterval(simulationInterval);
  simulationInterval = null;
}

範例程式碼

這個範例展示了如何使用 Leaflet 與 Leaflet Routing Machine,並結合 Geolocation API 來實現叫車服務的基礎功能。

➡️ 範例程式碼連結

如果對 Leaflet 或 Routing Machine 的其他功能感興趣,歡迎參考官方文件

小結

透過這個範例,我們可以看到 Leaflet 和 Leaflet Routing Machine 的強大功能,能輕鬆地在地圖上實現導航與追蹤等應用,不僅可用於叫車服務,也適合物流追蹤、旅遊規劃、甚至是遊戲開發。

有任何問題歡迎留言討論唷。

歡迎給我點鼓勵,讓我知道你來過 :)

可愛又迷人的 Web API 系列文章:

MUKI says:

如果文章有幫助到你,歡迎分享給更多人知道。文章內容皆為 MUKI 本人的原創文章,我會經常更新文章以及修正錯誤內容,因此轉載時建議保留原出處,避免出現版本不一致或已過時的資訊。

本文地址:https://muki.tw/geolocation-api-leaflet-routing-machine/ 已複製

Subscribe
Notify of
guest

0 則留言
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Copyright © since 2008 MUKI space* / omegaSS theme All Rights Reserved.