ARTICLE DETAIL

资讯详情

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

地图SDK路径规划实战:高德百度接入对比与避坑指南

地图SDK路径规划实战:高德百度接入对比与避坑指南 1. 为什么我建议你用地图SDK而不是自己在App里画路径做App开发的人最常问我一个问题我的App里已经有地图了为什么还要跳转到百度地图或高德地图去规划路线直接在地图上画一条线不行吗这个问题的核心要分场景看。如果你的用户只是想在地图上看到一条起终点连线那完全可以用Map SDK自带的折线绘制接口解决这不涉及路径规划只是图形绘制。但如果用户需要的是“从当前位置到某商场步行12分钟、驾车8公里、避开拥堵路段点一下开始导航”那件事背后涉及的就不是画线这么简单而是整套路线计算、交通态势分析、导航引导能力。简单的路径展示自己做没有问题可一旦涉及出行引导我强烈建议接入百度和高德的SDK。原因有几个。第一路径规划不是单纯算一条最短路径。真实的路线计算要考虑路网拓扑、单行线、禁转限制、实时路况、封路信息、不同交通工具的通行规则。百度地图和高德地图在这块积累了多年的路网数据个人团队完全不可能自建同等规模的数据源。第二App内嵌的地图SDK和导航SDK是有分工的。用Map SDK只是完成地图展示和Markers叠加真正发起路径规划需要调用路线规划API各家的叫法略有不同百度叫RoutePlan或Direction API高德叫Route API。这个API返回的不是地图图片而是结构化的路线数据包括每一段路的坐标点集合、预计里程、预计耗时、红绿灯数量、导航指令等。第三从体验上说绝大多数用户对地图的认知是“我点一下地图告诉我怎么走”。如果你的App做的是电商、社交、企业服务这类主要业务地图只是辅助能力最优做法就是让App调起系统里的百度地图或高德地图App去完成路径规划与导航你的App只需要把起终点坐标传过去。第四如果你希望用户不离开你的App就能看到路线和导航信息那就必须在App内集成各家导航/地图SDK并且自己处理路线解析、图标绘制、语音播报接入。这条路可以做但工程量会明显上升特别是导航状态机、偏航重算、电子眼播报这些模块坑很深。所以我会把技术方案分成两档轻量集成和深度集成。轻量集成适合大多数业务深度集成适合需要完整地图能力的产品比如打车、配送、户外运动。下面所有内容围绕这两档方案展开同时覆盖百度地图和高德地图两家的接入差异因为很多项目两边都要接入或者在不同阶段会切换。2. 拨开地图调用的三种常见姿势URI Scheme、Map SDK、Web API在动手写代码之前先理清一个非常容易混淆的概念层次。很多新人拿到百度地图或者高德地图的文档看到一堆API Request和参数列表瞬间就懵了。其实对App团队来说地图能力只有三种打开方式。2.1 姿势一URI Scheme调起各家地图App这是成本最低的方式也是我最推荐大部分工具类、业务类App先采用的方案。它的原理很简单通过系统URL Scheme拉起已安装的百度地图或高德地图App并把起终点、出行方式等参数拼在URL里。以iOS为例调起高德地图的导航URL大致长这样NSString *urlString iosamap://path?sourceApplicationmyAppsidBGVIS1slat39.92848272slon116.39560823sname我的位置didBGVIS2dlat39.98848272dlon116.47560823dname目的地dev0t0; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];Android上高德的URL调起类似只是前缀变成了androidamap://。百度地图的调起前缀则是baidumap://iOS和Android略有参数差异。这种方案的好处是显而易见的你的App不用申请导航SDK权限不用做大量地图渲染相关的工作。路线计算、导航引导、语音播报、实时路况全部由专业地图App完成。只需要处理用户没装对应地图App时的降级逻辑比如跳到Web版路线页。但是它的限制也同样明显用户必须安装了目标地图App否则URL打不开。你在整个导航过程中拿不到任何路径规划数据路线详情、剩余里程、偏航回调都无法回传给你。无法把路线绘制到你自己的地图页面上。所以URI方案适合对地图能力要求不高的场景比如外卖出餐后“帮我去店里”、后台服务通知“点击查看维修人员位置”这种动作。但对于运动记录、配送轨迹、车联网类产品这个方案远远不够。2.2 姿势二Map SDK 路径规划API在自己页面绘制路线这是我要重点展开的方案也是标题里“路径规划”最常被期望实现的能力。它的工作方式是App内嵌入高德地图或百度地图的Map SDK地图渲染在自己页面上然后调用路线规划API拿到路线数据再通过SDK提供的Polyline接口把路线绘制出来用户看到的是App内的完整路径。这个方案的价值在于你可以完全掌控UI可以在地图上叠加业务标记比如店铺位置、骑手当前位置、途径点、路况说明甚至可以对路线做二次加工比如把规划路线的每一段单独标色、标注途经点。数据流向也很清晰App向地图SDK申请Key并初始化。用户输入或定位得到起终点经纬度。App调用路径规划API如高德的AMapRouteSearch或百度的BMKRouteSearch。SDK异步返回路线结果里面包含多条路线方案通常为1-3条每条方案有耗时、距离、路线坐标点串、导航指令列表。Auto把坐标点串解析成地图Polyline覆盖物加到地图上同时用Markers标出起终点。用户点击某条方案可以再次调用SDK的导航能力或者在App内模拟导航。这种方案是主流的App内路径规划实现方式后面的章节我会详细拆每一步的代码细节和坑点。2.3 姿势三Web API / 服务端API数据给后端用还有一种不太被前端开发者注意的场景你不是在App上展示路线而是要在服务端计算路线比如运营后台估算配送时间、自动化系统做路径优化。这时候需要调用的是百度和高德的Web服务API高德叫Web服务百度叫WebAPI。典型用法是服务端发起HTTP请求传入起点、终点、出行方式拿到的还是路线数据但通常只会用到其中几个字段比如距离、时间。这种调用不占用App安装包体积也不受SDK限制但它的key管理、配额和计费逻辑和SDK端不一样需要单独申请。对App开发者来说Web API还有一个辅助用途当需要在服务端把一组坐标点重新排序或做近似路线匹配时可以借助它的接口计算。比如你的后台收到用户上报的多个点想判断它是否在某条路线上这是路线匹配不是路径规划但底层数据源是一致的。三种姿势的选择顺序我个人的建议是默认先上URI调起App因为它的开发和维护成本最低可以快速验证业务当产品经理提出“要在我们的页面里看到路线”时再升级到Map SDK 路径规划API如果产品要做“批量路线计算”“后台费用预估”Web API才是正确答案。3. 高德地图接入实战从Key申请到核心API调用这一节我们走高德的完整流程。我假设读者已经有一个能正常编译的Android或iOS工程并且对地图SDK的初始化有基本概念。所有经验都是基于我实际接入过程中反复踩坑得来的尽量给你能直接照做的步骤。3.1 高德的Key申请与Android Studio环境配置高德的Key申请页在高德开放平台控制台里可以创建应用每个应用可以获得多个Key。这里有一个最容易踩的坑高德的Key是绑定应用包名和SHA1指纹的。Android端在控制台填写SHA1时很多人直接用了debug签名的SHA1结果发布版本换签名后地图无法显示。我的建议是在项目配置阶段就用release签名文件生成一个正式的Key同时把debug和release的签名信息都维护好。如果项目里用到了多渠道打包每个渠道的签名不一致也会导致Key校验失败这一点在测试环境不会暴露但到了分发平台会有用户反馈地图白屏。具体在build.gradle里引入SDK是这样的dependencies { implementation com.amap.api:3dmap:9.7.2 implementation com.amap.api:search:9.7.2 implementation com.amap.api:location:6.4.2 }高德的包分得很细3D地图包用于地图展示search包里面包含路径规划相关APIlocation包负责定位。有人只引入3D地图和search却发现路线API报错就是因为漏了location。如果只是做路径规划不显示地图可以不用引入3D地图包但一般产品都会要求显示地图所以三件套建议都加。Manifest权限配置方面高德要求的权限在文档里都有但有一个容易遗漏的是android.permission.ACCESS_NETWORK_STATE没有这个权限SDK初始化可能不报错但后续所有网络请求都会失败。另一个需要注意的权限是Android 10及以上的分区存储如果用户选择“仅使用期间允许”定位权限且App在后台做轨迹记录需要额外处理前台服务逻辑。在布局中放一个地图容器初始化逻辑一般写在onCreateprivate MapView mMapView; private AMap aMap; mMapView findViewById(R.id.map_view); mMapView.onCreate(savedInstanceState); if (aMap null) { aMap mMapView.getMap(); }如果在Fragment中使用生命周期方法一个都不能少。特别是onDestroy()里一定要调mMapView.onDestroy()否则会造成内存泄漏在反复切换页面时会看到内存一路飙升。这不算路径规划的核心但属于长期运行App必须处理的售后问题。3.2 高德路径规划API调用与路线绘制高德路径规划的入口是AMapRouteSearch你可以把它理解成一个异步任务发起器。调用过程分三步构建请求参数、发起异步搜索、在回调里解析结果并绘制。先看请求参数public void searchRoute(double startLat, double startLng, double endLat, double endLng) { AMapRouteSearch search new AMapRouteSearch(this); // 设置搜索条件 RouteSearch.FromAndTo fromAndTo new RouteSearch.FromAndTo( new LatLonPoint(startLat, startLng), new LatLonPoint(endLat, endLng) ); // 驾驶路径规划 RouteSearch.DriveRouteQuery query new RouteSearch.DriveRouteQuery( fromAndTo, RouteSearch.DrivingDefault, null, null, ); search.calculateDriveRouteAsyn(query); }这里的DrivingDefault是策略类型高德提供了多个策略比如避开高速、避开收费、躲避拥堵等。初学者最容易犯的错误是把策略参数和出行方式混在一起。高德把驾车、步行、骑行、公交分成了四组APIcalculateDriveRouteAsyn对应驾车calculateWalkRouteAsyn对应步行calculateBusRouteAsyn对应公交calculateRideRouteAsyn对应骑行。回调实现RouteSearch.OnRouteSearchListener只关心onDriveRouteSearched方法Override public void onDriveRouteSearched(DriveRouteResult result, int errorCode) { if (errorCode ! 1000 || result null) { // 错误处理 return; } ListDrivePath paths result.getPaths(); if (paths ! null !paths.isEmpty()) { DrivePath firstPath paths.get(0); ListLatLonPoint points firstPath.getSteps().get(0).getPolyline(); // 注意多段steps拼成完整路线 } }高德返回的Path是由多个Step组成的每个Step有独立的Polyline坐标点串。完整路线需要把多个Step的坐标点串拼接起来而不是只取第一个Step的Polyline否则你会发现路线图只是画了一小段感觉像“断头路”。拼接方式很简单遍历path.getSteps()把每个Step的getPolyline()结果依次add到同一个ListLatLonPoint里。绘制路线用的是AMap的addPolyline接口PolylineOptions options new PolylineOptions(); options.addAll(convertedPoints); options.color(Color.BLUE); options.width(12); Polyline polyline aMap.addPolyline(options);这里还有一个需要特别注意的问题坐标类型。高德SDK内部使用GCJ-02坐标系它的所有接口返回的坐标都是GCJ-02。如果你的起点坐标是从GPS模块直接拿到的原始WGS-84坐标必须经过坐标转换再传给高德SDK。高德SDK提供了CoordinateConverter但很多人在定位场景下忽略了这个步骤导致规划的路线起点和实际位置偏差几十米到几百米不等。转换代码示例CoordinateConverter converter new CoordinateConverter(); converter.from(CoordinateConverter.CoordType.GPS); converter.coord(new LatLonPoint(gpsLat, gpsLng)); LatLonPoint convert converter.convert();如果是用高德自己的定位SDK拿到的坐标就不用转换因为AMapLocation返回的坐标已经是GCJ-02。如果是接的第三方定位或者从后端下发的坐标务必要确认坐标系类型。这块在后续“坐标与坐标系”章节我会专门展开因为有太多人被它坑了。3.3 公交出行公交路径规划的隐藏坑公交路径规划和高德其他路径规划最大的不同是它的返回结构复杂得多。公交结果里有多条方案每条方案里包含多个公交段TransitSegment每段又包含步行段、公交线路名称、站点列表、上下车站点等。如果你要在地图上把公交路线画出来需要把步行线条和公交线条分别用不同颜色、不同宽度绘制并且把站点位置画上小圆点。这个功能在出租车、顺风车App里不常用但在公共交通导航类产品里是刚需。实际做的时候有一个坑公交路线的坐标点串精度往往不高在部分城市会出现某些站点坐标偏差较大。有时间公交公司在路网里给的站牌坐标和真实站牌位置差几十米这是数据处理层面的问题无法靠前端修正。你只能让UI设计时允许一定的视觉误差不要过度依赖站点坐标做精确的到达判断。公交路径规划的请求参数和驾车不同虽然都走AMapRouteSearch但用的是calculateBusRouteAsyn传入的不是策略值而是城市编码或城市名称RouteSearch.BusRouteQuery query new RouteSearch.BusRouteQuery( fromAndTo, RouteSearch.BusDefault, city, // 起终点城市名或citycode 1 ); search.calculateBusRouteAsyn(query);注意公交路径规划必须指定起点城市、终点城市如果起终点在同一城市只填写城市名即可。跨城公交查询的返回数据会膨胀得很厉害因为中间可能包含多段铁路/公路行程UI层要做好“等待时间长”的提示。4. 百度地图接入实战与高德的差异点逐一对比很多项目面临的地图选型问题不是选高德还是百度而是两个都要。原因很实际用户手机上装哪个地图App不一定产品希望优先调用自己已有的地图App。如果App内集成了高德SDK做路径规划时却调起百度地图App会让用户觉得体验割裂。因此同时接入两家SDK或至少同时支持URI Scheme调起是有必要的。我先说结论百度地图SDK和高德SDK的整体架构相似都是MapView搜索组件回调但细节差异非常多。下面我会列举我踩过的差异点并给出建议的适配方式。4.1 百度地图SDK包结构与初始化差异百度的SDK分“基础地图”和“检索功能”两块基础地图包负责地图渲染检索功能包含路线规划。引入方式和Gradle依赖差不多dependencies { implementation com.baidu.lbsyun:BaiduMapSDK_Map:7.6.2 implementation com.baidu.lbsyun:BaiduMapSDK_Search:7.6.2 }百度对Key的校验比高德更严格。百度的Key需要填写应用包名和发布版SHA1在Debug调试时如果没配置Debug签名地图会显示网格或直接黑屏。而且百度SDK的初始化不是写在onCreate里而是通常放在Application的onCreate中public class App extends Application { Override public void onCreate() { super.onCreate(); SDKInitializer.initialize(this); } }如果你把它放在Activity里初始化当SDK回调到某些异步线程时上下文已经丢失容易出现不确定崩溃。这里要强调SDKInitializer.initialize只需要调用一次建议在Application里做配合Provider初始化更佳。4.2 百度路径规划API的调用方式百度的是RoutePlanSearch类。构建请求发起异步搜索回调结果逻辑与高德一模一样的模板但类名和方法名都有明显差异。public void searchDriveRoute(String startName, double startLat, double startLng, String endName, double endLat, double endLng) { RoutePlanSearch search RoutePlanSearch.newInstance(); PlanNode startNode PlanNode.withLocation(new LatLng(startLat, startLng)); PlanNode endNode PlanNode.withLocation(new LatLng(endLat, endLng)); DrivingRoutePlanOption option new DrivingRoutePlanOption() .from(startNode) .to(endNode) .policy(DrivingRoutePlanOption.DrivingPolicy.ECAR_DIS_FIRST); search.drivingSearch(option); search.setOnGetRoutePlanResultListener(new OnGetRoutePlanResultListener() { Override public void onGetDrivingRouteResult(DrivingRouteResult drivingRouteResult) { // 处理行车路线结果 } // ... 其余回调 }); }百度的DrivingRouteResult里的路线数据是以routeLine为单位每个routeLine有getDrivingStep()每个Step里有点集合。绘制路线的方式也是Polyline但百度地图的Polyline接口叫OverlayOptions配合BaiduMap.addOverlay()PolylineOptions polylineOption new PolylineOptions() .points(pointsList) .width(12) .color(0xFF0000FF) .visible(true); mBaiduMap.addOverlay(polylineOption);两个SDK在路线绘制的基本操作上几乎一致所以在项目中做一层接口抽象是明智的。我自己的做法是不直接引高德/百度类型进业务层而是定义一套中立的路线数据模型如RoutePath、RouteStep、LatLngPoint再由两个适配器分别将各SDK返回结果转换为这套模型。业务层只操作模型哪个SDK出问题就换哪个或者开售后工单互不污染。4.3 调用策略与降级方案设计在双地图场景下我强烈建议做一层“调度策略”而不是固定写死调哪个SDK。比如高德是主地图百度是备选。当高德路径规划请求失败或返回空结果时自动尝试百度如果两个都失败则退回到URI Scheme调起手机里已安装的地图App。具体判断依据可以是高德返回错误码非1000成功码或者result.getPaths()为空。请求超时比如设置了15秒超时后仍无回调。用户设备上存在高德App但高德SDK初始化失败少见但可能发生比如内存被系统回收。降级的体验要注意不能在用户看到“路径规划中”提示时突然换成另一种地图UI上最好是无缝切换后台完成失败探测后再更新路线。处理得好用户根本感知不到你在多家地图之间做了来回切换。5. 两条路径规划方案背后不能忽视的六大坑这部分是我最想分享的因为大部分开发者在跑通Demo后都会松一口气然后上线几天就被用户反馈打脸。以下六个坑是我在真实项目里遇到的每个都有对应的解决思路和代码层面的处理方案。5.1 坑一坐标系混乱导致路线偏移甚至无法规划这是路径规划里最隐蔽也最容易出现的问题。国内地图使用的是GCJ-02坐标系俗称火星坐标系而GPS设备导出的是WGS-84坐标系。高德和百度SDK默认都认为自己拿到的是符合自家坐标系的经纬度。如果你直接把WGS-84坐标传给高德或百度路线可能能从A点算到B点但地图上的起点Marker会偏离真实位置几十到几百米。更严重的是如果你把高德返回的路线上传到后端再把这些坐标传到百度地图上展示偏移会更明显。处理方案很直接从定位开始就要明确坐标系并在进入地图SDK前统一转换。高德提供CoordinateConverter百度提供CoordinateConverter这个同名类但API不同。我在项目里一般会写一个工具类封装WGS-84转GCJ-02和GCJ-02转BD-09百度用的算法保证任何来源的坐标都能正确落到地图上。// 高德坐标系转换WGS84 - GCJ02 public class GpsUtils { private static final double A 6378245.0; private static final double EE 0.00669342162296594323; public static LatLng gpsToGaode(double lat, double lng) { if (outOfChina(lat, lng)) { return new LatLng(lat, lng); } double dLat transformLat(lng - 105.0, lat - 35.0); double dLng transformLng(lng - 105.0, lat - 35.0); double radLat lat / 180.0 * Math.PI; double magic Math.sin(radLat); magic 1 - EE * magic * magic; double sqrtMagic Math.sqrt(magic); dLat (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * Math.PI); dLng (dLng * 180.0) / (A / sqrtMagic * Math.cos(radLat) * Math.PI); return new LatLng(lat dLat, lng dLng); } }市面上还有带“百度坐标”说明的数据源它可能用的是BD-09。对这种情况自己写转换算法稍复杂我建议直接用百度SDK提供的CoordinateConverter转换代码更可靠com.baidu.mapapi.utils.CoordinateConverter converter new com.baidu.mapapi.utils.CoordinateConverter(); converter.from(com.baidu.mapapi.utils.CoordinateConverter.CoordType.GPS); converter.coord(new LatLng(gpsLat, gpsLng)); LatLng convertResult converter.convert();关于坐标系还有一个进阶问题当从高德路径规划返回坐标后直接传到百度地图展示时如果不做BD-09转换整条路线会整体平移甚至穿越道路。处理方法是在适配器层做统一转换不要让原始坐标暴露到上层。5.2 坑二安卓端定位权限与后台位置获取的冲突Path planning在大多数场景下需要拿到当前位置作为起点这时会依赖于定位权限。高德和百度的定位SDK都要求ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION权限。但在Android 10及以上如果App在后台需要持续获取定位比如做一个运动轨迹记录App就不只是权限问题了还涉及前台服务类型声明。路径规划本身一般是前台瞬间操作不存在后台定位要求但如果你要做运动记录、行车轨迹记录就必须考虑前台Service。很多开发者把定位请求和导航SDK的初始化混在一起当一个普通的Activity请求失败后后续所有定位相关的路径规划全部无法工作。我的建议在进入地图页面之前先通过一个独立的定位管理层获取当前位置。这个管理层负责权限申请、定位结果缓存、和地图SDK的坐标转换。地图页面只关心“传入的起终点坐标是什么”而不是自己再去要定位权限。这样可以避免定位权限弹窗和地图SDK初始化同时进行的冲突。5.3 坑三路径规划结果的RouteType与路况信息误判高德和百度在驾车路径规划结果里都会包含一条“推荐路线”和若干条“备选路线”每条路线都有独立的耗时、距离和路况描述。很多新手会直接取第一条路线即官方推荐但用户真实偏好可能不是推荐路线。举个例子高德返回的方案可能包含三条第一条是躲避拥堵但距离多8公里第二条是距离最短但红绿灯特别多第三条是收费高速。如果产品没有明确要求不应该只展示第一条应该把候选路线全部展示给用户让用户自己选。否则你会收到大量“为什么不走XXX路”“为什么绕路”的投诉。代码层面获取所有方案比较容易高德的DriveRouteResult.getPaths()返回的就是方案列表百度是DrivingRouteResult.getRouteLines()。UI上可以使用底部卡片滑动切换方案每条卡片展示距离、时间、红绿灯数和主要途经路名。还有一个细节路线耗时是SDK估算值不能当作计费依据。高德和百度对距离和耗时的估算口径不一样同一段路程高德算出来的时间和百度可能有20%的差距。这种差异在较短的路程中可能不明显但在跑几十公里时误差会被放大。如果你的App涉及里程计费建议以高德/百度返回的距离字段为参考但价格计算要用自己的规则而不是直接用两家返回结果。5.4 坑四Polyline坐标点过多导致渲染卡顿地图绘制路径时从路线规划API拿到的坐标点串可能非常密集尤其是跨城路线Polyline可能有几千个点。如果你的地图页面同时还要显示大量商铺标记、车辆位置、用户头像渲染压力会成倍上升。我的做法是使用路径抽稀算法Douglas-Peucker对坐标点做减密处理在保证路线形状基本符合真实道路的前提下把点数从几千降低到几百。这个算法逻辑不复杂网上有很多现成实现我就不贴完整代码了。另一个有效手段是使用地图SDK的轨迹平滑能力。高德提供AMapUtils.calculateLineDistance和PolylineOptions的setDottedLine等方法但真正提升渲染性能的还是控制Overlay数量。每一条独立的Polyline都是一个Overlay对象几千个点如果全放在一个PolylineOptions里性能往往优于拆成几十个小PolylineOptions。所以当你把多个Step拼接成完整路线后一定要把拼接结果放到同一个PolylineOptions里addAll不要一个Step一个Overlay。5.5 坑五高德返回错误码10021等异常含义与排查链路我用高德做过多次地图集成遇到过最典型的一个失败码是onRegeocodeSearchSearched回调里的errorCode10021。虽然这个错误出现在逆地理编码接口但排查思路对路线规划同样适用。10021在官方文档里的含义是“key无效或过期”。很多人的第一反应是重新申请Key但问题往往不在Key本身。我遇到的实际原因是在多个模块中初始化了高德SDK的多个实例导致Key与包名指纹在校验时匹配混乱。还有一次是项目启用了混淆SDK内部的包名被混淆导致签名校验失败。排查链路建议按这个顺序走确认Manifest里com.amap.api.v2.apikey值没有拼写错误没有多余空格。确认打包签名与高德控制台填写的SHA1一致尤其注意debug和release的差异。关闭混淆试一次如果可以检查混淆规则文件是否把高德的相关类keep住。检查高德SDK初始化是否在onCreate前完成比如在静态代码块里提前调用。如果用的是高德定位SDK还需要确认定位权限已授予否则某些接口会直接失败。百度地图也有类似问题错误码在onGetDrivingRouteResult回调里通过result.error字段返回。百度的error0才是成功非0就要根据文档逐项排查。最常见的百度错误是202表示“无效参数”通常因为你把PlanNode构建成了null或者经纬度范围不对。5.6 坑六多路线中选择路线的UI交互与用户预期管理路径规划SDK会返回多条路线但你不能直接把所有路线一次性全部画在地图上。如果同时画三条颜色不同的线用户会看花眼而且点击切换时的交互也容易乱。建议的做法是默认只画推荐路线的Polyline其他备选路线用“卡片列表”形式放在底部。用户点击某个卡片地图才切换为那条路线的Polyline。这样既展示了多条路线又不会让地图信息过载。另外要特别注意路线高亮颜色的可辨识度。高德推荐的策略是未选中的路线灰色显示选中的路线用主题蓝色或绿色显示宽度也更粗。被很多人忽略的是路线透明度灰色路线的透明度最好控制在30%-50%之间这样既能看出路线走向又不会抢夺主要路线的视觉焦点。6. 进阶再把剩余距离、剩余时间、模拟导航串起来如果你只做“显示一条路线”这个动作那前面的内容基本够用了。但很多需求方会提“用户能不能点击路线就开始模拟导航”这里的模拟导航是指不跳转地图App在你的App页面上按照路线行进实时显示剩余距离、剩余时间并通过语音播报转弯提示。高德和百度都提供了导航SDK和地图SDK是独立模块。高德叫AMapNavi百度叫BaiduNavi。导航SDK比路径规划SDK重得多但它是完整导航体验的基石。这里我会给出两种中间态方案方便你判断是否需要引入完整的导航SDK。6.1 中间态方案一路径规划 语音播报指令列表路径规划API已经返回了每个Step的instruction导航指令只是你不一定注意到。高德的DriveStep.getInstruction()、百度的DrivingStep.getInstructions()都能拿到类似“前方300米右转进入XX路”的文字描述。如果你只是想在用户点击路线后播放一小段语音提示完全不需要引入导航SDK只需要把路线Step里的命令文本抽出来传给TTS播放工具即可。这么做的好处是把导航SDK的重型依赖挡在门外代价是它没有偏航重算能力。如果用户路线走错了你不会收到任何回调路由也不会自动更新。它只适合“展示路线语音播报引导开头”的场景比如线下活动引导用户从停车场到入口。6.2 中间态方案二路径规划 轨迹点匹配 自定义UI如果你打算做一个运动记录App它的一大需求是“我沿着这条路线跑App上能显示我跑到哪了”。这种场景其实不需要完整导航SDK。你只需要用路径规划API拿到路线坐标点串。实时获取定位轨迹点。不断计算当前位置到路线起点的累计距离找到离当前位置最近的路线点。这本质上是一个“点到路线的最短距离”计算不需要实时计算完整路线。实现思路是把路线坐标点串看成一个点序列每一步更新时计算当前定位点到所有路线点的距离取最小值所在的路线段然后把这个路线段的剩余点连接起来就是剩余路线。这样既实现了在路线上的“行进动画”又避开了导航SDK的复杂状态机。这种方案的性能瓶颈在于坐标点数量级如果路线是几千个点每秒钟做一次全量距离计算会很吃力。优化的办法是把坐标点串均匀采样成每10米一个点计算量可以降低一个数量级。实用下来移动端完全没问题帧率也稳定。6.3 完整导航SDK的引入时机与风险提示在上述两个中间态方案都无法满足需求时比如要做车道级导航、实景导航、全语音交互那就只能引入完整导航SDK了。但我要提醒你不要把完整的导航SDK和路径规划SDK混为一谈。导航SDK是一个独立的重型模块它需要自己的Key、独立的初始化流程、大量的权限前台服务、后台定位、语音组件、蓝牙等还会增大安装包体积。高德的导航SDK会引入约10MB左右的体积视混淆和裁剪情况而定百度的导航模块体积更大。而且导航SDK对Android系统和手机品牌兼容性要求很高部分国产手机在后台运行时如果被系统限制会导致导航定位漂移、语音播报中断。如果你只是做路径规划不涉及实时导航引导建议不要贪功能把导航SDK引入进来。这是我的真实教训某次为了给外卖骑手App增加“骑行导航”直接接入了完整导航SDK结果包体积增加了15MB还引出一堆横竖屏切换、后台定位权限的适配问题最后被迫回了中间态方案二。7. 地图选型之外路径规划在真实业务里的应用边界聊到这儿你可能会觉得路径规划的技术栈已经全部展开了但还有一个经常被忽略的问题路径规划在实际业务中应该长在哪里是客户端、服务端还是轻量前端7.1 客户端路径规划 vs 服务端路径规划数据应该放在哪如果是C端用户主动发起的一次性路径规划放在客户端调用SDK是最自然的因为你要在地图上展示、要响应用户操作。但如果你要做的是“这个区域里50个点之间最短路径安排”比如配送员一天要走几十家门店这就不是S-D路径规划了而是多点的路线顺序优化问题。后一类场景不要想着在客户端一点点调SDK更合适的做法是让服务端调Web API把多个点一次性提交拿到一个顺序优化后的路线结果再下发到客户端去展示。高德的Web服务有一个“路径规划”接口输入起点、终点、途经点数组返回总距离和总时间可以辅助做这种排序优化。但是服务端路径规划也有它的坑单次请求的途经点数量有限制高德一般是16个百度是16个而且途经点过多时返回耗时会明显增加。如果要做50个点的优化不能直接一次性传50个途经点你得把问题拆分或者引入真正的路由优化引擎。这类引擎开源的有VROOM、OSRM等它们用的是OpenStreetMap路网数据能处理多点多约束的优化问题但复杂度不在本文范围内。我只是想提醒你不要把所有地图问题都寄希望于地图厂商的路径规划接口。7.2 骑行、步行、驾车三类规划的策略差异与应用适配高德和百度都在同一套SDK里支持驾车、步行、骑行和公交。表面上看方法名不同而已但策略上差异非常大。驾车路径规划默认会走“最快路线”策略但它会把实时拥堵考虑进去所以如果你设置避开高速得到的结果可能多出10-20分钟。步行路径规划几乎不考虑路况只按照人行道、过街天桥、地下通道等路网计算。骑行则有自己的一套规则比如绿道、单行线是否允许骑行、车辆是否允许上桥等。在我做过的骑行App项目里最常被用户吐槽的就是“凭什么让我走机动车快速路”。后来排查发现是我们在骑行路径规划时没有设置正确的策略参数高德默认骑行策略是可以走辅路和部分城市快速路这对城市通勤尚可但对休闲骑行来说非常危险。正确做法是在骑行策略里避开限行路段并参考骑行偏好Courteous、Fastest等。高德骑行路径规划支持RouteSearch.BikeRouteQuery但策略参数不如驾车那么丰富。所以做任何地图功能前先想清楚用户的真实出行方式。如果App的用户以步行为主你却在起终点之间规划驾车路线并展示“5分钟”这就是产品逻辑错误。还有一点步行和骑行路线图在表现上也有区别最好用不同的Polyline颜色、线宽并在地图底部明确标注“步行/骑行/驾车”减少歧义。7.3 微信小程序、Web端和原生App的路径规划差异现在的业务往往是多端的App、小程序、H5网页。三个端的路径规划打开方式并不一致很多团队只做了App端却忽略小程序端的适配导致用户在微信里分享路线时体验割裂。小程序端的做法和App有很大区别。微信小程序本身不能直接集成高德/百度完整SDK的地图渲染能力但它提供了wx.openLocation能调起微信内置地图也能显示路线引导。如果要在小程序里实现路径规划通常的做法是用微信小程序的wx.request请求高德/百度Web API拿到路线数据。再用小程序的cover-view和canvas或第三方地图组件如腾讯位置服务小程序SDK它支持直接展示路线把路线画出来。但腾讯地图坐标体系又是另一个坐标系GCJ-02所以在多端共用时坐标转换策略必须抽成公共模块否则会出现“App端规划正常小程序端路线偏移半条街”的问题。百度地图为小程序也提供了专门的接入方式我记得主要是URL API和Web服务API的组合。如果目标是“让微信用户打开小程序后能直接调起高德地图App”那就又回到URI Scheme调起方案只是发起端变成了小程序环境。这个流程有一个要注意的点微信内置浏览器有一定限制部分URL Scheme在iOS端的微信里会拦截需要做页面内提示“请在浏览器中打开”之类的引导。类似的兼容问题在App的WebView里也常见所以不要假设“能调起地图App的URL在任意环境都通用”。8. 我在实际项目里沉淀下来的调试验证工具与最后经验最后一个部分不打算再给大段代码因为前面已经覆盖了核心路径。这里写一些项目管理和调试层面的实用经验它们其实比单个API更容易让项目质量提升。8.1 把高德和百度调试工具做进自己的Debug模式很多团队接入地图SDK后遇到问题才临时去看官方Demo效率很低。我的习惯是在Debug模式里做一个“地图调试面板”把以下信息实时打印到屏幕上当前定位坐标原始GPS和高德/百度转换后坐标分别显示。当前是否使用高德/百度路径规划SDK成功还是失败错误码是什么。最近一次路径规划返回的路线条数、距离、时间和路线点数量。这样在真机上测试时不需要连Android Studio看日志尤其是当你不方便插电脑时比如你在户外开车测试屏幕上的调试信息会非常有用。而且Debug面板可以随手开关Release包里直接关闭几乎不影响业务代码。8.2 记录每一次路径规划请求的参数和结果用于后续问题回溯路径规划是典型的“偶发问题多”的功能。今天用户报“路线错了”你无法立刻复现因为他的位置、时间、路况全都变了。为了能定位问题我建议在请求发起时把起终点坐标、策略类型、请求时间、地图厂商、返回的错误码或成功码以及结果里第一条路线的耗时和距离全部记录到日志系统。这样即使不能完全复现现场也可以从日志中看出是路线数据没返回还是返回了但被UI层丢弃或者是因为坐标转换错误导致路线起点偏移。我在多个项目上靠这种日志定位了至少三类问题其中之一是“用户反馈点开始导航没有反应”最终发现是App在前台运行时定位权限被系统回收返回了空坐标而UI层没做空数据判断。8.3 最后想说的一点心里话地图SDK接入表面上是配置Key、调接口真正考验的是坐标体系理解、异常处理设计、多端一致性维护。路径规划功能做出来不难但要做到让用户在任何情况下都用得舒服、出问题能找到原因需要投入大量细致工作。我个人的体会是不要把百度地图和高德地图当成一个黑盒SDK而要把它们理解为两套不同的数据服务。每个SDK的文档、错误码、坐标系、返回字段都有各自风格接入前一定要花时间读文档而不是从网上复制一段Demo就开始改。很多线上问题回看官方文档时都会看到明确说明只是我们初期没重视。如果你现在的项目正卡在坐标偏移、路线绘制不完整或者双地图切换报错希望这篇内容能直接帮你少走几步弯路。做地图功能耐心比代码能力重要测试真机比模拟器重要多拿不同地区、不同城市的路线结果对比往往能发现很多隐藏问题。
返回列表