ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

App内百度/高德地图路径规划实战:SDK接入、坐标转换与避坑指南

App内百度/高德地图路径规划实战:SDK接入、坐标转换与避坑指南 直接讲结论想在 App 里做路线规划绕不开百度地图和高德地图这两家。我前前后后接了不少带地图的项目从商家配送调度到户外运动记录无一例外都要在 App 内实现“输入起终点、展示路线、调用导航”这条链路。这篇文章不聊虚的把我在实际项目里接入百度、高德地图 SDK、实现路径规划的全过程拆开讲包括方案选型、坐标系差异、代码怎么写、参数怎么调、跳转导航怎么处理以及那些文档里看不到的坑。内容偏 Android 实战iOS 和小程序的关键差异我也会顺带提适合正在做地图功能的客户端开发、跨端开发以及准备做同城配送、运动健身、户外导航类 App 的技术负责人参考。1. 项目概述与技术背景1.1 这个项目到底要解决什么问题需求本身听起来不复杂用户打开 App输入或选择目的地App 里直接展示一条完整路线点击“开始导航”可以跳到百度或高德地图 App或者直接在页面内加载导航视图。但真正落到工程上会发现这个需求背后藏着几个独立的技术环节地理编码把文字地址转成经纬度坐标比如“北京西站”转成北纬39.894、东经116.321定位拿到用户当前经纬度或者从业务服务器下发起点坐标路径规划根据起终点坐标向地图服务商请求一条或多条可行路线返回的数据包括路线距离、预计耗时、各个路段的具体坐标点串路线渲染把返回的坐标点串连成一条折线叠加到地图上导航把路线结果传给导航模块或直接调起外部地图 App。路径规划是整个链路里业务价值最高的部分。地图展示解决的是“我在哪”和“周围有什么”路径规划解决的是“我怎么过去”这个直接产生行动的问题。对这个功能来说选哪家地图服务商直接决定了路线的合理性、接口的稳定性和后续的维护成本。1.2 为什么百度、高德是绕不开的选择国内地图服务的可选面其实很窄主流就是百度和高德两家。腾讯地图偶尔会在小程序场景里出现但完整度和开发者生态跟这两家差距明显单纯做步行导航都费劲。我从 2017 年开始做地图相关项目最深的感受是百度地图和高德地图功能覆盖上是高度重合的定位、逆地理编码、POI 搜索、路径规划、导航、地图样式定制这些能力两边都有。真正的差异集中在三点定位引擎不同。高德用的是高德自己的定位 SDK底层结合 GPS、Wi-Fi、基站和陀螺仪数据做融合定位百度用的是百度定位 SDK同样多源融合。实际精度差别不大室外一般都能做到 10 米内但在室内、高楼遮挡、隧道等场景两家表现会有起伏不能一概而论。坐标系不同。高德使用 GCJ-02 坐标系火星坐标百度在 GCJ-02 基础上又做了二次加密偏移形成 BD-09 坐标系。这两者不能混用同一个经纬度直接拿去另一家地图显示会出现几十到几百米的偏移。开发者接入方式有细微差别。SDK 接口设计、文档完善程度、收费政策、技术支持响应速度都有差异。早期百度地图 SDK 集成包更大接口更多高德后来居上文档更清晰社区解法更多所以我个人在高德上的踩坑经验也更多一些。如果你的 App 只需要在国内做路径规划做方案对比的重点不是“哪家地图底图更好看”而是“哪家的接口跟你的业务更匹配”。这个后面展开讲。1.3 适用场景与目标用户从我的项目经历来看路径规划这个能力会出现在这些地方同城配送、跑腿类 App骑手端需要从接单点到用户位置的路线展示距离和预计送达时间点击按钮直接拉起导航运动健身类 App跑步、骑行路线记录运动前规划一条路线运动中实时导航引导户外旅行、自驾游类 App多途经点路线规划一键生成行程企业级位置服务巡检、销售门店拜访、工程车辆调度等场景通常需要把路径规划和业务系统打通批量计算距离和耗时微信小程序或 H5 内嵌页轻量场景下不接全套 SDK直接调起外部地图 App 或使用 Web API。如果你只是想在 H5 或小程序里做个简单的“查看路线”按钮完全没必要集成全套 SDK用系统级的 URI Scheme 调起外部地图 App 就够了但如果你要深度定制路线样式、交互方式、路况图层那 SDK 集成是唯一选择。这个选型逻辑后面会有专门一节。2. 百度与高德地图的方案对比与选型逻辑2.1 坐标系差异是第一个必须搞清楚的坑我见过太多项目挂在坐标系上。刚入行的同事经常拿着从高德定位 SDK 拿到的经纬度直接丢给百度地图 SDK 去画路线结果路线起点偏出去好几十米怎么也找不出原因。国内地图的坐标系政策比较复杂简单说就是WGS-84GPS 原始坐标国际通用但国内地图不允许直接使用GCJ-02国测局加密坐标也称火星坐标高德和腾讯地图使用这套BD-09百度在 GCJ-02 基础上再次偏移仅百度地图使用。高德开放平台的文档里明确写着他们对外提供的坐标系统是 GCJ-02。百度地图对外则是 BD-09。两个坐标系之间的换算不是简单的加减百度官方提供了一套坐标转换 API也有写死的转换算法网上流传着各种版本的转换代码精度参差不齐。我在工程里的原则是只信任官方转换接口不自己造轮子。如果你需要同时接入两家建议在后端做一层坐标统一比如统一存储 GCJ-02调百度接口时再转 BD-09。这样前端拿到的坐标始终一致业务逻辑不会被套餐坐标系搞乱。2.2 API 设计、文档质量与社区生态对比高德的接口风格更接近现代 REST 风格命名清晰回调结果统一走 ResultCallback 或监听器百度早期的 SDK 接口命名偏传统部分接口返回 JSON 字符串需要自己解析。文档方面高德的接入指南、API 参考、示例代码做得相对完善每个接口都有在线运行示例排查问题效率高很多。社区活跃度也是重要参考。我在实际开发中搜索“高德地图 路径规划”相关的问题基本都能在 Stack Overflow、思否、掘金上找到解法百度地图的问题往往要翻到百度地图开发者社区或者很久远的博客文章。当然这不是绝对的只是我个人的体感仅供参考。2.3 收费政策与免费额度别让“免费”带偏了前几年地图服务商的收费政策普遍宽松个人开发者和小企业基本感觉不到收费压力。但最近两年随着成本压力加大两家都陆续调整了收费规则。高德的“API 收费坑人”这类吐槽主要集中在这几个地方免费额度只覆盖基础调用量超过后按梯度计费部分高级能力如导航、路径规划的高并发单独计费企业级应用要求企业认证并签署商业合同线上突发流量导致调用量暴涨费用随之飙升。我的做法是不管选哪家先在控制台看清楚免费额度和计费规则然后在后端接口做流控和缓存。路径规划结果在短时间内比如 5 分钟对同一 OD 对起终点对是基本不变的完全可以用 Redis 缓存兜底既减少调用量又降低延迟还能保护额度。2.4 我的选型建议如果 App 定位全国市场且需要深度定制地图样式、路线交互我建议把高德作为首选同时保留百度的切换能力。理由有三个高德文档更清晰社区解法更多团队上手成本低高德的 URI 调起兼容性更好跳转外部导航时不易踩系统的坑高德的路径规划策略对步行、骑行等轻出行场景更友好路线更贴近真实路径。如果是纯导航场景比如货运、网约车百度地图在部分城市的路况数据有优势这时候可以优先考虑百度。但做技术选型不要只看地图商还要看你自己 App 的核心场景是谁在什么场景下使用这条路线。核心场景确定了API 选型才有判断依据。3. 接入前准备与环境配置3.1 注册开发者账号、创建应用、申请 API Key这一步没什么技术含量但很多人会在这里磨蹭好久。以高德为例打开高德开放平台注册账号并完成实名认证进入控制台创建应用为应用添加“Key”平台要求填写应用名称、类型Android 或 iOS、安全码。Android 平台会要求填写发布版安全码SHA1和调试版安全码把生成的 Key 复制到工程配置文件里。需要注意高德的 Key 和包名、SHA1 是绑定的。调试用 debug.keystore 生成的 SHA1 和上线用 release keystore 生成的 SHA1 不一样如果你只在调试环境测试线上却用另签名的包去调接口会直接鉴权失败。解决方法是把调试版和发布版的 SHA1 都配置到同一 Key 下或者为不同环境申请独立 Key。百度地图的流程类似也是在百度地图开放平台创建应用填写应用包名和发布版 SHA1拿到 AccessKey。百度的 Key 绑定的是包名不直接绑定签名但这里有个坑晚传的包名和实际包名不一致也会鉴权失败。3.2 Android 端依赖引入与基础配置我的项目大多基于 Android Studio 和 Gradle。以高德地图 SDK 为例最新版本的接入方式已经支持通过 Maven 仓库引入在项目根 build.gradle 里配置仓库allprojects { repositories { maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/gradle-plugin } google() mavenCentral() maven { url https://maven.aliyun.com/repository/jcenter } } }然后在模块级 build.gradle 里添加依赖dependencies { implementation com.amap.api:3dmap:9.8.0 // 3D地图 implementation com.amap.api:search:9.7.0 // 搜索与路径规划 implementation com.amap.api:location:6.4.0 // 定位 }接着在 AndroidManifest.xml 里配置权限和应用 Key!-- 网络权限 -- uses-permission android:nameandroid.permission.INTERNET / !-- 定位权限 -- uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / !-- 读取Wi-Fi状态用于辅助定位 -- uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / uses-permission android:nameandroid.permission.CHANGE_WIFI_STATE / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE /然后在 Application 的 onCreate 里初始化public class MapApp extends Application { Override public void onCreate() { super.onCreate(); // 高德地图 SDK 初始化参数为 Android 应用包名对应的 Key AMapLocationClient.updatePrivacyShow(getApplicationContext(), true, true); AMapLocationClient.updatePrivacyAgree(getApplicationContext(), true); // 高德3D地图SDK初始化 MapView mapView new MapView(this); } }高德从某个版本开始要求在初始化前处理隐私协议回调不处理的话定位和地图功能会被限制。我第一次接新版 SDK 时被这个搞得莫名其妙后来才反应过来是隐私合规在起作用。百度地图的依赖配置类似dependencies { implementation com.baidu.lbsyun:BaiduMapSDK_Map:7.5.7 implementation com.baidu.lbsyun:BaiduMapSDK_Search:7.5.7 }初始化时在 Application 的 onCreate 里调用SDKInitializer.initialize(Context)。3.3 iOS 端的关键配置简述iOS 接入要高很多权限核心配置就两件Info.plist 里配置定位权限描述keyNSLocationWhenInUseUsageDescription/key string需要定位权限以获取当前位置/string keyNSLocationAlwaysAndWhenInUseUsageDescription/key string需要后台定位以持续记录您的运动轨迹/string注册高德 AppKey 时填写 iOS Bundle Identifier并在 AppDelegate 里调用AMapServices.shared().apiKey 你的Key。iOS 端的道路规划 API 和 Android 端的设计语言不太一样高德 iOS SDK 使用 block 回调百度 iOS SDK 使用 delegate。两者思路一致参数名也基本对应从 Android 转 iOS 的同学注意下异步回调方式即可。4. 两种接入方式SDK 集成与 URI 调起4.1 为什么需要两种方式我发现很多产品经理和非技术同学有个误区路径规划就只能在 App 内嵌地图 SDK。实际上最简单的路径规划方案可能一行代码都不用写。路径规划的实现路径有两条集成地图 SDK在 App 内加载完整地图视图调用路径规划接口路线直接绘制在地图上同时可以深度定制交互比如点击路线、切换路线、展示路况。调起外部地图 App通过 URL Scheme 或 Universal Link 直接打开百度/高德地图 App传入起终点坐标和策略参数由外部 App 完成路径规划和导航展示。什么时候用 SDK什么时候用外部调起我的判断标准很简单如果你的 App 核心流程里用户需要在地图上持续停留、多次交互、甚至预览多条路线就老老实实接 SDK如果你只需要一个“到这去”的入口按钮点完跳转到地图 App 就好完全没必要背 SDK 的包体和维护成本。举个实际例子我在一个跑步类 App 项目里需要在运动前让用户选择一条跑步路线并在 App 内实时显示当前位置和剩余距离。这个必须用 SDK 集成因为用户要长时间盯着 App 地图路线的展示风格也要跟整个 App 的视觉语言统一。另一个外卖项目里用户根本不需要在地图页面停留点“导航”按钮直接拉起外部地图就行集成 SDK 反而是浪费。4.2 URI 调起外部地图的具体实现这是最轻量的路径规划方案强烈建议每个地图相关项目都顺手把这两个调起逻辑封装好业务里随时能用。以 Android 为例调起高德地图 App 进行驾车路径规划public static void startNavByAmap(Context context, LatLng endLatLng, String endName) { try { // 构建调起高德地图的 uri具体参数参考高德开放平台调起地图文档 StringBuilder uri new StringBuilder(androidamap://navi?sourceApplicationmyapp); // 终点坐标和名称 uri.append(lat).append(endLatLng.latitude); uri.append(lon).append(endLatLng.longitude); uri.append(name).append(endName); // 导航方式0 速度快1 费用少2 路程短3 不走高速4 躲避拥堵5 不走高速且躲避拥堵 uri.append(dev0); uri.append(style2); Intent intent new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(uri.toString())); context.startActivity(intent); } catch (Exception e) { // 未安装高德地图拉取应用市场下载页 Toast.makeText(context, 未安装高德地图, Toast.LENGTH_SHORT).show(); openMarket(context, com.autonavi.minimap); } }调起百度地图进行路径规划public static void startNavByBaidu(Context context, LatLng endLatLng, String endName) { try { // 百度的URI scheme需要先通过百度地图API校验和转换坐标 StringBuilder uri new StringBuilder(baidumap://map/direction); uri.append(?destination).append(endName); // 终点坐标百度使用BD-09坐标 uri.append(coord_typebd09ll); uri.append(lat).append(endLatLng.latitude); uri.append(lng).append(endLatLng.longitude); uri.append(modedriving); Intent intent new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(uri.toString())); context.startActivity(intent); } catch (Exception e) { Toast.makeText(context, 未安装百度地图, Toast.LENGTH_SHORT).show(); openMarket(context, com.baidu.BaiduMap); } }几个细节说明一下高德的androidamap://navi默认跳转到导航界面不是路径规划预览页如果只是展示路线不带导航用androidamap://route参数类似百度地图调起前需要把一个可选的origin参数带上或者不带地图 App 会默认从当前位置出发打开外部地图前先检查应用是否安装没装的话要引导去应用市场不然 startActivity 会抛 ActivityNotFoundException百度地图调起参数里的coord_type必须写对否则终点坐标跑到海里去。iOS 端调起逻辑类似只是 scheme 不同高德 iOS 的 scheme 也是iosamap://path百度baidumap://map/direction。4.3 与微信小程序跳转的配合热搜词里有一条“从微信小程序跳转到高德app”这是很多业务方的真实需求。小程序的路径规划通常有两种做法使用微信小程序的地图组件map配合腾讯位置服务或高德地图的 Web API 在服务器端完成路径规划再把路线坐标点传给前端渲染。这种方案的定位和路线展示都在小程序内完成但导航还是得通过外部 App 或者腾讯地图进行。使用wx.openLocation打开地图无法指定具体导航 App。如果你一定要从小程序拉起高德地图 App 进行导航常见做法是用web-view组件加载高德地图调起链接或者通过 h5 页面中转后调起 Scheme。需要注意小程序对 URL Scheme 白名单管控较严实际调起成功率和用户当前使用的微信版本、系统权限都有关系建议在小程序之外保留一个“复制地址到系统浏览器打开”的降级方案。这个点在我们做企业微信集成的项目里踩过不少坑后面在常见问题部分详细展开。5. 路径规划的完整实操以高德地图为例5.1 场景设定配送员从 A 到 B我拿一个典型的同城配送场景来走完整流程因为这类业务最依赖路径规划而且对路线质量要求高。业务背景用户在 B 点下单配送员在 A 点接单系统需要给配送员展示从 A 到 B 的驾车路线并在 App 内完成路线绘制配送员点击按钮后跳转导航。这个场景下的路径规划有几个特征起点通常是定位当前坐标终点是业务服务器下发的订单地址坐标需要一个“距离”、“预计时间”的展示方便配送员判断这单值不值得接需要支持路线预览配送员可能想看一眼路线走向需要一键跳到外部导航因为完整的语音引导、实时路况偏离重算自研成本极高。下面从代码层面完整实现。5.2 初始化地图与定位先声明地图控件com.amap.api.maps.MapView android:idid/map_view android:layout_widthmatch_parent android:layout_heightmatch_parent /在 Activity/Fragment 中管理地图生命周期public class RouteActivity extends AppCompatActivity { private MapView mapView; private AMap aMap; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_route); mapView findViewById(R.id.map_view); mapView.onCreate(savedInstanceState); if (aMap null) { aMap mapView.getMap(); } // 开启室内地图室内外一体化展示 aMap.showIndoorMap(true); // 开启实时路况图层路线规划时可以叠加路况数据 aMap.setTrafficEnabled(true); // 设置地图缩放级别配送场景下让用户能看到周边环境 aMap.moveCamera(CameraUpdateFactory.zoomTo(14f)); } Override protected void onResume() { super.onResume(); mapView.onResume(); } Override protected void onPause() { super.onPause(); mapView.onPause(); } Override protected void onDestroy() { super.onDestroy(); mapView.onDestroy(); } }这一步没什么花活但要记住地图控件的生命周期必须和 Activity 生命周期绑定否则旋转屏幕或切换到后台再回来地图会白屏、闪退这是高德地图最常见的问题之一。定位部分我通常直接用高德定位 SDK拿到一次当前位置后作为路径规划的起点public class LocationHelper { private AMapLocationClient locationClient null; public void startLocate(Context context, LocationCallback callback) { try { AMapLocationClientOption option new AMapLocationClientOption(); // 高精度定位模式GPS和网络定位结合 option.setLocationMode(AMapLocationClientOption.AMapLocationMode.High_Accuracy); // 单次定位 option.setOnceLocation(true); // 设置定位超时时间单位毫秒 option.setHttpTimeOut(10000); locationClient new AMapLocationClient(context); locationClient.setLocationOption(option); locationClient.setLocationListener(aMapLocation - { if (aMapLocation ! null aMapLocation.getErrorCode() 0) { double lat aMapLocation.getLatitude(); double lng aMapLocation.getLongitude(); callback.onSuccess(lat, lng); } else { callback.onFail(aMapLocation ! null ? aMapLocation.getErrorInfo() : 未知错误); } }); locationClient.startLocation(); } catch (Exception e) { e.printStackTrace(); } } }注意高德单次定位回调是异步的建议在页面加载时就开始定位等用户点击“开始规划”时直接使用缓存的位置避免用户等待定位造成体验割裂。5.3 发起路径规划请求并处理回调高德路径规划接口的核心是RouteSearch构造RouteSearch.FromAndTo起终点对象然后根据出行方式选择不同的请求类。public class RoutePlanner { private RouteSearch routeSearch; private AMap aMap; public interface RouteCallback { void onRouteResult(ListRouteResult routes); void onRouteError(String message); } public void searchDrivingRoute(LatLng start, LatLng end, ListLatLng wayPoints, RouteCallback callback, Context context) { routeSearch new RouteSearch(context); routeSearch.setRouteSearchListener(new RouteSearch.OnRouteSearchListener() { Override public void onBusRouteSearched(BusRouteResult busRouteResult, int code) { // 公交路线结果回调 } Override public void onDriveRouteSearched(DriveRouteResult driveRouteResult, int code) { if (code 1000 driveRouteResult ! null) { ListDrivePath paths driveRouteResult.getPaths(); ListRouteResult resultList new ArrayList(); for (DrivePath path : paths) { RouteResult result new RouteResult(); result.distanceMeters path.getDistance(); result.durationSeconds path.getDuration(); // 获取该路径的坐标点串用于绘制路线 ListLatLng points new ArrayList(); for (DriveStep step : path.getSteps()) { points.addAll(step.getPolyline()); } result.polyline points; resultList.add(result); } callback.onRouteResult(resultList); } else { callback.onRouteError(高德驾车路线规划失败errorCode code); } } Override public void onWalkRouteSearched(WalkRouteResult walkRouteResult, int code) { // 步行路线结果回调 } Override public void onRideRouteSearched(RideRouteResult rideRouteResult, int code) { // 骑行路线结果回调 } }); // 构造起终点 RouteSearch.FromAndTo fromAndTo new RouteSearch.FromAndTo( new LatLonPoint(start.latitude, start.longitude), new LatLonPoint(end.latitude, end.longitude)); // 合并途经点途经点下限0上限16 if (wayPoints ! null !wayPoints.isEmpty()) { ListLatLonPoint way new ArrayList(); for (LatLng latLng : wayPoints) { way.add(new LatLonPoint(latLng.latitude, latLng.longitude)); } fromAndTo.setPassedByPoints(way); } // 驾车路线规划请求 RouteSearch.DriveRouteQuery query new RouteSearch.DriveRouteQuery( fromAndTo, RouteSearch.DrivingDefault, // 默认策略综合考虑距离和速度 null, null, null); routeSearch.calculateDriveRouteAsyn(query); } }回调里的code是一个很关键的字段。高德 Web 服务返回的 code 体系里10000 表示成功1000 是接口返回格式里的状态码高德 SDK 里成功通常返回 1000。但不同接口的成功码可能不一样建议每个接口都去官方文档里确认一次别靠记忆写死。5.4 在地图上绘制路线与起终点标记路线回调完成后把 Polyline 绘制到地图上public class RouteRenderer { public static void drawRoute(AMap aMap, ListLatLng polyline) { if (polyline null || polyline.size() 2) { return; } // 构建折线对象设置宽度和颜色 PolylineOptions options new PolylineOptions() .addAll(polyline) .width(12f) .color(Color.parseColor(#1A73E8)) .setCustomTexture(BitmapDescriptorFactory.fromResource(R.drawable.route_line_texture)); // 清除旧路线 aMap.clear(); // 添加起终点标记 aMap.addMarker(new MarkerOptions() .position(polyline.get(0)) .title(起点) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN))); aMap.addMarker(new MarkerOptions() .position(polyline.get(polyline.size() - 1)) .title(终点) .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED))); // 绘制路线 aMap.addPolyline(options); // 自适应缩放让整条路线完整展示在地图上 LatLngBounds.Builder boundsBuilder new LatLngBounds.Builder(); for (LatLng point : polyline) { boundsBuilder.include(point); } aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 100)); } }这里有两个实操要点aMap.clear()会把之前添加的 Marker、Polyline、GroundOverlay 等全部清掉。如果你的页面上有其他业务图层不要直接 clear而是单独保存路线 Polyline 对象在需要更新时 remove 指定对象路线自适应缩放时newLatLngBounds的 padding 值不要设 0否则边界上的点会紧贴屏幕边缘视觉上很挤一般给 80~120dp 的 padding 比较合理。5.5 百度地图 SDK 的差异实现百度地图路径规划的核心类是RoutePlanSearch接口和高德非常相似但有几个常见差异百度用OnGetRoutePlanResultListener监听结果驾车路线回调是onGetDrivingRouteResult百度路径规划需要先设置searchType和起终点PlanNode并通过DrivingRoutePlanOption传入策略百度返回的路线点串是通过routeLine.getAllStep()逐个 step 拿 polyline 拼出来的验证下来有一些 step 的 polyline 是空的需要做一级空判断百度的路线策略枚举和高德不同DrivingRoutePlanOption.DrivingPolicy.ECAR_DIS_FIRST表示距离优先。我用下来最大感受是百度接口命名偏老派但功能都齐全只要耐心看文档就能接上。唯一要特别留意的就是坐标转换百度路线点串返回的是 BD-09 坐标如果地图控件用的高德一定要先做一次转换再绘制。5.6 多路线展示与用户选择同一个起终点高德驾车规划默认会返回最多 4 条备选路线。做配送和出行产品时我强烈建议把多路线展示做出来因为不同路线的时间、距离差异会影响用户决策。多路线展示的本质是维护一个“当前选中的路线索引”点击路线或点击底部的路线卡片时更新选中状态并重新绘制 Polyline。关键逻辑是选中的路线用高亮颜色、宽线条绘制未选中的路线用灰色半透明线条绘制同时在地图底部显示当前选中路线的距离和预计耗时。高德的DriveRouteResult里返回的每条DrivePath都带有getTolls()过路费字段和getRestriction()限行提示这些在网约车、货运场景里很有用可以在路线卡片上直接展示。我第一次做配送端时忽略了过路费字段后来配送员总抱怨实际成本和预估不一致才想起来路径规划结果里本来就有这个数据。6. 参数调优与业务场景适配6.1 路线策略参数速度优先还是距离优先高德驾车路线规划支持多种策略常见的几个DrivingDefault综合策略系统自动平衡距离和耗时DrivingSaveMoney避免收费路段适合货车和顺风车DrivingShortDistance距离优先路线可能绕小路DrivingNoHighWay不走高速适合本地短途配送DrivingAvoidCongestion躲避拥堵适合时效要求高的订单DrivingNoHighWayAvoidCongestion不走高速且躲避拥堵DrivingNoHighWayAvoidCongestionSaveMoney三者同时。百度地图的策略枚举基本对齐只是命名不同。实际项目中不要只看“默认策略”要结合业务场景定制。做外卖配送的午高峰和晚高峰要开启躲避拥堵做货运的要开启不走高速或避免过路费做代驾的要考虑路线长度而不是时间。策略参数的效果可以直接在回调里看数据对比同样的 OD 对DrivingShortDistance返回的距离可能缩短 10%~20%但耗时会明显拉长DrivingAvoidCongestion返回的耗时预估更接近真实但距离可能变长。我一般会在后台做 A/B 对比用历史订单的回传耗时校准策略选择。6.2 多途经点的处理不只是多传几个坐标那么简单支持途经点是一个容易想简单但实际上细节很多的功能。高德驾车规划的途经点上限是 16 个百度也类似。我的项目里做过一个“多点巡检”功能一次路线要经过 5 个门店实测下来有几个问题途经点顺序会影响路线规划结果。同一个 OD 对即使相同的途经点集合顺序不同总距离和耗时差异很大途经点必须在地理位置上合理如果途经点绕路太远服务商会直接返回“途经点无法到达”或只规划一段路线多个途经点的路线在回调里也是一个DrivePath但该DrivePath内部的 steps 会被拆分成多个 segment绘制路线时要把所有 segment 的坐标点串拼起来否则路线中间会出现断点。我做多点路线规划时会在前端先把途经点按业务顺序排好并在界面上提供拖拽排序让用户自己决定访问顺序。系统层面则把“途经点顺序”和“预计耗时”一起传给用户方便对比调整。6.3 路况图层与红绿灯信息热搜词里出现“高德地图红绿灯算法”这其实是高德在地图数据和导航引擎里做的一个增强能力在路线规划、导航过程中高德会在部分城市展示红绿灯倒计时辅助司机判断路口等待时间。对开发者而言这个能力不需要额外接入只要在导航 SDK 里开启相关配置就能在导航页面看到。但如果你是在自绘路线的地图页面上想自己做一个“红绿灯等待时间”提示暂时拿不到官方数据源——这是高德内部的算法产出不对外提供完整接口。我在一个项目里尝试过用电摩、手机传感器采集等待时间数据来建立算法模型但最终因为数据量不足、路况变化太快放弃了。现阶段最靠谱的办法是把路线规划和导航交给地图 SDK红绿灯信息在导航阶段自然展示不必重复造轮子。路况图层是可以在自绘地图上开启的高德和百度都提供实时路况图层接口。但要注意路况图层的刷新有延迟且对短途路线影响不大长途路线显示路况后用户会倾向于选择躲避拥堵的策略体验会更好。6.4 步行、骑行、公交策略的适配细节路径规划并不只属于驾车场景。跑步、骑行类 App 用的就是步行和骑行路线接口。骑行路线规划的关键参数是骑行速度假设高德内部对骑行速度有自己的假设不同道路条件下会浮动。实测下来高德的骑行规划更推荐走城市绿道和公园道路路线更符合骑行者的实际需求。步行路线规划则有几个特殊点高德步行路线会结合天桥、地下通道、人行横道等设施优先规划步行友好的路径步行路线通常不受交通管制影响但会考虑台阶和通行性如果业务需要一段“步行公交”的混合路线需要分别调步行和公交接口再自己拼接展示。公交路线规划是所有模式里最复杂的。高德返回的BusRouteResult里有换乘方案、步行路段、公交线路名称、发车时间、末班车时间等数据结构比驾车深好几层。如果是做公交类产品建议直接使用高德的完整公交车方案数据结构不要自己简化因为你不知道产品后面会不会要求展示“下一班车还有几分钟”。6.5 动态避障与路径重算的工程启示热搜词里有一条“动态避障小车路径规划”这更多是机器人、自动驾驶领域的内容但做 App 路线规划时也有类似概念路线规划完成后用户偏离路线时如何重新规划。地图 SDK 本身不负责重算重算逻辑在导航引擎里。高德导航 SDK 在用户偏航时会自动重新规划前 3 秒内出来新路线这个能力做得比较成熟。百度导航同样支持自动偏航重算。但如果你是自己画线的非导航场景偏航检测就得自己写。我的做法是每隔 3~5 秒取一次定位点计算到路线 Polyline 的垂直距离如果超过 30 米且持续 8 秒以上就认为用户偏航重新调用路径规划接口。计算点到线段距离的算法网上有很多但要注意坐标系一致性别拿 GCJ-02 的坐标和 BD-09 的路线点串直接算否则距离全错。7. 常见问题与排查技巧实录7.1 定位失败或定位偏差巨大排查顺序一般是权限 → 网络 → 芯片 → 高楼遮挡 → Key。先确认 AndroidManifest 里定位权限是否配置且运行时动态权限是否已授予。Android 6.0 以后只写 Manifest 权限是不够的必须在代码里动态申请确认手机没开省电模式省电模式会限制 GPS 和网络定位频率导致定位超时在高楼林立的市中心GPS 信号不稳定定位 SDK 会退回基站和 Wi-Fi 定位精度下降是正常的如果定位结果偶尔漂移上百米检查是否没有执行aMap.moveCamera()或没有设置setMyLocationEnabled(true)有时候地图控件显示的当前位置和实际定位点不是同一个来源。高德定位有AMapLocation.getErrorCode()每个错误码都有对应含义开发期建议把 errorCode 和 errorInfo 都打出来非常省事。7.2 路线规划请求成功了但没有返回路线这个问题的出现概率极高原因通常是下面这几种API Key 不正确或者 Key 绑定的包名、SHA1 和当前环境不一致。这种情况会报key 鉴权失败或返回 code 2001/2002 等鉴权错误码起终点坐标为空或超出范围。高德对国内坐标有范围校验如果你传入境外坐标或者 0,0 这样的占位符会直接返回失败策略参数和途经点冲突。例如同时设置“不走高速”但途经点本身在高速服务区服务商会认为无法到达起始点或终点恰好落在禁行区域比如步行规划请求起终点在封闭路段服务商会返回无路线。排查时最好的办法是先不传策略和途经点用最简单的起终点试一次如果通了再逐步加上参数找到是哪个参数影响。这个“二分排除法”在地图类接口调试里非常有效。7.3 路线画出来偏移到奇怪的位置八成是坐标系问题。确认三个点定位 SDK 返回的坐标系高德定位返回 GCJ-02百度定位返回 BD-09路径规划接口使用的输入坐标系高德要求 GCJ-02百度要求 BD-09地图控件底层使用的坐标系高德控件显示 GCJ-02百度控件显示 BD-09。如果定位用了高德但路线规划用的百度那起点坐标要先用百度提供的坐标转换 API 转到 BD-09。反之亦然。最容易出问题的是从第三方数据源比如自己服务器的 GPS 坐标直接拿来用这种 WGS-84 坐标必须做一层转换再丢给地图否则偏移 100~300 米是常态。7.4 Android 混淆导致路线起终点异常地图 SDK 内部大量使用反射和注解如果你开启了代码混淆且没配 keep 规则运行时会直接崩或者静默返回失败。高德的官方混淆配置大概是这样的-keep class com.amap.api.**{*;} -keep class com.autonavi.**{*;} -keep class com.loc.**{*;}百度类似-keep class com.baidu.**{*;} -keep class vi.com.**{*;} -keep class com.baidu.mapapi.**{*;}配置好混淆规则后建议在 release 包里跑一次完整的“定位 → 路径规划 → 绘制路线”流程因为混淆问题只在 release 包复现debug 包完全看不出来。7.5 小程序跳转高德地图的成功率问题前面提到过小程序调起外部地图 App 的场景这里单独展开讲一下我踩过的坑。小程序环境中wx.openLocation只能打开腾讯地图且不能直接指定导航 App。如果你的业务强依赖高德或百度地图 App常规做法是在小程序里用web-view加载一个自己服务器的 H5 页面H5 页面里写一段调起地图 App 的 JS 逻辑使用window.location.href跳转到iosamap://path?或baidumap://map/direction?浏览器在 Android 端识别 scheme 后会调起对应 AppiOS 端因为 Universal Link 的限制成功率不高建议在 H5 页面展示提示文案如果未能调起复制链接到 Safari 打开。注意WebView 中调起 Scheme 依赖当前 App 的拦截逻辑如果企业微信或微信内置浏览器对 scheme 做了拦截无论你怎么调整参数都白搭。小程序端的“复制链接到浏览器打开”是一个稳定可用的降级方案。7.6 免费额度用超了怎么办这件事我吃过一次亏。某个项目上线后用户量平稳增长我以为调用量远没到付费门槛结果某个月账单突然涨到好几千。复盘后发现问题出在每次用户打开 App 都自动发起一次路线规划请求即使他没有查看路线前端没有做缓存起终点相同的请求反复打到服务器后端没有做流控某个大面积刷单行为直接打爆了调用量。现在我的习惯是在前端统一封装一个“路线规划 RequestManager”同一个 OD 对在 3 分钟内的请求直接返回缓存结果后端再做一层 Redis 缓存和限流单位时间超出限额的直接拒绝并返回友好提示。这样既保护了免费额度也提升了响应速度。7.7 离线场景下的路径规划处理热搜词里出现“高德地图Android离线包下载”说明有人关心离线场景。这里要明确路径规划本身是强网络依赖的在线服务离线地图包只能解决地图瓦片展示不能替代在线路径规划。高德和百度都有离线地图包下载能力下载后地图显示不会白屏但路径规划请求仍然要走网络。我做过一个户外场景的 App用户经常在无网络区域活动我们的方案是预下载当前城市的地图瓦片用户在离线区域时只展示基础地图不做路线规划一旦网络恢复再自动请求最优路线。如果你真的需要离线机器人式的路径规划那要自己实现格网算法或引入开源路由引擎比如 OSRM、Valhalla这个工程量和数据量都很庞大一般商业 App 不太会自研。8. 多端适配与工程架构经验8.1 iOS 端路径规划的两个差异点iOS 端接入百度、高德 SDK思路跟 Android 几乎一致架构和参数都对齐。两个容易出问题的差异点是高德 iOS SDK 使用 Objective-C 的 block 回调如果涉及多线程更新 UI要注意回到主线程处理路线绘制否则会出现子线程更新 UI 被系统警告甚至崩溃iOS 端调起外部地图的 Universal Link 需要在 Apple Developer 后台配置 Associated Domains并且要在地图开放平台登记对应的 link配置完成后才能稳定地从 Safari 或 WebView 调起。8.2 小程序与 H5 的轻量方案前面提到了小程序用 WebView 中转其实有些场景可以不用这么绕。如果你的目标是让用户在小程序里能看到路线预览可以使用腾讯位置服务腾讯地图的路线规划插件通过map组件的polyline属性绘制 routes 返回的坐标串。缺点是路线数据格式来自腾讯地图和高德、百度的请求参数不一致需要单独适配。H5 页面做路径规划最简单的方案是高德/百度 Web Service API前后端分离的开发模式下通常把计算放在后端前端只负责把路线点串交给地图 JS API 渲染。这样可以避免跨域和 Key 泄露问题也方便缓存和审计。8.3 抽象一层地图 Provider避免绑死单一厂商接了两家地图之后我最大的架构体会是从一开始就应该把地图能力封装成接口而不是把页面代码直接耦合在某一家 SDK 上。我现在的工程结构里会定义一个IMapProvider接口public interface IMapProvider { void init(Context context, String apiKey); MapView getMapView(Context context); void drawRoute(ListLatLng points, RouteStyle style); void clearRoute(); void locateOnce(LocateCallback callback); void startNavi(Context context, LatLng destination, String destName); void destroy(); }然后分别实现AmapProvider和BaiduProvider业务层只依赖接口不关心具体厂商。这样做的好处有几个切换厂商时业务代码零改动可以同时接入两家按城市或按用户偏好动态切换任何一个厂商出故障或涨价都能快速替换。抽象层的成本不算高但收益时间跨度很长。我经历过一个项目从百度切高德因为当时没有抽象层改了一个星期才全部替换完后来另一个项目从高德增加百度支持只花了一天。9. 从路径规划到导航的进阶扩展9.1 路线规划与导航的边界很多产品经理会把“路线规划”和“导航”混为一谈但工程上这是两个完全不同的模块。路径规划只是“算出一条线”而导航需要实时定位、偏航检测、语音播报、车道级引导、电子眼提醒等功能。自研导航的成本远高于接入导航 SDK所以我的原则是规划自己画、导航交给 SDK。高德和百度的导航 SDK 都支持“传入一条已规划好的路线直接开始导航”这样可以避免导航从头规划。高德的做法是把DriveRouteResult里的路线转成AMapNaviPath再传给AMapNavi启动导航。这样组合的好处是用户在你的 App 里看到的是你自己的路线风格点击导航后沿用这条路线而不是在地图 App 里重新规划一条路线体验上更连贯。9.2 实时路况与动态重规划的结合道路状况是实时变化的规划完成后 10 分钟原本预计 20 分钟的路线可能因为事故变成 40 分钟。针对时效敏感业务我建议在导航或路线预览页增加“刷新路线”按钮触发重新规划对比新旧路线的时间差异让用户自己决定是否切换。更进一步的做法是监听导航 SDK 的偏航或重新规划回调当新路线比旧路线明显更优时比如时间节省超过 20%主动弹出提示让用户选择。这种体验细节很加分但也容易打扰用户建议给这个提示加一个“本次不再提示”的开关。9.3 路径规划在无人配送、动态避障场景的借鉴其实路径规划算法不止应用于地图导航机器人、AGV 小车、无人机这些领域也有大量路径规划需求比如热搜词里的“动态避障小车路径规划”和“泊车路径规划算法”。这些领域用的算法比如 A*、Dijkstra、RRT、人工势场法本质上是处理和 App 地图路线规划类似的问题在一个带障碍物的环境里找出一条从起点到终点且满足某种约束距离、时间、能耗的最优路径。地图导航里高德和百度已经在做“实时路况 历史路况预测”结合的动态规划这在某种程度上就是移动车辆领域的动态避障。你在做 App 功能时如果未来业务向 IoT、无人车方向延伸把路径规划的核心逻辑从“调用地图 API”提升到“理解图搜索与优化”层面会很有帮助。至少从工程架构上地图 Provider 抽象层这套设计放到机器人调度系统里同样成立——不同的底盘厂商就是不同的 Provider调度中心只管发指令不用关心具体底盘怎么执行。10. 我的真实体会与实操建议写了这么多最后聊一点个人经验和感受希望对正在做这个功能的你有点参考价值。地图类功能看起来是个“标准件”但实际上它跟业务绑定得极深。同一套路径规划接口做外卖、做打车、做跑步、做巡检参数调优方向完全不同界面交互也完全不一样。不要指望从网上下一个 Demo 改改就用一定要先想清楚你的用户在什么场景下触发路线规划他关心的是距离最短、时间最短还是避开高速、避开拥堵。一个很容易被忽略的建议是地图 SDK 的升级要谨慎。SDK 版本更新通常承担兼容性和新功能但也可能带来接口行为变化。我遇到过升级高德地图 SDK 后某条路线的出参距离从米变成了千米的大乌龙排查了半天才发现是新版 SDK 改了单位。升级前先看 changelog升级后务必跑一遍完整回归别偷懒。另一个建议是早点把 Key 管理和配额监控做起来。地图服务商对 Key 的鉴权、配额管理越来越严格线上事故里“Key 失效导致全 App 地图不可用”的概率远高于代码 bug。建议把 Key 配置放到后端下发前端启动时拉取这样万一 Key 出问题可以实时替换不用重新发版。最后想说的是地图这个领域真正的难度不在于“调用接口”而在于“对地理空间的理解”。路线规划返回的数据不仅仅是画一条线更是你对用户业务逻辑的映射。多花点时间研究高德和百度返回的数据结构把每个字段都吃透比你多写十个界面都有价值。
返回列表