Python调用高德API实现多点路径规划与可视化
1. 项目缘起从“两点一线”到“多点串联”的规划需求在地图应用开发中我们最常接触的是起点到终点的路径规划。无论是日常通勤导航还是外卖配送的取送路线A点到B点的逻辑已经非常成熟。然而在实际的业务场景中尤其是在物流配送、巡检路线、多点打卡、旅行规划等领域我们面临的往往是更复杂的“多点串联”问题。比如一个快递员需要从仓库出发依次前往A、B、C三个小区派件最后返回仓库或者一个销售代表需要一天内拜访位于城市不同区域的五家客户。这时简单的“起点-终点”规划就无能为力了我们需要的是能够处理多个途经点的路径规划。高德地图的Web服务API提供了强大的路径规划能力其中就包括支持多个途经点的驾车路径规划服务。这个功能的核心价值在于它不仅能计算出连接所有点的最短或最快路径还能智能地优化途经点的顺序虽然高德API本身不提供自动排序优化但我们可以基于其返回的结果进行二次处理并最终将这条复杂的路线清晰地展示在地图上。对于开发者而言尤其是使用Python进行数据处理和自动化脚本开发的工程师将这个能力集成到自己的系统中可以极大地提升业务流程的自动化水平和决策效率。我最近就在一个仓储物流的调度系统中用到了这个功能。需求很简单系统每天会生成一批需要上门取件的订单这些订单散落在城市各处。我们需要为每位取件员规划一条合理的路线让他一次性尽可能多地完成取件并返回站点。手动在地图App上一个个添加途经点显然不现实而通过Python调用高德API我们就能自动完成路径计算和可视化甚至能估算出总耗时和里程为排班和考核提供数据支持。接下来我就把这个从零到一的过程包括关键的踩坑点和优化心得完整地分享出来。2. 前期准备高德API密钥与Python环境搭建工欲善其事必先利其器。在开始写代码之前有两项基础工作必须完成获取高德API的访问凭证以及配置好Python开发环境。2.1 申请并配置高德Web服务API密钥高德地图开放平台为开发者提供了丰富的API其中路径规划属于“Web服务”类别。使用任何Web服务API都需要一个名为“Key”的密钥。第一步注册与登录访问高德开放平台官网使用手机号或邮箱完成注册并登录。这个过程比较常规按照指引操作即可。第二步创建新应用登录后进入控制台在“应用管理”页面点击“创建新应用”。应用名称可以填写为“路径规划测试”或你的项目名称应用类型选择“Web服务”。这里有一个关键点即使我们最终用Python脚本调用也因为调用的是HTTP接口所以选择“Web服务”而不是“Android/iOS”等端类型。第三步为应用添加Key应用创建成功后在应用列表中找到它点击右侧的“添加Key”按钮。在弹出的窗口中需要填写以下信息Key名称 自定义如“路径规划Key”。服务平台 务必选择“Web服务”。这是最容易出错的一步选错了会导致后续API调用失败。IP白名单可选 如果你希望限制该Key只能在特定服务器IP上调用可以在此处填写。对于本地测试和学习可以暂时留空但这意味着任何知道该Key的人都可以使用存在被盗用的风险。正式上线前强烈建议配置IP白名单。提交后系统会生成一个一串由字母和数字组成的Key例如3d3c2a1b2c3d4e5f6g7h8i9j0k1l2m。请妥善保管这个Key它相当于访问高德API的密码。注意高德对免费额度有每日调用次数限制。路径规划API个人开发者每日有固定次数的免费调用额度超出后需要付费。开发测试时完全够用但如果是正式业务需要关注调用量并考虑购买商用套餐。2.2 Python环境与必要库安装本项目对Python版本要求不高Python 3.6及以上均可。我们将主要用到以下几个库requests: 用于向高德API发送HTTP请求获取返回的JSON数据。这是最核心的库。json: Python标准库用于解析API返回的JSON格式数据。folium: 一个非常强大的Python地图可视化库基于Leaflet.js。它可以让我们用极简的代码生成交互式HTML地图并轻松地在地图上添加标记、折线等元素。pandas (可选但推荐): 如果途经点数据是存储在Excel或CSV文件中用pandas来读取和处理数据会非常方便。安装这些库非常简单打开你的终端命令行使用pip命令即可pip install requests folium pandas如果你使用PyCharm、VSCode等IDE也可以在集成的终端中执行上述命令。对于VSCode用户确保你的Python解释器已正确配置可以通过点击编辑器右下角选择或通过命令面板CtrlShiftP搜索“Python: Select Interpreter”来设置。环境准备好后我们可以新建一个Python文件例如multi_point_route.py开始正式的编码工作。3. 核心实现分步拆解多路径点规划整个流程可以清晰地分为三个步骤构建API请求、解析返回的路线数据、将路线绘制到地图上。我们一步一步来实现。3.1 构建并发送API请求高德驾车路径规划API支持途经点的端点URL是固定的。我们需要按照其规定的参数格式构造一个HTTP GET请求。首先定义一些基础变量import requests import json # 你的高德API Key your_amap_key ‘你的高德Key这里替换成真实的字符串’ # API基础URL base_url “https://restapi.amap.com/v3/direction/driving” # 定义起点、终点和途经点 # 格式经度纬度。可以从高德地图官网的坐标拾取工具获取。 origin “116.481028,39.989643” # 起点例如北京某点 destination “116.434446,39.90816” # 终点例如北京另一点 waypoints [ “116.4976,39.9851”, # 途经点1 “116.4622,39.9918”, # 途经点2 “116.3574,39.9933” # 途经点3 ]这里有一个关键细节高德API的waypoints参数要求途经点以“经度纬度”的格式组成字符串并且多个点之间用“|”竖线分隔。所以我们需要对列表进行格式化处理。接下来构造参数字典并发送请求# 将途经点列表格式化为API要求的字符串”经度纬度|经度纬度|...” waypoints_str “|”.join(waypoints) # 构造请求参数 params { “key”: your_amap_key, “origin”: origin, “destination”: destination, “waypoints”: waypoints_str, # 这是支持多点的关键参数 “strategy”: “0”, # 策略0-速度优先1-费用优先2-距离优先3-不走高速4-躲避拥堵5-多策略 “extensions”: “all” # 返回结果详略base-基本all-全部包含路径坐标点 } # 发送GET请求 response requests.get(base_url, paramsparams) data response.json() # 将响应解析为JSON即Python字典参数详解与避坑点strategy: 这个参数决定了路径计算的策略。2距离优先并不总是最短的路径因为它可能包含小路。对于城市配送我通常先用0速度优先或4躲避拥堵试算再结合实际路况判断。5多策略会返回多条路径但注意免费版可能不支持。extensions:务必设置为”all”。如果设为”base”返回的数据将不包含具体的路径坐标点列表polyline字段只有总距离和耗时我们就无法在地图上画出路线了。这是新手最容易忽略导致地图空白的一个坑。坐标顺序 高德使用的是GCJ-02坐标系火星坐标系。如果你手中的坐标是WGS-84GPS原始坐标系或BD-09百度坐标系必须先进行坐标转换否则规划出的路线会严重偏移。高德开放平台也提供了坐标转换API。3.2 解析API返回的复杂路线数据请求成功后data变量就是一个包含所有路线信息的字典。它的结构比较复杂我们需要像剥洋葱一样一层层取出所需信息。首先检查请求是否成功if data[“status”] “1” and data[“info”] “OK”: # 请求成功开始解析 route data[“route”] paths route[“paths”] # 通常带途经点的规划只返回一条最优路径 if paths: first_path paths[0] # 提取总距离米和总耗时秒 total_distance first_path[“distance”] # 单位米 total_duration first_path[“duration”] # 单位秒 # 核心提取路径的坐标点序列 # ‘steps’ 是路线的分段信息每个step是一段独立的驾驶指令如“沿XX路向北行驶” steps first_path[“steps”] all_polyline_points [] # 用于存储所有解码后的坐标点 for step in steps: # 每一步的路径坐标都被编码成一个特殊的字符串为了减少数据传输量 polyline step[“polyline”] # 这个字符串需要解码成具体的经纬度列表 decoded_points decode_polyline(polyline) all_polyline_points.extend(decoded_points) # 提取途经点的顺序API返回的‘waypoints’顺序就是实际经过的顺序 waypoint_order [] if “waypoints” in first_path: for wp in first_path[“waypoints”]: waypoint_order.append((wp[“location”], wp[“name”])) else: print(f“请求失败: {data[‘info’]}”) print(data)上面的代码引入了一个关键函数decode_polyline。高德为了压缩数据将连续的经纬度坐标编码成了一个字符串。我们需要将其解码回[(lng1, lat1), (lng2, lat2), …]的格式。以下是该函数的实现def decode_polyline(polyline_str): “”“解码高德API返回的polyline字符串为坐标列表。”“” coordinates [] index 0 length len(polyline_str) lat 0 lng 0 while index length: shift 0 result 0 while True: b ord(polyline_str[index]) - 63 index 1 result | (b 0x1f) shift shift 5 if b 0x20: break dlat ~(result 1) if (result 1) else (result 1) lat dlat shift 0 result 0 while True: b ord(polyline_str[index]) - 63 index 1 result | (b 0x1f) shift shift 5 if b 0x20: break dlng ~(result 1) if (result 1) else (result 1) lng dlng coordinates.append((round(lng * 1e-6, 6), round(lat * 1e-6, 6))) # 顺序为 (经度 纬度) return coordinates这个解码算法是高德/谷歌地图编码标准直接复制使用即可。这里有一个坐标顺序的坑解码后得到的坐标是(经度 纬度)而很多地图库包括我们下面要用的folium的坐标输入顺序是(纬度 经度)。在后续绘制时需要注意转换。3.3 使用Folium绘制交互式路线地图数据解析完成后我们就可以用Folium来生成一个漂亮的HTML地图了。Folium的语法非常直观。import folium # 1. 创建地图底图以起点为中心 # 注意folium的坐标顺序是 [纬度 经度]我们需要将起点坐标反转。 start_lat, start_lng map(float, origin.split(“,”)[::-1]) m folium.Map(location[start_lat, start_lng], zoom_start12) # 2. 将解码后的路径点绘制到地图上折线 # 解码出的 all_polyline_points 是 (经度纬度)需要转换为 (纬度经度) 供folium使用 route_coords [(lat, lng) for lng, lat in all_polyline_points] # 注意这里的反转 folium.PolyLine( route_coords, weight8, # 线宽 color‘blue’, # 线条颜色 opacity0.7, # 透明度 popup‘规划路线’ # 点击线条显示的提示 ).add_to(m) # 3. 标记起点、终点和途经点 # 标记起点绿色 start_name “起点” folium.Marker( location[start_lat, start_lng], popupstart_name, iconfolium.Icon(color‘green’, icon‘play’, prefix‘fa’) # 使用FontAwesome图标 ).add_to(m) # 标记终点红色 end_lat, end_lng map(float, destination.split(“,”)[::-1]) end_name “终点” folium.Marker( location[end_lat, end_lng], popupend_name, iconfolium.Icon(color‘red’, icon‘stop’, prefix‘fa’) ).add_to(m) # 标记途经点蓝色并编号 for i, wp in enumerate(waypoints): wp_lng, wp_lat map(float, wp.split(“,”)) folium.Marker( location[wp_lat, wp_lng], popupf“途经点 {i1}”, iconfolium.Icon(color‘blue’, icon‘flag’, prefix‘fa’) ).add_to(m) # 4. 添加一个浮动窗口显示路线概要信息可选但很实用 distance_km total_distance / 1000 duration_min total_duration / 60 summary_html f“”” div style“position: fixed; bottom: 50px; left: 50px; width: 300px; height: 120px; background-color: white; border:2px solid grey; z-index:9999; font-size:14px; padding:10px; border-radius:5px;” b路线规划摘要/bbr 总距离: b{distance_km:.2f} 公里/bbr 预计耗时: b{duration_min:.1f} 分钟/bbr 途经点数量: b{len(waypoints)} 个/b /div “”” m.get_root().html.add_child(folium.Element(summary_html)) # 5. 保存地图为HTML文件 output_file “multi_point_route.html” m.save(output_file) print(f“地图已生成: {output_file}”)运行完上述代码会在当前目录下生成一个multi_point_route.html文件。用浏览器打开它你就能看到一个交互式地图清晰地展示了从起点出发依次经过所有途经点最终到达终点的完整驾车路线。你可以用鼠标拖拽地图、缩放、点击标记点查看信息。4. 进阶优化与实战中的坑点把基础功能跑通只是第一步。在实际项目应用中我们会遇到更多具体问题。下面分享几个我踩过坑后总结的进阶处理技巧。4.1 途经点顺序优化与“旅行商问题”简化高德API的waypoints参数有一个重要特性它默认按照你传入的顺序依次经过这些点。它不会自动为你优化顺序以求得全局最短路径。这在实际业务中往往是不经济的。例如起点S终点E途经点A B C。如果你传入的顺序是 [A, B, C]那么路线就是 S-A-B-C-E。但如果最优顺序是 S-B-A-C-EAPI不会告诉你。解决方案引入简单启发式算法对于点数不多比如少于10个的情况我们可以实现一个简单的优化算法。一个最直观的“最近邻贪心算法”步骤如下从起点开始将其作为“当前点”。在所有未访问的途经点中找到离“当前点”距离最近的一个点将其作为下一个目标点。将该点标记为已访问并将其设为新的“当前点”。重复步骤2-3直到所有途经点都被访问。最后从最后一个途经点前往终点。我们可以利用高德的距离测量API/v3/distance来批量计算两点间的驾车距离注意不是直线距离作为“成本”依据。下面是一个简化的代码思路def optimize_waypoints_order(origin, waypoints_list, destination, api_key): “”“使用最近邻贪心算法优化途经点顺序。”“” unvisited waypoints_list[:] # 未访问点列表副本 optimized_order [] current_point origin while unvisited: # 调用高德距离测量API计算current_point到所有unvisited点的距离 # 这里需要构造批量请求或循环调用注意API频次限制 # 假设 get_driving_distance(point_a, point_b) 是一个返回驾车距离的函数 distances [] for wp in unvisited: dist get_driving_distance(current_point, wp, api_key) distances.append((dist, wp)) # 找到距离最近的点 _, nearest_point min(distances, keylambda x: x[0]) optimized_order.append(nearest_point) unvisited.remove(nearest_point) current_point nearest_point # 最后加上终点虽然不参与排序但用于计算最后一段成本 return optimized_order重要提醒这个方法得到的不一定是数学上的最优解那是NP-Hard的旅行商问题但在大多数实际物流场景中它能得到一个显著优于乱序的、可接受的“较优解”。如果点数很多15则需要考虑更专业的优化算法或运筹学库如OR-Tools。4.2 处理API限流、异常与数据完整性在自动化脚本中网络请求和外部API调用必须考虑健壮性。1. 请求限流与重试机制高德API有QPS每秒查询率限制。如果短时间内发送大量请求会收到错误码。我们需要在代码中添加重试逻辑和延时。import time from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def safe_amap_request(url, params): “”“带重试机制的请求函数。”“” response requests.get(url, paramsparams, timeout10) response.raise_for_status() # 如果HTTP状态码不是200抛出异常 data response.json() if data.get(“status”) ! “1”: # 业务逻辑失败也可能需要重试这里根据错误码判断 if data.get(“infocode”) in [“10001”, “10002”]: # 例如Key错误重试无意义 raise ValueError(f“API业务错误: {data}”) else: raise Exception(f“可重试的API错误: {data}”) # 触发重试 return data这里使用了tenacity库来实现优雅的重试。你也可以用简单的try-except和time.sleep实现。2. 数据解析的防御性编程API返回的数据结构可能因为参数不同而变化。在访问深层字典键值时使用.get()方法并提供默认值可以避免KeyError导致程序崩溃。# 不安全的写法 polyline step[“polyline”] # 如果step中没有”polyline”键程序崩溃 # 安全的写法 polyline step.get(“polyline”) if not polyline: print(“警告当前step未找到polyline数据跳过”) continue3. 坐标漂移与纠偏如果你发现画出来的路线和标记点在地图底图上存在几十到几百米的偏移那几乎可以肯定是坐标系不匹配。Folium的底图默认是WGS-84坐标。而高德返回的polyline和location是GCJ-02坐标。虽然在小比例尺下看起来还行但在大比例尺高缩放级别下偏差明显。解决方案在将坐标传递给Folium之前先进行坐标系转换。你可以使用专业的库如coord-convert或transbigdata或者调用高德提供的坐标转换API/v3/assistant/coordinate/convert进行批量转换。这是一个影响成果准确性的关键步骤在正式项目中不容忽视。4.3 性能提升异步请求与批量处理当需要为成百上千个订单即成千上万个途经点组合规划路线时串行调用API会非常慢。此时可以考虑异步IO。使用aiohttp进行异步请求import aiohttp import asyncio async def fetch_route(session, params): async with session.get(base_url, paramsparams) as response: return await response.json() async def plan_routes_for_multiple_tasks(list_of_param_sets): async with aiohttp.ClientSession() as session: tasks [] for params in list_of_param_sets: task asyncio.create_task(fetch_route(session, params)) tasks.append(task) results await asyncio.gather(*tasks, return_exceptionsTrue) # 处理results包含所有规划结果 return results # 在主函数中运行 loop asyncio.get_event_loop() all_route_results loop.run_until_complete(plan_routes_for_multiple_tasks(your_param_list))这样做可以将数十秒的等待时间压缩到几秒内。但务必注意高德API有严格的QPS限制异步请求虽然快但瞬间并发过高会导致请求被拒绝。需要在代码中通过信号量Semaphore来控制最大并发数将其限制在高德允许的QPS之下例如免费版可能为10 QPS。semaphore asyncio.Semaphore(5) # 限制最大并发数为5 async def fetch_route_with_limit(session, params): async with semaphore: # 控制并发 await asyncio.sleep(0.1) # 每个请求之间再增加一点间隔 return await fetch_route(session, params)5. 项目集成与输出定制化将上述功能模块化集成到更大的系统中并根据业务需求定制输出是体现其价值的最终环节。5.1 封装为可复用的Python类或函数一个好的实践是将整个路径规划与绘图流程封装成一个类。这样不仅代码清晰也便于在其他项目中调用。class AmapRoutePlanner: def __init__(self, api_key): self.api_key api_key self.base_url “https://restapi.amap.com/v3/direction/driving” def plan_route(self, origin, destination, waypoints, strategy0): # 封装请求、解析、解码polyline的逻辑 # … return { “points”: all_polyline_points, # 解码后的路径点 “distance”: total_distance, “duration”: total_duration, “waypoint_order”: waypoint_order } def plot_route_to_html(self, route_result, output_path“route.html”): # 封装使用Folium绘图并保存的逻辑 # … return output_path # 使用示例 planner AmapRoutePlanner(your_amap_key) result planner.plan_route(origin, destination, waypoints) html_file planner.plot_route_to_html(result, “delivery_route_001.html”)5.2 生成带业务信息的定制化地图报告单纯的地图线条可能不够。我们可以在地图上集成更多业务信息。信息弹窗 除了popup可以使用folium.Popup创建更复杂的HTML内容显示客户姓名、电话、预计到达时间ETA。不同颜色的标记 根据站点的状态如已签收、待取件、异常使用不同颜色的图标。添加圆形区域 使用folium.Circle标记服务范围或禁行区域。生成静态图片 如果需要将地图插入报告或邮件可以使用selenium或playwright无头浏览器打开HTML文件并截图。虽然Folium本身不直接支持保存为图片但这是可行的绕行方案。5.3 与数据处理管道结合Pandas示例在实际业务中途经点信息很可能来自数据库或Excel表格。Pandas是处理这类数据的利器。import pandas as pd # 假设有一个CSV文件 orders.csv包含站点信息 df pd.read_csv(“orders.csv”) # 列包括order_id, customer_name, lng, lat, priority # 将DataFrame中的坐标转换为API需要的格式 waypoints_from_df df.apply(lambda row: f“{row[‘lng’]},{row[‘lat’]}”, axis1).tolist() # 根据优先级字段排序简单的业务逻辑 df_sorted df.sort_values(by“priority”, ascendingFalse) optimized_waypoints df_sorted.apply(lambda row: f“{row[‘lng’]},{row[‘lat’]}”, axis1).tolist() # 调用我们的规划器 result planner.plan_route(warehouse_location, return_depot_location, optimized_waypoints) # 在地图标记中显示订单ID和客户名 for _, row in df_sorted.iterrows(): folium.Marker( location[row[‘lat’], row[‘lng’]], popupf“订单: {row[‘order_id’]}br客户: {row[‘customer_name’]}”, iconfolium.Icon(color‘orange’) ).add_to(m)通过这样的结合我们就实现了一个从原始业务数据到可视化配送路线的完整自动化流程。这个流程可以集成到定时任务中每天自动生成配送员的路线图极大地提升了调度效率。