高德地图开发相关总结

目录

1.坐标初始化方法
2.打开第三方地图应用App进行导航及坐标转换
3.地图最简单的定位及气泡图标展示
4.地图路径规划(起点到终点的距离和时间)
5.地图路径规划画折线(起终点两点折线)
6.地图画区域图形多边形遮盖物
7.地图气泡的聚合功能
8.App内部地图导航开发(包含更换终点重新导航)
9.地图根据经纬度进行逆编码
10.地图SDK导入工程及plist权限添加
11.导航中添加自定义view(包含剩余距离和时间)
12.集中简单看下关键类以及方法

高德地图官方文档:https://lbs.amap.com/api/ios-sdk/summary/
百度地图开发相关总结:https://www.jianshu.com/p/f910e2c4586e

1.坐标初始化方法

同百度地图开发相关总结

2.打开第三方地图应用App进行导航及坐标转换

同百度地图开发相关总结

3.地图最简单的定位及气泡展示讲解(司机分布例子)

场景需求:以当前定位为中心,一公里为半径的分布区域内展示周边代驾司机的分布情况,正在忙碌非空闲的用红色图标表示,空闲的用蓝色图标表示。中间的定位图标一直至于地图中心位置,地图可以拖动进行刷新周边司机分布情况。

举例:司机分布控制器 BBXDriverDistributionViewController.m

司机分布效果图

1.基础类以及方法等

基础类属性等

基本方法1
基本方法2
基本方法3
懒加载1
懒加载2

2.网络请求方法(获取数据添加点到地图)

#pragma mark -- 网络请求
//网络请求,刷新界面
- (void)refreshEventWithLat:(NSString *)lat Lng:(NSString *)lng {
    
    NSDictionary *params = @{
                             @"line_id":[BBXDriverInfo shareManager].line_id?:@"",
                             @"max_distance":@(1000),//1公里范围内
                             @"min_distance":@(0),
                             @"location":@{@"lat":lat?:@"",
                                           @"lng":lng?:@""
                                          }
                             };
    NSString *url = [NSString stringWithFormat:@"%@/position/getDesignatedDriverPosition", kNewDomainBBXTest];//获取代驾司机分布
    [SVProgressHUD show];
    @weakify(self);
    [BBXAPIHTTP yh_requestBBXWithParameter:params adnFinishBlock:^(RequestResultObj *resultObj) {
        @strongify(self);
        [SVProgressHUD dismiss];
        if([resultObj.data isKindOfClass:[NSDictionary class]]) {
            
            NSInteger status = [resultObj.data[@"status"] integerValue];
            NSString * message = resultObj.data[@"message"];
            if(status == 200) {//成功
                [self.orderArray removeAllObjects];//移除所有数据
                [self.mapView removeAnnotations:self.mapView.annotations];//移除地图上的所有标注
                NSArray *dataA = [resultObj.data[@"result"] copy];
                self.orderArray = [BBXRepDriveDistributeModel mj_objectArrayWithKeyValuesArray:dataA];
                
                //重新排序
                NSMutableArray *kongArr = [[NSMutableArray alloc] init];//空闲
                NSMutableArray *noKongArr = [[NSMutableArray alloc] init];//非空闲
                for (BBXRepDriveDistributeModel *disModel in self.orderArray) {
                    if (kStrEqual(disModel.show_status, @"0")) {//0:空闲
                        [kongArr addObject:disModel];
                    } else {
                        [noKongArr addObject:disModel];
                    }
                }
                NSArray *kong = [NSArray arrayWithArray:kongArr];//空闲
                NSArray *noKong = [NSArray arrayWithArray:noKongArr];//非空闲
                noKong = [noKong arrayByAddingObjectsFromArray:kong];//kong排在noKong后面,后加入描点在最上面
                self.orderArray = [NSMutableArray arrayWithArray:noKong];
                
                for (NSDictionary *dict in self.orderArray) {
                    NSDictionary *poiDict = dict;
                    BBXRepDriveDistributeModel *poi = [BBXRepDriveDistributeModel mj_objectWithKeyValues:poiDict];
                    BBXRepDrivePointAnnotation *annotation = [[BBXRepDrivePointAnnotation alloc] init];
                    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([poi.location.lat doubleValue], [poi.location.lng doubleValue]);
                    annotation.coordinate = coordinate;
                    annotation.distributrModel = poi;
                    [self.mapView addAnnotation:annotation];
                }
            } else {
                [SVProgressHUD dismiss];
                [self.view showRequestFailToast:message];
            }
        } else{
            [SVProgressHUD dismiss];
            [self.view showRequestFailToast:nil];
        }
    } andRequestURl:url toCrypt:YES];
}

3.地图定位代理方法

#pragma mark -- 高德地图代理方法:定位delegate
//连续定位回调函数.注意:如果实现了本方法,则定位信息不会通过amapLocationManager:didUpdateLocation:方法回调。
- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location reGeocode:(AMapLocationReGeocode *)reGeocode {
    
}

//设备方向改变时回调函数
- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
    if (!newHeading) {
        return;
    }
}

//地图区域改变完成后会调用此接口(位置变更的回调如拖动地图)
- (void)mapView:(MAMapView *)mapView regionDidChangeAnimated:(BOOL)animated wasUserAction:(BOOL)wasUserAction {
    
    CGPoint touchPoint = self.mapView.center;
    CLLocationCoordinate2D touchMapCoordinate = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];//移动地图屏幕中心点的经纬度:touchMapCoordinate
    NSLog(@"touching %f,%f",touchMapCoordinate.latitude,touchMapCoordinate.longitude);
    NSString *lat = [NSString stringWithFormat:@"%f",touchMapCoordinate.latitude];
    NSString *lng = [NSString stringWithFormat:@"%f",touchMapCoordinate.longitude];
    self.refreshLat = lat;
    self.refreshLng = lng;
    [self refreshEventWithLat:lat Lng:lng];
}

4.地图代理方法

#pragma mark -- 高德地图代理方法:展示点击delegate
//地图初始化完毕时会调用此接口
- (void)mapViewDidFinishLoadingMap:(MAMapView *)mapView {
    
    [self.mapView setCenterCoordinate:[BBXLocationManager shareManager].location.coordinate];//设置当前地图中心点
}

//根据anntation生成对应的View(设置大头针样式)
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation {
    
    NSString *IdentifierStr2 = @"bbxAnnotationSend2";
    BBXRepDriveAnnotationView *annotationView2 = (BBXRepDriveAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:IdentifierStr2];
    if (!annotationView2) {
        annotationView2 = [[BBXRepDriveAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:IdentifierStr2];
    }
    BBXRepDrivePointAnnotation *bbx = (BBXRepDrivePointAnnotation *)annotation;
    annotationView2.draggable = NO;//禁止标注在地图上拖动
    annotationView2.annotation = bbx;
    if (self.orderArray.count > 0) {
        annotationView2.distribuModel = bbx.distributrModel;
    }
    annotationView2.canShowCallout = NO;
    annotationView2.centerOffset=CGPointMake(0,0);
    return annotationView2;
}

//点地图上的气泡时回调
- (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view {
    if ([view isKindOfClass:[BBXShareView class]]) {
        BBXShareView *annotationview = (BBXShareView *)view;
        BBXShare *s = view.annotation;//当前点击拿到的点
    }
}

5.初始化地图以及定位对象

//初始化地图
- (void)initMap {
    self.mapView = [[MAMapView alloc] init];
    self.mapView.delegate = self;
    [self.view addSubview:self.mapView];
    [self.mapView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    self.mapView.showsUserLocation = YES;
    self.mapView.zoomLevel = 18;//地图缩放级别
    self.mapView.zoomEnabled = NO;//不能缩放
    self.mapView.userTrackingMode = MAUserTrackingModeFollow;
    self.mapView.rotateEnabled = NO;//是否支持旋转
    self.mapView.rotateCameraEnabled = NO;
    [self.locationManager startUpdatingLocation];//连续定位回调方法(location定位结果)
    [self.locationManager startUpdatingHeading];//设备朝向回调方法;如果设备支持方向识别,则会通过代理回调方法
    self.represent = [[MAUserLocationRepresentation alloc] init];
    self.represent.showsAccuracyRing = NO;//精度圈是否显示
    self.represent.image = kImage(@"bbx_carLocation");
    [self.mapView updateUserLocationRepresentation:self.represent];
}

其中上述代码中涉及的相关类:同百度地图开发相关总结

4.地图路径规划(起点到终点的距离和时间)

地图路径规划:也叫路线规划,就是规划起点到终点所需要的路程和时间。这个规划的结果可能有多条,根据不同规划策略有不同结果,比如最短距离方案、最快时间方案等,依照自己需求而定。这个就相当于用地图App进行起终点导航的时候会优先规划好路线让你选择一个进行导航。

App导航路径规划效果图

高德App导航路径规划效果图

bbx路径规划效果图及选中气泡
bbx路径规划效果图及选中气泡

1.准备工作

1.引入文件名以及协议
#import <AMapNaviKit/AMapNaviKit.h>
#import <AMapSearchKit/AMapSearchKit.h>
#import <AMapFoundationKit/AMapFoundationKit.h>
#import <AMapLocationKit/AMapLocationKit.h>
协议:<MAMapViewDelegate, AMapSearchDelegate, AMapLocationManagerDelegate>

2.相关属性及视图创建
@property (nonatomic, strong) MAMapView *mapView;//地图视图
@property (nonatomic, strong) AMapLocationManager *locationManager;//高德地图负责获取定位相关信息
@property (nonatomic, strong) MAUserLocation *userLocation;//司机当前位置对象
@property (nonatomic, strong) MAUserLocationRepresentation *represent;//定位图层图标
@property (nonatomic, strong) AMapSearchAPI *mapSearch;//路径规划
@property (nonatomic, strong) NSMutableArray *orderArray;//订单数据数组

//初始化地图
- (void)initMap {

    self.mapView = [[MAMapView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.mapView];
    self.mapView.delegate = self;
    self.mapView.showsUserLocation = YES;
    self.mapView.zoomLevel = 15;//地图缩放级别
    self.mapView.userTrackingMode = MAUserTrackingModeFollow;
    self.mapView.rotateEnabled = NO;//是否支持旋转
    self.mapView.rotateCameraEnabled = NO;
    self.represent = [[MAUserLocationRepresentation alloc] init];
    self.represent.showsAccuracyRing = NO;//精度圈是否显示
    [self.locationManager startUpdatingLocation];//连续定位回调方法(location定位结果)
    [self.locationManager startUpdatingHeading];//设备朝向回调方法;如果设备支持方向识别,则会通过代理回调方法
    self.represent.image = kImage(@"bbx_carLocation");
    [self.mapView updateUserLocationRepresentation:self.represent];
}

//订单数组
- (NSMutableArray *)orderArray {
    if (!_orderArray) {
        _orderArray = [[NSMutableArray alloc] init];
    }
    return _orderArray;
}

//司机当前位置对象
- (MAUserLocation *)userLocation {
    if (!_userLocation) {
        _userLocation = [[MAUserLocation alloc] init];
    }
    return _userLocation;
}

//定位管理类
- (AMapLocationManager *)locationManager {
    if (!_locationManager) {
        _locationManager = [[AMapLocationManager alloc] init];
        _locationManager.delegate = self;
        [_locationManager setPausesLocationUpdatesAutomatically:NO];
        [_locationManager setAllowsBackgroundLocationUpdates:NO];
    }
    return _locationManager;
}

//路线规划
- (AMapSearchAPI *)mapSearch {
    if (!_mapSearch) {
        _mapSearch = [[AMapSearchAPI alloc] init];
        _mapSearch.delegate = self;
    }
    return _mapSearch;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.navigationController.navigationBar.hidden = NO;
    self.title = @"到达出发地";
    [self createMainView];//创建主界面,包含创建地图
    [self refreshEvent:YES];//显示底部弹框
    [self setAddressAnnotation];//网络请求方法中获取到数据也会调用这个方法
}

2.核心代码

1.设置地图标注,路径规划初始化
//设置地图标注视图(即地图中显示“xx公里xx分钟”气泡)
- (void)setAddressAnnotation {
    
    if (self.orderArray.count == 0 || !self.orderArray) {
        [self.mapView removeAnnotations:[self.mapView annotations]];//移除当前地图View的已经添加的标注数组
        return;
    }
    
    //添加大头针设置完标注坐标后,后调用viewForAnnotation代理方法,可在该代理方法中设置大头针的样式
    for (BBXOrderListModel *orderModel in self.orderArray) {
        [self creatPlan:orderModel];
    }
    
    //设置地图的显示区域
    for (BBXOrderListModel *orderModel in self.orderArray) {
        if (!orderModel.endAddress || orderModel.endAddress == nil || kStrEqual(orderModel.endAddress, @"")) {
            [self.mapView setCenterCoordinate:[BBXLocationManager shareManager].location.coordinate];//设置当前地图中心点
        } else {
            [self showAllAnnotationWithAllPointArray:self.allOrderPointArr];//显示全部
        }
    }
    
    //设置弹窗控件模型
    if (self.currentOrderModel) {
        BBXOrderViewModel * model = [[BBXOrderViewModel alloc] initWithOrderModel:self.currentOrderModel];
        [self.mapPopupView setViewModel:model bbxMapVCType:BBXRepDrivingMapVCTypeWithNetContractPickMap];
    }
}

//初始化司机路径规划
- (void)creatPlan:(BBXOrderListModel *)model {
    
    //发起驾车路线规划
    AMapDrivingRouteSearchRequest *navi = [[AMapDrivingRouteSearchRequest alloc] init];
    AMapGeoPoint *endPoint;
    navi.origin = [AMapGeoPoint locationWithLatitude:BBXLocationManager.shareManager.location.coordinate.latitude longitude:BBXLocationManager.shareManager.location.coordinate.longitude];//起点(司机位置)
    CLLocation *listLocation = [[CLLocation alloc] initWithLatitude:model.end_lat longitude:model.end_lng];
    CLLocationDegrees lat = listLocation.coordinate.latitude;
    CLLocationDegrees lng = listLocation.coordinate.longitude;
    endPoint = [AMapGeoPoint locationWithLatitude:lat longitude:lng];
    navi.destination = endPoint;//终点(叫代驾或叫车的人的位置)
    navi.strategy = 2;//距离优先(传一个想要的规划策略给高德)
    [self.mapSearch AMapDrivingRouteSearch:navi];//发起驾车路径规划查询
}

2.#para mark -- 路径规划结果回调方法
//默认是驾车路径,需要哪种路径规划可以通过判断request设置,比如骑行 AMapRidingRouteSearchRequest,驾车 AMapDrivingRouteSearchRequest
//路线规划搜索结果(默认驾车)
- (void)onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response {
    if (response.route == nil) {//路线规划出错(太近检索失败)
        return;
    }
    AMapPath *path = [response.route.paths firstObject];
    NSLog(@"start:%f---%f,end:%f---%f", response.route.origin.latitude, response.route.origin.longitude, response.route.destination.latitude, response.route.destination.longitude);
    NSLog(@"distance: %ld", (long)path.distance);
    [self.receiveMapVM updateClusters:response];
}

//路径规划处理详情
- (void)updateClusters:(AMapRouteSearchResponse *)response {
    if (!response) {
        return;
    }
    
    AMapRoute *route = response.route;
    AMapPath *path = [route.paths firstObject];
    NSInteger currentDis = path.distance;
    NSInteger driverTimeHours = path.duration/3600;
    NSInteger driverTimeMinutes = path.duration/60;
    NSInteger minDis = NSIntegerMax;
    if (currentDis < minDis) {
        minDis = currentDis;
    }
    for (BBXOrderListModel *model in self.orderArray) {
        if (model.order_type == OrderType_Bus &&
            !([model.locations.start.area_level isEqualToString:@"1"] || [model.locations.start.area_level isEqualToString:@"3"])) {// 快线-并且站点名称无详细地址(后台返回经纬度只有小数点5位)
            NSDictionary *dataDict = [NSDictionary dictionaryWithJsonString:model.locations.start.station_location];
            if([dataDict isKindOfClass:[NSDictionary class]]) {
                CLLocationDegrees lat = [dataDict[@"lat"] doubleValue];
                CLLocationDegrees lng = [dataDict[@"lng"]doubleValue];
                CLLocationCoordinate2D startPt = (CLLocationCoordinate2D){lat, lng};
                // 判断当前路线是属于哪个订单,当然如果多个订单在同一个位置,或者附近就直接一个气泡显示
                if (route.origin.latitude - startPt.latitude > -0.0001
                    && route.origin.latitude - startPt.latitude < 0.0019
                    && route.origin.longitude- startPt.longitude > -0.0001
                    && route.origin.longitude- startPt.longitude < 0.0019) {
                    
                    model.dis = minDis?minDis:99999999;
                    
                    if (driverTimeHours>0 && driverTimeMinutes>0) {//计算路径规划的时间
                        model.driverTime = [NSString stringWithFormat:@"约%.ld小时%.ld分钟",(long)driverTimeHours,(long)driverTimeMinutes];
                    } else if (driverTimeHours>0 && driverTimeMinutes<=0) {
                        model.driverTime = [NSString stringWithFormat:@"约%.ld小时",(long)driverTimeHours];
                    } else if (driverTimeHours<=0 && driverTimeMinutes>0) {
                        model.driverTime = [NSString stringWithFormat:@"约%.ld分钟",(long)driverTimeMinutes];
                    } else if (driverTimeHours<=0 && driverTimeMinutes<=0) {
                        model.driverTime = @"约1分钟";
                    }
                }
            }
        } else {
            // 其他线路
            if (route.destination.latitude - model.start_lat > -0.000009
                && route.destination.latitude - model.start_lat < 0.000009
                && route.destination.longitude- model.start_lng > -0.000009
                && route.destination.longitude- model.start_lng < 0.000009) {
                model.dis = minDis?minDis:99999999;
                if (driverTimeHours>0 && driverTimeMinutes>0) {//计算路径规划的时间
                    model.driverTime = [NSString stringWithFormat:@"约%.ld小时%.ld分钟",(long)driverTimeHours,(long)driverTimeMinutes];
                } else if (driverTimeHours>0 && driverTimeMinutes<=0) {
                    model.driverTime = [NSString stringWithFormat:@"约%.ld小时",(long)driverTimeHours];
                } else if (driverTimeHours<=0 && driverTimeMinutes>0) {
                    model.driverTime = [NSString stringWithFormat:@"约%.ld分钟",(long)driverTimeMinutes];
                } else if (driverTimeHours<=0 && driverTimeMinutes<=0) {
                    model.driverTime = @"约1分钟";
                }
            } else {
                NSLog(@"");
            }
        }
    }
    [self addAnnotationsToMapView:self.mapView];//聚合详情代码,此处省略,包含标注点添加到地图
}

3.其他相关方法

//根据anntation生成对应的View(自定义标注点View)
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation {
    
    if ([annotation isKindOfClass:[BBXShare class]]) {
        
        //根据指定标识查找一个可被复用的标注View
        NSString *IdentifierStr = @"bbxAnnotation";
        BBXShareView *annotationView = (BBXShareView *)[mapView dequeueReusableAnnotationViewWithIdentifier:IdentifierStr];
        if (!annotationView) {//创建标识视图
            annotationView = [[BBXShareView alloc] initWithAnnotation:annotation reuseIdentifier:IdentifierStr];
        }
        BBXShare *bbxAn = (BBXShare *)annotation;
        annotationView.annotation = bbxAn;
        annotationView.tag = bbxAn.tag;//地图大头针的tag
        bbxAn.model.isMap = YES;
        annotationView.model = bbxAn.model;
        annotationView.canShowCallout = NO;//当为YES时,view被选中时会弹出气泡,annotation必须实现了title这个方法
        annotationView.model.order_typeCarpool = bbxAn.order_typeCarpool;
        annotationView.model.order_typeGoods = bbxAn.order_typeGoods;
        annotationView.model.order_typeFastLine = bbxAn.order_typeFastLine;
        annotationView.model.order_typeCity = bbxAn.order_typeCity;
        annotationView.model.order_typeCharter = bbxAn.order_typeCharter;
        annotationView.isSendMap = NO;
        annotationView.size = bbxAn.size;
        
        //接客地图
        if ([bbxAn.model.order_id isEqualToString:self.currentOrderModel.order_id] && self.hasPickUpOrder) {//当前选中的标注点
            
            annotationView.leftImg.image = [UIImage imageNamed:@"map-driving"];//代驾
            if (annotationView.rightLb.text.length>=14 && annotationView.rightLb.text.length<=16) {
                annotationView.pinBgView.image = kImage(@"bbx_bgBlueLong");
                annotationView.rightLb.textColor = BBXBlack;
            } else if (annotationView.rightLb.text.length>16) {
                annotationView.pinBgView.image = kImage(@"bbx_bgBlueLong2");
                annotationView.rightLb.textColor = BBXBlack;
            } else {
                annotationView.pinBgView.image = kImage(@"bbx_bgBlue");
                annotationView.rightLb.textColor = BBXWhite;
            }
            annotationView.centerOffset = CGPointMake(0, -(annotationView.pinBgView.size.height/2));
        } else {//未选中的标注点
            
            //代驾订单这边气泡一直都选中且只有一个,所以背景跟上面选中设置一样
            if (bbxAn.model.order_status == Order_state_sended) {//已派单
                annotationView.leftImg.image = [UIImage imageNamed:@"map-driving"];//代驾
                if (annotationView.rightLb.text.length>=14 && annotationView.rightLb.text.length<=16) {
                    annotationView.pinBgView.image = kImage(@"bbx_bgBlueLong");
                    annotationView.rightLb.textColor = BBXBlack;
                } else if (annotationView.rightLb.text.length>16) {
                    annotationView.pinBgView.image = kImage(@"bbx_bgBlueLong2");
                    annotationView.rightLb.textColor = BBXBlack;
                } else {
                    annotationView.pinBgView.image = kImage(@"bbx_bgBlue");
                    annotationView.rightLb.textColor = BBXWhite;
                }
                annotationView.centerOffset = CGPointMake(0, -(annotationView.pinBgView.size.height/2));
            }
        }
        return annotationView;
    }
    return nil;
}

//地图初始化完毕时会调用此接口
- (void)mapViewDidFinishLoadingMap:(MAMapView *)mapView {
   //[self showAllAnnotationWithAllPointArray:self.allOrderPointArr];//显示全部
}
网络请求数据获取订单数组:self.orderArray 为数据源,此处详情省略

5.地图路径规划画折线(起终点两点折线)

地图路径规划画折线:即将起点到终点按要求进行的路线规划连成一条线在地图上呈现。折线途经点设置可以理解起点到终点过程中还有好几个点需要连接起来,按照起点与途经点以及终点这些综合的实际情况规划一条最优的路径顺序串联起来。

需求场景举例1:以代驾司机当前位置为起点,下单代驾的人的起点为终点,画折线进行骑行路劲规划。

需求场景举例2:以司机当前位置为起点,有n个订单乘客需要接,最后一个接的乘客当做终点,剩下乘客当做途径点进行路线规划串联起来。此处高德地图未讲解,参考百度地图途经点。

其他说明:下面用的画折线方法为:polylineWithCoordinate,还有一种通过经纬度方法为:polylineWithPoints

相关链接:
https://lbs.amap.com/api/ios-sdk/guide/draw-on-map/draw-polyline
https://www.jianshu.com/p/79b455c74679

折线效果图

画折线-最简单2个点即起终点折线设置

画折线-接送客地图途经点设置效果图

1.准备工作

对于一些准备工作比如地图头文件和协议引用、地图及定位、路线规划等属性的创建、界面基础创建以及数据源接口请求获取原理同上面,此处省略,这部分功能重点看下面核心代码。

2.核心代码
2.1最简单的2个点画折线(起终点)

//1.发起路线规划(获取数据源接口成功的地方调用这个)
- (void)updateViewByShowingOrderListModel:(BBXOrderListModel *)orderListModel {

    //发起骑行路线规划
    AMapRidingRouteSearchRequest *navi = [[AMapRidingRouteSearchRequest alloc] init];
    AMapGeoPoint *endPoint;
    navi.origin = [AMapGeoPoint locationWithLatitude:BBXLocationManager.shareManager.location.coordinate.latitude longitude:BBXLocationManager.shareManager.location.coordinate.longitude];//起点
    CLLocation *listLocation = [[CLLocation alloc] initWithLatitude:self.orderListModel.start_lat longitude:self.orderListModel.start_lng];
    CLLocationDegrees lat = listLocation.coordinate.latitude;
    CLLocationDegrees lng = listLocation.coordinate.longitude;
    endPoint = [AMapGeoPoint locationWithLatitude:lat longitude:lng];
    navi.destination = endPoint;//终点(叫代驾或叫车的人的位置)
    navi.type = 1;//推荐路线
    [self.mapSearch AMapRidingRouteSearch:navi];//发起骑行路径规划查询
}

#pragma mark -- 高德路线规划
//2.骑行路线规划结果
- (void)onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response {
    
    NSArray *array = [NSArray arrayWithArray:self.mapView.annotations];
    [self.mapView removeAnnotations:array];
    array = [NSArray arrayWithArray:self.mapView.overlays];
    [self.mapView removeOverlays:array];//移除遮盖物
    
    if (response.route == nil) {//路线规划出错(太近检索失败)

        AMapRoute *route = response.route;
        AMapPath *path = [route.paths firstObject];

        //添加起点标注
        BBXPassAnnotation *startAnnotation = [[BBXPassAnnotation alloc] init];
        startAnnotation.type = @"start";
        CLLocation * driverLocation = [BBXLocationManager shareManager].location;
        startAnnotation.coordinate = driverLocation.coordinate;
        [self.mapView addAnnotation:startAnnotation];

        //添加终点标注
        BBXPassAnnotation  *endAnnotation = [[BBXPassAnnotation alloc] init];
        endAnnotation.type = @"end";
        CLLocation *listLocation = [[CLLocation alloc] initWithLatitude:self.orderListModel.start_lat longitude:self.orderListModel.start_lng];
        endAnnotation.coordinate = listLocation.coordinate;
        [self.mapView addAnnotation:endAnnotation];

        //泡泡弹出的地点(叫代驾的人所在的点)
        BBXPassAnnotation *passAn = [[BBXPassAnnotation alloc] init];
        passAn.type = @"Pop";
        passAn.title = @"距离车主1.0米";
        CLLocation *palocation = [[CLLocation alloc] initWithLatitude:self.orderListModel.start_lat longitude:self.orderListModel.start_lng];
        passAn.coordinate = palocation.coordinate;
        [self.mapView addAnnotation:passAn];
        
        //画路线规划的折线
        MAPolyline *polyline = [self polylinesForPath:path];//轨迹点
        [self.mapView addOverlay:polyline];//画路线规划的折线
        [self mapViewFitPolyLine:polyline];//根据polyline设置地图范围
        return;
    } else {
        
        AMapPath *path = [response.route.paths firstObject];
        NSInteger mile = path.distance;
       //float distance= MKMetersBetweenMapPoints(startPoint,endPoint);//计算指定两点之间的距离
        if (mile>=0 && mile<=100.0) {//100米认为比较近
            [BBXProgressHUD showInfo:@"距离太近,无法规划路线"];
            return;
        }
        //路线规划成功
        if ([request isKindOfClass:[AMapRidingRouteSearchRequest class]]) {//骑行路径规划
            
            AMapRoute *route = response.route;
            AMapPath *path = [route.paths firstObject];
            NSInteger size = [path.steps count];
           
            for (int i = 0; i < size; i++) {
                                
                if (i == 0) {
                    //添加起点标注
                    BBXPassAnnotation *startAnnotation = [[BBXPassAnnotation alloc] init];
                    startAnnotation.type = @"start";
                    CLLocation *startLocat = [[CLLocation alloc] initWithLatitude:route.origin.latitude longitude:route.origin.longitude];
                    startAnnotation.coordinate = startLocat.coordinate;
                    [self.mapView addAnnotation:startAnnotation];
                }

                if(i == size-1) {
                    //添加终点标注
                    BBXPassAnnotation *endAnnotation = [[BBXPassAnnotation alloc] init];
                    endAnnotation.type = @"end";
                    CLLocation *endLocat = [[CLLocation alloc] initWithLatitude:route.destination.latitude longitude:route.destination.longitude];
                    endAnnotation.coordinate = endLocat.coordinate;
                    [self.mapView addAnnotation:endAnnotation];
                }
            }

            //泡泡弹出的地点(叫代驾的人所在的点)
            BBXPassAnnotation *passAn = [[BBXPassAnnotation alloc] init];
            passAn.type = @"Pop";
            CLLocation *listLocation = [[CLLocation alloc] initWithLatitude:self.orderListModel.start_lat longitude:self.orderListModel.start_lng];
            passAn.coordinate = listLocation.coordinate;
            float distance = path.distance;
            if (distance >= 100.0) {
                NSString *kmString = [BBXMileageConvert meterTurnKilometreOneDecimalWith:distance];//米转公里
                passAn.title = [NSString stringWithFormat:@"距离车主%@公里", kmString];
            } else {
                if (distance<=10.0) {
                    distance = 1.0;
                    passAn.title = [NSString stringWithFormat:@"距离车主%.1f米", distance];
                } else {
                    passAn.title = [NSString stringWithFormat:@"距离车主%.1f米", distance];
                }
            }
            [self.mapView addAnnotation:passAn];

            //画路线规划的折线
            MAPolyline *polyline = [self polylinesForPath:path];//轨迹点
            [self.mapView addOverlay:polyline];//画路线规划的折线
            [self mapViewFitPolyLine:polyline];//根据polyline设置地图范围
        }
    }
}

//3.路径规划检索失败
- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error {
    NSLog(@"Error: %@", error);
}

//4.路线解析-轨迹点
- (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;
}

//5.解析经纬度
- (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;
}

//6.根据polyline设置地图范围
- (void)mapViewFitPolyLine:(MAPolyline *) polyLine {
    CGFloat leftTopX, leftTopY, rightBottomX, rightBottomY;
    if (polyLine.pointCount < 1) {
        return;
    }
    MAMapPoint pt = polyLine.points[0];//BMKMapPoint
    // 左上角顶点
    leftTopX = pt.x;
    leftTopY = pt.y;
    // 右下角顶点
    rightBottomX = pt.x;
    rightBottomY = pt.y;
    for (int i = 1; i < polyLine.pointCount; i++) {
        MAMapPoint pt = polyLine.points[i];
        leftTopX = pt.x < leftTopX ? pt.x : leftTopX;
        leftTopY = pt.y < leftTopY ? pt.y : leftTopY;
        rightBottomX = pt.x > rightBottomX ? pt.x : rightBottomX;
        rightBottomY = pt.y > rightBottomY ? pt.y : rightBottomY;
    }

    MAMapRect rect;//BMKMapRect
    rect.origin = MAMapPointMake(leftTopX, leftTopY);//BMKMapPointMake
    rect.size = MAMapSizeMake(rightBottomX - leftTopX, rightBottomY - leftTopY);//BMKMapSizeMake
    UIEdgeInsets padding = UIEdgeInsetsMake(175, 175, 175, 175);//这个自己根据需求调整
    MAMapRect fitRect = [self.mapView mapRectThatFits:rect edgePadding:padding];
    [self.mapView setVisibleMapRect:fitRect];
}

//7.设置折线的样式
- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id<MAOverlay>)overlay {
    if ([overlay isKindOfClass:[MAPolyline class]]){
        MAPolylineRenderer *polylineView = [[MAPolylineRenderer alloc] initWithOverlay:overlay];//BMKPolylineView
        polylineView.strokeColor = [UIColor colorWithRed:51/255.0 green:149/255.0 blue:255/255.0 alpha:1.0];//折线颜色
        polylineView.lineWidth = 6.0; //折线宽度
        polylineView.lineJoinType = kMALineJoinRound;//连接类型
        return polylineView;//BMKOverlayView
    }
    return nil;
}

//8.根据数据生成地图标注视图
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation {

    if ([annotation isKindOfClass:[BBXPassAnnotation class]]){
        MAAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"passenger"];//BMKAnnotationView

        if (!annotationView){
            annotationView = [[MAAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"passenger"];
        }
        annotationView.canShowCallout = NO;
        BBXPassAnnotation * ann = (BBXPassAnnotation *)annotation;
        if ([ann.type isEqualToString:@"start"]) { //起点(司机所在地)
            annotationView.image = [UIImage imageNamed:@"icon_map_designatedDriver"];//代驾
            annotationView.centerOffset = CGPointMake(0, -13);
        } else if ([ann.type isEqualToString:@"Pop"]){ //终点(叫代驾人所在地),弹出泡泡
            annotationView.image = [UIImage imageNamed:@"grab_pop"];
            self.distancelabel = [[UILabel alloc] init];
            [annotationView addSubview:self.distancelabel];
            self.distancelabel.frame = CGRectMake(0, 0, annotationView.bounds.size.width, annotationView.bounds.size.height - 5);
            self.distancelabel.textAlignment = NSTextAlignmentCenter;
            self.distancelabel.text = ann.title;
            self.distancelabel.font  = [UIFont boldSystemFontOfSize:15];
            self.distancelabel.textColor = [UIColor whiteColor];
            annotationView.centerOffset = CGPointMake(0, -annotationView.bounds.size.height/2 - 30);
        } else if ([ann.type isEqualToString:@"end"]){ //终点(叫代驾人所在地)
            annotationView.image = [UIImage imageNamed:@"grab_start"];
            annotationView.centerOffset = CGPointMake(0, -13);
        }
        return annotationView;
    }
    return nil;
}

//9.用户位置更新后,会调用此函数(userLocation 新的用户位置)
- (void)compositeManager:(AMapNaviCompositeManager *_Nonnull)compositeManager updateNaviLocation:(AMapNaviLocation *_Nullable)naviLocation {
    CLLocation *listLocation = [[CLLocation alloc] initWithLatitude:naviLocation.coordinate.latitude longitude:naviLocation.coordinate.longitude];
    self.mapView.centerCoordinate = listLocation.coordinate;//更新地图中心点,不然偶尔会跑到北京天安门去
}

2.2设置途经点画折线

详情参考百度地图开发相关总结

#para mark -- 途经点相关
//途经点 AMapGeoPoint 数组,目前最多支持6个途经点
@property (nonatomic, copy) NSArray<AMapGeoPoint *> *waypoints;

2.3其他备注

轨迹点:在调试过程中,对比用同样的方法 polylineWithPoints 画折线的过程中,轨迹点高德地图返回结果为经纬度,百度地图返回结果好像为坐标,再考虑换地图开发的时候注意一下。

高德地图返回结果:经纬度
百度地图返回结果:好像坐标?
百度地图返回结果:好像坐标?

6.地图画区域图形多边形遮盖物

场景:根据需求确定某个区域为上下车区域,超出这个区域即提醒用户超出范围,如下图移动地图定位在蓝色区域内可以上车,上方气泡展示该点的地址或者提示语,这个蓝色区域就是根据接口返回的几个点生成的自定义多边形。

其他例子参考:
https://www.jianshu.com/p/fb1177cac1ec?open_source=weibo_search&ivk_sa=1024320u
https://blog.csdn.net/xuanweihong_ios/article/details/90573787

区域多边形效果图

区域多边形效果图g

7.地图气泡的聚合功能

点聚合功能:可通过缩小地图层级,将定义范围内的多个标注点聚合显示成一个标注点,解决加载大量点要素到地图上产生覆盖现象的问题并提高性能。 多个气泡标注点可以聚合成一个气泡,点击聚合的气泡可以分解成多个具体的气泡,也可以通过缩放地图进行气泡聚合和分开。

场景需求描述:如下图刚进入接客地图的时候,需要根据司机当前位置和所有点的位置进行布局展示,要求计算出一个合适的范围能合理展示所有气泡和司机位置,气泡点如果是同一个位置或者两个点相差在50米及以内则将点聚合在一起展示。在最初的地图展示之后,点击单个气泡则气泡变蓝色且弹出该气泡的底部面板展示订单信息和操作(如导航按钮);如果点击聚合气泡则分解在地图上以合适的比例尺展示出所有分解的点,缩放地图的时候,如果气泡缩小很近很近融合在一起展示新的聚合气泡,再缩放放大地图,则聚合气泡随着放大的比例尺也会慢慢分解开展示具体的气泡。

具体讲解:https://www.jianshu.com/p/29794c293e7e

聚合效果图

地图官方效果图

刚进入接客地图展示所有点
点击3单聚合气泡分解成3个具体气泡
缩放地图气泡进行融合成大气泡

8.App内部地图导航开发(包含更换终点重新导航)

App内部导航就是在App中集成地图导航SDK,按导航开发文档接入配置好相关配置比如Key,在需要导航的地方再进行相关代码API进行调用地图导航相关的类进行导航,其中需要用到起终点的相关坐标。需要注意的是进入导航中的这个导航页,高德地图导航页是个控制器,而百度地图是地图的一个view不是控制器,在做页面跳转啥的时候需要注意考虑这个。

更换终点重新导航:高德地图官方暂时没有开放API实现导航中改变终点重新导航,后续再总结。这边用另外2个方法代替,本质都是再次同样调用发起导航算咯方法。

效果图:

接客地图导航按钮入口

方法1:进入导航中
方法1:进入导航中-缩放效果
方法1:进入导航中-导航页3D图:是个控制器
方法2:进入导航选择页
方法2:开始导航进入导航页.PNG
方法2:进入导航选择页VC-导航页3D图

相关代码:
1.AppDelegate.m 中进行导航相关配置

地图相关配置

2.BBXRepDrivePickMapViewController.m 代驾接客地图导航按钮入口

BBXRepDrivePickMapViewController.m 代驾接客地图导航按钮回调方法

BBXMapDriverGuiderManager中跳转高德导航页面,2种方式

3.开始导航
方法1:自定义导航控制器 BBXMapDriverGuideViewController

1.BBXMapDriverGuideViewController.h
#import <UIKit/UIKit.h>
#import "BBXOrderViewModel.h"
#import "BBXBusPassengerOrderModel.h"

NS_ASSUME_NONNULL_BEGIN

@interface BBXMapDriverGuideViewController : BBXBaseViewController

@property (nonatomic, strong) BBXOrderViewModel *orderViewModel;
@property (nonatomic, copy) void(^carTakeUPBlock)();//接客地图导航
@property (nonatomic, copy) void(^carOffBlock)();
@property (nonatomic, copy) void(^updatePhoneCallback)(void);
@property (nonatomic, assign) BOOL isOrderListJump;//地图导航是否是从任务列表跳过来
@property (nonatomic, assign) NSInteger viewTag;

@end
NS_ASSUME_NONNULL_END
2.BBXMapDriverGuideViewController.m(重点看核心代码)
#import "BBXMapDriverGuideViewController.h"
#import <AMapNaviKit/AMapNaviKit.h>
#import "BBXLocationManager.h"
#import "BBXMapNavigationPopupView.h"
#import "BBXOrderInfo.h"
#import "BBXCallUtil.h"
#import "BBXAudioManager.h"
#import "AppDelegate+TCP.h"
#import "BBXPassengerOrderDetail.h"
#import "BBXMapDriverGuideManager.h"
#import "BBXMapGuideViewModel.h"
#import <CoreLocation/CLLocationManager.h>

@interface BBXMapDriverGuideViewController ()<AMapNaviDriveManagerDelegate,AMapNaviDriveViewDelegate,AMapNaviDriveDataRepresentable,BBBXMapNavigationPopupViewDelegate, AMapNaviCompositeManagerDelegate>

@property (nonatomic, strong) AMapNaviDriveView *driveView;
@property (nonatomic, strong) AMapNaviCompositeManager *compositeManager;
@property (nonatomic, strong) BBXMapNavigationPopupView *mapNavigationPopupView;//导航信息弹窗
@property (nonatomic, strong) UIButton *exitMapButton;//退出导航按钮
@property (nonatomic, strong) UILabel *routeInfoLab;
@property (nonatomic, strong) AMapNaviPoint *endPoint;
@property (nonatomic, assign) NSInteger messageType; //接收到的后台通知类型
@property (nonatomic, strong) CLLocationManager *clManager;
@property (nonatomic, strong) BBXMapGuideViewModel *mapGuideVM;
@property (nonatomic, assign) BOOL isLoadAMap;//是否加载了高德地图

@end

@implementation BBXMapDriverGuideViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.messageType = -1;//初始化为无效值
    [self initDriveView];//创建导航地图页面
    [self initDriveManager];//导航相关类
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController.navigationBar setHidden:YES];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.navigationController.navigationBar setHidden:YES];
    if (self.mapNavigationPopupView.superview == nil) {
        if(self.orderViewModel) {
            [self.view addSubview:self.mapNavigationPopupView];
            self.mapNavigationPopupView.orderViewModel = self.orderViewModel;
        }
    }
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.navigationController.navigationBar setHidden:NO];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [[AMapNaviDriveManager sharedInstance] stopNavi];
    [[AMapNaviDriveManager sharedInstance] removeDataRepresentative:self.driveView];
    [[AMapNaviDriveManager sharedInstance] setDelegate:nil];
    BOOL success = [AMapNaviDriveManager destroyInstance];
    NSLog(@"单例是否销毁成功 : %d",success);
    if (self.mapNavigationPopupView) {
        [self.mapNavigationPopupView removeFromSuperview];
        self.mapNavigationPopupView = nil;
    }
}

#pragma mark -- 初始化页面、数据 (核心代码)
//创建导航地图页面
- (void)initDriveView {
    
    if (self.driveView == nil) {
        self.driveView = [[AMapNaviDriveView alloc] initWithFrame:self.view.bounds];
        self.driveView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        self.driveView.showUIElements = YES;
        self.driveView.showBrowseRouteButton = NO;
        self.driveView.showGreyAfterPass = YES;
        self.driveView.showMode = AMapNaviDriveViewShowModeCarPositionLocked;
        self.driveView.trackingMode = AMapNaviViewTrackingModeCarNorth;
        self.driveView.autoSwitchShowModeToCarPositionLocked = YES;
        self.driveView.showMoreButton = YES;
        UIView *mapTopView = [self.driveView valueForKey:@"topInfoContainerView"];
        CGFloat y = mapTopView?CGRectGetMaxY(mapTopView.frame):145;
        CGFloat width = 240;
        self.routeInfoLab = [[UILabel alloc] initWithFrame:CGRectMake((SCREEN_WIDTH-width)*0.5, y, width, 40)];
        self.routeInfoLab.backgroundColor = HEXACOLOR(0x1F2B3C, 0.94);
        self.routeInfoLab.textColor = [UIColor whiteColor];
        self.routeInfoLab.font = [UIFont systemFontOfSize:16];
        self.routeInfoLab.layer.cornerRadius = 6.0;
        self.routeInfoLab.layer.masksToBounds = YES;
        self.routeInfoLab.textAlignment = NSTextAlignmentCenter;
        self.routeInfoLab.hidden = YES;
        [mapTopView.superview insertSubview:self.routeInfoLab atIndex:0];
        [self.driveView setDelegate:self];
        [[AMapNaviDriveManager sharedInstance] addDataRepresentative:self];
        [self.view addSubview:self.driveView];
        if(self.orderViewModel) {
            [self.view addSubview:self.mapNavigationPopupView];//添加地图导航弹窗
            self.routeInfoLab.hidden = NO;
            self.mapNavigationPopupView.orderViewModel = self.orderViewModel;
        }
    }
}

//导航相关类
- (void)initDriveManager {
    
    [[AMapNaviDriveManager sharedInstance] setDelegate:self];
    [[AMapNaviDriveManager sharedInstance] setMultipleRouteNaviMode:YES];//多路线
    [AMapNaviDriveManager sharedInstance].isUseInternalTTS = YES;
    //将driveView添加为导航数据的Representative,使其可以接收到导航诱导数据
    [[AMapNaviDriveManager sharedInstance] addDataRepresentative:self.driveView];
    
    //导航起点终点经纬度
    CLLocationDegrees dest_lat = 0;//纬度
    CLLocationDegrees dest_lng = 0;//经度
    BBXOrderListModel *orderListModel = self.orderViewModel.orderModel;
    dest_lat = orderListModel.start_lat;
    dest_lng = orderListModel.start_lng;
    CLLocationCoordinate2D startCoor = [BBXLocationManager shareManager].location.coordinate;//导航起点
    CLLocationCoordinate2D endCoor = CLLocationCoordinate2DMake(dest_lat, dest_lng);//导航终点
    //将经纬度坐标转换为投影后的直角地理坐标
    MKMapPoint startPoint = MKMapPointForCoordinate(startCoor);
    MKMapPoint endPoint = MKMapPointForCoordinate(endCoor);
    self.endPoint = [AMapNaviPoint locationWithLatitude:dest_lat longitude:dest_lng];
    //计算指定两点之间的距离
    float distance= MKMetersBetweenMapPoints(startPoint,endPoint);
    if (distance < 100) {
        NSString *spekStr = @"距离太近,不能导航";
        [BBXAudioManager sharedAudioManager].spekType = @"0";
        [[AppDelegate shareAppDelegate] judgeVoicePlayWithModel:nil Speking:spekStr spekType:[BBXAudioManager sharedAudioManager].spekType];
        [BBXProgressHUD showInfo:spekStr];
        [BBXProgressHUD dismiss];
        [self.navigationController popViewControllerAnimated:YES];
        return;
    }
    CLLocationCoordinate2D gaodeStart = startCoor;
    CLLocationCoordinate2D gaodeEnd = endCoor;
    AMapNaviPOIInfo *startInfo = [[AMapNaviPOIInfo alloc] init];
    startInfo.locPoint = [AMapNaviPoint locationWithLatitude:gaodeStart.latitude longitude:gaodeStart.longitude];
    AMapNaviPOIInfo *endInfo = [[AMapNaviPOIInfo alloc] init];
    endInfo.locPoint = [AMapNaviPoint locationWithLatitude:gaodeEnd.latitude longitude:gaodeEnd.longitude];
    //根据高德POIInfo进行驾车路径规划发起算路
    if (distance >= 100*1000) {
        [[AMapNaviDriveManager sharedInstance] calculateDriveRouteWithStartPOIInfo:startInfo endPOIInfo:endInfo wayPOIInfos:nil drivingStrategy:AMapNaviDrivingStrategyMultipleShortestTimeDistance];
    } else {
        [[AMapNaviDriveManager sharedInstance] calculateDriveRouteWithStartPOIInfo:startInfo endPOIInfo:endInfo wayPOIInfos:nil drivingStrategy:AMapNaviDrivingStrategyMultipleDefault];
    }
}

#pragma mark -- 驾车导航管理类 AMapNaviDriveManagerDelegate (核心代码)
//驾车路径规划成功后的回调函数
- (void)driveManager:(AMapNaviDriveManager *)driveManager onCalculateRouteSuccessWithType:(AMapNaviRoutePlanType)type {
    
    //算路成功后开始GPS实时导航
    [[AMapNaviDriveManager sharedInstance] startGPSNavi];//注意:必须在路径规划成功的情况下,才能够开始实时导航
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.driveView.showUIElements = YES;
        UIView *laneInfoView = [self.driveView valueForKey:@"laneInfoView"];
        laneInfoView.y += 50;
        UIView *mapTopView = [self.driveView valueForKey:@"topInfoContainerView"];
        CGFloat y = mapTopView?CGRectGetMaxY(mapTopView.frame)+10:145;
        self.routeInfoLab.y = y;
        [self.view addSubview:self.exitMapButton];
    });
}

//驾车路径规划失败后的回调函数
- (void)driveManager:(AMapNaviDriveManager *)driveManager onCalculateRouteFailure:(NSError *)error {
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self initDriveManager];
        });
    } else {
        [BBXProgressHUD showErrorWithStatus:@"路径规划失败,请重新规划"];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
        });
    }
}

//导航信息更新回调
- (void)driveManager:(AMapNaviDriveManager *)driveManager updateNaviInfo:(nullable AMapNaviInfo *)naviInfo{
    NSLog(@"%@", naviInfo);
    UIView *bottomInfoView = [self.driveView valueForKey:@"bottomInfoView"];
    UILabel *remainInfoLab = [bottomInfoView valueForKey:@"remainInfoLabelPor"];
    if (remainInfoLab) {
        NSString *text = remainInfoLab.text;
        NSInteger routeTrafficLights = naviInfo.routeRemainTrafficLightCount;
        NSArray *array = [text componentsSeparatedByString:@"\n"];
        if (array.count >= 1) {
            text = [array firstObject];
        }
        text = [text stringByReplacingOccurrencesOfString:@"剩余" withString:@""];
        text = [text stringByAppendingString:@"   "];
        NSMutableAttributedString *attri = [[NSMutableAttributedString alloc] init];
        NSTextAttachment *attch = [[NSTextAttachment alloc] init];
        attch.image = kImage(@"bbx_roundtraffic");
        NSAttributedString *imageText = [NSAttributedString attributedStringWithAttachment:attch];
        NSAttributedString *frontText = [[NSAttributedString alloc] initWithString:text];
        NSAttributedString *backText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", routeTrafficLights]];
        [attri appendAttributedString:frontText];
        [attri appendAttributedString:imageText];
        [attri appendAttributedString:backText];
        //text = [text stringByAppendingString:[NSString stringWithFormat:@"        🚥%ld个", routeTrafficLights]];
        //text = [text stringByReplacingOccurrencesOfString:@"\n" withString:[NSString stringWithFormat:@" 🚥%ld         ", (long)routeTrafficLights]];
        self.routeInfoLab.attributedText = attri;
    }
}

//导航界面更多按钮点击时的回调函数
- (void)driveViewMoreButtonClicked:(AMapNaviDriveView *)driveView {
    AMapNaviCompositeUserConfig *userConfig = [[AMapNaviCompositeUserConfig alloc] init];
    BOOL ret = [userConfig setRoutePlanPOIType:AMapNaviRoutePlanPOITypeEnd location:self.endPoint name:@"北京" POIId:nil];
    [userConfig setStartNaviDirectly:YES];
    [userConfig setShowVoiceAssistEnable:YES];
    [self.compositeManager presentRoutePlanViewControllerWithOptions:userConfig];
}

//发生错误时,会调用此方法
- (void)compositeManager:(AMapNaviCompositeManager *_Nonnull)compositeManager error:(NSError *_Nonnull)error {
    NSLog(@"error:%@", error);
}

#pragma mark AMapNaviDriveViewDelegate
//导航界面关闭按钮点击时的回调函数
- (void)driveViewCloseButtonClicked:(AMapNaviDriveView *)driveView{
    [self.navigationController popViewControllerAnimated:YES];
}

#pragma mark -- 底部订单面板信息view代理方法
//右滑控件值变化的代理方法;orderModel:当前订单model
- (void)mapNavigationPopupViewSliderValueChangingWith:(BBXOrderListModel *)orderModel {
    
    if (orderModel.order_status == Order_state_sended) {//已派单 -- 司机对指定订单执行上车操作
        if (self.isOrderListJump == YES) {//订单列表跳进来滑动底部按钮
            WeakSelf
            [self.mapGuideVM passengerGetOnWithOrderModel:orderModel completeBlock:^(NSDictionary * _Nonnull responseDict) {
                StrongSelf
                //公式计价并且乘客已上车,则刷新价格
                if (orderModel.showInGongshijija) {
                    [BBXOrderInfo shareManager].priceBlock = ^(NSDictionary *priceDict) {
                        if (!self) return;
                        dispatch_async(dispatch_get_main_queue(), ^{
                            [self.orderViewModel refreshPrice];
                            self.mapNavigationPopupView.orderViewModel = weakSelf.orderViewModel;//更新数据
                        });
                    };
                }
                //改变终点(司机接到乘客后,导航终点变成乘客要去的那个点),重新导航
                [self.navigationController popViewControllerAnimated:YES];
            }];
            /*
            __weak typeof(self)weakSelf = self;
            [[NetworkClient sharedInstance] passengerGetOnWithOrderModel:orderModel completeBlock:^(NSDictionary *responseDictionary) {
                NSInteger status = [responseDictionary[@"status"] integerValue];
                //网络请求失败
                if (status != 0) {
                    NSString *message = responseDictionary[@"message"];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [SKToast showWithText:message];
                    });
                    return;
                }
                
                //公式计价并且乘客已上车,则刷新价格
                if (orderModel.showInGongshijija) {
                    [OrderInfo shareManager].priceBlock = ^(NSDictionary *priceDict) {
                        if (!weakSelf) return;
                        dispatch_async(dispatch_get_main_queue(), ^{
                            [weakSelf.orderViewModel refreshPrice];
                            weakSelf.mapNavigationPopupView.orderViewModel = weakSelf.orderViewModel;//更新数据
                        });
                    };
                }
                //改变终点(司机接到乘客后,导航终点变成乘客要去的那个点),重新导航
                [self.navigationController popViewControllerAnimated:YES];
            }];*/
        } else {
            //接客地图-进入导航页里面滑动接客时候的回调
            [self.navigationController popViewControllerAnimated:YES];
            if (self.carTakeUPBlock) {
                self.carTakeUPBlock();
            }
        }
    } else if (orderModel.order_status == Order_state_get_on) {//已上车 -- 退出导航
        [self.navigationController popViewControllerAnimated:YES];
        if (self.carOffBlock) {
            self.carOffBlock();
        }
    }
}

#pragma mark -- 按钮响应
//退出导航
- (void)exitMap {
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
    } else {
        [self dismissViewControllerAnimated:YES completion:nil];
    }
}

#pragma mark -- 懒加载
//导航组件类
- (AMapNaviCompositeManager *)compositeManager {
    if (!_compositeManager) {
        _compositeManager = [[AMapNaviCompositeManager alloc] init];
        _compositeManager.delegate = self;//如果需要使用AMapNaviCompositeManagerDelegate的相关回调(如自定义语音、获取实时位置等),需要设置delegate
    }
    return _compositeManager;
}

//导航底部信息弹窗
- (BBXMapNavigationPopupView *)mapNavigationPopupView {
    if (!_mapNavigationPopupView) {
        _mapNavigationPopupView = [[BBXMapNavigationPopupView alloc] initWithFrame:CGRectMake(0, ([UIScreen mainScreen].bounds.size.height - 190), [UIScreen mainScreen].bounds.size.width, 205)];
        _mapNavigationPopupView.delegate = self;
    }
    return _mapNavigationPopupView;
}

//退出导航按钮
- (UIButton *)exitMapButton {
    if (!_exitMapButton) {
        UIImage * image = kImage(@"BBXMapNaviManager_exit");
        //获取状态栏的高度
        CGFloat statusHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
        CGFloat x = [UIScreen mainScreen].bounds.size.width - 15 - 50;
        CGFloat y = 30 + statusHeight;
        _exitMapButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _exitMapButton.frame = CGRectMake(x, y, 50, 50);
        [_exitMapButton setImage:image forState:UIControlStateNormal];
        [_exitMapButton addTarget:self action:@selector(exitMap) forControlEvents:UIControlEventTouchUpInside];
    }
    return _exitMapButton;
}

//处理订单业务逻辑刷新等
- (BBXMapGuideViewModel *)mapGuideVM {
    if (!_mapGuideVM) {
        _mapGuideVM = [[BBXMapGuideViewModel alloc]init];
        _mapGuideVM.orderRefresnCB = ^(BBXOrderListModel *newOrderModel) {
            self.orderViewModel = [[BBXOrderViewModel alloc] initWithOrderModel:newOrderModel];
            dispatch_async(dispatch_get_main_queue(), ^{
                self.mapNavigationPopupView.orderViewModel = self.orderViewModel;//根据更新后的当前订单model信息,更新右滑控、地址信息、订单类型等界面信息
            });
        };
    }
    return _mapGuideVM;
}

- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
//    [[AMapNaviDriveManager sharedInstance] stopNavi];
//    [[AMapNaviDriveManager sharedInstance] removeDataRepresentative:self.driveView];
//    [[AMapNaviDriveManager sharedInstance] setDelegate:nil];
//    BOOL success = [AMapNaviDriveManager destroyInstance];
//    NSLog(@"单例是否销毁成功 : %d",success);
}

@end

方法2:先选择导航方案:自定义导航控制器 BBXMapCompositeViewController

1.BBXMapCompositeViewController.h
#import <Foundation/Foundation.h>
#import "BBXOrderViewModel.h"
#import "BBXMapOtherInfoModel.h"

NS_ASSUME_NONNULL_BEGIN

@class BBXBusPassengerOrderModel;

@interface BBXMapCompositeViewController : NSObject

@property (nonatomic, strong) BBXOrderViewModel *orderViewModel;
@property (nonatomic, strong) BBXBusPassengerOrderModel *busflightModel;
@property (nonatomic, strong) BBXMapOtherInfoModel *otherInfo;
@property (nonatomic, assign) BOOL isOffStation; // 是否下车点位
- (void)showGaodeCompositeVC;
- (void)showGaodeCompositeFlightVC;

@end
NS_ASSUME_NONNULL_END
2.BBXMapCompositeViewController.m
#import "BBXMapCompositeViewController.h"
#import <AMapNaviKit/AMapNaviKit.h>
#import <ReactiveObjC/ReactiveObjC.h>
#import "UIViewController+BBX.h"

static AMapNaviCompositeManager *s_compositeManager;
@interface BBXMapCompositeViewController ()<AMapNaviCompositeManagerDelegate>

@property (nonatomic, strong) AMapNaviCompositeManager *compositeManager;

@end

@implementation BBXMapCompositeViewController

- (void)showGaodeCompositeVC {
    if (!self.orderViewModel) {
        return;
    }
    BBXOrderListModel *orderListModel = self.orderViewModel.orderModel;
    AMapNaviPoint *endPoint = nil;
    NSString *destAddress = nil;
    if (orderListModel.order_status == Order_state_get_on) {
        CLLocationCoordinate2D endCoor = {orderListModel.end_lat, orderListModel.end_lng};
        endPoint = [AMapNaviPoint locationWithLatitude:endCoor.latitude longitude:endCoor.longitude];
        destAddress = orderListModel.endAddress;
    } else {
        CLLocationCoordinate2D endCoor = {orderListModel.start_lat, orderListModel.start_lng};
        endPoint = [AMapNaviPoint locationWithLatitude:endCoor.latitude longitude:endCoor.longitude];
        destAddress = orderListModel.startAddress;
    }
    AMapNaviCompositeUserConfig *userConfig = [[AMapNaviCompositeUserConfig alloc] init];
    //设置起点、终点、途径点(最多支持三个途径点)来调起路线规划页面,自动完成算路. 如果不设置起点,默认使用的是“我的位置”
    [userConfig setRoutePlanPOIType:AMapNaviRoutePlanPOITypeEnd location:endPoint name:destAddress POIId:nil];
    [userConfig setShowVoiceAssistEnable:YES];
    [self.compositeManager presentRoutePlanViewControllerWithOptions:userConfig];//导航组件页面
}

- (AMapNaviCompositeManager *)compositeManager {
    if (!_compositeManager) {
        _compositeManager = [[AMapNaviCompositeManager alloc] init];
        _compositeManager.delegate = self;//如果需要使用AMapNaviCompositeManagerDelegate的相关回调(如自定义语音、获取实时位置等),需要设置delegate
//        s_compositeManager = _compositeManager;
    }
    return _compositeManager;
}

//退出导航(退到上一个页面即选择导航方案页面)
- (void)compositeManager:(AMapNaviCompositeManager *_Nonnull)compositeManager didBackwardAction:(AMapNaviCompositeVCBackwardActionType)backwardActionType {
    if (self.otherInfo.backEventCB) {
        self.otherInfo.backEventCB();
    }
}

@end

更换终点重新导航

更换终点重新导航:高德地图官方暂时没有开放API实现导航中改变终点重新导航,后续再总结。这边用另外2个方法代替,本质都是再次同样调用发起导航算咯方法。
方法1:退出导航自动再次调用发起导航算路进入导航;开始导航之后在接到第一个乘客滑动接到乘客回调,回调成功之后退出导航同时判断是否还有下一个有序接客订单,有的话则再调用开始导航的回调进入导航中,这个过程需要将退出导航和进入导航的动画关闭,这样会显得中间过渡的效果相对快速好一点不那么卡顿。
方法2:导航中不退出直接再次调用发起导航算路的方法;开始导航之后在接到第一个乘客滑动接到乘客回调,回调成功之后再次同样调用发起导航算路的方法,地图会自动刷新切换到下一个导航的地点,但是这样有一个bug就是切换的时候不会播放语音:“准备出发,全程多少公里大致需要多少分钟啥的”,这个后续再研究。

高德技术咨询-暂时没有开放API实现导航中改变终点重新导航
智能调度有途经点效果图;点需要放大地图看具体位置,如点3看着不在折线上放大地图看就在折线。这个以百度地图为例展示效果,为了方便demo没有换高德地图,效果一样的只是高德地图样式稍微有点不同
开始导航-进入高德地图导航,滑动接到乘客进行更换终点重新导航

方法1:退出导航自动再进入

接客地图-开始导航按钮回调方法传值跳转高德地图进行导航

高德地图核心代码-获取经纬度开始导航1
高德地图核心代码-获取经纬度开始导航2
接客地图-退出再调用开始导航按钮回调进入导航

方法2:不退出导航直接更换终点

接客地图-开始导航按钮回调方法传值跳转高德地图进行导航

高德地图核心代码-获取经纬度开始导航1
高德地图核心代码-获取经纬度开始导航2
接客地图-更换终点重新导航
接客地图-重新导航发起算路
高德地图-刷新订单面板,判断是否退出高德导航页面

高德地图导航需要遵循协议

在使用高德地图导航的时候需要遵循相关协议,进入导航的时候通过弹框让用户同意这个协议否则无法进行导航,用户如果同意了则后面不再弹框,如果不同意则每次进入导航都弹框。协议链接:https://lbs.amap.com/pages/privacy/

高德地图相关协议

核心代码:

1.弹框管理类-AMapPrivacyUtility.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN

typedef void(^PrivacyUtilBlock)();

/*
 * 隐私合规使用demo 工具类
 */
@interface AMapPrivacyUtility : NSObject

/**
 * @brief 通过这个方法来判断是否同意隐私合规
 * 1.如果没有同意隐私合规,则创建的SDK manager 实例返回 为nil, 无法使用SDK提供的功能
 * 2.如果同意了下次启动不提示 的授权,则不会弹框给用户
 * 3.如果只同意了,则下次启动还要给用户弹框提示
 */
+ (BOOL)handlePrivacyAgreeStatus:(PrivacyUtilBlock)Handler;

@end
NS_ASSUME_NONNULL_END


2.弹框管理类-AMapPrivacyUtility.m(弹框UI通过BBXGaodeMapPrivacyView的xib绘制,此处省略)
#import "AMapPrivacyUtility.h"
#import <AMapNaviKit/AMapNaviKit.h>
#import <UIKit/UIKit.h>
#import "BBXGaodeMapPrivacyView.h"

@implementation AMapPrivacyUtility

+ (void)showPrivacyInfoInWindow:(UIWindow *)window handler:(PrivacyUtilBlock)handler {
    
    NSArray * nib = [[NSBundle mainBundle] loadNibNamed:@"BBXGaodeMapPrivacyView" owner:self options:nil];
    BBXGaodeMapPrivacyView *privacyView = [nib objectAtIndex:0];
    privacyView.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    privacyView.disagreeBlock = ^{
        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"agreeStatus"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        //更新用户授权高德SDK隐私协议状态. since 8.1.0
        [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusNotAgree];
        //[privacyView removeFromSuperview];
    };
    privacyView.agreeBlock = ^{
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"agreeStatus"];
        //更新用户授权高德SDK隐私协议状态. since 8.1.0
        [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
        [[AMapNaviManagerConfig sharedConfig] updatePrivacyShow:AMapPrivacyShowStatusDidShow privacyInfo:AMapPrivacyInfoStatusDidContain];
        // 通知地图导航页刷新
        if (handler) {
            handler();
        }
    };
    [[[UIApplication sharedApplication].windows lastObject] addSubview:privacyView];
}

+ (BOOL)handlePrivacyAgreeStatus:(PrivacyUtilBlock)handler {
    //判断是否同意了隐私协议下次不提示
    if(![[NSUserDefaults standardUserDefaults] boolForKey:@"agreeStatus"]){
        //添加隐私合规弹窗
        [self showPrivacyInfoInWindow:[UIApplication sharedApplication].delegate.window handler:handler];
        return NO;
    }
    [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
    [[AMapNaviManagerConfig sharedConfig] updatePrivacyShow:AMapPrivacyShowStatusDidShow privacyInfo:AMapPrivacyInfoStatusDidContain];
    return YES;
}

@end

3.高德地图导航VC-BBXMapDriverGaodeViewController.m 中调用
- (void)viewDidLoad {
    [super viewDidLoad];
    self.messageType = -1; //初始化为无效值
    //判断是否遵循协议进行弹框
    BOOL isPrivacy = [AMapPrivacyUtility handlePrivacyAgreeStatus:^{
        [self initGuideMapData];
    }];
    
    if (isPrivacy) {
        [self initGuideMapData];
    }
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(gaodeNaviUpdateMapNaviPopupView:) name:@"GaodeNaviUpdateMapNaviPopupView" object:nil];//导航中更换终点导航刷新订单面板
}

9.地图根据经纬度进行逆编码

逆编码:即逆地理编码也叫反编码,指的是将经纬度转换成地理位置信息,如地名、所在的省份或城市等,地图提供了相应的API可以方便调用。
相关链接:https://blog.csdn.net/chongran9230/article/details/100804174

正地理编码:也直接叫地理编码,指的是将地理位置名称转换成经纬度。

10.地图SDK导入工程及plist权限添加

高德地图相关SDK:地图、导航、定位、鹰眼
高德地图使用相关权限

判断手机App地图定位权限是否有打开:

举例:如果App定位权限不是“始终”,则提醒打开
- (void)jugdeIsOpenAlwaysLocation {
    
    //如果用户选择的定位权限不是“始终”,则弹窗让用户修改权限
    if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedAlways) {
        UIAlertController * alertController = [UIAlertController alertControllerWithTitle:@"提示" message:@"定位权限请选择“始终”,否则服务无法使用!" preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            [self.navigationController popToRootViewControllerAnimated:YES];
        }];
        UIAlertAction * okAction = [UIAlertAction actionWithTitle:@"前往开启" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
        }];
        [alertController addAction:cancelAction];
        [alertController addAction:okAction];
        [self presentViewController:alertController animated:YES completion:nil];
        return;
    }
}

11.导航中添加自定义view(包含剩余距离和时间)

如下图,可以在导航中添加自定义view,比如按钮、底部订单面板、剩余距离和时间 view 等等;可以看到下图有添加面板和没有面板的效果。

1.按钮、底部订单面板

没有加面板
没有加面板-点击一下地图效果,可以点继续导航恢复到上面效果
有添加面板页面

高德地图导航VC核心代码:(部分回调啥的删减)

#import "BBXMapDriverGaodeViewController.h"
#import <AMapNaviKit/AMapNaviKit.h>
#import "LocationManager.h"
#import "BBXMapNavigationPopupView.h"
#import "NetworkClient+Driver.h"
#import "OrderInfo.h"
#import "NetworkClient+Order.h"
#import "BBXCallUtil.h"
#import "Request.h"
#import "BBXAudioManager.h"
#import "AppDelegate+TCP.h"
#import "BBXPassengerOrderDetail.h"
#import "AMapPrivacyUtility.h"
#import "BBXMapPrivacyViewController.h"
#import "BBXMapDriverGaodeManager.h"
#import <CoreLocation/CLLocationManager.h>

@interface BBXMapDriverGaodeViewController ()<AMapNaviDriveManagerDelegate,AMapNaviDriveViewDelegate,AMapNaviDriveDataRepresentable,BBBXMapNavigationPopupViewDelegate, AMapNaviCompositeManagerDelegate>

@property (nonatomic, strong) AMapNaviDriveView *driveView;
@property (nonatomic, strong) AMapNaviCompositeManager *compositeManager;
@property (nonatomic, strong) BBXMapNavigationPopupView *mapNavigationPopupView;//导航信息弹窗
@property (nonatomic, strong) UIButton *exitMapButton;//退出导航按钮
@property (nonatomic, strong) UILabel *routeInfoLab;
@property (nonatomic, strong) NSTimer *dataTimer;
@property (nonatomic, strong) AMapNaviPoint *endPoint;
@property (nonatomic, assign) NSInteger messageType; //接收到的后台通知类型(当后台操作乘客上车时类型为5,如果是公式计价单,要开启实时计价)
@property (nonatomic, strong) CLLocationManager *clManager;
@property (nonatomic, assign) BOOL isLoadAMap;//是否加载了高德地图

@end

@implementation BBXMapDriverGaodeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.messageType = -1;//初始化为无效值
    BOOL isPrivacy = [AMapPrivacyUtility handlePrivacyAgreeStatus:^{
        [self initGuideMapData];
    }];
    if (isPrivacy) {
        [self initGuideMapData];
    }
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(gaodeNaviUpdateMapNaviPopupView:) name:@"GaodeNaviUpdateMapNaviPopupView" object:nil];//导航中更换终点导航刷新订单面板
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController.navigationBar setHidden:YES];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self.navigationController.navigationBar setHidden:YES];
    if (self.mapNavigationPopupView.superview == nil) {
        if(self.orderViewModel || self.multipleModel) {
            [self.view addSubview:self.mapNavigationPopupView];
            if (self.multipleModel) {
                [self.mapNavigationPopupView setViewModel:self.orderViewModel multipleModel:self.multipleModel viewTag:self.viewTag];
            } else {
                self.mapNavigationPopupView.orderViewModel = self.orderViewModel;
            }
        }
    }
    if (self.busflightModel) {
        
    } else {
        //启动5秒刷新1次订单的定时器
        if(!_dataTimer && self.orderViewModel.orderModel.showInGongshijija) {
            NSInteger timeInterval = 5;
            _dataTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(refreshEvent) userInfo:nil repeats:YES];
        }
        //公式计价并且乘客已上车,则刷新价格
        if (self.orderViewModel.orderModel.showInGongshijija &&
            self.orderViewModel.orderModel.order_status >= Order_state_get_on) {
            @weakify(self);
            [OrderInfo shareManager].priceBlock = ^(NSDictionary *priceDict) {
                @strongify(self);
                if (!self) return
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.orderViewModel refreshPrice];
                    self.mapNavigationPopupView.orderViewModel = self.orderViewModel;//更新数据
                });
            };
        }
    }
}

- (void)viewDidDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidDisappear:animated];
    if(self.dataTimer){
        [self.dataTimer invalidate];
        self.dataTimer = nil;
    }
    [self.navigationController.navigationBar setHidden:NO];
    [[AMapNaviDriveManager sharedInstance] stopNavi];
    [[AMapNaviDriveManager sharedInstance] removeDataRepresentative:self.driveView];
    [[AMapNaviDriveManager sharedInstance] setDelegate:nil];
    BOOL success = [AMapNaviDriveManager destroyInstance];
    NSLog(@"单例是否销毁成功 : %d",success);
    if (self.mapNavigationPopupView) {
        [self.mapNavigationPopupView removeFromSuperview];
        self.mapNavigationPopupView = nil;
    }
}

- (void)initGuideMapData {
    [self initDriveView];
    [self initDriveManager];//开始导航
}

//UI
- (void)initDriveView {
    if (self.driveView == nil) {
        self.driveView = [[AMapNaviDriveView alloc] initWithFrame:self.view.bounds];
        self.driveView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        self.driveView.showUIElements = YES;
        self.driveView.showBrowseRouteButton = NO;
        self.driveView.showGreyAfterPass = YES;
        self.driveView.showMode = AMapNaviDriveViewShowModeCarPositionLocked;
        self.driveView.trackingMode = AMapNaviViewTrackingModeCarNorth;
        self.driveView.autoSwitchShowModeToCarPositionLocked = YES;
        self.driveView.showMoreButton = YES;
        UIView *mapTopView = [self.driveView valueForKey:@"topInfoContainerView"];
        CGFloat y = mapTopView?CGRectGetMaxY(mapTopView.frame):145;
        CGFloat width = 240;
        self.routeInfoLab = [[UILabel alloc] initWithFrame:CGRectMake((SCREEN_WIDTH-width)*0.5, y, width, 40)];
        self.routeInfoLab.backgroundColor = HEXACOLOR(0x1F2B3C, 0.94);
        self.routeInfoLab.textColor = [UIColor whiteColor];
        self.routeInfoLab.font = [UIFont systemFontOfSize:16];
        self.routeInfoLab.layer.cornerRadius = 6.0;
        self.routeInfoLab.layer.masksToBounds = YES;
        self.routeInfoLab.textAlignment = NSTextAlignmentCenter;
        self.routeInfoLab.hidden = YES;
        [mapTopView.superview insertSubview:self.routeInfoLab atIndex:0];
        [self.driveView setDelegate:self];
        [[AMapNaviDriveManager sharedInstance] addDataRepresentative:self];
        [self.view addSubview:self.driveView];
        if(self.orderViewModel || self.multipleModel){
            //添加地图导航弹窗
            [self.view addSubview:self.mapNavigationPopupView];
            self.routeInfoLab.hidden = NO;
            if (self.multipleModel) {
                [self.mapNavigationPopupView setViewModel:self.orderViewModel multipleModel:self.multipleModel viewTag:self.viewTag];
            } else {
                self.mapNavigationPopupView.orderViewModel = self.orderViewModel;
            }
        }
    }
}

//订单刷新通知响应函数
- (void)refreshEvent {
    if (!self.orderViewModel) return;
    __weak typeof(self)weakSelf = self;
    //获取调度id
    [[NetworkClient sharedInstance] requestOrderListWithCompleteBlock:^(NSDictionary *responseObject) {
        NSArray *IDArray = responseObject[@"list"];
        NSMutableDictionary *paraDict;
        if (!IDArray || IDArray.count == 0) return;
        paraDict = [responseObject mutableCopy];
        [paraDict setObject:[[NSMutableArray alloc] init] forKey:@"complationArr"];
        //根据调度id请求订单列表
        [Request requestGetOrderListsWithDict:paraDict AndComplation:^(NSDictionary *dict) {
            if (!dict || [dict[@"status"] integerValue] != 0) return;
            if (isNullArray(dict[@"list"])) return;
            NSArray <NSDictionary *> *listArr = dict[@"list"];
            if (listArr.count == 0) return;
            NSArray <OrderListModel *>*orderArr = [[NSArray alloc] init];
            NSMutableArray * mutArr = [[NSMutableArray alloc] init];
            for (NSDictionary * dic in listArr) {
                NSArray * arr = dic[@"order_arr"];
                if (arr != nil) {
                    [mutArr addObjectsFromArray:arr];
                }
            }
            orderArr = [mutArr copy];
            if (orderArr.count == 0) return;
            //更新当前订单model信息
            OrderListModel *oldOrderModel = self.orderViewModel.orderModel;
            NSPredicate *predicate = [NSPredicate predicateWithFormat:@"order_id == %@", oldOrderModel.order_id];
            OrderListModel *newOrderModel = [[orderArr filteredArrayUsingPredicate:predicate] firstObject];
            if (self.messageType == 5) {//后台操作乘客上车,判断是否是公式计价的订单,如果是的话要开启实时计价
                if (newOrderModel.order_type == OrderType_city ||
                    newOrderModel.order_type == OrderType_DesignatedDriver){
                    //市内公式计价(代驾的计价方式与市内一样)
                    BOOL isInCityGongshi = ((newOrderModel.order_type == OrderType_city || newOrderModel.order_type == OrderType_DesignatedDriver) && newOrderModel.calc_type == 1);
                    //城际包车公式计价
                    BOOL isCharteredGongshi = newOrderModel.bussiness_type != 0 && newOrderModel.order_type == OrderType_CarChartered;
                    if (isInCityGongshi) {
                        //如果是市内公式计价,再判断订单id是否是要计价的订单id
                        newOrderModel.showInGongshijija = [newOrderModel.order_id isEqualToString:[BBXDriverInfo shareManager].onCarOrder_id];
                    } else if (isCharteredGongshi) {
                        newOrderModel.showInGongshijija = YES;
                    }
                   //启动5秒刷新1次订单的定时器
                    if(!weakSelf.dataTimer) {
                      NSInteger timeInterval = 5;
                       weakSelf.dataTimer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(refreshEvent) userInfo:nil repeats:YES];
                   }
                } else {
                   newOrderModel.showInGongshijija = oldOrderModel.showInGongshijija;
                }
            } else {
                newOrderModel.showInGongshijija = oldOrderModel.showInGongshijija;
            }
            if (newOrderModel) {
                self.orderViewModel = [[OrderViewModel alloc] initWithOrderModel:newOrderModel];
                //根据更新后的当前订单model信息,更新右滑控、地址信息、订单类型等界面信息
                dispatch_async(dispatch_get_main_queue(), ^{
                    self.mapNavigationPopupView.orderViewModel = self.orderViewModel;
                });
            }
        }];
    }];
}

//开始导航-网约司机
- (void)initDriveManager {
    [[AMapNaviDriveManager sharedInstance] setDelegate:self];
    [[AMapNaviDriveManager sharedInstance] setMultipleRouteNaviMode:YES];//多路线
    [AMapNaviDriveManager sharedInstance].isUseInternalTTS = YES;
    //将driveView添加为导航数据的Representative,使其可以接收到导航诱导数据
    [[AMapNaviDriveManager sharedInstance] addDataRepresentative:self.driveView];
    
    CLLocationDegrees dest_lat = 0;//纬度
    CLLocationDegrees dest_lng = 0;//经度
    OrderListModel *orderListModel = self.orderViewModel.orderModel;
    //终点坐标经纬度
    if (orderListModel.order_status == 6 || orderListModel.order_status == Order_state_sended) {//接客 (那个6不懂)
        dest_lat = orderListModel.start_lat;
        dest_lng = orderListModel.start_lng;
    } else {//送客
        dest_lat = orderListModel.end_lat;
        dest_lng = orderListModel.end_lng;
    }
    //导航起点终点经纬度
    CLLocationCoordinate2D startCoor = [LocationManager shareManager].location.coordinate;//导航起点
    CLLocationCoordinate2D endCoor = CLLocationCoordinate2DMake(dest_lat, dest_lng);//导航终点
    //将经纬度坐标转换为投影后的直角地理坐标
    MKMapPoint startPoint = MKMapPointForCoordinate(startCoor);
    MKMapPoint endPoint = MKMapPointForCoordinate(endCoor);
    self.endPoint = [AMapNaviPoint locationWithLatitude:dest_lat longitude:dest_lng];
    //计算指定两点之间的距离
    float distance= MKMetersBetweenMapPoints(startPoint,endPoint);
    if (distance < 100) {
        NSString *spekStr = @"距离太近,不能导航";
        [BBXAudioManager sharedAudioManager].spekType = @"0";
        [[AppDelegate shareAppDelegate] judgeVoicePlayWithModel:nil Speking:spekStr spekType:[BBXAudioManager sharedAudioManager].spekType];
        [SKToast showWithText:spekStr];
        [SVProgressHUD dismiss];
        [self.navigationController popViewControllerAnimated:YES];
        return;
    }
    CLLocationCoordinate2D gaodeStart = [HZLocationConverter transformFromBaiduToGCJ:startCoor];
    CLLocationCoordinate2D gaodeEnd = [HZLocationConverter transformFromBaiduToGCJ:endCoor];
    AMapNaviPOIInfo *startInfo = [[AMapNaviPOIInfo alloc] init];
    startInfo.locPoint = [AMapNaviPoint locationWithLatitude:gaodeStart.latitude longitude:gaodeStart.longitude];
    AMapNaviPOIInfo *endInfo = [[AMapNaviPOIInfo alloc] init];
    endInfo.locPoint = [AMapNaviPoint locationWithLatitude:gaodeEnd.latitude longitude:gaodeEnd.longitude];
    if (distance >= 100*1000) {//大于100公里用不一样的策略
        [[AMapNaviDriveManager sharedInstance] calculateDriveRouteWithStartPOIInfo:startInfo endPOIInfo:endInfo wayPOIInfos:nil drivingStrategy:AMapNaviDrivingStrategyMultipleShortestTimeDistance];
    } else {
        [[AMapNaviDriveManager sharedInstance] calculateDriveRouteWithStartPOIInfo:startInfo endPOIInfo:endInfo wayPOIInfos:nil drivingStrategy:AMapNaviDrivingStrategyMultipleDefault];
    }
}

//退出导航按钮回调
- (void)exitMap {
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
    }
    else {
        [self dismissViewControllerAnimated:YES completion:nil];
    }
}

- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark -- 导航相关协议AMapNaviDriveManagerDelegate、AMapNaviDriveViewDelegate
//驾车路径规划成功后的回调函数
- (void)driveManager:(AMapNaviDriveManager *)driveManager onCalculateRouteSuccessWithType:(AMapNaviRoutePlanType)type {
    NSLog(@"onCalculateRouteSuccess");
    //算路成功后开始GPS导航
    [[AMapNaviDriveManager sharedInstance] startGPSNavi];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        self.driveView.showUIElements = YES;
        UIView *laneInfoView = [self.driveView valueForKey:@"laneInfoView"];
        laneInfoView.originY += 50;
        UIView *mapTopView = [self.driveView valueForKey:@"topInfoContainerView"];
        CGFloat y = mapTopView?CGRectGetMaxY(mapTopView.frame)+10:145;
        self.routeInfoLab.originY = y;
        [self.view addSubview:self.exitMapButton];
    });
}

//驾车路径规划失败后的回调函数. 从5.3.0版本起,算路失败后导航SDK只对外通知算路失败,SDK内部不再执行停止导航的相关逻辑.因此,当算路失败后,不会收到 driveManager:updateNaviMode: 回调; AMapNaviDriveManager.naviMode 不会切换到 AMapNaviModeNone 状态, 而是会保持在 AMapNaviModeGPS or AMapNaviModeEmulator 状态.
- (void)driveManager:(AMapNaviDriveManager *)driveManager onCalculateRouteFailure:(NSError *)error{
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self initDriveManager];
        });
    } else {
        [SKToast showWithText:@"路径规划失败,请重新规划"];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
        });
    }
}

//导航界面更多按钮点击时的回调函数
- (void)driveViewMoreButtonClicked:(AMapNaviDriveView *)driveView {
    AMapNaviCompositeUserConfig *userConfig = [[AMapNaviCompositeUserConfig alloc] init];
    BOOL ret = [userConfig setRoutePlanPOIType:AMapNaviRoutePlanPOITypeEnd location:self.endPoint name:@"北京" POIId:nil];
    [userConfig setStartNaviDirectly:YES];
    [userConfig setShowVoiceAssistEnable:YES];
    [self.compositeManager presentRoutePlanViewControllerWithOptions:userConfig];
}

//导航信息更新回调
- (void)driveManager:(AMapNaviDriveManager *)driveManager updateNaviInfo:(nullable AMapNaviInfo *)naviInfo{
    NSLog(@"%@", naviInfo);
    UIView *bottomInfoView = [self.driveView valueForKey:@"bottomInfoView"];
    UILabel *remainInfoLab = [bottomInfoView valueForKey:@"remainInfoLabelPor"];
    if (remainInfoLab) {
        NSString *text = remainInfoLab.text;
        NSInteger routeTrafficLights = naviInfo.routeRemainTrafficLightCount;
        NSArray *array = [text componentsSeparatedByString:@"\n"];
        if (array.count >= 1) {
            text = [array firstObject];
        }
        text = [text stringByReplacingOccurrencesOfString:@"剩余" withString:@""];
        text = [text stringByAppendingString:@"   "];
        NSMutableAttributedString *attri = [[NSMutableAttributedString alloc] init];
        NSTextAttachment *attch = [[NSTextAttachment alloc] init];
        attch.image = [UIImage imageNamed:@"bbx_roundtraffic"];
        NSAttributedString *imageText = [NSAttributedString attributedStringWithAttachment:attch];
        NSAttributedString *frontText = [[NSAttributedString alloc] initWithString:text];
        NSAttributedString *backText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%ld", routeTrafficLights]];
        [attri appendAttributedString:frontText];
        [attri appendAttributedString:imageText];
        [attri appendAttributedString:backText];
//        text = [text stringByAppendingString:[NSString stringWithFormat:@"        🚥%ld个", routeTrafficLights]];
//        text = [text stringByReplacingOccurrencesOfString:@"\n" withString:[NSString stringWithFormat:@" 🚥%ld         ", (long)routeTrafficLights]];
        self.routeInfoLab.attributedText = attri;
    }
}

//发生错误时,会调用此方法
- (void)compositeManager:(AMapNaviCompositeManager *_Nonnull)compositeManager error:(NSError *_Nonnull)error {
    NSLog(@"error:%@", error);
}

//导航界面关闭按钮点击时的回调函数
- (void)driveViewCloseButtonClicked:(AMapNaviDriveView *)driveView{
    NSLog(@"");
    [self.navigationController popViewControllerAnimated:YES];
}

#pragma mark -- 懒加载
//导航信息弹窗
- (BBXMapNavigationPopupView *)mapNavigationPopupView {
    if (!_mapNavigationPopupView) {
        _mapNavigationPopupView = [[BBXMapNavigationPopupView alloc] initWithFrame:CGRectMake(0, ([UIScreen mainScreen].bounds.size.height - 190), [UIScreen mainScreen].bounds.size.width, 205)];
        _mapNavigationPopupView.delegate = self;
        //_mapNavigationPopupView.hidden = YES;//隐藏订单面板信息
    }
    return _mapNavigationPopupView;
}

//退出导航按钮
- (UIButton *)exitMapButton {
    if (!_exitMapButton) {
        UIImage * image = [UIImage imageNamed:@"BBXMapNaviManager_exit"];
        //获取状态栏的高度
        CGFloat statusHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
        CGFloat x = [UIScreen mainScreen].bounds.size.width - 15 - 50;
        CGFloat y = 30 + statusHeight;
        _exitMapButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _exitMapButton.frame = CGRectMake(x, y, 50, 50);
        [_exitMapButton setImage:image forState:UIControlStateNormal];
        [_exitMapButton addTarget:self action:@selector(exitMap) forControlEvents:UIControlEventTouchUpInside];
    }
    return _exitMapButton;
}

- (AMapNaviCompositeManager *)compositeManager {
    if (!_compositeManager) {
        _compositeManager = [[AMapNaviCompositeManager alloc] init];//初始化
        _compositeManager.delegate = self;//如果需要使用AMapNaviCompositeManagerDelegate的相关回调(如自定义语音、获取实时位置等),需要设置delegate
    }
    return _compositeManager;
}

@end

2.剩余距离和时间

剩余距离和时间效果图
剩余距离和时间 view
获取剩余距离和时间的高德 API 方法
高德地图相关 API

12.集中简单看下关键类以及方法

相关链接:https://www.jianshu.com/p/9102ca8e72ca

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

推荐阅读更多精彩内容