[iOS 利用MapKit和CoreLocation框架打造精简的定位和导航]
运行效果:
一.利用<CoreLocation/CoreLocation.h>定位
创建变量 CLLocationManager *locationManager ,并加入<CLLocationManagerDelegate>协议
以下是Viewdidload里需要初始化的参数:
self.locationManager = [[CLLocationManager alloc]init]; [self.locationManager setDelegate:self]; [self.locationManager requestAlwaysAuthorization]; [self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest]; [self.locationManager setDistanceFilter:kCLDistanceFilterNone]; if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [locationManager requestWhenInUseAuthorization]; } [self.locationManager startUpdatingLocation];
开始定位后,结果会用过这个函数回调,locations数组含有一切定位信息:时间,经纬度等
// Location Manager Delegate Methods - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { NSLog(@"%@", [locations lastObject]); }
需要注意的是:在iOS8之后,使用地图需要请求权限,需要在info.plist文件中加入2个字段:
NSLocationWhenInUseDescription
NSLocationAlwaysUsageDescription
并且在使用定位前需要运行函数:
[self.locationManager requestAlwaysAuthorization];
或 [locationManager requestWhenInUseAuthorization];
二、 利用Mapkit使用地图服务
创建变量 MKMapView *regionMapView ,并加入<MKMapViewDelegate>协议
初始化参数:
self.regionMapView.delegate = self; self.regionMapView.showsUserLocation = YES;
关于地图,有个大坑是目前各大地图的坐标系都不一样。
国际标准是WGS-84,比如谷歌、苹果地图提供的API等都是基于这个坐标的。
天朝为了“照顾大家安全”,立了个新标准GCJ-02,比如高德地图就是基于它的。也就是所谓的火星坐标系。另外百度为了"更好地保护大家隐私",也有自己的二次加密后的标准BD-09.
如果直接使用国外那些不安全的地图提供的经纬度来插到我们天朝地图上,很可能就存在很大偏移。所以作为地图开发者我们首先要熟练运用6种坐标互相转换算法。
国外《-》高德 高德《-》百度 百度《-》国外
所以上面利用CoreLocation定位出的经纬度显示是有偏差的(定位出的是WGC-84,国内目前是高德地图),要想正确显示就要用算法转换(WGC-84转GCJ-02)。
或者有另外一个方法,利用MapKit的回调函数,得到的结果是经过转换的。可见MapKit内部应该是已经调用了定位的。
#pragma mark MKMapViewDelegate -user location定位变化 -(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{ _userlocation=userLocation; self.nowCoords = [userLocation coordinate]; NSLog(@"定位到当前位置"); _updateInt++; //放大地图到自身的经纬度位置。 self.userRegion = MKCoordinateRegionMakeWithDistance(self.nowCoords, 200, 200); if(_updateInt==1||_iffollowed==YES){ [self.regionMapView setRegion:self.userRegion animated:NO]; } //仅在打开地图后,第一次更新地理信息时,确定使用者的大致地理位置 if (_updateInt<=1) { //CLGeocoder 是谷歌接口通过经纬度查询大致地址 NSLog(@"通过经纬度查询地理信息"); CLGeocoder *geocoder = [[CLGeocoder alloc] init]; [geocoder reverseGeocodeLocation:[userLocation location] completionHandler:^(NSArray *array, NSError *error) { if (array.count > 0) { CLPlacemark *placemark = [array objectAtIndex:0]; _myregion=[placemark region]; NSString *region = [placemark.addressDictionary objectForKey:@"SubLocality"]; NSString *address = [placemark.addressDictionary objectForKey:@"Name"]; self.regionStr = region; self.addressStr = address; self.city = placemark.locality; NSLog(@"当前使用者所在:地点名:%@,地址:%@,城市:%@",self.regionStr,self.addressStr,self.city); }else{ self.regionStr = @""; self.addressStr = @""; self.city = @""; NSLog(@"未查询到有效地址"); } }]; } //判断是否是否要根据运动路线绘图 if (![[NSString stringWithFormat:@"%0.8f",[[userLocation location] coordinate].latitude] isEqualToString:[NSString stringWithFormat:@"%0.8f",self.centerCoordinate.latitude]] ) { //做点什么 return; } }
使用地图主要有2个基本:(1)地理编码:地址转经纬度。
(2)地理反编码:经纬度转成地址。
(1)地址转经纬度的方法(具体功能为:输入查找地点,然后显示在地图上)
方法有2个:一个是采用MK框架自带的接口 CLGeocoder,该接口还有其他的用法,按住CMD+点击CLGeocoder就会出现该类的接口。
CLGeocoder *geocoder = [[CLGeocoder alloc] init]; [geocoder geocodeAddressString:@"屏峰" completionHandler:^(NSArray *placemarks, NSError *error) { if ([placemarks count] > 0 && error == nil){ NSLog(@"Found %lu placemark(s).", (unsigned long)[placemarks count]); CLPlacemark *firstPlacemark = [placemarks objectAtIndex:0]; NSLog(@"Longitude = %f", firstPlacemark.location.coordinate.longitude); NSLog(@"Latitude = %f", firstPlacemark.location.coordinate.latitude); } else if ([placemarks count] == 0 && error == nil){ NSLog(@"Found no placemarks."); } else if (error != nil){ NSLog(@"An error occurred = %@", error); } }];
另一种就是使用百度或者其他地图的接口,比如我的Demo中使用的就是百度接口,不方便的是,百度接口获得的经纬度需要转换才能正确的“插”在高德上。
百度接口为:http://api.map.baidu.com/place/search?&query=西湖®ion=杭州&output=json&key=bqApldE1oh6oBb98VYyIfy9S
主要是4个参数。效果如下:
(2)地理反编码:经纬度转成地址。
方法同上,反过来也有接口。
第一种是Mapkit接口:
CLGeocoder *geocoder = [[CLGeocoder alloc] init]; [geocoder reverseGeocodeLocation:[userLocation location] completionHandler:^(NSArray *array, NSError *error) { if (array.count > 0) { CLPlacemark *placemark = [array objectAtIndex:0]; _myregion=[placemark region]; NSString *region = [placemark.addressDictionary objectForKey:@"SubLocality"]; NSString *address = [placemark.addressDictionary objectForKey:@"Name"]; self.regionStr = region; self.addressStr = address; self.city = placemark.locality; NSLog(@"当前使用者所在:地点名:%@,地址:%@,城市:%@",self.regionStr,self.addressStr,self.city); }else{ self.regionStr = @""; self.addressStr = @""; self.city = @""; NSLog(@"未查询到有效地址"); } }];
第二种是其他接口,同样传入参数是经纬度,返回地理信息。
效果:
三、地图操作:
1.地图依据经纬度插点:
主要利用Mapkit接口:(如果要发现某个类有哪些好玩的接口可以按住CMD+该类名进去看看有哪些函数,然后去搜搜这些函数的用法或看看官方文档)
[map addAnnotation:annotation];
后者,也就是那个棒棒糖属于MKAnnotation类,这个类有很多自定义UI的例子,通过在上面加label,加button,可以扩展获得很多功能。(比如显示该地点的具体图片文字信息等)
2.地图画线进行导航:
这些奇葩的功能都是由你所使用的地图API提供的,我这里使用的Mapkit,所以我使用的就是Mapkit的画线接口。
另外神奇的是,你可以通过[UIApplication sharedApplication]使用其他地图来构造导航路线。
(1)把导航的繁重任务交给其他专业的地图吧,这里只是大概代码(具体参见我的DEMO):
-(void)doAcSheet{ NSArray *appListArr = [CheckInstalledMapAPP checkHasOwnApp]; NSString *sheetTitle = [NSString stringWithFormat:@"导航到 %@",[self.navDic objectForKey:@"address"]]; UIActionSheet *sheet; if ([appListArr count] == 2) { sheet = [[UIActionSheet alloc] initWithTitle:sheetTitle delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:appListArr[0],appListArr[1], nil]; }else if ([appListArr count] == 3){ sheet = [[UIActionSheet alloc] initWithTitle:sheetTitle delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:appListArr[0],appListArr[1],appListArr[2], nil]; }else if ([appListArr count] == 4){ sheet = [[UIActionSheet alloc] initWithTitle:sheetTitle delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:appListArr[0],appListArr[1],appListArr[2],appListArr[3], nil]; }else if ([appListArr count] == 5){ sheet = [[UIActionSheet alloc] initWithTitle:sheetTitle delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:appListArr[0],appListArr[1],appListArr[2],appListArr[3],appListArr[4], nil]; } sheet.actionSheetStyle = UIActionSheetStyleBlackOpaque; [sheet showInView:self.view]; } -(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{ NSString *btnTitle = [actionSheet buttonTitleAtIndex:buttonIndex]; if (buttonIndex == 0) { CLLocationCoordinate2D to; to.latitude = _naviCoordsGd.latitude; to.longitude = _naviCoordsGd.longitude; MKMapItem *currentLocation = [MKMapItem mapItemForCurrentLocation]; MKMapItem *toLocation = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithCoordinate:to addressDictionary:nil]]; toLocation.name = _addressStr; [MKMapItem openMapsWithItems:[NSArray arrayWithObjects:currentLocation, toLocation, nil] launchOptions:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:MKLaunchOptionsDirectionsModeDriving, [NSNumber numberWithBool:YES], nil] forKeys:[NSArray arrayWithObjects:MKLaunchOptionsDirectionsModeKey, MKLaunchOptionsShowsTrafficKey, nil]]]; } if ([btnTitle isEqualToString:@"google地图"]) { NSString *urlStr = [NSString stringWithFormat:@"comgooglemaps://?saddr=%.8f,%.8f&daddr=%.8f,%.8f&directionsmode=transit",self.nowCoords.latitude,self.nowCoords.longitude,self.naviCoordsGd.latitude,self.naviCoordsGd.longitude]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]]; }else if ([btnTitle isEqualToString:@"高德地图"]){ NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"iosamap://navi?sourceApplication=broker&backScheme=openbroker2&poiname=%@&poiid=BGVIS&lat=%.8f&lon=%.8f&dev=1&style=2",self.addressStr,self.naviCoordsGd.latitude,self.naviCoordsGd.longitude]]; [[UIApplication sharedApplication] openURL:url]; }else if ([btnTitle isEqualToString:@"百度地图"]){ double bdNowLat,bdNowLon; bd_encrypt(self.nowCoords.latitude, self.nowCoords.longitude, &bdNowLat, &bdNowLon); NSString *stringURL = [NSString stringWithFormat:@"baidumap://map/direction?origin=%.8f,%.8f&destination=%.8f,%.8f&&mode=driving",bdNowLat,bdNowLon,self.naviCoordsBd.latitude,self.naviCoordsBd.longitude]; NSURL *url = [NSURL URLWithString:stringURL]; [[UIApplication sharedApplication] openURL:url]; }else if ([btnTitle isEqualToString:@"显示路线"]){ [self drawRout]; } }
(2)还是试试自己画线好了O(∩_∩)O:(主要是提供两个点的参数)
-(void)drawRout{ MKPlacemark *fromPlacemark = [[MKPlacemark alloc] initWithCoordinate:_nowCoords addressDictionary:nil]; MKPlacemark *toPlacemark = [[MKPlacemark alloc] initWithCoordinate:_naviCoordsGd addressDictionary:nil]; MKMapItem *fromItem = [[MKMapItem alloc] initWithPlacemark:fromPlacemark]; MKMapItem *toItem = [[MKMapItem alloc] initWithPlacemark:toPlacemark]; [self.regionMapView removeOverlays:self.regionMapView.overlays]; [self findDirectionsFrom:fromItem to:toItem]; } #pragma mark - ios7路线绘制方法 -(void)findDirectionsFrom:(MKMapItem *)from to:(MKMapItem *)to{ MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init]; request.source = from; request.destination = to; request.transportType = MKDirectionsTransportTypeWalking; if (ISIOS7) { request.requestsAlternateRoutes = YES; } MKDirections *directions = [[MKDirections alloc] initWithRequest:request]; //ios7获取绘制路线的路径方法 [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) { if (error) { NSLog(@"error:%@", error); } else { MKRoute *route = response.routes[0]; [self.regionMapView addOverlay:route.polyline]; } }]; } - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{ MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay]; renderer.lineWidth = 5.0; renderer.strokeColor = [UIColor redColor]; return renderer; }
GithubDemo:https://github.com/rayshen/ShenMapViewDemo
___________________________________________________
专注iOS/前端开发,广泛涉猎多种平台和技术,欢迎交流
可以在微博关注并@沈z伟