iOS 高德地图 路径规划

前言

本文章只针对iOS系统高德地图的路径规划功能,关于集成和配置请参考高德开放平台。之前我也在网上看过别人写的博客,但是用AMapSearchAPI实现路径规划的时候在路径解析的时候掉进了一个小坑(绘制在地图上的路径在拐角处不连贯现象,如下图)。所以写这篇文章记录一下自己踩到的坑,也希望能帮到需要的人。

拐角处不连贯现象.png

出行路径规划:

出行路径规划分为“驾车出行路线规划”、“步行出行路线规划”、“公交出行路线规划”和“骑行出行路线规划”。在路径规划上并无差异,下面的代码均以骑行出行路线规划为例。
设置起点和终点,通过AMapSearchKitAMapNaviKit的路线规划功能返回来的数据,解析出其中的线路经纬度数据,再通过绘制折线功能向地图上绘制路径。
折线类为 MAPolyline,由一组经纬度坐标组成,并以有序序列形式建立一系列的线段。iOS SDK支持在3D矢量地图上绘制带箭头或有纹理等样式的折线,同时可设置折线端点和连接点的类型,以满足各种绘制线的场景。
其中需要注意的是这两种API返回来的数据结构是不一样的,因此经纬度的解析方法也有点差异。

高德 iOS SDK 的 Pod 库的名称如下表:
IFDA版本 NO IDFA版本 备注
3D地图SDK AMap3DMap AMap3DMap-NO-IDFA 3D地图与2D地图不能同时使用
2D地图SDK AMap2DMap AMap2DMap-NO-IDFA 3D地图与2D地图不能同时使用
搜索功能 AMapSearch AMapSearch-NO-IDFA
定位SDK AMapLocation AMapLocation-NO-IDFA
导航SDK AMapNavi AMapNavi-NO-IDFA 已包含3D地图,无需单独引入3D地图

一、AMapSearchAPI:

初始化搜索类:
_search = [[AMapSearchAPI alloc]init];
_search.delegate = self;
设置起点和终点并发起骑行路线规划:

当检索成功时,会进到 onRouteSearchDone 回调函数中,在该回调中,通过解析 AMapRouteSearchResponse 获取将步行规划路线的数据显示在地图上。

AMapRidingRouteSearchRequest *navi = [[AMapRidingRouteSearchRequest alloc] init];
/* 出发点. */
navi.origin = [AMapGeoPoint locationWithLatitude:self.startLocation.latitude longitude:self.startLocation.longitude];
/* 目的地. */
navi.destination = [AMapGeoPoint locationWithLatitude:self.endLocation.latitude longitude:self.endLocation.longitude];
[self.search AMapRidingRouteSearch:navi];//发起骑行路线规划

注意:
“AMapRidingRouteSearchRequest”为骑行路线规划类
“AMapDrivingRouteSearchRequest” -- 驾车出行路线规划
“AMapWalkingRouteSearchRequest” -- 步行出行路线规划类
“AMapTransitRouteSearchRequest” -- 公交出行路线规划类

路径规划搜索回调:
- (void)onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response{
    if (response.route == nil){
        return;
    }
    
    if (response.count > 0){
        //直接取第一个方案
        AMapPath *path = response.route.paths[0];
        //移除旧折线对象
        [_mapView removeOverlay:_polyline];
        //构造折线对象
        _polyline = [self polylinesForPath:path];
        //添加新的遮盖,然后会触发代理方法(- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id<MAOverlay>)overlay)进行绘制
        [_mapView addOverlay:_polyline];
    }
}
路线解析并返回折线对象(MAPolyline):
//路线解析
- (MAPolyline *)polylinesForPath:(AMapPath *)path{
    if (path == nil || path.steps.count == 0){
        return nil;
    }
    NSMutableString *polylineMutableString = [@"" mutableCopy];
    for (AMapStep *step in path.steps) {
        [polylineMutableString appendFormat:@"%@;",step.polyline];
    }
    
    NSUInteger count = 0;
    CLLocationCoordinate2D *coordinates = [self coordinatesForString:polylineMutableString
                                                     coordinateCount:&count
                                                          parseToken:@";"];
    
    MAPolyline *polyline = [MAPolyline polylineWithCoordinates:coordinates count:count];
    
    free(coordinates), coordinates = NULL;
    return polyline;
}

//解析经纬度
- (CLLocationCoordinate2D *)coordinatesForString:(NSString *)string
                                 coordinateCount:(NSUInteger *)coordinateCount
                                      parseToken:(NSString *)token{
    if (string == nil){
        return NULL;
    }
    
    if (token == nil){ 
        token = @",";
    }
    
    NSString *str = @"";
    if (![token isEqualToString:@","]){
        str = [string stringByReplacingOccurrencesOfString:token withString:@","];
    }else{
        str = [NSString stringWithString:string];
    }
    
    NSArray *components = [str componentsSeparatedByString:@","];
    NSUInteger count = [components count] / 2;
    if (coordinateCount != NULL){
        *coordinateCount = count;
    }
    CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D*)malloc(count * sizeof(CLLocationCoordinate2D));
    
    for (int i = 0; i < count; i++){
        coordinates[i].longitude = [[components objectAtIndex:2 * i]     doubleValue];
        coordinates[i].latitude  = [[components objectAtIndex:2 * i + 1] doubleValue];
    }
    return coordinates;
}

特别注意- (MAPolyline *)polylinesForPath:(AMapPath *)path一定要只返回一个MAPolyline对象,不能以数组形式返回多个。
path.steps是路段基本信息数组,规划的路径会被分成若干段路段,之前犯了一个错误就是直接通过循环将每一个路段都创建成一个 MAPolyline对象以数组返回,结果就出现开头所说的“拐角处不连贯现象”。
这里我通过

NSMutableString *polylineMutableString = [@"" mutableCopy];
    for (AMapStep *step in path.steps) {
        [polylineMutableString appendFormat:@"%@;",step.polyline];
    }

将所有路段的经纬度字符串拼接成一条完整的经纬度字符串。


二、 AMapNaviRideManager

通过导航SDK进行路径规划。导入头文件#import <AMapNaviKit/AMapNaviKit.h>,代理AMapNaviRideManagerDelegate

初始化骑行导航管理类:
_naviRideManager = [[AMapNaviRideManager alloc] init];
_naviRideManager.delegate = self;

“AMapNaviRideManager”为骑行导航管理类
“AMapNaviDriveManager” -- 驾车导航管理类
“AMapNaviWalkManager” -- 步行导航管理类

设置起点和终点并发起骑行路线规划:

以下算路方法需要高德坐标(GCJ02)。
不带起点的骑行路径规划带起点的骑行路径规划,不带起点规划默认起点为用户当前位置,推荐使用带起点的路径规划方法,因为不带起点的路径规划方法会因定位失败导致使用默认在北京的位置,从而使算路结果异常。这里不同类型的路径规划方法略微不同,下面以骑行导航管理类路径规划为例。

AMapNaviPoint *startPoint = [AMapNaviPoint locationWithLatitude:latitude longitude:longitude];
AMapNaviPoint *endPoint   = [AMapNaviPoint locationWithLatitude:latitude longitude:longitude];
[self.naviRideManager calculateRideRouteWithStartPoint:startPoint endPoint:endPoint];
骑行路径规划的回调函数:
//骑行路径规划成功
- (void)rideManagerOnCalculateRouteSuccess:(AMapNaviRideManager *)rideManager{
    if (rideManager.naviRoute.routeCoordinates.count > 0){
        
        [_mapView removeOverlays:_mapView.overlays];
        //添加新的遮盖,然后会触发代理方法进行绘制
        [_mapView addOverlay:[self polylinesForPath:rideManager.naviRoute.routeCoordinates]];
    }
}
//骑行路径规划失败后的回调函数
-(void)rideManager:(AMapNaviRideManager *)rideManager onCalculateRouteFailure:(NSError *)error{
    NSLog(@"骑行路径规划失败 : %@", error);
}
路线解析并返回折线对象(MAPolyline):

导航路径规划返回的是导航路线的所有形状点,所以解析相对简单一点

//路线解析
- (MAPolyline *)polylinesForPath:(NSArray *)path{
    if (path == nil || path.count == 0){
        return nil;
    }
    CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D*)malloc(path.count * sizeof(CLLocationCoordinate2D));
    for (int i = 0 ; i < path.count; i ++) {
        AMapNaviPoint *point = path[i];
        coordinates[i].longitude = point.longitude;
        coordinates[i].latitude  = point.latitude;
    }
    MAPolyline *polyline = [MAPolyline polylineWithCoordinates:coordinates count:path.count];
    free(coordinates), coordinates = NULL;
    return polyline;
}

三、 设置折线的样式:

不管是通过AMapSearchKit还是AMapNaviKit进行的路线规划,最终都在- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id<MAOverlay>)overlay方法设置折线的样式

- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id<MAOverlay>)overlay{
    if ([overlay isKindOfClass:[MAPolyline class]]){
        MAPolyline *polyline = (MAPolyline *)overlay;
        MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:polyline];
        
        //添加纹理图片
        //若设置了纹理图片,设置线颜色、连接类型和端点类型将无效。
        polylineRenderer.strokeImage  = [UIImage imageNamed:@"wenli"];
        polylineRenderer.lineWidth    = 20.f;
        
//        polylineRenderer.strokeColor  = [UIColor colorWithRed:0 green:1 blue:0.5 alpha:0.8];
//        polylineRenderer.lineJoinType = kMALineJoinRound;
//        polylineRenderer.lineCapType  = kMALineCapRound;
//        polylineRenderer.miterLimit   = 1.f;
        
        return polylineRenderer;
    }
    return nil;
}

设置折线的纹理图片(仅 3D 地图支持)。
纹理素材格式:纹理图片须是正方形,宽高是2的整数幂,如64*64,否则无效;若设置了纹理图片,设置线颜色、连接类型和端点类型将无效。

注意:目前仅支持对折线设置纹理,其余覆盖物目前暂不支持设置纹理。

图片可以是这样:


wenli.png

也可以根据实际需求让UI帮你做,只需符合上面的条件。

最终效果:

最终效果.png

-END-

如果此文章对你有帮助,希望给个❤️。有什么问题欢迎在评论区探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容