首页 > 其他分享 >基于高德驾车路径API的多点路径规划

基于高德驾车路径API的多点路径规划

时间:2024-10-30 15:48:05浏览次数:5  
标签:distance self 路径 API lat path lng 高德

 

一般的,路径规划是指在一定的环境模型和约束条件(如路程最短、时间最快、费用最小等)下,寻找一条从起点到终点的最优路径。在此基础上,多点路径规划旨在为用户或车辆提供一条经过多个指定地点的最优行驶路线。其在共享出行、物流配送、公共交通规划等领域有着广泛的应用前景。

定制公交作为共享出行的一种创新模式,正逐步成为城市交通体系中不可或缺的一部分。它通过精准匹配乘客需求与公交线路,有效缓解了城市拥堵问题,同时也为乘客提供了更为便捷、舒适的出行体验。在公交降本增效的背景下,定制公交以其高效、灵活的特点,成为优化公交资优配置,提升公交系统的运营效率,降低运营成本的重要途径。在公交资源供需匹配的过程中即定制公交路线规划过程中,多点路径规划成为需要解决的核心问题。

高德地图开放平台的路径规划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

 

标签:distance,self,路径,API,lat,path,lng,高德
From: https://www.cnblogs.com/ddzhen/p/18515555

相关文章

  • 为.Net项目添加动态库加载路径
    为.Net项目添加动态库加载路径_51CTO博客_linux动态库加载路径本文分别基于.NetFramework和.NetCore的WPF应用程序为例,来说明如何为.Net项目添加自定义动态库加载路径。本文基于.NetCore创建WPF时,使用了.Net5作为目标框架。1、.NetFramework在基于.NetFramework的WPF项目......
  • Quick Action获取recordId 与 objectApiName的问题
    在QuickAction中引用lwc组件时,recordId与objectApiName会undefined,但html页面中用到recordId的地方能正常使用,在connectedCallback中打印recordId却找不到,因为在QuickAction中获取recordId,与connectedCallback并非顺序执行,通常在调用connectdCallback后填充。如果想在QuickAct......
  • docker 安装gitea后,查找对应配置文件路径
    不能直接在系统的根目录访问/data/gitea/conf/app.ini,因为这个路径通常是在Docker容器内的。如果你想在主机上访问这个文件,必须首先确认它是否已挂载到主机的某个目录。如果你想访问app.ini,请按照以下步骤操作:检查Docker容器的挂载:使用以下命令查看容器的详细信息,特别......
  • 【无人机】基于GWO算法、MP-GWO灰狼算法、灰狼-布谷鸟优化算法、CS-GWO多种群灰狼优化
        ......
  • 【无人机路径规划】基于深度强化学习的多无人机辅助边缘计算网络路径规划(Matlab代码实
    ......
  • 《vue3第六章》其他,包含:全局API的转移、其他改变
    @目录六、其他1.全局API的转移2.其他改变六、其他1.全局API的转移Vue2.x有许多全局API和配置。例如:注册全局组件、注册全局指令等。//注册全局组件Vue.component('MyButton',{data:()=>({count:0}),template:'<button@click="count++">Clicke......
  • 《vue3第四章》Composition API 的优势,包含Options API 存在的问题、Composition API
    @目录四、CompositionAPI的优势1.OptionsAPI存在的问题2.CompositionAPI的优势四、CompositionAPI的优势1.OptionsAPI存在的问题使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。CompositionAPI的优势-图1.gifComposition......
  • 什么是API接口?
    API是指应用程序接口,是一种连接不同软件应用程序的桥梁,以实现相互通信和数据交换的手段。随着互联网技术的发展,API接口越来越广泛地应用于各种企业业务中。本文将从API接口的基本概念、作用、优缺点等多个角度进行探讨。一、基本概念API是指应用程序接口,是软件开发中用于......
  • 【前端】在 Next.js 中添加对 API 的监控和日志记录
    API的监控和日志记录对于维护系统的稳定性和性能至关重要。良好的监控和日志记录可以帮助您及时发现和解决问题。以下是一些常用的监控和日志记录实践和技术:1.日志记录使用框架内置的日志功能Next.js本身提供了基本的日志记录功能,但您可能需要更详细的日志来调试问题......
  • Dingdone和Apicloud开发出的APP的区别在哪里
    Dingdone和Apicloud是两个流行的移动应用开发平台,它们在许多方面具有不同的特点和优势。本文将详细探讨:1、开发环境和工具集的差异;2、编程语言和框架支持的对比;3、开发效率和灵活性的区别;4、社区支持和资源的差异。例如,Dingdone可能更专注于提供快速开发的解决方案,而Apicloud可能......