百度地图开发相关总结

目录

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

百度地图官方文档:https://lbsyun.baidu.com/index.php?title=%E9%A6%96%E9%A1%B5
高德地图开发相关总结:https://www.jianshu.com/p/770728626874

1.坐标经纬度初始化方法

  • 坐标初始化写法(3种)
1. CLLocationCoordinate2D coords = (CLLocationCoordinate2D){0, 0};//纬度,经度
2. CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(39.915352,116.397105);//纬度,经度
3.常用方法:
CLLocation *location = [[CLLocation alloc] initWithLatitude:startLat longitude:startLng];
CLLocationCoordinate2D coordinate = location.coordinate;
CLLocationCoordinate2D startCoor = CLLocationCoordinate2DMake(startLat, startLng);

补充:其中initWithLatitude方法说明如下
- (instancetype)initWithLatitude:(CLLocationDegrees)latitude
    longitude:(CLLocationDegrees)longitude;

说明1:
@property (nonatomic,copy) NSString *lat;//纬度
@property (nonatomic,copy) NSString *lng;//经度
NSString *latStr = self.addressModel.lat;
NSString *lngStr = self.addressModel.lng;
CLLocationDegrees lat = [latStr doubleValue];
CLLocationDegrees lng = [lngStr doubleValue];
CLLocation * userLocation = [[CLLocation alloc] initWithLatitude:lat longitude:lng];

说明2:
float _lat; 或 CGFloat _lat;
float _lng; 或 CGFloat _lng;
CLLocation *location = [[CLLocation alloc] initWithLatitude:_lat longitude:_lng];
其他举例:最好还是不要直接用字符串形式上传经纬度,这边是用经纬度
其他举例:最好还是不要直接用字符串形式上传经纬度,这边是用经纬度
其他举例
  • 将CLLocationCoordinate2D类型转换为数字或字符串
CLLocationCoordinate2D centerCoord; 
centerCoord.latitude = self.locModel.userLocation.coordinate.latitude; 
centerCoord.longitude = self.locModel.userLocation.coordinate.longitude; 
NSString * tmpLat = [[NSString alloc] initWithFormat:@"%g",centerCoord.latitude]; 
NSString * tmpLong = [[NSString alloc] initWithFormat:@"%g",centerCoord.longitude]; 
NSLog("用户的纬度为:%@",tmpLat); 
NSLog("用户的经度是:%@",tmpLong); 
  • 将字符串转换成CLLocationCoordinate2D类型
NSString *latitude = @“25.678”;
NSString *longitude = @“116.789”;
CLLocationDegrees dest_lat = [latitude doubleValue];
CLLocationDegrees dest_lng = [longitude doubleValue];
CLLocationCoordinate2D endCoor = CLLocationCoordinate2DMake(dest_lat, dest_lng);

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

App内打开第三方地图进行导航;举例以下例子:后台用的百度坐标,返回百度坐标,可以直接打开百度地图。如果要打开高德和腾讯则需要先把坐标转成火星坐标再去调用,不然就会有定位偏差。注意:调用百度的时候下面链接里面的这个方法会有定位偏差错误(@"baidumap://map/direction?origin={{我的位置}}&destination=latlng:%@,%@|name=北京&mode=driving&coord_type=gcj02"),使用这个方法正确不会偏差(@"baidumap://map/direction?origin={{我的位置}}&destination=%f,%f&mode=driving&src=ios.LanHai.%@")

苹果原生地图(苹果 ios 用的是 CoreLocation 定位框架,其获取的是地球坐标;苹果对于火星地图在使用 isShowUserLocation=YES 时用的是火星坐标)、高德地图(火星坐标:对地球坐标处理之后得到的)、百度地图(熊掌坐标或叫百度坐标,对火星坐标处理)。定位如果使用 CLLocationManager 实现定位服务,通过 GPS 设备获得的坐标是地球坐标(真实的经纬度坐标;其坐标是球面坐标的),在国内使用时需要转换成火星坐标。高德地图和百度地图都不直接使用苹果原生的 CLLocationManager 实现定位,而是对其做了二次封装,提供了自己的定位服务的 API,通过高德地图或百度地图定位服务获得的坐标已经是修正后的“火星坐标或百度坐标,就不要通过代码进行坐标转换”。

相关链接:
https://www.jianshu.com/p/1768de507727
https://blog.csdn.net/ruglcc/article/details/52023274
https://www.jianshu.com/p/ada613ed5549
https://www.jianshu.com/p/fbf25d379959

常用地图坐标系介绍:

  • WGS-84:是国际标准,GPS坐标(Google Earth使用、或者GPS模块)
  • GCJ-02:中国坐标偏移标准,Google地图、高德、腾讯使用
  • BD-09 :百度坐标偏移标准,Baidu地图使用

下面.m文件坐标转换常用的第三方HZLocationConverter:https://juejin.im/post/6844903591090585607

  • 封装打开第三方地图方法的代码
.h  文件
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSUInteger, BBXOpenThreeMapManagerType) {

    BBXOpenThreeMapManagerType_Baidu, //百度
    BBXOpenThreeMapManagerType_Gaode, //高德
    BBXOpenThreeMapManagerType_Tenxun, //腾讯
    BBXOpenThreeMapManagerType_Apple, //苹果
};

@interface BBXOpenThreeMapManager : NSObject

@property (nonatomic, assign) BBXOpenThreeMapManagerType openThreeMapType;

// 调用第三方地图导航
+ (void)gotoThreeMapToNavgationWithMapType:(BBXOpenThreeMapManagerType)type Longitude:(NSString *)longitude Latitude:(NSString *)latitude;

@end
.m文件(坐标转换用的第三方:HZLocationConverter)
// 调用第三方地图导航
+ (void)gotoThreeMapToNavgationWithMapType:(BBXOpenThreeMapManagerType)type Longitude:(NSString *)longitude Latitude:(NSString *)latitude {
    
    if (type == BBXOpenThreeMapManagerType_Baidu) {// 百度地图
        
        if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"baidumap://"]]) {
            
            NSString *appName = [NSBundle mainBundle].infoDictionary[@"CFBundleDisplayName"];
            NSString *urlString1 = [[NSString stringWithFormat:@"baidumap://map/direction?origin={{我的位置}}&destination=%f,%f&mode=driving&src=ios.LanHai.%@",[latitude doubleValue],[longitude doubleValue],appName] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];//帮邦行司机端bundle:com.LanHai.bbxDriver
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString1]];
        } else {
            
            [SKToast showWithText:@"您未安装地图APP,无法导航"];
        }
    } else if (type == BBXOpenThreeMapManagerType_Gaode) {// 高德地图-GCJ-02火星坐标(后台传回来的是百度坐标,需要转换)
        
        if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"iosamap://"]]) {
            
            CLLocationDegrees dest_lat = [latitude doubleValue];
            CLLocationDegrees dest_lng = [longitude doubleValue];
            CLLocationCoordinate2D endCoor = CLLocationCoordinate2DMake(dest_lat, dest_lng);
            CLLocationCoordinate2D gaodeLoca = [HZLocationConverter transformFromBaiduToGCJ:endCoor];
            
            NSString *urlString2 = [[NSString stringWithFormat:@"iosamap://navi?sourceApplication=%@&backScheme=%@&lat=%f&lon=%f&dev=0&style=2",@"导航功能",@"nav123456",gaodeLoca.latitude,gaodeLoca.longitude] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString2]];
        } else {
            
            [SKToast showWithText:@"您未安装地图APP,无法导航"];
        }
    } else if (type == BBXOpenThreeMapManagerType_Tenxun) {// 腾讯地图-GCJ-02火星坐标
        
        if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"qqmap://"]]) {
            
            CLLocationDegrees dest_lat = [latitude doubleValue];
            CLLocationDegrees dest_lng = [longitude doubleValue];
            CLLocationCoordinate2D endCoor = CLLocationCoordinate2DMake(dest_lat, dest_lng);
            CLLocationCoordinate2D gaodeLoca = [HZLocationConverter transformFromBaiduToGCJ:endCoor];
            
            NSString *urlString3 = [[NSString stringWithFormat:@"qqmap://map/routeplan?from=我的位置&type=drive&tocoord=%f,%f&to=终点&coord_type=1&policy=0",gaodeLoca.latitude,gaodeLoca.longitude] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString3]];
        } else {
            
            [SKToast showWithText:@"您未安装地图APP,无法导航"];
        }
    } else if (type == BBXOpenThreeMapManagerType_Apple) {// 苹果地图
        
            CLLocationCoordinate2D loc = CLLocationCoordinate2DMake([latitude doubleValue], [longitude doubleValue]);//终点坐标
            MKMapItem *currentLoc = [MKMapItem mapItemForCurrentLocation];//用户位置
            MKMapItem *toLocation = [[MKMapItem alloc]initWithPlacemark:[[MKPlacemark alloc]initWithCoordinate:loc addressDictionary:nil] ];
            NSArray *items = @[currentLoc,toLocation];//终点位置
            NSDictionary *dic = @{
                                  MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving,
                                  MKLaunchOptionsMapTypeKey : @(MKMapTypeStandard),
                                  MKLaunchOptionsShowsTrafficKey : @(YES)
                                  };
            [MKMapItem openMapsWithItems:items launchOptions:dic];
    } else {
        
        NSLog(@"没有发现地图");
    }
}

- (void)setOpenThreeMapType:(BBXOpenThreeMapManagerType)openThreeMapType {
    
    self.openThreeMapType = openThreeMapType;
}

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?:@""
                                          }
                             };
    [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:[BBXURL getReplaceAllDriveDistribution] toCrypt:YES];//获取代驾司机分布接口
}

3.地图定位代理方法

#pragma mark -- 地图定位代理方法:BMKLocationManagerDelegate
//设备朝向回调方法(heading设备朝向)
- (void)BMKLocationManager:(BMKLocationManager *)manager didUpdateHeading:(CLHeading *)heading {
    if (!heading) {
        return;
    }
    self.userLocation.heading = heading;//设备朝向
    [self.mapView updateLocationData:self.userLocation];//更新当前设备朝向
}

//地图区域改变完成后会调用此接口(位置变更的回调如拖动地图)
- (void)mapView:(BMKMapView *)mapView regionDidChangeAnimated:(BOOL)animated reason:(BMKRegionChangeReason)reason {
    
    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];
}

#para mark -- 实现该方法,否则定位图标不出现
//连续定位回调方法(location定位结果)
- (void)BMKLocationManager:(BMKLocationManager *)manager didUpdateLocation:(BMKLocation *)location orError:(NSError *)error {
    if (error) {
        NSLog(@"locError:{%ld - %@};", (long)error.code, error.localizedDescription);
    }
    if (!location) {
        return;
    }
    self.userLocation.location = location.location;//司机当前位置信息
    [self.mapView updateLocationData:self.userLocation];//实现该方法,否则定位图标不出现
}

4.地图代理方法

#pragma mark -- 地图代理方法:BMKMapViewDelegate
//地图初始化完毕时会调用此接口
- (void)mapViewDidFinishLoading:(BMKMapView *)mapView {
    
    [self.mapView setCenterCoordinate:[LocationManager shareManager].location.coordinate]; //设置当前地图中心点
}

//根据anntation生成对应的View(设置大头针样式)
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id <BMKAnnotation>)annotation {
    
    //int x = arc4random()%(100-1+1)+1;
    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;
    annotationView2.distribuModel = bbx.distributrModel;
    annotationView2.canShowCallout = NO;
    annotationView2.centerOffset=CGPointMake(0,0);
    return annotationView2;
}

//当选中一个annotation views时调用 - 点击大头针(自定义视图)
- (void)mapView:(BMKMapView *)mapView didSelectAnnotationView:(BMKAnnotationView *)view {
    
    if ([view isKindOfClass:[BBXRepDriveAnnotationView class]]) {
        BBXRepDriveAnnotationView *annotationview = (BBXRepDriveAnnotationView *)view;
    }
}

//当点击annotation view弹出的泡泡时调用 - 点击大头针上气泡
- (void)mapView:(BMKMapView *)mapView annotationViewForBubble:(BMKAnnotationView *)view {
    if ([view isKindOfClass:[BBXRepDriveAnnotationView class]]) {

    }
}

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

//初始化地图
- (void)initMap {
    self.mapView = [[BMKMapView alloc] init];
    self.mapView.delegate = self;
    [self.view addSubview:self.mapView];
    [self.mapView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    [self.mapView setZoomLevel:18];//地图缩放级别
    self.mapView.zoomEnabled = NO;//不能多点缩放
    self.mapView.zoomEnabledWithTap = NO;//不能缩放
    self.mapView.overlookEnabled = NO;//能否支持俯仰角
    self.mapView.showsUserLocation = NO;//设定是否显示定位图层
    self.mapView.userTrackingMode = BMKUserTrackingModeHeading;//普通定位模式
    [self.locationManager startUpdatingLocation];//开始连续定位
    [self.locationManager startUpdatingHeading];//开始获取设备朝向(GPS)
    
    //定位图层自定义样式参数
    self.param = [[BMKLocationViewDisplayParam alloc] init];
    self.param.isAccuracyCircleShow = NO;//是否显示精度圈
    self.param.canShowCallOut = NO;//是否显示气泡
    self.param.locationViewImage = [UIImage imageNamed:@"bbx_carLocation"];//定位图标名称
    self.mapView.rotateEnabled = NO;//设定地图View能否支持旋转
    self.mapView.showsUserLocation = NO;//设定是否显示定位图层
    self.param.locationViewHierarchy = LOCATION_VIEW_HIERARCHY_TOP;//定位图标一直处于上层
    self.param.locationViewOffsetY = 0;//定位图标Y轴偏移量(屏幕坐标)
    [self.mapView updateLocationViewWithParam:self.param];//动态定制我的位置样式
}

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

//定位管理类
- (BMKLocationManager *)locationManager {
    if (!_locationManager) {
        _locationManager = [[BMKLocationManager alloc] init];
        _locationManager.delegate = self;
        _locationManager.coordinateType = BMKLocationCoordinateTypeBMK09LL;//设定定位坐标系类型,默认为 BMKLocationCoordinateTypeGCJ02
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;//设定定位精度,默认为 kCLLocationAccuracyBest
        _locationManager.activityType = CLActivityTypeAutomotiveNavigation;//设定定位类型,默认为 CLActivityTypeAutomotiveNavigation
        _locationManager.pausesLocationUpdatesAutomatically = NO;//指定定位是否会被系统自动暂停,默认为NO
        _locationManager.allowsBackgroundLocationUpdates = NO;//是否允许后台定位,默认为NO。只在iOS 9.0及之后起作用。设置为YES的时候必须保证 Background Modes 中的 Location updates 处于选中状态,否则会抛出异常。由于iOS系统限制,需要在定位未开始之前或定位停止之后,修改该属性的值才会有效果。
        _locationManager.locationTimeout = 10;//指定单次定位超时时间,默认为10s,最小值是2s。注意单次定位请求前设置。注意: 单次定位超时时间从确定了定位权限(非kCLAuthorizationStatusNotDetermined状态)后开始计算。
    }
    return _locationManager;
}

其中上述代码中涉及的相关类:

6.自定义大头针view
BBXRepDriveAnnotationView.h

#import <BaiduMapAPI_Map/BMKAnnotationView.h>
#import "BBXRepDriveDistributeModel.h"
NS_ASSUME_NONNULL_BEGIN

@interface BBXRepDriveAnnotationView : BMKAnnotationView

@property (nonatomic, strong) BBXRepDriveDistributeModel *distribuModel;
@property (nonatomic, strong) UIImageView *pinBgView;

@end

NS_ASSUME_NONNULL_END

BBXRepDriveAnnotationView.m

#import "BBXRepDriveAnnotationView.h"

@implementation BBXRepDriveAnnotationView

- (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString*)reuseIdentifier {
    if(self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) {
        [self setBounds:CGRectMake(0,0,60,60)];
        [self addSubview:self.pinBgView];
    }
    return self;
}

-(void)setDistribuModel:(BBXRepDriveDistributeModel *)distribuModel {
    if (kStrEqual(distribuModel.show_status, @"1")) {//1:非空闲
        self.pinBgView.image = kImage(@"driverService");
    } else {//0:空闲
        self.pinBgView.image = kImage(@"icon_map_designatedDriver");
    }
}

- (UIImageView *)pinBgView {
    if (!_pinBgView) {
        _pinBgView = [[UIImageView alloc]init];
        [self addSubview:_pinBgView];
        _pinBgView.userInteractionEnabled = YES;
        _pinBgView.contentMode = UIViewContentModeCenter;
        [_pinBgView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.top.bottom.mas_equalTo(0);
        }];
    }
    return _pinBgView;
}

@end

7.地图中心蓝色定位点自定义view
BBXMapAnnotionView.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BBXMapAnnotionView : UIView

@property (strong, nonatomic) UIImageView *bgIconImageV;

//定位成功后,更新起点标志的显示
-(void)localGeoSuccessUpdateAnnotionUI:(NSString *)addressStr;

@end

NS_ASSUME_NONNULL_END

BBXMapAnnotionView.m

#import "BBXMapAnnotionView.h"

@implementation BBXMapAnnotionView

- (instancetype)init {
    self = [super init];
    if (self) {
        [self buildStartAnnotionView];
    }
    return self;
}

- (void)buildStartAnnotionView {
    self.bgIconImageV = [[UIImageView alloc] init];
    self.bgIconImageV.image = [UIImage imageNamed:@"driverNewStart"];
    [self addSubview:self.bgIconImageV];
    CGSize size = [UIImage imageNamed:@"driverNewStart"].size;
    [self.bgIconImageV mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(self.mas_top).mas_offset(5);
        make.left.mas_equalTo(self.mas_left);
        make.width.mas_equalTo(size.width);
        make.height.mas_equalTo(size.height);
    }];
}

//在非服务范围的提示
-(void)localUnServerAnnotationUI{
    self.bgIconImageV.image = [UIImage imageNamed:@""];//请选择服务范围内地址
    [self.bgIconImageV mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(self.mas_top).mas_offset(5);
        make.left.mas_equalTo(self.mas_left).offset(-35);
        make.width.mas_equalTo([NSNumber numberWithInt:190]);
        make.height.mas_equalTo([NSNumber numberWithInt:103]);
    }];
}

@end

8.BMKPointAnnotation一个点的annotation
BBXRepDrivePointAnnotation.h

#import <Foundation/Foundation.h>
#import <BaiduMapAPI_Map/BMKMapComponent.h>

@class BBXRepDriveDistributeModel;

NS_ASSUME_NONNULL_BEGIN

@interface BBXRepDrivePointAnnotation : BMKPointAnnotation

@property (nonatomic, strong) BBXRepDriveDistributeModel *distributrModel;
/** 标注点的protocol,提供了标注类的基本信息函数*/
@property (nonatomic, weak) id<BMKAnnotation> delegate;

@end

NS_ASSUME_NONNULL_END

BBXRepDrivePointAnnotation.m

#import "BBXRepDrivePointAnnotation.h"

@implementation BBXRepDrivePointAnnotation

@end

9.司机分布model:
BBXRepDriveDistributeModel.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface DistributeLocation :NSObject
@property (nonatomic , copy) NSString              * lat;
@property (nonatomic , copy) NSString              * lng;

@end

@interface DistributeId :NSObject
@property (nonatomic , copy) NSString              * distributeOid;

@end

@interface Current_location :NSObject
@property (nonatomic , copy) NSString              * ex_lat;
@property (nonatomic , copy) NSString              * ex_lng;

@end

@interface BBXRepDriveDistributeModel :NSObject
@property (nonatomic , copy) NSString              * car_class_name;
@property (nonatomic , strong) DistributeLocation              * location;
@property (nonatomic , copy) NSString              * lot_iscity;
@property (nonatomic , copy) NSString              * status;
@property (nonatomic , copy) NSString              * app_channel;
@property (nonatomic , strong) DistributeId              * distributeId;
@property (nonatomic , copy) NSString              * ch_self_status;
@property (nonatomic , copy) NSString              * dis;
@property (nonatomic , copy) NSString              * line_id;
@property (nonatomic , copy) NSString              * type;
@property (nonatomic , copy) NSString              * az;
@property (nonatomic , copy) NSString              * uid;
@property (nonatomic , strong) Current_location              * current_location;
@property (nonatomic , copy) NSString              * atime;
@property (nonatomic , copy) NSString              * car_no;
@property (nonatomic , copy) NSString              * show_status;
@property (nonatomic , copy) NSString              * driver_type;
@property (nonatomic , copy) NSString              * al;
@property (nonatomic , copy) NSString              * iv;
@property (nonatomic , copy) NSArray<NSString *>              * line_ids;
@property (nonatomic , copy) NSString              * app;

@end

BBXRepDriveDistributeModel.m

#import "BBXRepDriveDistributeModel.h"

@implementation DistributeLocation

@end

@implementation DistributeId

//自定义属性名
+ (NSDictionary *)mj_replacedKeyFromPropertyName {//MJ
    return @{@"distributeOid":@"$oid"};
}

+ (NSDictionary *)modelCustomPropertyMapper {//YYModel
    return @{@"distributeOid":@"$oid"};
}

@end

@implementation Current_location

@end

@implementation BBXRepDriveDistributeModel

//自定义类名
+ (NSDictionary *)objectClassInArray {//MJ
    return @{@"_id" : @"DistributeId",@"location" : @"DistributeLocation"};
}

+ (NSDictionary *)modelContainerPropertyGenericClass {//YYModel
    return @{@"_id" : [DistributeId class],@"location" : [DistributeLocation class]};
}

//自定义属性名
+ (NSDictionary *)mj_replacedKeyFromPropertyName {//MJ
    return @{@"distributeId":@"_id"};
}

+ (NSDictionary *)modelCustomPropertyMapper {//YYModel
    return @{@"distributeId":@"_id"};
}

@end

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

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

App导航路径规划效果图

百度地图App导航效果图

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

1.准备工作

1.引入文件名以及协议
#import <BaiduMapAPI_Map/BMKMapView.h>
#import <BaiduMapAPI_Map/BMKMapComponent.h>
#import <BaiduMapAPI_Utils/BMKGeometry.h>
#import <BaiduMapAPI_Base/BMKBaseComponent.h>
#import <BaiduMapAPI_Search/BMKSearchComponent.h>
协议:<BMKMapViewDelegate,BMKRouteSearchDelegate, BNNaviUIManagerDelegate,BMKLocationManagerDelegate,BNNaviRoutePlanDelegate>

2.相关属性及视图创建
@property (nonatomic, strong) BMKMapView *mapView;//地图
@property (nonatomic, strong) BMKLocationManager *locationManager;//负责获取定位相关信息类
@property (nonatomic, strong) BMKUserLocation *userLocation;//司机当前位置对象
@property (nonatomic, strong) NSMutableArray *orderArray;//订单数据数组
@property (nonatomic, strong) BMKLocationViewDisplayParam *param;//定位图层图标
@property (nonatomic, strong) BMKRouteSearch *routeSearch;//路径规划

//初始化地图
- (void)initMap {
    
    self.mapView = [[BMKMapView alloc] initWithFrame:CGRectZero];
    self.mapView.delegate = self;
    self.mapZoomLevel = 15;//地图缩放级别
    [self.mapView setZoomLevel:self.mapZoomLevel];//级别,3-19
    [self.view addSubview:self.mapView];
    self.mapView.overlookEnabled = NO;//设定地图View能否支持俯仰角
    [self.mapView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    [self.locationManager startUpdatingLocation];//开始连续定位
    [self.locationManager startUpdatingHeading];//开始获取设备朝向(GPS)
    self.mapView.showsUserLocation = NO;//设定是否显示定位图层
    self.mapView.userTrackingMode = BMKUserTrackingModeHeading;//普通定位模式
    
    //定位图层自定义样式参数
    self.param = [[BMKLocationViewDisplayParam alloc] init];
    self.param.isAccuracyCircleShow = NO;//是否显示精度圈
    self.param.canShowCallOut = NO;//是否显示气泡
    self.param.locationViewImage = [UIImage imageNamed:@"bbx_carLocation"];//定位图标名称
    self.mapView.rotateEnabled = NO;//设定地图View能否支持旋转
    self.mapView.showsUserLocation = YES;//设定是否显示定位图层
    self.param.locationViewHierarchy = LOCATION_VIEW_HIERARCHY_TOP;//LocationView在mapview上显示的层级:最上面
    self.param.locationViewOffsetY = 0;//定位图标Y轴偏移量(屏幕坐标)
    [self.mapView updateLocationViewWithParam:self.param];//动态定制我的位置样式
}

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

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

//负责获取定位相关信息类
- (BMKLocationManager *)locationManager {
    if (!_locationManager) {
        //初始化BMKLocationManager类的实例
        _locationManager = [[BMKLocationManager alloc] init];
        //设置定位管理类实例的代理
        _locationManager.delegate = self;
        //设定定位坐标系类型,默认为 BMKLocationCoordinateTypeGCJ02
        _locationManager.coordinateType = BMKLocationCoordinateTypeBMK09LL;
        //设定定位精度,默认为 kCLLocationAccuracyBest
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        //设定定位类型,默认为 CLActivityTypeAutomotiveNavigation
        _locationManager.activityType = CLActivityTypeAutomotiveNavigation;
        //指定定位是否会被系统自动暂停,默认为NO
        _locationManager.pausesLocationUpdatesAutomatically = NO;
        /**
         是否允许后台定位,默认为NO。只在iOS 9.0及之后起作用。
         设置为YES的时候必须保证 Background Modes 中的 Location updates 处于选中状态,否则会抛出异常。
         由于iOS系统限制,需要在定位未开始之前或定位停止之后,修改该属性的值才会有效果。
         */
        _locationManager.allowsBackgroundLocationUpdates = NO;
        /**
         指定单次定位超时时间,默认为10s,最小值是2s。注意单次定位请求前设置。
         注意: 单次定位超时时间从确定了定位权限(非kCLAuthorizationStatusNotDetermined状态)
         后开始计算。
         */
        _locationManager.locationTimeout = 10;
    }
    return _locationManager;
}

//路线规划
- (BMKRouteSearch *)routeSearch {
    if (!_routeSearch) {
        _routeSearch = [[BMKRouteSearch alloc] init];
        _routeSearch.delegate = self;
    }
    return _routeSearch;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self initMap];//初始化地图
    [self refreshEvent:NO];//网络请求刷新界面
    [self setAddressAnnotation];//网络请求方法中获取到数据也会调用这个方法
}

2.核心代码

1.设置地图标注,路径规划初始化
//设置地图标注视图(即地图中显示“xx公里xx分钟”气泡)
- (void)setAddressAnnotation {
    
    if (self.orderArray.count == 0 || !self.orderArray) {
        [self.mapView removeAnnotations:[self.mapView annotations]];//移除当前地图View已经添加的标注数组
        return;
    }
    
    /*
     添加大头针
     设置完标注坐标后,后调用(- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id <BMKAnnotation>)annotation)代理方法
     可在该代理方法中设置大头针的样式
     */
    for (OrderListModel *orderModel in self.orderArray) {
        [self creatPlan:orderModel];//路径规划初始化(核心代码,看这个即可)
    }

    [self showAllAnnotationWithAllPointArray:self.allOrderPointArr];//设置地图的显示区域,展示所有点(这里的相关计算不讲放在地图聚合功能讲)
    //[self.mapView setCenterCoordinate:[LocationManager shareManager].location.coordinate];//设置当前地图的中心点,改变该值时地图的比例尺级别不会发生变化
    if (self.currentOrderModel) {//设置点击气泡后弹窗控件面板数据
        OrderViewModel *model = [[OrderViewModel alloc] initWithOrderModel:self.currentOrderModel];
        [self.mapPopupView setViewModel:model bbxMapVCType:BBXMapVCTypeWithNone];
    }
}

//初始化司机路径规划(骑行+驾车)
- (void)creatPlan:(OrderListModel *)model {
    
    if (model.order_type == OrderType_DesignatedDriver) {//代驾(骑行路线规划)
                
        BMKPlanNode *end = [[BMKPlanNode alloc] init];//终点(叫代驾或叫车的人的位置)
        CLLocationCoordinate2D coordinate;
        coordinate.latitude = model.start_lat;
        coordinate.longitude = model.start_lng;
        end.pt = model.startLocation.coordinate;
        __weak typeof(self)weakSelf = self;
        [[LocationManager shareManager] getDriverBeidouLocaition:^(CLLocationCoordinate2D coordinate) {//获取司机经纬度
            BMKPlanNode *start = [[BMKPlanNode alloc] init];//起点(代驾司机位置)
            start.pt = coordinate;
            //骑行路线规划
            BMKRidingRoutePlanOption *ridingRoutePlanOption= [[BMKRidingRoutePlanOption alloc] init];
            ridingRoutePlanOption.from = start;
            ridingRoutePlanOption.to = end;
            //发起骑行路径规划请求
            BOOL flag = [weakSelf.routeSearch ridingSearch:ridingRoutePlanOption];
            if (!flag){
                [SKToast showWithText:@"骑行规划检索发送失败"];
            }
        }];
    } else {//其他普通类型(驾车路线规划)
        
        BMKPlanNode *start = [[BMKPlanNode alloc] init];//起点(司机位置)
        CLLocationCoordinate2D coordinate1;
        coordinate1.latitude = [LocationManager shareManager].location.coordinate.latitude;
        coordinate1.longitude = [LocationManager shareManager].location.coordinate.longitude;
        start.pt = coordinate1;
        BMKPlanNode *end = [[BMKPlanNode alloc] init];
        CLLocationCoordinate2D coordinate;
        coordinate.latitude = model.start_lat;
        coordinate.longitude = model.start_lng;
        end.pt = model.startLocation.coordinate;
        
        //开车路线规划
        BMKDrivingRoutePlanOption * drivingRoutePlanOption= [[BMKDrivingRoutePlanOption alloc] init];
        drivingRoutePlanOption.from = start;
        drivingRoutePlanOption.to = end;
        drivingRoutePlanOption.drivingPolicy = BMK_DRIVING_DIS_FIRST;//传一个想要的规划策略给百度(最短距离,BMK_DRIVING_DIS_FIRST:1;百度默认0为时间最短)
        //节点数组
        NSMutableArray *nodesArray = [[NSMutableArray alloc]initWithCapacity:1];
        BMKPlanNode *startNode = [[BMKPlanNode alloc] init];
        startNode.pt = CLLocationCoordinate2DMake(model.start_lat, model.start_lng);
        startNode.name = @"fromeTip";
        [nodesArray addObject:startNode];
        drivingRoutePlanOption.wayPointsArray = nodesArray;//驾车途经点数组
        //发起驾车路径规划请求
        BOOL flag = [self.routeSearch drivingSearch:drivingRoutePlanOption];
        if (!flag){
            [SKToast showWithText:@"驾车路线规划检索发送失败"];
        } else {
            [SKToast showWithText:@"成功"];
        }
    }
}

2.路径规划结果回调方法
#pragma mark -- 路径规划代理方法
/**
 *返回驾车的路线绘制搜索结果
 *@param searcher 搜索对象
 *@param result 搜索结果,类型为BMKDrivingRouteResult
 *@param error 错误号,@see BMKSearchErrorCode
 */
//驾车路径规划
- (void)onGetDrivingRouteResult:(BMKRouteSearch*)searcher result:(BMKDrivingRouteResult*)result errorCode:(BMKSearchErrorCode)error {
    
    if (error == BMK_SEARCH_ST_EN_TOO_NEAR) {//起终点太近
        [SKToast showWithText:@"距离太近,无法规划路线"];
    } else {
        
        //检索结果正常返回、其他规划错误
        NSInteger minDis = NSIntegerMax;
        for (BMKDrivingRouteLine *route in result.routes) {
            
            //计算路径规划的距离
            NSInteger currentDis = route.distance;
            if (currentDis < minDis) {
                minDis = currentDis;
            }
            //计算路径规划的时间
            NSInteger driverTimeHours = route.duration.hours;
            NSInteger driverTimeMinutes = route.duration.minutes;
            NSArray *startOpint = route.wayPoints;
            BMKPlanNode *startNode = [[BMKPlanNode alloc] init];
            startNode = startOpint[0];//取第一个
            [self.mapView removeAnnotations:self.mapView.annotations];//移除当前地图View已经添加的标注数组
            
            NSArray *tmpArray = [self.orderArray copy];
            for (int i = 0; i<tmpArray.count; i++) {//路径规划核心代码看这个
                
                OrderListModel *models1 = tmpArray[i];
                if (models1.order_type == OrderType_Bus && !([models1.locations.start.area_level isEqualToString:@"1"] || [models1.locations.start.area_level isEqualToString:@"3"])) {//快线-并且站点名称无详细地址(后台返回经纬度只有小数点5位)
                    
                    NSDictionary *dataDict = [NSDictionary dictionaryWithJsonString:models1.locations.start.station_location];
                    if([dataDict isKindOfClass:[NSDictionary class]]) {
                        CLLocationDegrees lat = [dataDict[@"lat"] doubleValue];//如果字典为空lat取:CLLocationDegrees lat = [models1.locations.start.lat doubleValue];
                        CLLocationDegrees lng = [dataDict[@"lng"]doubleValue];//如果字典为空lng取models1.locations.start.lng
                        CLLocationCoordinate2D startPt = (CLLocationCoordinate2D){lat, lng};
                        if (startNode.pt.latitude - startPt.latitude > -0.0001 &&startNode.pt.latitude - startPt.latitude < 0.0019  && startNode.pt.longitude- startPt.longitude > -0.0001 &&startNode.pt.longitude- startPt.longitude < 0.0019) {
                            [self.orderArray removeObject:models1];//先移除
                            models1.dis = minDis?minDis:99999999;//计算路径规划的距离
                            if (driverTimeHours>0 && driverTimeMinutes>0) {//计算路径规划的时间
                                models1.driverTime = [NSString stringWithFormat:@"约%.ld小时%.ld分钟",(long)driverTimeHours,(long)driverTimeMinutes];
                            } else if (driverTimeHours>0 && driverTimeMinutes<=0) {
                                models1.driverTime = [NSString stringWithFormat:@"约%.ld小时",(long)driverTimeHours];
                            } else if (driverTimeHours<=0 && driverTimeMinutes>0) {
                                models1.driverTime = [NSString stringWithFormat:@"约%.ld分钟",(long)driverTimeMinutes];
                            } else if (driverTimeHours<=0 && driverTimeMinutes<=0) {
                                models1.driverTime = @"约1分钟";
                            }
                            [self.orderArray addObject:models1];//再添加(保证数据最新)
                        }
                    }
                } else {//其他订单
                    
                    if (startNode.pt.latitude - models1.start_lat > -0.000009 &&startNode.pt.latitude - models1.start_lat < 0.000009  && startNode.pt.longitude- models1.start_lng > -0.000009 &&startNode.pt.longitude- models1.start_lng < 0.000009) {//0.000009等这个参数自己看下调试多个订单进行调整
                        [self.orderArray removeObject:models1];
                        models1.dis = minDis?minDis:99999999;//计算路径规划的距离
                        if (driverTimeHours>0 && driverTimeMinutes>0) {//计算路径规划的时间
                            models1.driverTime = [NSString stringWithFormat:@"约%.ld小时%.ld分钟",(long)driverTimeHours,(long)driverTimeMinutes];
                        } else if (driverTimeHours>0 && driverTimeMinutes<=0) {
                            models1.driverTime = [NSString stringWithFormat:@"约%.ld小时",(long)driverTimeHours];
                        } else if (driverTimeHours<=0 && driverTimeMinutes>0) {
                            models1.driverTime = [NSString stringWithFormat:@"约%.ld分钟",(long)driverTimeMinutes];
                        } else if (driverTimeHours<=0 && driverTimeMinutes<=0) {
                            models1.driverTime = @"约1分钟";
                        }
                        [self.orderArray addObject:models1];
                    } else {
                        NSLog(@"");
                    }
                }
            }
        }
        
        NSMutableArray *temArray = [NSMutableArray array];
        for (OrderListModel *model in self.orderArray) {
            if (model.order_status == Order_state_sended && model.order_status != Order_state_get_on && !(model.order_origin.intValue == 999)) {
                [temArray addObject:model];//接客地图订单
            }
        }
        [self.clusterManager clearClusterItems];
        self.clusterManager.dataArray = temArray;//聚合
        double timeSecond = 0;
        timeSecond = temArray.count>9?1.5:1.1;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeSecond * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//设置1.1秒延迟,解决标注气泡一直刷新回调,出现一闪一闪的bug
            [self updateClusters];//聚合方法处理(包括气泡添加到地图上:“self.mapView addAnnotations:”,此处详情省略)
        });
    }
}

//骑行路线规划(代驾)
-(void)onGetRidingRouteResult:(BMKRouteSearch*)searcher result:(BMKRidingRouteResult*)result errorCode:(BMKSearchErrorCode)error {

    if (error == BMK_SEARCH_ST_EN_TOO_NEAR) {//起点,终点距离太近
        [SKToast showWithText:@"距离太近,无法规划路线"];
    } else {
        //正常+路线规划出错
        BMKRidingRouteLine* plan = (BMKRidingRouteLine*)[result.routes objectAtIndex:0];
        NSMutableArray *tmpArray = [NSMutableArray array];
        for (OrderListModel *models1 in self.orderArray) {
            //计算路径规划的距离
            models1.dis = plan.distance?plan.distance:99999999;
            //计算路径规划的时间
            NSInteger driverTimeHours = plan.duration.hours;
            NSInteger driverTimeMinutes = plan.duration.minutes;
            if (driverTimeHours>0 && driverTimeMinutes>0) {
                models1.driverTime = [NSString stringWithFormat:@"约%.ld小时%.ld分钟",(long)driverTimeHours,(long)driverTimeMinutes];
            } else if (driverTimeHours>0 && driverTimeMinutes<=0) {
                models1.driverTime = [NSString stringWithFormat:@"约%.ld小时",(long)driverTimeHours];
            } else if (driverTimeHours<=0 && driverTimeMinutes>0) {
                models1.driverTime = [NSString stringWithFormat:@"约%.ld分钟",(long)driverTimeMinutes];
            } else if (driverTimeHours<=0 && driverTimeMinutes<=0) {
                models1.driverTime = @"约1分钟";
            }
            [tmpArray addObject:models1];
        }
        
        NSMutableArray *temArray = [NSMutableArray array];
        for (OrderListModel *model in tmpArray) {
            if (model.order_status == Order_state_sended && model.order_status != Order_state_get_on && !(model.order_origin.intValue == 999)) {
                [temArray addObject:model];
            }
        }
        [self.clusterManager clearClusterItems];
        self.clusterManager.dataArray = temArray;//聚合
        [self updateClusters];
    }
}

3.其他相关方法

//根据anntation生成对应的View(设置大头针样式)
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id <BMKAnnotation>)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];
        }
        annotationView.maxOrderNum = self.orderArray.count;
        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.model_type isEqualToString:@"货"]) {
            annotationView.leftImg.image = [UIImage imageNamed:@"goodsMap"];
        } else {
            annotationView.leftImg.image = [UIImage imageNamed:@"peopleMap"];//气泡最左边小图标,如绿色头像图标
        }
        if ([bbxAn.model.order_id isEqualToString:self.currentOrderModel.order_id] && self.hasPickUpOrder) {//接客地图界面
            //气泡选中,self.currentOrderModel 这个表示当前选中的气泡;bbxAn.model.order_id 和 self.currentOrderModel.order_id 都有值
            annotationView.pinBgView.image = kImage(@"bbx_bgBlue");
            annotationView.rightLb.textColor = BBXWhite;
            annotationView.centerOffset = CGPointMake(0, -(annotationView.pinBgView.size.height/2));
        } else {
            //bbxAn.model.order_id有值,self.currentOrderModel.order_id为空
            annotationView.pinBgView.image = kImage(@"bbx_bgWhite");
            annotationView.rightLb.textColor = BBXBlack;
            annotationView.centerOffset = CGPointMake(0, -(annotationView.pinBgView.size.height/2));
        }
        return annotationView;
    }
    return nil;
}

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

5.地图路径规划画折线及途经点设置(bbx司机端智能调度按顺序接送乘客)

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

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

需求场景举例2:以司机当前位置为起点,有n个订单乘客需要接,最后一个接的乘客当做终点,剩下乘客当做途径点进行路线规划串联起来。

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

相关链接:
https://www.jianshu.com/p/ce02bc7d9805
https://www.jianshu.com/p/caf2e185462c

折线效果图

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

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

1.准备工作

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

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

//1.发起路线规划(获取数据源接口成功的地方调用这个)
- (void)updateViewByShowingOrderListModel:(OrderListModel *)orderListModel {
    
    //终点(叫代驾或叫车的人的位置)
    CLLocation *listLocation = [[CLLocation alloc] initWithLatitude:self.orderListModel.start_lat longitude:self.orderListModel.start_lng];
    BMKPlanNode *end = [[BMKPlanNode alloc] init];
    end.pt = listLocation.coordinate;
    //获取司机经纬度
    __weak typeof(self)weakSelf = self;
    [[LocationManager shareManager] getDriverBeidouLocaition:^(CLLocationCoordinate2D coordinate) {
        BMKPlanNode *start = [[BMKPlanNode alloc] init];//起点(代驾司机位置)
        start.pt = coordinate;
        BMKRidingRoutePlanOption *ridingRoutePlanOption = [[BMKRidingRoutePlanOption alloc] init];//骑行路线规划
        ridingRoutePlanOption.from = start;
        ridingRoutePlanOption.to = end;
        BOOL flag = [weakSelf.routeSearch ridingSearch:ridingRoutePlanOption];//发起骑行路线规划
        if (!flag){
            [SKToast showWithText:@"骑行规划检索发送失败"];
        }
    }];
}

//2.骑行路线规划结果
- (void)onGetRidingRouteResult:(BMKRouteSearch*)searcher result:(BMKRidingRouteResult*)result errorCode:(BMKSearchErrorCode)error {

    NSArray *array = [NSArray arrayWithArray:self.mapView.annotations];
    [self.mapView removeAnnotations:array];
    array = [NSArray arrayWithArray:self.mapView.overlays];
    [self.mapView removeOverlays:array];//先移除地图上的遮盖物、折线
    
    if (error == BMK_SEARCH_NO_ERROR) {
        
        //1.检索结果正常返回
        BMKRidingRouteLine *plan = (BMKRidingRouteLine*)[result.routes objectAtIndex:0];//取第一个
        NSInteger size = [plan.steps count];
        int planPointCounts = 0;
        
        for (int i = 0; i < size; i++) {
            BMKRidingStep *transitStep = [plan.steps objectAtIndex:i];
            if (i == 0) {//添加起点标注
                BBXPassAnnotation *startAnnotation = [[BBXPassAnnotation alloc] init];
                startAnnotation.type = @"start";
                startAnnotation.coordinate = plan.starting.location;
                [self.mapView addAnnotation:startAnnotation];
            }
            if(i == size-1) {//添加终点标注
                BBXPassAnnotation *endAnnotation = [[BBXPassAnnotation alloc] init];
                endAnnotation.type = @"end";
                endAnnotation.coordinate = plan.terminal.location;
                [self.mapView addAnnotation:endAnnotation];
            }
            planPointCounts += transitStep.pointsCount;//轨迹点总数累计
        }
        
        //泡泡弹出的地点(叫代驾的人所在的点)
        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 = plan.distance;//distanceMiles的值是后台推送过来的值,是两点的直线距离,这边将它改为百度地图规划的距离
        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];

        //轨迹点
        BMKMapPoint * temppoints = new BMKMapPoint[planPointCounts];
        int i = 0;
        for (int j = 0; j < size; j++) {
            BMKRidingStep *transitStep = [plan.steps objectAtIndex:j];
            int k=0;
            for(k=0;k<transitStep.pointsCount;k++) {
                temppoints[i].x = transitStep.points[k].x;
                temppoints[i].y = transitStep.points[k].y;
                i++;
            }
        }
        
        //画路线规划的折线
        BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:temppoints count:planPointCounts];
        [self.mapView addOverlay:polyLine];//添加路线overlay
        delete []temppoints;
        [self mapViewFitPolyLine:polyLine];//根据polyline设置地图范围
    } else if (error == BMK_SEARCH_ST_EN_TOO_NEAR) {
        
        //2.起终点距离太近
        [SKToast showWithText:@"距离太近,无法规划路线"];
    } else {
        
        //3.路线规划出错-BMK_SEARCH_RESULT_NOT_FOUND(太近检索失败)
        BMKRidingRouteLine* plan = (BMKRidingRouteLine*)[result.routes objectAtIndex:0];
        NSInteger size = 3;//[plan.steps count];
        int planPointCounts = 0;
        //添加起点标注
        BBXPassAnnotation *startAnnotation = [[BBXPassAnnotation alloc] init];
        startAnnotation.type = @"start";
        CLLocation *driverLocation = [LocationManager shareManager].location;
        BMKPlanNode *startPlanNode = [[BMKPlanNode alloc] init];
        startPlanNode.pt = driverLocation.coordinate;
        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];
        BMKPlanNode *end = [[BMKPlanNode alloc] init];
        end.pt = listLocation.coordinate;
        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];
        //轨迹点
        BMKMapPoint *temppoints = new BMKMapPoint[planPointCounts];
        int i = 0;
        for (int j = 0; j < size; j++) {
            BMKRidingStep *transitStep = [plan.steps objectAtIndex:j];
            int k=0;
            for(k=0;k<transitStep.pointsCount;k++) {
                temppoints[i].x = transitStep.points[k].x;
                temppoints[i].y = transitStep.points[k].y;
                i++;
            }
        }
        //画路线规划的折线
        BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:temppoints count:planPointCounts];
        [self.mapView addOverlay:polyLine]; // 添加路线overlay
        delete []temppoints;
        [self mapViewFitPolyLine:polyLine];//根据polyline设置地图范围
    }
}

//3.根据polyline设置地图范围
- (void)mapViewFitPolyLine:(BMKPolyline *) polyLine {
    CGFloat leftTopX, leftTopY, rightBottomX, rightBottomY;
    if (polyLine.pointCount < 1) {
        return;
    }
    BMKMapPoint pt = polyLine.points[0];
    // 左上角顶点
    leftTopX = pt.x;
    leftTopY = pt.y;
    // 右下角顶点
    rightBottomX = pt.x;
    rightBottomY = pt.y;
    for (int i = 1; i < polyLine.pointCount; i++) {
        BMKMapPoint 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;
    }
    
    BMKMapRect rect;
    rect.origin = BMKMapPointMake(leftTopX, leftTopY);
    rect.size = BMKMapSizeMake(rightBottomX - leftTopX, rightBottomY - leftTopY);
    UIEdgeInsets padding = UIEdgeInsetsMake(100, 100, 100, 100);//这个自己根据需求调整
    BMKMapRect fitRect = [self.mapView mapRectThatFits:rect edgePadding:padding];
    [self.mapView setVisibleMapRect:fitRect];
}

//4.设置路线规划折线的样式
- (BMKOverlayView *)mapView:(BMKMapView *)mapView viewForOverlay:(id <BMKOverlay>)overlay{
    if ([overlay isKindOfClass:[BMKPolyline class]]){
        BMKPolylineView* polylineView = [[BMKPolylineView alloc] initWithOverlay:overlay];
        polylineView.strokeColor = [UIColor colorWithRed:51/255.0 green:149/255.0 blue:255/255.0 alpha:1.0];//折线颜色
        polylineView.lineWidth = 6.0;//折线宽度
        return polylineView;
    }
    return nil;
}

//5.根据数据生成地图标注视图(气泡)
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id <BMKAnnotation>)annotation {
    
    if ([annotation isKindOfClass:[BBXPassAnnotation class]]){
        BMKAnnotationView *annotationView =[mapView dequeueReusableAnnotationViewWithIdentifier:@"passenger"];
        if (!annotationView) {
            annotationView = [[BMKAnnotationView 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"];
            if (!self.distancelabel) {
                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;
}

//6.用户位置更新后,会调用此函数(userLocation 新的用户位置)
- (void)didUpdateBMKUserLocation:(BMKUserLocation *)userLocation {
    self.mapView.centerCoordinate = userLocation.location.coordinate;//更新地图中心点,不然偶尔会跑到北京天安门去
}

2.2设置途经点画折线(智能调度功能中有)

智能调度有途经点效果图;点需要放大地图看具体位置,如点3看着不在折线上放大地图看就在折线

2.2.1 智能调度相关基础(地图那些基础创建以及数据获取等此处省略..)

@property (nonatomic, strong) BBXDiaoduStartNavView *diaoduNavView;//智能调度-开始导航
@property (nonatomic, strong) BBXDiaoduAilistModel *diaoduAiListModel;//智能调度ai_list
@property (nonatomic, strong) BMKPolyline *polyline;//智能调度折线
@property (nonatomic, strong) NSArray *ailistArray;//智能调度信息

//智能调度开始导航
- (BBXDiaoduStartNavView *)diaoduNavView {
    if (!_diaoduNavView) {
        _diaoduNavView = [[BBXDiaoduStartNavView alloc]init];
        [self.view addSubview:_diaoduNavView];
        [_diaoduNavView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.bottom.mas_equalTo(0);
            if (isFullScreen) {
                make.height.mas_equalTo(90+115);
            } else {
                make.height.mas_equalTo(93+115);
            }
        }];
    }
    return _diaoduNavView;
}

//智能调度ai_list
- (BBXDiaoduAilistModel *)diaoduAiListModel {
    if (!_diaoduAiListModel) {
        _diaoduAiListModel = [[BBXDiaoduAilistModel alloc]init];
    }
    return _diaoduAiListModel;
}

//智能调度画折线
- (BMKPolyline *)polyline {
    if (!_polyline) {
        _polyline = [[BMKPolyline alloc]init];
    }
    return _polyline;
}

//智能调度ailist
- (NSArray *)ailistArray {
    if (!_ailistArray) {
        _ailistArray = [[NSArray alloc]init];
    }
    return _ailistArray;
}

2.2.2 途经点设置核心代码

1.设置地图标注,路径规划初始化
- (void)setAddressAnnotation {
    
    NSArray *allOverlays = self.mapView.overlays;
    [self.mapView removeOverlays:allOverlays];//这里先删除智能调度所有覆盖物(即折线)
    
    if (self.orderArray.count == 0 || !self.orderArray) {
        [self.mapView removeAnnotations:[self.mapView annotations]];//移除当前地图View的已经添加的标注数组
        return;
    }
    
    if (self.allOrderPointArr.count <= 1) {
        if (self.ailistArray.count>0) {//智能调度
            //没有一个点也要保留出口点
        } else {
            [self.mapView removeAnnotations:[self.mapView annotations]];//移除掉旧的标注
        }
    }
        
    /*
     添加大头针
     设置完标注坐标后,后调用(- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id <BMKAnnotation>)annotation)代理方法
     可在该代理方法中设置大头针的样式
     */
    for (OrderListModel *orderModel in self.orderArray) {
        if (self.ailistArray.count>0) {//智能调度
            if ([BBXDiaoduSortArrayManager shareManager].pickArray.count>0) {//其中pickArray为接客有序订单数组
                if (orderModel.order_status == Order_state_sended) {
                    [self creatPlan:orderModel];//只画线接客状态(核心代码)
                    break;//跳出for循环,只执行一次
                }
            }
        } else {
            [self creatPlan:orderModel];
        }
    }
    [self showAllAnnotationWithAllPointArray:self.allOrderPointArr];//设置地图的显示区域,展示所有点(这里的相关计算不讲放在地图聚合功能讲)
    if (self.currentOrderModel) {//设置点击气泡后弹窗控件面板数据
        OrderViewModel * model = [[OrderViewModel alloc] initWithOrderModel:self.currentOrderModel];
        [self.mapPopupView setViewModel:model bbxMapVCType:BBXMapVCTypeWithNone];
    }
}

//初始化司机路径规划(核心:设置途经点数组)
- (void)creatPlan:(OrderListModel *)model {
    
    //起点(司机位置)
    BMKPlanNode* start = [[BMKPlanNode alloc] init];
    CLLocationCoordinate2D coordinate1;
    coordinate1.latitude = [LocationManager shareManager].location.coordinate.latitude;//24.492317988662283;
    coordinate1.longitude = [LocationManager shareManager].location.coordinate.longitude;//118.18693394711573;
    start.pt = coordinate1;
    BMKPlanNode *end = [[BMKPlanNode alloc] init];

    if (self.ailistArray.count>0) {//智能调度
        
//                coordinate.latitude = model.start_lat;
//                coordinate.longitude = model.start_lng;
//                end.pt = model.startLocation.coordinate;//这个写法规划线路有问题
        
        NSMutableArray *temArray = [NSMutableArray array];
        temArray = [BBXDiaoduSortArrayManager shareManager].pickArray;
        OrderListModel *lastModel = temArray.lastObject;
        end.pt = lastModel.startLocation.coordinate;
        //end.pt = diaoduChuEndCoor;//高速出口
        BMKDrivingRoutePlanOption *drivingRoutePlanOption= [[BMKDrivingRoutePlanOption alloc] init];//开车路线规划
        drivingRoutePlanOption.from = start;
        drivingRoutePlanOption.to = end;
        drivingRoutePlanOption.drivingPolicy = BMK_DRIVING_DIS_FIRST;//传一个想要的规划策略给百度(最短距离,BMK_DRIVING_DIS_FIRST:1;百度默认0为时间最短)
        
        //途径点坐标数组
        NSMutableArray <BMKPlanNode *> *wayPointsArray = [[NSMutableArray alloc]init];
        [temArray enumerateObjectsUsingBlock:^(OrderListModel * _Nonnull listModel, NSUInteger idx, BOOL * _Nonnull stop) {
            //数据源中最后一个元素作为终点,不加入途径点数组
            if(idx != temArray.count - 1){//&& idx!=0 奔溃
                CLLocationCoordinate2D coor;
                coor.latitude = listModel.start_lat;
                coor.longitude = listModel.start_lng;
                BMKPlanNode *after = [[BMKPlanNode alloc]init];
                after.pt = coor;
                [wayPointsArray addObject:after];
            }
        }];
        drivingRoutePlanOption.wayPointsArray = wayPointsArray;
        
        //发起驾车路径规划请求
        BOOL flag = [self.routeSearch drivingSearch:drivingRoutePlanOption];
        if (!flag){
            [SKToast showWithText:@"驾车路线规划检索发送失败"];
        } else {
            [SKToast showWithText:@"成功"];
        }
    }
}

2.路径规划结果回调方法
#pragma mark -- 路径规划
/**
 *返回驾车的路线绘制搜索结果
 *@param searcher 搜索对象
 *@param result 搜索结果,类型为BMKDrivingRouteResult
 *@param error 错误号,@see BMKSearchErrorCode
 */
- (void)onGetDrivingRouteResult:(BMKRouteSearch*)searcher result:(BMKDrivingRouteResult*)result errorCode:(BMKSearchErrorCode)error {
        
    if (error == BMK_SEARCH_ST_EN_TOO_NEAR) {//起点,终点距离太近
        [SKToast showWithText:@"距离太近,无法规划路线"];
    } else {
        
        //检索结果正常返回、其他规划错误
        if ([searcher isEqual:self.routeSearch]) {
            if (self.ailistArray.count>0) {//智能调度
                //规划线路
                //1.坐标点的个数
                __block NSUInteger pointCount = 0;
                BMKDrivingRouteLine *routeline = (BMKDrivingRouteLine *)result.routes.firstObject;//获取所有驾车路线中第一条路线
                [routeline.steps enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    BMKDrivingStep *step = routeline.steps[idx];
                    pointCount += step.pointsCount;//遍历所有路段统计路段所经过的地理坐标集合内点的个数
                }];

                //2.指定的直角坐标点数组
                BMKMapPoint *points = new BMKMapPoint[pointCount];
                __block NSUInteger j = 0;
                [routeline.steps enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    BMKDrivingStep *step = routeline.steps[idx];
                    for (NSUInteger i = 0; i < step.pointsCount; i ++) {
                        points[j].x = step.points[i].x;//将每条路段所经过的地理坐标点赋值
                        points[j].y = step.points[i].y;
                        j ++;
                    }
                }];

                //3.根据指定直角坐标点生成一段折线
                self.polyline = [BMKPolyline polylineWithPoints:points count:pointCount];
                [self.mapView addOverlay:self.polyline];
                [self mapViewFitPolyline:self.polyline withMapView:self.mapView];//根据polyline设置地图范围
            }
        }
    }
}

3.根据polyline设置地图范围
- (void)mapViewFitPolyline:(BMKPolyline *) polyLine withMapView:(BMKMapView *)mapView {
    CGFloat leftTopX, leftTopY, rightBottomX, rightBottomY;
    if (polyLine.pointCount < 1) {
        return;
    }
    BMKMapPoint pt = polyLine.points[0];
    // 左上角顶点
    leftTopX = pt.x;
    leftTopY = pt.y;
    // 右下角顶点
    rightBottomX = pt.x;
    rightBottomY = pt.y;
    for (int i = 1; i < polyLine.pointCount; i++) {
        BMKMapPoint 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;
    }
    
    BMKMapRect rect;
    rect.origin = BMKMapPointMake(leftTopX, leftTopY);
    rect.size = BMKMapSizeMake(rightBottomX - leftTopX, rightBottomY - leftTopY);
    UIEdgeInsets padding = UIEdgeInsetsMake(100, 100, 100, 100);
    BMKMapRect fitRect = [self.mapView mapRectThatFits:rect edgePadding:padding];
    [self.mapView setVisibleMapRect:fitRect];
}

4.设置路线规划折线的样式
- (BMKOverlayView *)mapView:(BMKMapView *)mapView viewForOverlay:(id<BMKOverlay>)overlay {
    if ([overlay isKindOfClass:[BMKPolyline class]]) {
        BMKPolylineView* polylineView = [[BMKPolylineView alloc] initWithOverlay:overlay];
        polylineView.strokeColor = [UIColor colorWithRed:51/255.0 green:149/255.0 blue:255/255.0 alpha:1.0];//折线颜色
        polylineView.lineWidth = 6.0; //折线宽度
        return polylineView;
    }
    return nil;
}

5.根据数据生成地图标注视图(气泡)
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id <BMKAnnotation>)annotation {
    
    if ([annotation isKindOfClass:[BBXShare class]]) {

        if (self.ailistArray.count>0) {//智能调度
            
            NSString *IdentifierStr = @"diaoduPickAnnotation";
            BBXDispatchShareView *annotationView = (BBXDispatchShareView *)[mapView dequeueReusableAnnotationViewWithIdentifier:IdentifierStr];
            if (!annotationView) {//创建标识视图
                annotationView = [[BBXDispatchShareView alloc] initWithAnnotation:annotation reuseIdentifier:IdentifierStr];
            }
            BBXShare *bbxAn = (BBXShare *)annotation;
            annotationView.annotation = bbxAn;
            annotationView.tag = bbxAn.tag;//地图大头针的tag
            bbxAn.model.isMap = YES;
            annotationView.pickArray = [BBXDiaoduSortArrayManager shareManager].pickArray;
            annotationView.isPickMapChuKou = YES;
            annotationView.model = bbxAn.model;
            return annotationView;
        }
    }
    return nil;
}

2.2.3 智能调度其他相关补充

是否为智能调度类型-数据源判断

智能调度模型中相关字段
  • BBXDispatchShareView.h
#import <BaiduMapAPI_Map/BMKAnnotationView.h>
#import "OrderListModel.h"
NS_ASSUME_NONNULL_BEGIN

@interface BBXDispatchShareView : BMKAnnotationView

@property (nonatomic, strong) OrderListModel *model;
@property (nonatomic, strong) UIImageView *pinBgView;//气泡背景图
@property (nonatomic, strong) UILabel *numLb;//接送客顺序
@property (nonatomic, strong) NSMutableArray *pickArray;//接客地图排序接单数组
@property (nonatomic, strong) NSMutableArray *sendArray;//送客地图排序接单数组
@property (nonatomic, assign) BOOL isPickMapChuKou;//是否是接客地图高速出口

@end

NS_ASSUME_NONNULL_END
  • BBXDispatchShareView.m
#import "BBXDispatchShareView.h"
#import "NSString+CJTextSize.h"
#import "BBXDiaoduSortArrayManager.h"

@implementation BBXDispatchShareView

- (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString*)reuseIdentifier {
    
    if(self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) {
        
        [self setBounds:CGRectMake(0,0,50,50)];//60,60
        [self addSubview:self.pinBgView];
        [self.pinBgView addSubview: self.numLb];
    }
    return self;
}

- (void)setPickArray:(NSMutableArray *)pickArray {
    _pickArray = pickArray;
}

- (void)setSendArray:(NSMutableArray *)sendArray {
    _sendArray = sendArray;
}

- (void)setIsPickMapChuKou:(BOOL)isPickMapChuKou {
    _isPickMapChuKou = isPickMapChuKou;
}

- (void)setModel:(OrderListModel *)model {
    _model = model;
    
    if (model.order_status == Order_state_sended) {//接客
        if (model.on_seq == 0 || !model.on_seq) {
            self.pinBgView.image = kImage(@"bbxMapPeople");
        } else {
            for (int i = 0; i<self.pickArray.count; i++) {
                if (model.order_id == [self.pickArray[i] order_id]) {
                    self.numLb.text = [NSString stringWithFormat:@"%d",i+1];
                    self.pinBgView.image = kImage(@"bbxMapPoint");
                    break;//跳出循环
                }
            }
        }
    } else if (model.order_status == Order_state_get_on) {//送客
        if (model.off_seq == 0 || !model.off_seq) {
            self.pinBgView.image = kImage(@"bbxMapPeople");
        } else {
            for (int i = 0; i<self.sendArray.count; i++) {
                if (model.order_id == [self.sendArray[i] order_id]) {
                    self.numLb.text = [NSString stringWithFormat:@"%d",i+1];
                    self.pinBgView.image = kImage(@"bbxMapPoint");
                    break;
                }
            }
        }
    } else {
        self.numLb.text = @"";
        if (self.isPickMapChuKou == YES) {//接客
            self.pinBgView.image = kImage(@"bbxMapExit");//出口
        } else {
            self.pinBgView.image = kImage(@"bbxMapEnter");//入口
        }
    }
}

- (UIImageView *)pinBgView {
    if (!_pinBgView) {
        _pinBgView = [[UIImageView alloc]init];
        [self addSubview:_pinBgView];
        _pinBgView.userInteractionEnabled = YES;
        _pinBgView.contentMode = UIViewContentModeCenter;
        [_pinBgView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.top.bottom.mas_equalTo(0);
        }];
    }
    return _pinBgView;
}

- (UILabel *)numLb {
    if (!_numLb) {
        _numLb = [[UILabel alloc]init];
        _numLb.backgroundColor = [UIColor clearColor];
        _numLb.textAlignment = NSTextAlignmentCenter;
        _numLb.font = kFont_Medium(19);
        _numLb.textColor = BBXWhite;
        [self.pinBgView addSubview:_numLb];
        [_numLb mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(2);
            make.centerX.mas_equalTo(self.pinBgView);
        }];
    }
    return _numLb;
}

2.3其他备注

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

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

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

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

其他例子参考:https://www.jianshu.com/p/606363ce04fb

区域多边形效果图

区域多边形效果图g

7.地图气泡的聚合功能

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

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

具体讲解:https://www.jianshu.com/p/9917da2fd025

聚合效果图

地图官方效果图

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

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

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

效果图:

接客地图导航按钮入口

进入导航中
进入导航中-缩放效果
进入导航中-导航页3D图:不是控制器是view

相关代码:

AppDelegate.m 中进行导航相关配置

地图导航等相关配置

BBXPickMapViewController.m 接客地图导航按钮入口

BBXPickMapViewController.m 接客地图导航按钮回调方法

自定义导航类 BBXMapNaviManager.m

#pragma mark -- 开始导航
//开始导航
- (void)beginMapNaviWithOrderViewModel:(OrderViewModel *)orderViewModel {
    
    [SVProgressHUD show];
    [SVProgressHUD dismissWithDelay:3];//设置延迟消失机制,避免转圈圈不消失
    self.orderViewModel = orderViewModel;
    if (_naviDisposable) {//App中RAC相关使用,这边讨论导航,RAC部分可忽略不计
        [_naviDisposable dispose];
    }
    //获取导航vc
    UIViewController *oldNaviViewController = [BNaviModel getInstance].naviViewController;
    _naviSignal = [[RACObserve([BNaviModel getInstance], naviViewController) filter:^BOOL(id  _Nullable value) {
        return value != nil && value != oldNaviViewController;
    }] take:1];

    @weakify(self);
    _naviDisposable = [_naviSignal subscribeNext:^(id  _Nullable x) {
        //直到[BNaviModel getInstance].naviViewController有值时才开始绑定视图事件
        @strongify(self);
        [self bindViewAction];
    }];
    
    CLLocationDegrees dest_lat = 0;//纬度
    CLLocationDegrees dest_lng = 0;//经度
    OrderListModel *orderListModel = self.orderViewModel.orderModel;
    //终点坐标经纬度
    if (orderListModel.order_status == Order_state_sended) {//接客
        if (orderListModel.order_type == OrderType_Bus) {// 快线-接客
            if ([orderListModel.locations.start.area_level isEqualToString:@"1"] || [orderListModel.locations.start.area_level isEqualToString:@"3"]) {//详细地址
                dest_lat = orderListModel.start_lat;
                dest_lng = orderListModel.start_lng;
            } else  {//站点
                NSDictionary *dataDict = [NSDictionary dictionaryWithJsonString:orderListModel.locations.start.station_location];
                if([dataDict isKindOfClass:[NSDictionary class]]) {
                    CLLocationDegrees lat = [dataDict[@"lat"] doubleValue];
                    CLLocationDegrees lng = [dataDict[@"lng"]doubleValue];
                    dest_lat = lat;
                    dest_lng = lng;
                }else{
                    CLLocationDegrees lat = [orderListModel.locations.start.lat doubleValue];
                    CLLocationDegrees lng = [orderListModel.locations.start.lng doubleValue];
                    dest_lat = lat;
                    dest_lng = lng;
                }
            }
        } else {//快线除外
            dest_lat = orderListModel.start_lat;
            dest_lng = orderListModel.start_lng;
        }
    } else {//送客
        //此处省略代码......逻辑同接客,start相关替换成end就行
    }
    
    //导航起点终点经纬度
    CLLocationCoordinate2D startCoor = [LocationManager shareManager].location.coordinate;//导航起点
    CLLocationCoordinate2D endCoor = CLLocationCoordinate2DMake(dest_lat, dest_lng);//导航终点
    //将经纬度坐标转换为投影后的直角地理坐标
    BMKMapPoint startPoint = BMKMapPointForCoordinate(startCoor);
    BMKMapPoint endPoint = BMKMapPointForCoordinate(endCoor);
    //计算指定两点之间的距离
    float distance= BMKMetersBetweenMapPoints(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];
        return;
    }
    
    //显示路线规划
    [self showRoutePlanSheetWithStartCoor:startCoor endCoor:endCoor];
}
#pragma mark -- 路线规划
//路线规划
- (void)showRoutePlanSheetWithStartCoor:(CLLocationCoordinate2D)startCoor endCoor:(CLLocationCoordinate2D)endCoor {
    
    [self actionRoutePlanWithStartCoor:startCoor endCoor:endCoor type:BBXRoutePlanTypeInside];
}

//根据起点和终点坐标,重新规划路线(导航核心代码)
- (void)actionRoutePlanWithStartCoor:(CLLocationCoordinate2D)startCoor endCoor:(CLLocationCoordinate2D)endCoor type:(BBXRoutePlanType)type {
    
    //App内导航
    if (type == BBXRoutePlanTypeInside) {//内置导航
        //路径规划节点--起点
        BNRoutePlanNode *startNode = [[BNRoutePlanNode alloc] init];
        startNode.pos = [[BNPosition alloc] init];
        startNode.pos.eType = BNCoordinate_BaiduMapSDK;//坐标系类型
        startNode.pos.x = startCoor.longitude;
        startNode.pos.y = startCoor.latitude;
        //路径规划节点--终点
        BNRoutePlanNode *endNode = [[BNRoutePlanNode alloc] init];
        endNode.pos = [[BNPosition alloc] init];
        endNode.pos.eType = BNCoordinate_BaiduMapSDK;
        endNode.pos.x = endCoor.longitude;
        endNode.pos.y = endCoor.latitude;

        [BNaviService_RoutePlan setDisableOpenUrl:YES];//禁用跳转到百度地图的原因是不会触发代理方法,不好控制
        /*
         发起算路
         eMode     算路方式,定义见BNRoutePlanMode
         naviNodes 算路节点数组,起点、途经点、终点按顺序排列,节点信息为BNRoutePlanNode结构
         naviTime  发起算路时间,用于优化算路结果,可以为nil
         delegate  算路委托,用于回调
         userInfo  用户需要传入的参数
        */
        [BNaviService_RoutePlan startNaviRoutePlan:BNRoutePlanMode_Recommend naviNodes:@[startNode,endNode] time:nil delegete:self userInfo:nil];//发起路径规划
        return;
    }
}
//上面代码涉及相关:
typedef NS_ENUM(NSInteger, BBXRoutePlanType) {
    BBXRoutePlanTypeInside  = 0,//内置导航
    BBXRoutePlanTypeGaoDe   = 1,//高德地图
    BBXRoutePlanTypeBaidu   = 2,//百度地图
    BBXRoutePlanTypeTencent = 3,//腾讯地图
    BBXRoutePlanTypeApple   = 4,//苹果地图
};

//RAC(导入头文件:#import <ReactiveObjC/ReactiveObjC.h>)
@property (nonatomic, strong) RACDisposable *naviDisposable;
@property (nonatomic, strong) RACSignal *naviSignal;
RAC相关补充

地图导航中更换终点发起重新导航

应用场景:接客地图导航中按顺序接客连续滑动操作
链接:https://www.jianshu.com/p/b619c4286d26

更换终点重新导航
更换终点重新导航

地图导航相关类

地图导航相关类

地图导航相关类
地图导航中更换终点-重新发起算路相关类

百度地图导航需要遵循协议

在使用百度地图导航的时候需要遵循相关服务声明条款,进入百度地图导航的时候会自动弹框如下图,需要用户接受这个条款否则无法进行导航,用户如果接受了则后面不再弹框,如果拒绝则每次进入导航都弹框。

百度地图相关条款

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

地理编码指的是将地址信息建立空间坐标关系的过程,又可分为正向地理编码和反向地理编码。

逆编码:即逆地理编码也叫反编码,指的是将经纬度转换成地理位置信息,如地名、所在的省份或城市等,百度地图提供了相应的API可以方便调用。反向地理编码实现了将地址坐标转换为标准地址的过程,提供了坐标定位引擎,帮助用户通过地面某个地物的坐标值来反向查询得到该地物所在的行政区划、所处街道、以及最匹配的标准地址信息。通过丰富的标准地址库中的数据,可帮助用户在进行移动端查询、商业分析、规划分析等领域创造无限价值。

相关文档链接:https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding-abroad

其他链接:https://blog.csdn.net/wjchao1990/article/details/51727669
https://xiaozhuanlan.com/topic/4968730251
https://blog.csdn.net/nameis2541748437/article/details/49766105
https://www.cnblogs.com/guitarandcode/p/5783805.html(CLPlacemark属性)

正地理编码:也直接叫地理编码,指的是将地理位置名称转换成经纬度。该功能适用于根据用户输入的地址确认用户具体位置的场景,常用于配送人员根据用户输入的具体地址找地点。

逆编码

1.逆编码创建

1.导入头文件
#import <BaiduMapAPI_Base/BMKBaseComponent.h>//引入base相关所有的头文件
#import <BaiduMapAPI_Map/BMKMapComponent.h>//引入地图功能所有的头文件

2.属性
@property (nonatomic, strong) BMKGeoCodeSearch *search;

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"地址选择";
    self.search = [[BMKGeoCodeSearch alloc] init];//地址选择逆编码
    self.search.delegate = self;//BMKGeoCodeSearchDelegate
}

2.发起逆编码请求

#pragma mark -- MapViewDelegate
//地图区域改变完成后会调用此接口
- (void)mapView:(BMKMapView *)mapView regionDidChangeAnimated:(BOOL)animated reason:(BMKRegionChangeReason)reason {
  
    BMKReverseGeoCodeSearchOption *reverseGeoCodeOption = [[BMKReverseGeoCodeSearchOption alloc]init];
    reverseGeoCodeOption.location = mapView.region.center;
    reverseGeoCodeOption.isLatestAdmin = YES;//是否访问最新版行政区划数据(仅对中国数据生效)
    if (reason == BMKRegionChangeReasonGesture || reason == BMKRegionChangeReasonEvent) {  //不是手指移动或者点击的,统一不处理
        //发起逆编码请求
        BOOL flag = [self.search reverseGeoCode: reverseGeoCodeOption];
        if (flag) {
            NSLog(@"逆geo检索发送成功");
        }  else  {
            NSLog(@"逆geo检索发送失败");
        }
    }
}

#pragma mark -- 按钮回调
//定位按钮
- (void)locationButtonAction {
    self.mapView.centerCoordinate = self.userLocation.location.coordinate;
    [self.mapView setZoomLevel:18];
    BMKReverseGeoCodeSearchOption *reverseGeoCodeOption = [[BMKReverseGeoCodeSearchOption alloc]init];
    reverseGeoCodeOption.location = self.userLocation.location.coordinate;
    reverseGeoCodeOption.isLatestAdmin = YES;
    //发起逆编码请求
    BOOL flag = [self.search reverseGeoCode: reverseGeoCodeOption];
    if (flag) {
        NSLog(@"逆geo检索发送成功");
    }  else  {
        NSLog(@"逆geo检索发送失败");
    }
}

3.逆编码结果

#pragma mark BMKGeoCodeSearchDelegate
//逆编码结果
- (void)onGetReverseGeoCodeResult:(BMKGeoCodeSearch *)searcher result:(BMKReverseGeoCodeSearchResult *)result errorCode:(BMKSearchErrorCode)error {
    if (error == BMK_SEARCH_NO_ERROR && self.mapView.zoomLevel >= 6.5) {//在此处理正常结果
        //定位在海上,没返回正常的地址,等同于失败
        if (!result.address || isNullString(result.address) || result.address.length == 0) {

        } else {
            
            NSMutableString *localAddress = [NSMutableString stringWithFormat:@"%@",result.address];
            if ([result.addressDetail.country isEqualToString:@"中国"] || [result.addressDetail.countryCode isEqualToString:@"0"]) {
                if (result.addressDetail.province && result.addressDetail.province.length > 0) {
                    if ([localAddress containsString:result.addressDetail.province]) {
                        NSRange range = [localAddress rangeOfString:result.addressDetail.province];
                        if(range.location != NSNotFound) {
                            [localAddress deleteCharactersInRange:NSMakeRange(range.location, result.addressDetail.province.length)];
                        }
                    }
                }
                
                if (result.addressDetail.city && result.addressDetail.city.length > 0) {
                    if ([localAddress containsString:result.addressDetail.city]) {
                        NSRange range = [localAddress rangeOfString:result.addressDetail.city];
                        if(range.location != NSNotFound) {
                            [localAddress deleteCharactersInRange:NSMakeRange(range.location, result.addressDetail.city.length)];
                        }
                    }
                }
            } else {
                
            }
            self.endAdressView.adressLabel.textColor = HEXCOLOR(0x333333);
            self.endAdressView.cityLabel.textColor = HEXCOLOR(0x333333);
            self.endAdressView.cityLabel.text = [NSString stringWithFormat:@"%@·",result.addressDetail.city];
            CGFloat lblW = [self.endAdressView.cityLabel.text cjTextWidthWithFont:kFont_Bold(19) infiniteWidthAndMaxHeight:35];
            if (lblW > 100) {
                lblW = 100;
            }
            [self.endAdressView.cityLabel mas_updateConstraints:^(MASConstraintMaker *make) {
                make.width.mas_equalTo(lblW);
            }];
            self.endAdressView.adressLabel.text = [NSString stringWithFormat:@"%@",localAddress];
            BMapAnnotation *annotation = [[BMapAnnotation alloc]init];
            //逆编码结果赋值
            annotation.cityItemName = result.addressDetail.city;
            annotation.latitude = result.location.latitude;
            annotation.longitude = result.location.longitude;
            annotation.district = result.addressDetail.district;
            annotation.province = result.addressDetail.province;
            annotation.coordinate = result.location;
            annotation.title = localAddress;
            SKAddressModel *address =  [[SKAddressModel alloc] initWithAnnotation:annotation];
            self.chooseAddress = address;
            self.mapAnnotionView.annotionDesLab.text = [NSString stringWithFormat:@"%@·%@",result.addressDetail.city,localAddress];
            [self.mapAnnotionView upDateUIFrameWithAdress:self.mapAnnotionView.annotionDesLab.text];
        }
    } else {
        //地址检索失败
    }
}

//逆编码结果(其他控制器中调用举例)
- (void)onGetReverseGeoCodeResult:(BMKGeoCodeSearch *)searcher result:(BMKReverseGeoCodeSearchResult *)result errorCode:(BMKSearchErrorCode)error {
    
    if (error == BMK_SEARCH_NO_ERROR) {
        if (searcher == self.search) {
            self.adCode = result.addressDetail.adCode;
            NSLog(@"速递-adCode返回结果:%@",self.adCode);
        } else if (searcher == self.searchTwo) {
            self.adCodeTwo = result.addressDetail.adCode;
            NSLog(@"速递-adCodeTwo返回结果:%@",self.adCodeTwo);
        }
    } else {
        NSLog(@"速递-检索失败");
    }
}
其中:@property (nonatomic, copy) NSString *adCode;//城市编码(城市的区id)

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

百度地图相关SDK:地图、导航、定位、鹰眼
百度地图使用相关权限
百度地图定位需要的权限,旧的百度地图开发定位的权限只需要一种现在最新的定位需要三种权限,添加不完整可能导致无法定位
百度地图新旧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,比如按钮、底部订单面板等等;可以看到下图有添加面板和没有面板的效果。
相关链接:https://www.jianshu.com/p/3a92e68addf5

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

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

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

推荐阅读更多精彩内容