// // LocationManager.m // jiaPei // // Created by 张嵘 on 2018/9/20. // Copyright © 2018年 JCZ. All rights reserved. // #import "LocationManager.h" #import static LocationManager *manger = nil; static dispatch_once_t onceToken; //复用annotationView的指定唯一标识 static NSString *annotationViewIdentifier = @"com.Baidu.BMKPointAnnotation"; @interface LocationManager () @property (nonatomic, strong) BMKMapManager *mapManager; //主引擎类 @property (nonatomic, strong) BMKLocationManager *locationManager; //定位对象 @property (nonatomic, strong) BMKUserLocation *userLocation;//当前位置对象 @property (nonatomic, assign) BOOL locationLock;//单次定位锁 @property (nonatomic, strong) LocCompletedBlock locCompletedBlock;//定位回调 @property (nonatomic, strong) BMKMapView *mapView;//地图View类 @property (nonatomic, assign) CGSize mapSize;//地图大小 @property (nonatomic, strong) UIImageView *locationView; @property (nonatomic, strong) UIButton *addressBtn; @property (nonatomic, copy) NSArray> *annotationArr; @property (nonatomic, strong) BMKGeoCodeSearch *searcher;//搜索对象 @property (nonatomic, strong) SearchCompletedBlock searchCompletedBlock;//搜索回调 @property (nonatomic, strong) ReverseCompletedBlock reverseCompletedBlock;//反地理编码回调 @property (nonatomic, strong) CLLocationManager *cLLocationManager; @end @implementation LocationManager + (LocationManager *)sharedManager { dispatch_once(&onceToken, ^{ manger = [[self alloc] init]; }); return manger; } - (instancetype)init { self = [super init]; if (self) { @weakify(self) dispatch_async(dispatch_get_main_queue(), ^{ @strongify(self) // 初始化定位SDK [[BMKLocationAuth sharedInstance] checkPermisionWithKey:BaiDuMapAK authDelegate:self]; [BMKMapManager setAgreePrivacy:YES]; //要使用百度地图,请先启动BMKMapManager self.mapManager = [[BMKMapManager alloc] init]; // ‼️重要:设置用户是否同意SDK隐私协议,请务必在BMKLocationManager和BMKGeoFenceManager实例化之前调用,否则实例化失败 [[BMKLocationAuth sharedInstance] setAgreePrivacy:YES]; /** 百度地图SDK所有API均支持百度坐标(BD09)和国测局坐标(GCJ02),用此方法设置您使用的坐标类型. 默认是BD09(BMK_COORDTYPE_BD09LL)坐标. 如果需要使用GCJ02坐标,需要设置CoordinateType为:BMK_COORDTYPE_COMMON. */ if ([BMKMapManager setCoordinateTypeUsedInBaiduMapSDK:BMK_COORDTYPE_GPS]) { NSLog(@"经纬度类型设置成功"); } else { NSLog(@"经纬度类型设置失败"); } //启动引擎并设置AK并设置delegate BOOL result = [self.mapManager start:BaiDuMapAK generalDelegate:self]; if (!result) { NSLog(@"启动引擎失败"); } self.cLLocationManager = [[CLLocationManager alloc] init]; [self.cLLocationManager requestWhenInUseAuthorization]; }); } return self; } - (void)dealloc { _mapView.delegate = nil; } #pragma mark - Custom Way /** 根据大小创建地图 @param size 大小 @return 返回地图 */ - (BMKMapView *)getMapViewWithSize:(CGSize)size { self.mapView = nil; _mapSize = size; [self.mapView addSubview:self.locationView]; return self.mapView; } /** 单次定位 @param completeBlock 定位回调 */ - (void)updateLocationWithCompleteBlock:(LocCompletedBlock)completeBlock { @weakify(self) self.locationLock = YES; _locCompletedBlock = completeBlock; self.locationManager.distanceFilter = kCLDistanceFilterNone; [self.locationManager requestLocationWithReGeocode:YES withNetworkState:YES completionBlock:^(BMKLocation * _Nullable location, BMKLocationNetworkState state, NSError * _Nullable error) { @strongify(self) if (_locCompletedBlock) { _locCompletedBlock(location != nil, location.location, location); } if (error) [self showErrorAlertWithError:error]; }]; } /** 连续定位 @param distanceFilter 设定定位的最小更新距离。默认为 kCLDistanceFilterNone @param completeBlock 定位回调 */ - (void)updateLocationWithDistanceFilter:(NSInteger)distanceFilter CompleteBlock:(LocCompletedBlock)completeBlock { [self.locationManager startUpdatingLocation]; self.locationManager.distanceFilter = distanceFilter? distanceFilter : kCLDistanceFilterNone; _locCompletedBlock = completeBlock; } /** 返回地址信息搜索结果 @param address 地址(必填) @param city 城市(选填) @param completeBlock 搜索回调 */ - (void)onGetGeoCodeWithAddress:(NSString *)address City:(NSString *)city completeBlock:(SearchCompletedBlock)completeBlock { BMKGeoCodeSearchOption *geoCodeSearchOption = [[BMKGeoCodeSearchOption alloc]init]; geoCodeSearchOption.city= city; geoCodeSearchOption.address = address; [self.searcher geoCode:geoCodeSearchOption]; _searchCompletedBlock = completeBlock; } /** 返回反地理编码搜索结果 @param location 定位 @param completeBlock 搜索回调 */ - (void)onGetReverseGeoCodeWithLocation:(CLLocation *)location completeBlock:(ReverseCompletedBlock)completeBlock { BMKReverseGeoCodeSearchOption *reverseGeoCodeSearchOption = [[BMKReverseGeoCodeSearchOption alloc]init]; reverseGeoCodeSearchOption.location = location.coordinate; [self.searcher reverseGeoCode:reverseGeoCodeSearchOption]; _reverseCompletedBlock = completeBlock; } /** 错误弹窗 */ - (void)showErrorAlertWithError:(NSError *)error { switch (error.code) { case BMKLocationErrorDenied: { if (!RQ_AD_MANAGER.isFirstAppLoad) { [RQ_SHARE_FUNCTION showAlertWithTitle:@"温馨提示" message:@"请在iPhone的“设置”-“隐私”-“定位服务”功能中,找到“极速驾培”打开位置访问权限" alertControllerStyle:UIAlertControllerStyleAlert cancelButtonTitle:@"确定" otherButtonTitles:nil otherButtonStyles:nil showInWindow:NO completion:nil]; } } break; default: break; } [_locationManager stopUpdatingLocation]; } /** 开始定位 */ - (void)beginLocation{ [self.locationManager startUpdatingLocation]; [self.locationManager startUpdatingHeading]; } #pragma mark - Search Data - (void)setupDefaultDataWithCLLocationCoordinate2D:(CLLocationCoordinate2D)locationCoordinate2D { // 初始化请求参数类BMKRecommendStopSearchOption的实例 BMKRecommendStopSearchOption *option = [[BMKRecommendStopSearchOption alloc] init]; // 推荐上车点经纬度 (必选) option.location = locationCoordinate2D; [self searchData:option]; } - (void)searchData:(BMKRecommendStopSearchOption *)option { // 初始化BMKRecommendStopSearch实例 BMKRecommendStopSearch *search = [[BMKRecommendStopSearch alloc] init]; // 推荐上车点检索的代理 search.delegate = self; // 初始化请求参数类BMKRecommendStopSearchOption的实例 BMKRecommendStopSearchOption *stopSearchOption = [[BMKRecommendStopSearchOption alloc] init]; // 推荐上车点经纬度 (必选) stopSearchOption.location = option.location; /// 推荐上车点检索 /// @param recommendStopOption 推荐上车点检索信息类 /// @return 成功返回YES,否则返回NO // BOOL flag = [search recommendStopSearch:stopSearchOption]; // if (flag) { // NSLog(@"推荐上车点检索成功"); // } else { // NSLog(@"推荐上车点检索失败"); // } } #pragma mark - BMKLocationAuthDelegate 定位鉴权代理 /** * @brief 返回授权验证错误 * @param iError 错误号 : 为0时验证通过,具体参加BMKLocationAuthErrorCode */ - (void)onCheckPermissionState:(BMKLocationAuthErrorCode)iError { if (_locCompletedBlock) { _locCompletedBlock(NO, nil, nil); } switch (iError) { case BMKLocationAuthErrorUnknown: NSLog(@"未知错误"); break; case BMKLocationAuthErrorSuccess: NSLog(@"鉴权成功"); break; case BMKLocationAuthErrorNetworkFailed: NSLog(@"因网络鉴权失败"); break; case BMKLocationAuthErrorFailed: NSLog(@"KEY非法鉴权失败"); break; default: break; } } /** 联网结果回调 @param iError 联网结果错误码信息,0代表联网成功 */ - (void)onGetNetworkState:(int)iError { if (0 == iError) { NSLog(@"联网成功"); } else { NSLog(@"联网失败:%d", iError); if (_searchCompletedBlock) { _searchCompletedBlock(NO, nil, iError); } if (_reverseCompletedBlock) { _reverseCompletedBlock(NO, nil, iError); } } } #pragma mark - BMKMapViewDelegate 地图代理 - (void)mapView:(BMKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { NSLog(@"地图拖动"); [UIView animateWithDuration:0.30 animations:^{ self.locationView.centerY -=8; } completion:^(BOOL finished) { self.locationView.centerY +=8; }]; CGPoint touchPoint = self.mapView.center; CLLocationCoordinate2D touchMapCoordinate = [self.mapView convertPoint:touchPoint toCoordinateFromView:self.mapView];//这里touchMapCoordinate就是该点的经纬度了 NSLog(@"touching %f,%f",touchMapCoordinate.latitude,touchMapCoordinate.longitude); [self setupDefaultDataWithCLLocationCoordinate2D:touchMapCoordinate]; BMKReverseGeoCodeSearchOption *reverseGeoCodeSearchOption = [[BMKReverseGeoCodeSearchOption alloc]init]; reverseGeoCodeSearchOption.location = touchMapCoordinate; [self.searcher reverseGeoCode:reverseGeoCodeSearchOption]; } /// 根据anntation生成对应的View /// @param mapView 地图View /// @param annotation 指定的标注 /// @return 生成的标注View - (__kindof BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id)annotation { if ([annotation isKindOfClass:[BMKPointAnnotation class]]) { /** 根据指定标识查找一个可被复用的标注,用此方法来代替新创建一个标注,返回可被复用的标注 */ BMKPinAnnotationView *annotationView = (BMKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationViewIdentifier]; UILabel *addressLabel = [UILabel rq_labelWithText:annotation.title font:RQRegularFont_14 textColor:RQColorFromHexString(@"#498EF5")]; addressLabel.numberOfLines = 0; if (!annotationView) { /** 初始化并返回一个annotationView @param annotation 关联的annotation对象 @param reuseIdentifier 如果要重用view,传入一个字符串,否则设为nil,建议重用view @return 初始化成功则返回annotationView,否则返回nil */ annotationView = [[BMKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationViewIdentifier]; //annotationView显示的图片,默认是大头针 // annotationView.image = nil; /** 默认情况下annotationView的中心点位于annotation的坐标位置,可以设置centerOffset改变 annotationView的位置,正的偏移使annotationView朝右下方移动,负的朝左上方,单位是像素 */ annotationView.centerOffset = CGPointMake(0, 0); /** 默认情况下, 弹出的气泡位于annotationView正中上方,可以设置calloutOffset改变annotationView的 位置,正的偏移使annotationView朝右下方移动,负的朝左上方,单位是像素 */ annotationView.calloutOffset = CGPointMake(0, 0); //是否显示3D效果,标注在地图旋转和俯视时跟随旋转、俯视,默认为NO annotationView.enabled3D = NO; //是否忽略触摸时间,默认为YES annotationView.enabled = YES; /** 开发者不要直接设置这个属性,若设置,需要在设置后调用BMKMapView的-(void)mapForceRefresh;方法 刷新地图,默认为NO,当annotationView被选中时为YES */ annotationView.selected = NO; //annotationView被选中时,是否显示气泡(若显示,annotation必须设置了title),默认为YES annotationView.canShowCallout = NO; /** 显示在气泡左侧的view(使用默认气泡时,view的width最大值为32, height最大值为41,大于则使用最大值) */ annotationView.leftCalloutAccessoryView = nil; /** 显示在气泡右侧的view(使用默认气泡时,view的width最大值为32, height最大值为41,大于则使用最大值) */ annotationView.rightCalloutAccessoryView = nil; /** annotationView的颜色: BMKPinAnnotationColorRed,BMKPinAnnotationColorGreen, BMKPinAnnotationColorPurple */ annotationView.pinColor = BMKPinAnnotationColorGreen; annotationView.displayMinLevel = 17; //设置从天而降的动画效果 annotationView.animatesDrop = NO; //当设为YES并实现了setCoordinate:方法时,支持将view在地图上拖动 annotationView.draggable = NO; //当前view的拖动状态 //annotationView.dragState; [annotationView addSubview:addressLabel]; [addressLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(annotationView.mas_right).mas_offset(8); make.centerY.mas_equalTo(annotationView); make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH / 3.f, 50.f)); }]; } else { [annotationView removeAllSubviews]; [annotationView addSubview:addressLabel]; [addressLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(annotationView.mas_right).mas_offset(8); make.centerY.mas_equalTo(annotationView); make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH / 3.f, 50.f)); }]; } return annotationView; } return nil; } #pragma mark - BMKGeoCodeSearchDelegate 搜索代理 - (void)onGetGeoCodeResult:(BMKGeoCodeSearch *)searcher result:(BMKGeoCodeSearchResult *)result errorCode:(BMKSearchErrorCode)error { if (_searchCompletedBlock) { _searchCompletedBlock(result? YES : NO, result, error); } } - (void)onGetReverseGeoCodeResult:(BMKGeoCodeSearch *)searcher result:(BMKReverseGeoCodeSearchResult *)result errorCode:(BMKSearchErrorCode)error { if (result) { if (result.poiRegions.count > 0) { BMKSearchRGCRegionInfo *info = result.poiRegions.firstObject; [self.addressBtn setTitleNormal:info.regionName]; } else { [self.addressBtn setTitleNormal:result.sematicDescription]; } } if (_reverseCompletedBlock) { _reverseCompletedBlock(result ? YES : NO, result, error); } } #pragma mark - BMKRecommendStopSearchDelegate /// 推荐上车点检索结果回调 /// @param searcher 搜索对象 /// @param recommendStopResult 搜索结果 /// @param errorCode 错误号,@see BMKSearchErrorCode - (void)onGetRecommendStopResult:(BMKRecommendStopSearch *)searcher result:(BMKRecommendStopSearchResult *)recommendStopResult errorCode:(BMKSearchErrorCode)errorCode { if (errorCode == BMK_SEARCH_NO_ERROR) { if (!recommendStopResult.recommendStopInfoList && recommendStopResult.recommendStopInfoList.count == 0) { return; } [_mapView removeAnnotations:self.annotationArr]; self.annotationArr = [recommendStopResult.recommendStopInfoList.rac_sequence.signal map:^id _Nullable(BMKRecommendStopInfo * _Nullable info) { BMKPointAnnotation *annotation = [[BMKPointAnnotation alloc] init]; annotation.title = info.name; annotation.coordinate = info.location; return annotation; }].toArray; [_mapView addAnnotations:self.annotationArr]; NSMutableArray *array = [NSMutableArray arrayWithArray:recommendStopResult.recommendStopInfoList]; for (int i = 0; i< array.count -1; i++) { BOOL isEnd = NO;//判断是否已排序完成 for (int j = 0; j < array.count -1 -i; j++) { BMKRecommendStopInfo *infoA = array[j]; CGFloat a = infoA.distance; BMKRecommendStopInfo *infoB = array[j+1]; CGFloat b = infoB.distance; if (a > b ) { isEnd = YES; [array exchangeObjectAtIndex:j withObjectAtIndex:j+1]; } } if (!isEnd) { break; } } if (array.count > 0) { BMKRecommendStopInfo *info = array.firstObject; BMKPointAnnotation *annotation = [[BMKPointAnnotation alloc] init]; annotation.title = info.name; annotation.coordinate = info.location; [_mapView selectAnnotation:annotation animated:YES]; BMKMapStatus *status = [[BMKMapStatus alloc] init]; status.targetGeoPt = info.location; status.fLevel = _mapView.zoomLevel; status.fRotation = _mapView.rotation; status.fOverlooking = _mapView.overlooking; [_mapView setMapStatus:status withAnimation:YES]; } BMKRecommendStopInfo *info = [recommendStopResult.recommendStopInfoList firstObject]; NSString *message = [NSString stringWithFormat:@"推荐上车点名称:%@\n推荐上车点地址:%@\n推荐点poi的uid:%@\n距查找点的距离:%.2lf\n经度:%.6lf\n纬度:%.6lf", info.name, info.address, info.uid, info.distance, info.location.longitude, info.location.latitude]; [jiaPeiManager QMLogStr:message]; // [NSObject rq_showAlertViewWithTitle:@"推荐上车点" message:message confirmTitle:@"确定"]; } else { // [_mapView removeAnnotations:self.annotationArr]; } } #pragma mark - BMKLocationManagerDelegate 定位代理 /** 用户拒绝开启定位服务等原因导致的定位失败会调用的方法 @brief 当定位发生错误时,会调用代理的此方法。 @param manager 定位 BMKLocationManager 类。 @param error 返回的错误,参考 CLError 。 */ - (void)BMKLocationManager:(BMKLocationManager *)manager didFailWithError:(NSError *)error { if (error) [self showErrorAlertWithError:error]; } /** 处理位置坐标更新 @brief 连续定位回调函数 @param manager 定位 BMKLocationManager 类。 @param location 定位结果,参考BMKLocation。 @param error 错误信息。 */ - (void)BMKLocationManager:(BMKLocationManager *)manager didUpdateLocation:(BMKLocation *)location orError:(NSError *)error { if (location) { self.userLocation.location = location.location; //实现该方法,否则定位图标不出现 [_mapView updateLocationData:self.userLocation]; //设置当前地图的中心点,改变该值时,地图的比例尺级别不会发生变化 _mapView.centerCoordinate = self.userLocation.location.coordinate; if (_locCompletedBlock) { _locCompletedBlock(location != nil , location.location, location); } [_locationManager stopUpdatingLocation]; [self setupDefaultDataWithCLLocationCoordinate2D:location.location.coordinate]; //发起反向地理编码检索 _searcher =[[BMKGeoCodeSearch alloc]init]; _searcher.delegate = self; BMKReverseGeoCodeSearchOption *reverseGeoCodeSearchOption = [[BMKReverseGeoCodeSearchOption alloc]init]; reverseGeoCodeSearchOption.location = location.location.coordinate; [_searcher reverseGeoCode:reverseGeoCodeSearchOption]; } } #pragma mark - Lazy loading - (BMKLocationManager *)locationManager { if (!_locationManager) { _locationManager = [[BMKLocationManager alloc] init]; _locationManager.delegate = self; _locationManager.coordinateType = BMKLocationCoordinateTypeBMK09LL; _locationManager.desiredAccuracy = kCLLocationAccuracyBest; _locationManager.activityType = CLActivityTypeAutomotiveNavigation; _locationManager.pausesLocationUpdatesAutomatically = NO; _locationManager.allowsBackgroundLocationUpdates = NO; _locationManager.locationTimeout = 10; } return _locationManager; } - (BMKUserLocation *)userLocation { if (!_userLocation) { _userLocation = [[BMKUserLocation alloc] init]; } return _userLocation; } - (BMKMapView *)mapView { if (!_mapView) { _mapView = [[BMKMapView alloc] initWithFrame:CGRectMake(0,0,_mapSize.width, _mapSize.height)]; //开启定位服务 [self beginLocation]; //设置mapView的代理 _mapView.delegate = self; //当前地图类型 _mapView.mapType = BMKMapTypeStandard; //设置地图比例尺级别 _mapView.zoomLevel = 19; //设置显示定位图层 _mapView.showsUserLocation = YES; //设置定位模式为普通模式 _mapView.userTrackingMode = BMKUserTrackingModeHeading; //设定显式比例尺 // _mapView.showMapScaleBar = YES; // 关闭旋转 _mapView.rotateEnabled = NO; _mapView.ChangeCenterWithDoubleTouchPointEnabled = NO; //配置定位图层个性化样式,初始化BMKLocationViewDisplayParam的实例 BMKLocationViewDisplayParam *param = [[BMKLocationViewDisplayParam alloc] init]; //设置定位图标(屏幕坐标)X轴偏移量为0 param.locationViewOffsetX = 0; //设置定位图标(屏幕坐标)Y轴偏移量为0 param.locationViewOffsetY = 0; //设置定位图层locationView在最上层(也可设置为在下层) param.locationViewHierarchy = LOCATION_VIEW_HIERARCHY_TOP; //设置显示精度圈(因“需求:可改变精度圈大小”,关闭默认精度圈) param.isAccuracyCircleShow = YES; //跟随态旋转角度是否生效 param.isRotateAngleValid = YES; //更新定位图层个性化样式 [_mapView updateLocationViewWithParam:param]; } return _mapView; } - (BMKGeoCodeSearch *)searcher { if (!_searcher) { _searcher =[[BMKGeoCodeSearch alloc]init]; _searcher.delegate = self; } return _searcher; } - (UIImageView *)locationView { if (!_locationView) { _locationView =[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"定位针"]]; _locationView.center = CGPointMake(self.mapView.width/2, self.mapView.height/2); [_locationView addSubview:self.addressBtn]; @weakify(self) [self.addressBtn mas_makeConstraints:^(MASConstraintMaker *make) { @strongify(self) make.bottom.mas_equalTo(_locationView.top).mas_offset(- _locationView.rq_height / 2.f); make.centerX.mas_equalTo(_locationView); make.size.mas_equalTo(CGSizeMake(RQ_SCREEN_WIDTH / 3.f, 54.f)); self.addressBtn.layer.cornerRadius = 27.f; }]; } return _locationView; } - (UIButton *)addressBtn { if (!_addressBtn) { _addressBtn = [UIButton buttonWithType:UIButtonTypeCustom]; _addressBtn.titleLabel.numberOfLines = 0; _addressBtn.titleLabel.font = RQRegularFont_14; [_addressBtn setBackgroundColor:RQColorFromHexString(@"#232323")]; [_addressBtn setTitleColor:RQColorFromHexString(@"#498EF5") forState:UIControlStateNormal]; } return _addressBtn; } @end