地图与定位(一)定位服务
来源:互联网 发布:单片机uart是什么意思 编辑:程序博客网 时间:2024/05/01 19:15
前言:
现在很多社交、电商、团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的。的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式。例如你到了一个陌生的地方想要查找附近的酒店、超市等就可以打开软件搜索周边;类似的,还有很多团购软件可以根据你所在的位置自动为你推荐某些商品。总之,目前地图和定位功能已经大量引入到应用开发中。在产品研发中有两个专业术语需要大家知道:一是LBS(Location Based Service)基于定位的服务,二是SoLoMo(Social Local Mobile )社交本地的移动应用,这都是需要我们提供地图和定位服务。
在后面的课程里就和大家一起看一下iOS如何进行地图和定位开发。iOS系统为了方便我们开发,提供了地图服务的框架。除此之外,实际的开发工作中,我们常常会使用一些第三发的SDK来实现地图服务,主要有高德地图和百度地图,这些我们后在后面的几节中讲到。
地图功能的实现离不开定位服务,下面我们还是先来看一下iOS系统的定位功能是如何实现的。
1. 定位服务
要实现地图、导航功能,往往需要先熟悉定位功能,iOS中的定位引擎是CoreLocation框架提供的,我们通过CoreLocation框架进行定位操作。CoreLocation自身可以单独使用,和地图开发框架MapKit完全是独立的,但是往往地图开发要配合定位框架使用。在Core Location中主要包含了定位、地理编码(包括反编码)功能。
我们先介绍一下iOS定位功能的实现。定位是一个很常用的功能,如一些地图软件打开之后如果用户允许软件定位的话,那么打开软件后就会自动锁定到当前位置,如果用户手机移动那么当前位置也会跟随着变化。要实现这个功能需要使用CoreLoaction中CLLocationManager类,首先看一下这个类的一些主要方法和属性:
类方法
说明
- (BOOL)locationServicesEnabled;
是否启用定位服务,通常如果用户没有启用定位服务可以提示用户打开定位服务
- (CLAuthorizationStatus)authorizationStatus;
定位服务授权状态,返回枚举类型:
kCLAuthorizationStatusNotDetermined: 用户尚未做出决定是否启用定位服务
kCLAuthorizationStatusRestricted: 没有获得用户授权使用定位服务,可能用户没有自己禁止访问授权
kCLAuthorizationStatusDenied :用户已经明确禁止应用使用定位服务或者当前系统定位服务处于关闭状态
kCLAuthorizationStatusAuthorizedAlways: 应用获得授权可以一直使用定位服务,即使应用不在使用状态
kCLAuthorizationStatusAuthorizedWhenInUse: 使用此应用过程中允许访问定位服务
除了CLLocationManager之外,CLLocation类也是在我们做定位服务中经常看到的,CLLocation常用来表示某个位置的地理信息,比如经纬度、海拔高度等,当然他也给我们提供了计算两个地理位置之间间距的方法。下面我们看一下CLLocation的常用属性和方法:
一般在开始定位之前,应用会向用户获取授权请求,在iOS7及以前的版本,如果在应用程序中使用定位服务只要在程序中调用startUpdatingLocation方法应用就会询问用户是否允许此应用是否允许使用定位服务,同时在提示过程中可以通过在info.plist中配置通过配置Privacy -Location Usage Description告诉用户使用的目的,当然这个配置是可选的。但是在iOS8中配置配置项发生了变化,我们可以通过配置NSLocationAlwaysUsageDescription或者 NSLocationWhenInUseUsageDescription来告诉用户使用定位服务的目的,并且注意这个配置是必须的,如果不进行配置则默认情况下应用无法使用定位服务,打开应用不会给出打开定位服务的提示,除非安装后自己设置此应用的定位服务。同时,在应用程序中需要根据配置对requestAlwaysAuthorization或locationServicesEnabled方法进行请求。
iOS8提供了更加人性化的定位服务选项。应用的定位服务不再仅仅是关闭或打开。现在,定位服务的启用提供了三个选项:永不、使用应用程序期间、和始终。同时,考虑到能耗问题,如果一款 App 要求始终能在后台开启定位服务,iOS 8 不仅会在首次打开 App 时主动向你询问,还会在日常使用中弹窗提醒你该 App 一直在后台使用定位服务,并询问你是否继续允许。
下面我们就来看一看iOS实现定位服务的具体步骤有哪些
- 判断硬件是否开启了定位服务
- 判断应用是否获取定位授权
- 初始化定位管家的对象,注意需要设置为全局变量
- 设置定位权限:iOS8的新特性,可以实现代理方法获取授权范围。注意需要修改plist文件NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。
- 设置定位服务的属性。
- 开启定位,实现代理方法,获取定位信息
示例代码
#import "ViewController.h"#import <CoreLocation/CoreLocation.h>@interface ViewController ()<CLLocationManagerDelegate>// 定位服务管家@property (nonatomic, strong)CLLocationManager *locationManager;@end@implementation ViewController/*定位的实现 1.导入框架 CoreLocation.framework 2.导入库文件 #import <CoreLocation/CoreLocation.h> 3.需要将定位管家 CLLocationManager 设置为全区变量 1.判断硬件是否开启了定位服务 2.初始化定位管家的对象,注意需要设置为全局变量 3.判断定位服务授权状态,设置定位权限:iOS8的新特性,可以实现代理方法获取授权范围。注意需要修改plist文件 NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。 4.设置定位服务的属性。 5.开启定位,实现代理方法,获取定位信息 */- (void)viewDidLoad { [super viewDidLoad]; // 1.判断硬件是否开启了定位服务 BOOL isOpen = [CLLocationManager locationServicesEnabled]; if (isOpen) { NSLog(@"定位服务已经打开"); }else { NSLog(@"定位服务未开启"); UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"你的定位服务未开启" message:@"请到setting开启定位服务" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; return; } // 判断应用是否获取定位授权 NSInteger status = [CLLocationManager authorizationStatus]; switch (status) { case 0: NSLog(@"kCLAuthorizationStatusNotDetermined--没有决定");// 用户从未选择过权限 break; case 1: NSLog(@"kCLAuthorizationStatusRestricted--没有许可");// 无法使用定位服务,该状态用户无法改变 break; case 2: NSLog(@"kCLAuthorizationStatusDenied--禁止使用");// 用户拒绝该应用使用定位服务,或是定位服务总开关处于关闭状态 break; case 3: NSLog(@"kCLAuthorizationStatusAuthorizedAlways--始终允许");// 大致是用户同意程序在任意时候使用地理位置 break; case 4: NSLog(@"kCLAuthorizationStatusAuthorizedWhenInUse--开启允许");// 大致是用户同意程序在可见时使用地理位置 break; default: break; } // 2.初始化定位管家的对象,注意需要设置为全局变量,因为我们需要一直持有定位管家的对象,局部变量使用后即被销毁,在代理方法中,无法获得该对象及其属性,所以需要设置为全局变量 [self locationManager]; // 3.判断定位服务授权状态,设置定位权限:iOS8的新特性,可以实现代理方法获取授权范围。注意需要修改plist文件NSLocationAlwaysUsageDescription/NSLocationWhenInUseUsageDescription。 if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) { // 判断授权状态,在授权未确定是获取授权,一旦确定即无法在程序中修改,只能在settings中对app做授权设置 if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {// 判断系统版本 大于8.0以上的版本可以手动获取授权 // 请求始终允许访问,包括进入后台后 对应info设置:NSLocationAlwaysUsageDescription// [self.locationManager requestAlwaysAuthorization]; // 请求当app打开时允许访问 对应info设置:NSLocationWhenInUseUsageDescription [self.locationManager requestWhenInUseAuthorization]; // 注意:修改plist文件 // NSLocationAlwaysUsageDescription---我想在后台还访问您的位置 // NSLocationWhenInUseUsageDescription---我想在我的app开启的时候使用您的位置,可以吗? } }else if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"你没有给该应用的定位服务授权" message:@"请到setting设置定位服务授权" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; return;// 没有给予授权 } // 4.设置定位服务的属性。 [self.locationManager setDesiredAccuracy:kCLLocationAccuracyThreeKilometers]; // 设置定位刷新距离,可以直接是由上面的参数指定 [self.locationManager setDistanceFilter:100];//移动距离大于distanceFilter就会定位,否则不会,避免频繁的定位,消耗电量 // 5.开启定位,实现代理方法,获取定位信息 [self.locationManager startUpdatingLocation];// [self.locationManager stopUpdatingLocation];// 获取定位数据后调用 // 开始追踪导航方向, [self.locationManager startUpdatingHeading];// [self.locationManager stopUpdatingHeading];// 停止追踪导航方向 // 开启区域追踪,需要传入一个追踪区域 CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(39.0, 116.0); CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:1000 identifier:@"找到你了"]; [self.locationManager startMonitoringForRegion:region];}// 懒加载定位管家- (CLLocationManager *)locationManager { if (!_locationManager) { _locationManager = [[CLLocationManager alloc] init]; _locationManager.delegate = self;// 设置定位服务的代理对象 } return _locationManager;}#pragma mark CLLocationManagerDelegate/** * 方法说明:当用户许可状态发生改变时,调用该方法 * * @param status:用户的许可状态 * * @return */- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{ NSLog(@"认证状态改变 status:%d",status);}/** * 方法说明:执行定位后调用该方法 * * @param * * @return */- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { // 停止定位(省电措施:只要不想用定位服务,或者获取定位信息后,就马上停止定位服务) [self.locationManager stopUpdatingLocation]; // 1.取出位置对象(数组中可能会有多个位置对象,取出第一个是最精确的) CLLocation *loc = [locations firstObject]; // 2.取出经纬度 coordinate:位置坐标 course:方向 CLLocationCoordinate2D coordinate = loc.coordinate; CLLocationDegrees longitude = coordinate.longitude; CLLocationDegrees latitude = coordinate.latitude; // 3.CLLocations的常用属性和方法 /* horizontalAccuracy,用来得到水平上的精确度,它的大小就是定位精度的半径,单位为米。获得的不是用户设置的精度 而是最终定位的精度,如果值为-1,则说明此定位不可信。 course 方向: 0 ~ 359.9 , 0 代表正北 speed 速度:m/s 获取两个位置之间的距离 - (CLLocationDistance)distanceFromLocation:(const CLLocation *)location */ NSLog(@"经度:%f \n 纬度:%f", longitude, latitude); NSLog(@"方向:%f \n 海拔:%f",loc.course, loc.altitude); NSLog(@"水平定位精度%f 竖直定位精度%f",loc.horizontalAccuracy,loc.verticalAccuracy); NSLog(@"速度:%f",loc.speed); // 计算2个经纬度之间的直线距离 CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:40 longitude:116]; CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:41 longitude:116]; // 计算2个经纬度之间的直线距离 CGFloat distance = [self countLineDistance:loc1 withLocation:loc2]; NSLog(@"%f",distance);}/** * 计算2个经纬度之间的直线距离 */- (double)countLineDistance:(CLLocation *)loc1 withLocation:(CLLocation *)loc2{ CLLocationDistance distance = [loc1 distanceFromLocation:loc2]; return distance;}/** * 方法说明: 导航方向发生变化的时候执行此方法 * * @param newHeading 方向: 0 ~ 359.9 , 0 代表正北 * * @return */- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{ NSLog(@"方向改变");}- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region{ NSLog(@"进入到该区域");}- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region{ NSLog(@"离开该区域");}@end
使用定位功能时,有几点需要我们注意:
- 定位频率和定位精度并不应当越精确越好,需要视实际情况而定,因为越精确越耗性能,也就越费电。
- 定位成功后会根据设置情况频繁调用-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray )locations方法,使用完定位服务后如果不需要实时监控应该立即关闭定位服务以节省资源,所以我们需要在合适的时候停止定位。
- -(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray )locations方法返回一组地理位置对象数组,每个元素一个CLLocation代表地理位置信息(包含经度、纬度、海报、行走速度等信息),之所以返回数组是因为有些时候一个位置点可能包含多个位置。
2. 地理编码和反地理编码
除了提供位置跟踪功能之外,在定位服务中还包含CLGeocoder类用于处理地理编码和逆地理编码功能。这个功能的实现主要是由CLGeocoder类提供的。CLGeocoder最主要的两个方法就是
- -(void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;// 根据给定的位置(通常是地名)确定地理坐标(经、纬度)。
- -(void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler; // 根据地理坐标(经、纬度)确定位置信息(街道、门牌等)。
在上面两个方法的方法中,当地理编码或是反编码结束时,会回调CLGeocodeCompletionHandler,当中传递给我们两个参数,一个NSArray,一个NSError。NSError是编码或者反编码错误的详情,数组中则存放着我们地理编码或是反地理编码后获取的地标信息,为什么是数组呢,因为我们可能查询到多个地标,比如“帝都”代表的地标就有多个。在CoreLocation框架中,地标是一个CLPlacemark的实例对象。下面我们先来看一下CLPlacemark的常用属性:
示例代码
#import "ViewController.h"#import <CoreLocation/CoreLocation.h>@interface ViewController ()@property (nonatomic, strong)CLGeocoder *geocoder;//地理编码器#pragma mark - 地理编码 根据地区名称查询所在的经纬度坐标@property (strong, nonatomic) IBOutlet UITextField *addressField;//输入地区名称@property (strong, nonatomic) IBOutlet UILabel *longitudeLabel;//显示地区的经度@property (strong, nonatomic) IBOutlet UILabel *latitudeLabel;//显示地区的纬度@property (strong, nonatomic) IBOutlet UILabel *detailAddressLabel;//显示地区的详细信息#pragma mark - 反地理编码 根据经纬度获取对应的地区名@property (strong, nonatomic) IBOutlet UITextField *reverseLongtitudeField;//查询地区的经度@property (strong, nonatomic) IBOutlet UITextField *reverseLatitudeField;//查询地区的纬度@property (strong, nonatomic) IBOutlet UILabel *reverseDetailAddressLabel;//查询的地区详细名称@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; //初始化地理编码器 if (!_geocoder) { _geocoder = [[CLGeocoder alloc] init]; }}// 地理编码- (IBAction)geocoder:(id)sender { //获取输入的城市名称 NSString *address = self.addressField.text; if (address.length == 0) return; [self getCoordinateByAddress:address withBlock:^(CLPlacemark *pm) { // 设置经纬度 self.latitudeLabel.text = [NSString stringWithFormat:@"%.2f", pm.location.coordinate.latitude]; self.longitudeLabel.text = [NSString stringWithFormat:@"%.2f", pm.location.coordinate.longitude]; // 设置具体地址 self.detailAddressLabel.text = pm.name; } withfaild:^(NSError *error) { self.detailAddressLabel.text = @"你找的地址可能不存在,请重新输入"; self.addressField.text = @""; }];}- (void)getCoordinateByAddress:(NSString *)address withBlock:(void(^)(CLPlacemark *pm))block withfaild:(void(^)( NSError *error))faild{ [_geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) { if (error) { // 有错误(地址乱输入) faild(error); } else { // 编码成功 // 取出最前面的地址(数组中可能存在过河产讯到的地址) CLPlacemark *pm = [placemarks firstObject]; // 回调数据 block (pm); NSLog(@"总共找到%ld个地址", placemarks.count); //遍历数组,获取所有查找到的城市 for (CLPlacemark *pm in placemarks) { NSLog(@"-----地址开始----"); // 枚举编译出得所有的地理信息 [pm.addressDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { NSLog(@"%@:%@", key, obj); }]; NSLog(@"-----地址结束----"); } } }];}- (IBAction)reverseGeocoder:(id)sender { // 1.包装位置,将输入的字符串设置为地理坐标 CLLocationDegrees latitude = [self.reverseLatitudeField.text doubleValue]; CLLocationDegrees longitude = [self.reverseLongtitudeField.text doubleValue]; CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude]; [self getAddressByLocation:loc withBlock:^(CLPlacemark *pm) { // 设置具体地址 self.reverseDetailAddressLabel.text = [NSString stringWithFormat:@"%@ %@ %@ %@ %@",pm.country,pm.locality,pm.subLocality,pm.thoroughfare,pm.subThoroughfare]; } withFaild:^(NSError *error) { self.reverseDetailAddressLabel.text = @"你找的地址可能只在火星有!!!"; }];}- (void)getAddressByLocation:(CLLocation *)loc withBlock:(void(^)(CLPlacemark *pm))block withFaild:(void(^)(NSError *error))faild { // 2.反地理编码 [_geocoder reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) { if (error) { // 有错误(地址乱输入) faild (error); } else { // 编码成功 // 取出最前面的地址 CLPlacemark *pm = [placemarks firstObject]; block (pm); NSLog(@"总共找到%ld个地址", placemarks.count); for (CLPlacemark *pm in placemarks) { NSLog(@"-----地址开始----"); // 获取所有的地标信息 [pm.addressDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { NSLog(@"%@:%@", key, obj); }]; NSLog(@"-----地址结束----"); } } }];}@end
- 地图与定位(一)定位服务
- 地图与定位(五)高德地图服务一
- 地图与定位(七)高德定位服务
- 地图与定位(六)高德地图服务二
- Android地图地理信息定位服务(一)
- swift 地图定位(一)
- Android开发--定位服务与百度地图
- 与高德地图的一些恩怨(一)定位
- 地图与定位(二)系统地图
- Android特色服务之定位服务(百度地图开发一)
- 高德地图定位、添加定位图标、连线(一)
- Android开发--地图与定位应用--申请Google地图服务(API Key)
- 定位与地图
- gps 定位与地图
- 地图与定位01
- 地图与定位
- 地图与定位
- iOS 地图与定位
- 严重:The web application [web01] appears to have started a thread named ...
- 【代班大咖—这个夏天,和大咖一起愉快的聊天】李善平—程序员职业发展之路
- 导读ICML2016 - Learning Convolutional Neural Networks for Graphs
- NodeJS 跨语言子进程持续通讯
- 羽毛球单打和双打的有效边界区域
- 地图与定位(一)定位服务
- 关于Android studio导入百度地图API的方法
- java.lang.IllegalStateException: No activity
- 朝鮮歷史筆寫本原稿 (韓長庚編)
- WINDOWS中NEXUS的安装使用【ATCO整理】
- Android水波纹特效的简单实现
- CentOS 6.5 安装与配置LAMP FTP
- 后台分页实现方式总结
- JQ-has和hasClass