基于高德駕車路徑API的多點路徑規劃
一般的,路徑規劃是指在一定的環境模型和約束條件(如路程最短、時間最快、費用最小等)下,尋找一條從起點到終點的最優路徑。在此基礎上,多點路徑規劃旨在為用戶或車輛提供一條經過多個指定地點的最優行駛路線。其在共享出行、物流配送、公共交通規劃等領域有著廣泛的應用前景。
定制公交作為共享出行的一種創新模式,正逐步成為城市交通體系中不可或缺的一部分。它通過精準匹配乘客需求與公交線路,有效緩解了城市擁堵問題,同時也為乘客提供了更為便捷、舒適的出行體驗。在公交降本增效的背景下,定制公交以其高效、靈活的特點,成為優化公交資優配置,提升公交系統的運營效率,降低運營成本的重要途徑。在公交資源供需匹配的過程中即定制公交路線規劃過程中,多點路徑規劃成為需要解決的核心問題。
高德地圖開放平臺的路徑規劃API是一項功能強大的服務,它能夠為開發者提供包括駕車、步行、騎行、公交等多種出行方式的路線規劃能力。這里以此駕車路徑規劃API為核心,結合旅行商路徑優化算法,實現多點路徑規劃方案,本質上是通過算法對需求點進行初步排序,進而通過多段疊加的方式,實現多點的路徑規劃。整體過程如下:
實現細節
為了充分演示和驗證該技術方案,這里以python FastAPI為后端框架,結合高德地圖開放平臺進行功能開發和演示。
開發過程主要分為3個部分,一是基于python的旅行商算法和后處理過程,其中后處理主要是對多段路徑進行合并,并對走回頭路等異常現象進行處理;二是基于FastAPI的后端服務開發,主要實現前端數據的接收和處理;三是前端頁面開發,主要用于處理用戶的交互邏輯,比如需求點的點選和顯示,規劃路徑的顯示等。
1?? 算法處理,文件名稱 ialgo.py
# -*- coding:utf-8 -*- import itertools from geopy.distance import geodesic from shapely.geometry import LineString from pygeoops import centerline import geopandas as gpd import requests import logging from shapely.geometry import MultiPoint, MultiLineString, MultiPolygon logging.basicConfig(level=logging.DEBUG) class TravelingSalesman: # 旅行商問題 # 輸入:經緯度點列表 # 輸出:排序好的點列表 def __init__(self, points): self.points = points # 經緯度點列表 self.min_distance = float('inf') # 最小距離初始化為無窮大 self.best_path = [] # 最優路徑 def calculate_distance1(self, point1, point2): """計算兩點間的歐幾里得距離""" print(point1[::-1], point2[::-1]) return geodesic(point1[::-1], point2[::-1]).m def calculate_distance(self, point1, point2): """計算兩點間的歐幾里得距離""" print(point1) print(point2) p1=[point1['lat'],point1['lng']] p2=[point2['lat'],point2['lng']] # 檢查輸入參數是否為元組或列表 if not (isinstance(p1, (tuple, list)) and isinstance(p2, (tuple, list))): raise ValueError("Points must be tuples or lists") # 檢查每個點是否包含兩個元素 if len(p1) != 2 or len(p2) != 2: raise ValueError("Each point must contain exactly two elements (latitude, longitude)") # 計算距離 distance = geodesic(p1, p2).m # 記錄日志 logging.debug(f"Calculating distance between {p1} and {p2}") logging.debug(f"Distance: {distance} meters") return distance def find_shortest_path(self): """找到旅行商問題的最短路徑""" # 生成所有可能的路徑 for path in itertools.permutations(self.points): distance = self.calculate_total_distance(path) if distance < self.min_distance: self.min_distance = distance self.best_path = path return self.best_path def calculate_total_distance(self, path): """計算給定路徑的總距離""" total_distance = 0 for i in range(len(path) - 1): total_distance += self.calculate_distance(path[i], path[i + 1]) return total_distance class iCenterline: def __init__(self, points, crs='epsg:4525'): # points 經緯度點列表 self.gps = gpd.GeoSeries(LineString(points), crs=4326).to_crs(crs) self.crs = crs def iSampleLine(self): sline = self.gps.geometry.values[0] sline = sline.buffer(30).buffer(-15) cline = centerline(sline) if cline.geom_type != "LineString": cls = [_.length for _ in cline.geoms] idx = cls.index(max(cls)) cline = cline.geoms[idx] gps = gpd.GeoSeries(cline, crs=self.crs).to_crs(4326) # gps.to_file('vector/cline.geojson', driver='GeoJSON') # return [{'lng': lng, 'lat': lat} for lng, lat in gps.geometry.values[0].coords] return [{'lng': lng, 'lat': lat} for lng, lat in extract_coords(gps.geometry.values[0])] def extract_coords(geometry): if isinstance(geometry, (MultiPoint, MultiLineString, MultiPolygon)): return [coord for part in geometry.geoms for coord in part.coords] else: return list(geometry.coords)
class AmapDriving: def __init__(self, api_key): self.api_key = api_key self.url = "https://restapi.amap.com/v3/direction/driving" def get_driving_route(self, origin, destination): params = { 'key': self.api_key, 'origin': origin, # 起點經緯度,格式為"經度,緯度" 'destination': destination # 終點經緯度,格式為"經度,緯度" } print(params) response = requests.get(self.url, params=params) route_info = response.json() if route_info.get('status') == '1': path_segments = route_info.get('route').get('paths')[0].get('steps') path = [] for ipath in path_segments: path.append(ipath.get('polyline')) path = ';'.join(path) else: path = '' print("駕車路線信息:", path) return path
2?? 后端服務,文件名稱app.py
# -*- coding:utf-8 -*- from fastapi import FastAPI, Body from fastapi.responses import FileResponse from pydantic import BaseModel from typing import List from ialgo import TravelingSalesman from ialgo import AmapDriving from ialgo import iCenterline app = FastAPI() class iloc(BaseModel): lat: float lng: float class Location(BaseModel): pts: List[iloc] @app.post("/location/") async def receive_location(location: Location): # 處理前端傳入的坐標點 pnts = [{"lat": _.lat, "lng": _.lng} for _ in location.pts] otravel = TravelingSalesman(pnts) pnts = otravel.find_shortest_path() # 點排序 path = [] # 高德規劃路徑 多段 for fp, tp in zip(pnts, pnts[1:]): oAmap = AmapDriving('****') # 這里為高德web服務API,需替換為有效的API-key print(fp, tp) org = '{},{}'.format(fp['lng'], fp['lat']) tpg = '{},{}'.format(tp['lng'], tp['lat']) amap_path = oAmap.get_driving_route(org, tpg) path.append(amap_path) path = ';'.join(path) # 高德路徑拼接 print(path) path = [eval(_) for _ in path.split(';')] print("=============================================") print(path) oline = iCenterline(path) # 計算中心線 防止回頭路 res = oline.iSampleLine() # 格式同 Location coords = [(item['lng'], item['lat']) for item in res] return coords @app.get("/") async def iclick_html(): return FileResponse('templates/pathBaseMultiPoint.html') if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
3?? 前端頁面 pathBaseMultiPoint.html 該演示工作該文件位于 templates文件夾下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>高德地圖示例</title> <style> #map { height: 600px; } #p2p { position: absolute; top: 10px; /* 調整按鈕的垂直位置 */ left: 130px; /* 調整按鈕的水平位置 */ z-index: 1000; /* 確保按鈕在地圖之上 */ padding: 10px; background-color: white; border: 1px solid #ccc; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); } #clean { position: absolute; top: 10px; /* 調整按鈕的垂直位置 */ left: 30px; /* 調整按鈕的水平位置 */ z-index: 1000; /* 確保按鈕在地圖之上 */ padding: 10px; background-color: white; border: 1px solid #ccc; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); } #path { position: absolute; top: 10px; /* 調整按鈕的垂直位置 */ left: 30px; /* 調整按鈕的水平位置 */ z-index: 1000; /* 確保按鈕在地圖之上 */ padding: 10px; background-color: white; border: 1px solid #ccc; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); } </style> </head> <body> <div id="map"></div> <button id="p2p" , class="btn">高德路徑</button> <button id="clean" , class="btn">清除路徑</button> <script src="https://webapi.amap.com/maps?v=1.4.15&key=YourAmapKey&plugin=AMap.Driving"></script> <script> //更換自己的API key js端的 var map = new AMap.Map('map', { center: [113.5, 34.8], // 經度, 緯度 zoom: 13 // 縮放級別 }); let points = []; let markers = []; let polyline; map.on('click', function (e) { var lat = e.lnglat.lat; var lng = e.lnglat.lng; console.log('點擊位置: ', lat, lng); points.push({ 'lat': lat, 'lng': lng }); var marker = new AMap.Marker({ position: e.lnglat }); map.add(marker); markers.push(marker); var infoWindow = new AMap.InfoWindow({ content: `點擊位置: ${lat.toFixed(5)}, ${lng.toFixed(5)}`, position: e.lnglat }); infoWindow.open(map, e.lnglat); }); document.getElementById("clean").addEventListener('click', function () { console.log('清除點'); points = []; if (polyline) { map.remove(polyline); }; if (markers) { markers.forEach(marker => { map.remove(marker); }); }; }); document.getElementById('p2p').addEventListener('click', async function () { console.log('排序被點擊'); if (points.length === 0) { console.log('沒有可用的路徑點'); return; } const result = await sendLocationToBackend(points); console.log('規劃的路徑點:', result); // 創建折線 polyline = new AMap.Polyline({ path: result, // 設置折線經過的坐標點數組 strokeColor: "#FF33FF", // 線顏色 strokeOpacity: 1, // 線透明度 strokeWeight: 3, // 線寬 strokeStyle: "solid", // 線樣式 strokeDasharray: [10, 5], // 自定義線段樣式,格式為[線段長度, 空白長度] lineJoin: 'round', // 折線拐點連接處樣式 isOutline: false // 是否繪制邊線外輪廓 }); // 將折線添加到地圖上 map.add(polyline); }); async function sendLocationToBackend(points) { const jsonData = { pts: points }; try { const response = await fetch('http://localhost:8000/location', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(jsonData) }); const data = await response.json(); console.log('Success:', data); return data; } catch (error) { console.error('Error:', error); } }; </script> </body> </html>
效果如下

參考:https://lbs.amap.com/api/javascript-api/guide/services/navigation

浙公網安備 33010602011771號