[IOS] 高德地图 不同类型 导航 线路规划及展示

(文章末尾可下载源码)

目前在试着做一个旅游类app项目,自己想加入一个功能:定位当前用户位置,并能规划出到旅游目的地的不同类型的线路及展示,界面参照拉钩网app工作地址详情页,采用高德地图的接口,琢磨下高德地图的demo,实现起来挺容易,直接上图:

2017-06-11 22.51.20.gif

1.集成高德地图

首先要集成高德地图,需要在高德地图官网申请高德地图的开发者账号,然后导入其第三方库等步骤,可以参考http://www.jianshu.com/p/bc9462f9c1e9

2.显示用户位置及目的地展示

通过第一步,地图应该能显示出来了(高兴),接下来就是在地图上显示出我们需要的信息,首先是目的地展示(这里我采用大头针来显示目的地),以及当前定位当前用户,使用高德地图的第三方库也都几句代码的事,官方文档也有说明这里也不多说直接上代码:
地图初始化完成后的方法中

_mapView.centerCoordinate=CLLocationCoordinate2DMake(self.ampDistancePoint.latitude,self.ampDistancePoint.longitude);//定位中心点
_mapView.zoomLevel=16.5;//地图缩放级别
_mapView.showsUserLocation = YES;  //显示定位蓝点

在这里要说一下大头针,大头针也可以参考高德地图中的点标记绘制来进行自定义,在viewDidAppear中

    MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
    pointAnnotation.coordinate = CLLocationCoordinate2DMake(self.ampDistancePoint.latitude,self.ampDistancePoint.longitude);
    pointAnnotation.title = self.model.name; //大头针
    [_mapView addAnnotation:pointAnnotation];

然后 实现 <MAMapViewDelegate> 协议中的 mapView:viewForAnnotation:回调函数,设置标注样式

- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id <MAAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MAPointAnnotation class]])
    {
        static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
        MAPinAnnotationView*annotationView = (MAPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
        if (annotationView == nil)
        {
            annotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
        }
        annotationView.canShowCallout= YES;       //设置气泡可以弹出,默认为NO
        annotationView.animatesDrop = YES;        //设置标注动画显示,默认为NO
        annotationView.draggable = YES;        //设置标注可以拖动,默认为NO
        annotationView.pinColor = MAPinAnnotationColorPurple;
        return annotationView;
    }
    return nil;
}

3.不同类型路线规划

关于路线规划,主要参照了官方demo,使用AMapSearchAPI类,发起路线规划请求,驾车和步行的路线规划区别不大,公共交通出行路线倒是有点不同。

3.1驾车路线规划发起

在这里我自定义了一个路线类型属性,1为驾车,2为公共交通,3为步行。这样做的用意,大家后面就知道了。

-(void)driveNav{
   
   routeType=1;//自定义的路线类型,1为驾车,2为公共交通,3为步行
   AMapDrivingRouteSearchRequest *navi = [[AMapDrivingRouteSearchRequest alloc] init];
   navi.requireExtension = YES;//是否返回扩展信息,默认为 NO
   navi.strategy = 5;// 驾车导航策略([default = 0]) 0-速度优先(时间);1-费用优先(不走收费路段的最快道路);2-距离优先;3-不走快速路;4-结合实时交通(躲避拥堵);5-多策略(同时使用速度优先、费用优先、距离优先三个策略);6-不走高速;7-不走高速且避免收费;8-躲避收费和拥堵;9-不走高速且躲避收费和拥堵
   /* 出发点. */
   navi.origin = [AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude
                                          longitude:_mapView.userLocation.location.coordinate.longitude];
   /* 目的地. */
   navi.destination =self.ampDistancePoint;
   [self.search AMapDrivingRouteSearch:navi];//驾车路线规划
}

3.2公共交通路线规划发起

公共交通不一样的点主要是多了一个city属性,必须要让高德地图知道是哪个城市的公共交通路线。

-(void)busNav{
    routeType=2;
    AMapTransitRouteSearchRequest *navi = [[AMapTransitRouteSearchRequest alloc] init];
    navi.requireExtension = YES;
    navi.strategy = 4;//公交换乘策略([default = 0])0-最快捷模式;1-最经济模式;2-最少换乘模式;3-最少步行模式;4-最舒适模式;5-不乘地铁模式
    navi.city =@"chongqing";
    /* 出发点. */
    navi.origin = [AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude
                                           longitude:_mapView.userLocation.location.coordinate.longitude];
    /* 目的地. */
    navi.destination =self.ampDistancePoint;
    [self.search AMapTransitRouteSearch:navi];//公共交通路线规
}

3.3步行路线规划发起

步行路线和驾车大致相同

-(void)WalkNav{
    routeType=3;
    AMapWalkingRouteSearchRequest *navi = [[AMapWalkingRouteSearchRequest alloc] init];
    /* 出发点. */
    navi.origin = [AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude
                                           longitude:_mapView.userLocation.location.coordinate.longitude];
    /* 目的地. */
    navi.destination = self.ampDistancePoint;
    [self.search AMapWalkingRouteSearch:navi];
}

4路径规划搜索回调

通过AMapSearchAPI类向服务器发起路线规划请求后,如规划成功则跳到onRouteSearchDone方法,response,包含类路线的所有信息耗时,距离,换乘等等,否则跳转到- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error方法中,并附带出错原因。

在这里我使用了预加载的模式,在用户跳转到本界面时,就自动发起多有类型路线的规划,等所有路线类型都规划完毕后,用户才能查看各种类型的导航路线。预加载可以让用户对三种路线规划耗时一目了然,增强用户体验。
在预加载这里有一个问题,如果同时发起三种类型的路线规划请求,将会收到三次回调,但是由于异步的关系,就不能知道哪次回调对应着的是哪种类型的路线规划请求。

所以在这里我用了一个笨方法,先发起步行路线规划,等收到回调后再发起公共交通路线规划,用routeType属性可以知道当前是哪种类型请求的回调,再发起驾车路线规划,等驾车路线规划收到回调,那么所有路线加载完毕,则隐藏小菊花,用户也就能查看到各种类型的导航路线了。

/* 路径规划搜索回调. */
- (void) onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response
{
    if (response.route == nil)
    {
        return;
    }
    /* 预加载*/
    if (routeType==1) {
        self.driveRoute=response.route;
        self.driveDration.text=[self timeFomart: response.route.paths[0].duration];//预计用时
        [MBProgressHUD hideHUDForView:self.navView animated:YES];//隐藏菊花
        self.distance.text =[self disFormat:self.driveRoute.paths[0].distance];//显示距离
        [self isHideNavView:false];//显示控件
    }
    if (routeType==2) {
        self.busRoute=response.route;
        if (response.route.transits!=nil && response.route.transits.count!=0) {
            if(response.route.transits.lastObject!=nil){
                self.busDration.text=[self timeFomart:response.route.transits[0].duration];
            }
        }else{
            self.busDration.text=@"暂无";
            self.busBtn.enabled=false;
        }
        [self driveNav];
    }
    if (routeType==3) {
        self.walkRoute=response.route;
        if (response.route.paths!=nil) {
            self.walkDration.text=[self timeFomart: response.route.paths[0].duration];
        }
        else{
            self.walkDration.text=@"暂无";
            self.walkBtn.enabled=false;
        }
        [self busNav];
    }
    self.route = response.route;
    self.currentCourse = 0;
}

如若出错,应该就是网络不好访问超时,要么就是超过距离,如超过了市区的公交线路导航,或其他原因系统无法规划出路线,那么则在出错处理中根据不同的类型进行错误的处理,并在此发起下一个类型的线路规划请求,在这里为了方便,出错的类型直接显示暂无= =。
//出错处理

- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error
{
    if (routeType==1) {
        self.driveDration.text=@"暂无";
        self.carBtn.enabled=false;
    }
    if (routeType==2) {
        self.busDration.text=@"暂无";
        self.busBtn.enabled=false;
        [self driveNav];
    }
    if (routeType==3) {
        self.walkDration.text=@"暂无";
        self.walkBtn.enabled=false;
        [self busNav];
    }

5解析路线及路线的绘制

路线的解析以及路线的绘制,如用户查看驾车路线时,直接解析已预加载好的路线规划,然后再进行路线的绘制即可,在这里我直接是用的官方demo里的代码,仅仅根据自己的需求及界面的美观小小的修改了一下。
路线的解析,注意有些类是demo中封装好的,非官方库中的。
注意:要先清楚地图上的所有绘制

//清理绘制路线
- (void)clear
{
    [self.naviRoute removeFromMapView];
}

点击驾车路线规划图标:

- (IBAction)drivingBtn:(id)sender {
    [self clear];
    routeType=1;
    self.distance.text =[self disFormat:self.driveRoute.paths[0].distance];
    [self presentCurrentCourse];//路线解析
}
//根据不同导航类型解析不同路线
- (void)presentCurrentCourse
{
    MANaviAnnotationType type = MANaviAnnotationTypeDrive;
    if (routeType==1) {
        type = MANaviAnnotationTypeDrive;
        self.naviRoute = [MANaviRoute naviRouteForPath:self.driveRoute.paths[self.currentCourse] withNaviType:type showTraffic:YES startPoint:[AMapGeoPoint  locationWithLatitude:_mapView.userLocation.location.coordinate.latitude longitude:_mapView.userLocation.location.coordinate.longitude]
                                              endPoint:self.ampDistancePoint];
        [self.naviRoute addToMapView:self.mapView];

    }
    if (routeType==2) {
        self.naviRoute = [MANaviRoute naviRouteForTransit:self.busRoute.transits[self.currentCourse] startPoint:[AMapGeoPoint  locationWithLatitude:_mapView.userLocation.location.coordinate.latitude longitude:_mapView.userLocation.location.coordinate.longitude]
                                                 endPoint:self.ampDistancePoint];
        [self.naviRoute addToMapView:self.mapView];
    }
    if (routeType==3) {
        type = MANaviAnnotationTypeWalking;
        self.naviRoute = [MANaviRoute naviRouteForPath:self.walkRoute.paths[self.currentCourse] withNaviType:type showTraffic:YES startPoint:[AMapGeoPoint  locationWithLatitude:_mapView.userLocation.location.coordinate.latitude longitude:_mapView.userLocation.location.coordinate.longitude]
                                              endPoint:self.ampDistancePoint];
        [self.naviRoute addToMapView:self.mapView];

    }    
    /* 缩放地图使其适应polylines的展示. */
    self.naviRoute.anntationVisible=YES;
    [self.mapView showOverlays:self.naviRoute.routePolylines edgePadding:UIEdgeInsetsMake(RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge) animated:YES];    
}

路线的绘制直接照搬官方demo中的(手动斜眼)

注意:地图的绘制分两个部分,路线的绘制以及路线节点(Annotation)的绘制

路线的绘制

//根据不同导航类型绘制路线
- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id <MAOverlay>)overlay// 任何遵循此协议的对象
{
    if ([overlay isKindOfClass:[LineDashPolyline class]])
    {
        MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:((LineDashPolyline *)overlay).polyline];
        polylineRenderer.lineWidth   = 8;
        polylineRenderer.lineDashPattern = @[@10, @15];
        polylineRenderer.strokeColor = [UIColor redColor];
        
        return polylineRenderer;
    }
    if ([overlay isKindOfClass:[MANaviPolyline class]])
    {
        MANaviPolyline *naviPolyline = (MANaviPolyline *)overlay;
        MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:naviPolyline.polyline];
        
        polylineRenderer.lineWidth = 8;
        
        if (naviPolyline.type == MANaviAnnotationTypeWalking)
        {
            polylineRenderer.strokeColor = self.naviRoute.walkingColor;
        }
        else if (naviPolyline.type == MANaviAnnotationTypeRailway)
        {
            polylineRenderer.strokeColor = self.naviRoute.railwayColor;
        }
        else
        {
            polylineRenderer.strokeColor = self.naviRoute.routeColor;
        }        
        return polylineRenderer;
    }
    if ([overlay isKindOfClass:[MAMultiPolyline class]])
    {
        MAMultiColoredPolylineRenderer * polylineRenderer = [[MAMultiColoredPolylineRenderer alloc] initWithMultiPolyline:(MAMultiPolyline *)overlay];
        polylineRenderer.lineWidth = 8;
        polylineRenderer.strokeColors = [self.naviRoute.multiPolylineColors copy];
        polylineRenderer.gradient = YES;
        return polylineRenderer;
    }
    return nil;
}  

路线节点的绘制

//根据导航类型绘制覆盖物
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id <MAAnnotation>)annotation
{  
    if ([annotation isKindOfClass:[MAPointAnnotation class]])
    {        
        static NSString *routePlanningCellIdentifier = @"RoutePlanningCellIdentifier";        
        MAAnnotationView *poiAnnotationView = (MAAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:routePlanningCellIdentifier];
        if (poiAnnotationView == nil)
        {
            poiAnnotationView = [[MAAnnotationView alloc] initWithAnnotation:annotation
                                                             reuseIdentifier:routePlanningCellIdentifier];
        }        
        poiAnnotationView.canShowCallout = YES;
        poiAnnotationView.image = nil;               
        if ([annotation isKindOfClass:[MANaviAnnotation class]])
        {
            switch (((MANaviAnnotation*)annotation).type)
            {
                case MANaviAnnotationTypeRailway:
                    poiAnnotationView.image = [UIImage imageNamed:@"railway_station"];
                    break;                    
                case MANaviAnnotationTypeBus:
                    poiAnnotationView.image = [UIImage imageNamed:@"bus"];
                    break;                    
                case MANaviAnnotationTypeDrive:
                    poiAnnotationView.image = [UIImage imageNamed:@"car"];
                    break;                    
                case MANaviAnnotationTypeWalking:
                    poiAnnotationView.image = [UIImage imageNamed:@"man"];
                    break;               
                default:
                    break;
            }
        }
        else
        {
            /* 起点. */   //绘制起点的红色大头针在这里哦
            if ([[annotation title] isEqualToString:self.model.name])
            {
                    static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
                    MAPinAnnotationView *annotationView = (MAPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
                    if (annotationView == nil)
                    {
                        annotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
                    }
                    annotationView.canShowCallout= YES;       //设置气泡可以弹出,默认为NO
                    annotationView.animatesDrop = YES;        //设置标注动画显示,默认为NO
                    annotationView.draggable = YES;        //设置标注可以拖动,默认为NO
                    annotationView.pinColor = MAPinAnnotationColorRed;
                    return annotationView;
            }
        }
        return poiAnnotationView;
    }
    return nil;
}

6其他

还有些边角代码:如时间格式转换,距离格式转换,string类型的经纬度转换等也一并献上吧,

-(AMapGeoPoint *)pointFormat:(NSString *)point{
    //经纬度格式转换
    NSArray *arry= [point componentsSeparatedByString:@","];
    double p2=((NSString *)arry[0]).doubleValue;
    double p1=((NSString *)arry[1]).doubleValue;
    return [AMapGeoPoint locationWithLatitude:CLLocationCoordinate2DMake(p1,p2).latitude
                                    longitude:CLLocationCoordinate2DMake(p1,p2).longitude];
}
//距离格式转换
-(NSString *)disFormat:(double)meters {
    double intDistance=(int)round(meters);
    return [NSString stringWithFormat:@"距离:%0.2fKM",intDistance/1000 ];
}
//时间格式转换
-(NSString *)timeFomart:(double)duration{
    return [NSString stringWithFormat:@"%0.0f分钟",duration/60];
}
//笨方法,隐藏加载中的控件
-(void)isHideNavView:(BOOL) ishide{
    self.busBtn.hidden=ishide;
    self.busDration.hidden=ishide;
    self.driveDration.hidden=ishide;
    self.carBtn.hidden=ishide;
    self.walkDration.hidden=ishide;
    self.walkBtn.hidden=ishide;
}

项目demo开源在github欢迎Star(害羞#)

注:需手动导入AmpFrameworks
https://github.com/calvinWen/AmpDemo

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

推荐阅读更多精彩内容