记录从百度,高德国内地图引擎的熟练操作到国际化App的Mapkit调用(内容涉及较广,后续会持续更新)
国内百度,高德地图引擎的api已经很成熟了,为什么我会选择苹果原生的Mapkit呢?
- 国际化项目,我们项目使用需求是在香港。实地考察发现,香港的高德地图针对细小的路段,偏差严重,百度地图相比精确一点,但2者最大的问题也就是地图的涂层绘制的很粗略(在香港的地理环境下)。
- 百度,高德用来乘车导航的,地铁线路还是可以的,但现在目标缩小到一个花园级别,范围需要精确,里面小路蜿蜒曲折,草坪和假山众多,需要体现的是游玩的乐趣。
- 高德,百度,谷歌(安卓)地图坐标系的区别,也是要去解决的问题,因为局域网的问题。内地游客可能使用的地图坐标系和当地游客显示不一样。这样就在还没到到达香港的时候,提前预览会出现大头针错乱还有地图overLayer的错位,地理围栏也是香港实测的经纬度矩形范围读取中心区域而来,所以会有偏移。
地图开发的共同问题
在进行地图开发过程中,我们一般能接触到以下三种类型的地图坐标系:
1.WGS-84原始坐标系,一般用国际GPS纪录仪记录下来的经纬度,通过GPS定位拿到的原始经纬度,Google和高德地图定位的的经纬度(国外)都是基于WGS-84坐标系的;但是在国内是不允许直接用WGS84坐标系标注的,必须经过加密后才能使用;
2.GCJ-02坐标系,又名“火星坐标系”,是我国国测局独创的坐标体系,由WGS-84加密而成,在国内,必须至少使用GCJ-02坐标系,或者使用在GCJ-02加密后再进行加密的坐标系,如百度坐标系。高德和Google在国内都是使用GCJ-02坐标系,可以说,GCJ-02是国内最广泛使用的坐标系;
3.百度坐标系:bd-09,百度坐标系是在GCJ-02坐标系的基础上再次加密偏移后形成的坐标系,只适用于百度地图。(目前百度API提供了从其它坐标系转换为百度坐标系的API,但却没有从百度坐标系转为其他坐标系的API)
二。为什么会发生偏移?
1.由于坐标系之间不兼容,如在百度地图上定位的经纬度拿到高德地图上直接描点就肯定会发生偏移;只考虑国内的情况,高德地图和Google地图是可以不经过转换也能够准确显示的(在国内用的都是GCJ-02坐标系);下面是收录了网上的WGS-84,GCJ-02,百度坐标系(bd-09)之间的相互转换的方法,经测试,是转换后相对准确可用的:
1 | package com.zehin.map.util; |
Mapkit 优点
- 根据地理自动切换更合适的地图引擎数据,国内它的数据是由高德地图提供的,香港就是tomtom
- 国际化App语言适配更优秀
- 适配最新13以上系统,黑暗模式的UI切换也兼容
- 点的聚合缩放更自然得体,对地图进行精准的控制
- WWDC 2019 发布会对苹果原生地图的革命更新,后续将会在车载,智能家居上出现
Mapkit 缺点
- 安卓一般用高德或百度的定位SDK,所以获取到的坐标也就是相对应的高德坐标或百度坐标,iOS中,CLLocationManager 获取到的是地球坐标,也就是WGS84,但是MKMapView 里面的那个 showUserLocation 是高德坐标,因为iOS 自带的地图是高德的,所以会有一个现象:如果直接拿CLLocationManager 获取到的坐标在 MKMapView 上面描点,就发现和 userLocation 那个蓝色的点有偏差。需要转换坐标系
做事前需要阅读相关文档
- [原生Mapkit]:(https://developer.apple.com/documentation/mapkit?language=objc)
- [百度地图API开发文档]:(https://lbsyun.baidu.com/index.php?title=iossdk)
- [高德地图API开发文档]:(https://lbs.amap.com/api/ios-sdk/summary/)
了解地图开发所需的基本
- 申请定位权限
- 查看定位权限
- 获取定位信息
- 获取指南针信息
- 地理编码
- 反地理编码
- 后台定位低功耗设置 等
一、模块与常见类
定位所包含的类都在CoreLocation模块中,所以必须导入import CoreLocation
CLLocation:表示某个位置的地理信息,比如经纬度、海拔等
CLLocationManager:定位管理器,可以理解为定位不能自己工作,需要有个管理者对它进行全过程监督。
CLGeocoder:地理编码,分为两种正向地理编码:根据位置信息,获取具体的经纬度等信息
反向地理编码:根据给定的经纬度等信息,获取位置信息
CLPlacemark:位置信息,包含的信息如国家、城市、街道等
CLLocationManagerDelegate:定位代理,不管是定位成功与失败,都会有相应的代理方法回调
具体的工作流程
(1)CLLocationManager发起定位,定位成功或者失败都会回调CLLocationManagerDelegate中相应的代理方法
(2)在成功的代理方法中获取 CLLocation 对象,进而获取经纬度
(3)通过 CLGeocoder获取经纬度对应的位置信息CLPlacemark
(4)通过CLPlacemark获取具体的位置信息
二、权限
iOS隐私保护是最好的,谈到需要定位的功能,第一次必须弹出对话框给用户选择
- 使用时才定位权限,使用这种,必须走两步
(1)程序中发起 requestWhenInUseAuthorization
(2)在info.plist对应的位置写明申请权限的具体原因 - 一直可以定位权限,使用这种,也是两步
(1)程序中发起 requestAlwaysAuthorization
(2)在info.plist对应的位置写明申请权限的具体原因
配置字段说明:
- iOS 8之前只需要配置 Privacy - Location Usage Description
- iOS 8 - iOS 10 只有两个配置:
Privacy - Location Always Usage Description
Privacy - Location When In Use Usage Description
- iOS 11之后多了Privacy - Location Always and When In Use Usage Description,所以iOS11之后必须配置的是
Privacy - Location When In Use Usage Description和Privacy - Location Always and When In Use Usage Description(重点来了)
1 | 注意:上架的App这个原因必须写明确 |
三、后台定位编译器需要的操作
首先在Capabilities中打开后台模式
前面说过定位权限分两种,针对这两种情况,后台定位的代码不一样,效果也不一样
- 使用时才定位权限需要加上locationManager.allowsBackgroundLocationUpdates = true
- 开启后台定位,而一直可以定位权限不需要写任何额外代码
使用时才定位权限退出后,手机顶部会有蓝条提示,而一直可以定位权限则没有
四、地图显示-MapView
MapKit的核心类为地图展示控件MKMapView,以下是常用的属性、对象方法以及代理方法。
- 属性所谓大头针就是地图上显示的这个标注:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/* 用户位置跟踪 */
@property (nonatomic) BOOL showsUserLocation;/*< 是否在地图上标注用户位置 */
@property (nonatomic, readonly) MKUserLocation *userLocation;/*< 用户位置 */
@property (nonatomic) MKUserTrackingMode userTrackingMode;/*< 用户跟踪类型 */
typedef NS_ENUM(NSInteger, MKUserTrackingMode) {
MKUserTrackingModeNone = 0, /*< 不跟踪 */
MKUserTrackingModeFollow, /*< 跟踪 */
MKUserTrackingModeFollowWithHeading, /*< 导航跟踪 */
};
/* 设置地图配置项 */
@property (nonatomic) MKMapType mapType;/*< 地图类型 */
@property (nonatomic, readonly) NSArray *annotations;/*< 大头针数组 */
typedef NS_ENUM(NSUInteger, MKMapType) {
MKMapTypeStandard = 0,/*< 标准地图 */
MKMapTypeSatellite,/*< 卫星地图 */
MKMapTypeHybrid,/*< 混合模式(标准+卫星) */
MKMapTypeSatelliteFlyover,/*< 3D立体卫星(iOS9.0) */
MKMapTypeHybridFlyover,/*< 3D立体混合(iOS9.0) */
}
/* 设置地图控制项 */
@property (nonatomic) BOOL zoomEnabled;/*< 是否可以缩放 */
@property (nonatomic) BOOL scrollEnabled;/*< 是否可以滚动 */
@property (nonatomic) BOOL rotateEnabled;/*< 是否可以旋转 */
@property (nonatomic) BOOL pitchEnabled;/*< 是否显示3D视角 */
/* 设置地图显示项 */
@property (nonatomic) BOOL showsBuildings;/*< 是否显示建筑物,只影响标准地图 */
@property (nonatomic) BOOL showsTraffic;/*< 是否显示交通,iOS9 */
@property (nonatomic) BOOL showsCompass;/*< 是否显示指南针,iOS9 */
@property (nonatomic) BOOL showsScale;/*< 是否显示比例尺,iOS9 */
- 对象方法:
1 | /* 添加大头针 */ |
- 常用代理方法MKMapViewDelegate:
1 | /* 地图加载完成会调用 */ |
初始化地图展示控件方法太简单,不多说了,和UI控件一样的操作,需要自定义多样化参考属性设置
用户位置跟踪,iOS8之前不谈,太简单了,iOS8之后需要获取前台或者前后台的定位服务授权,在plist里面设置,前面有讲到配置
不需要进行中心点的指定,默认会将当前位置设置为中心点并自动显示区域范围
只有定位到当前位置后mapView:DidUpdateUserLocation:代理方法才会调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38- (void)viewDidLoad {
[super viewDidLoad];
//获取定位服务授权
[self requestUserLocationAuthor];
//初始化MKMapView
[self initMapView];
}
- (void)requestUserLocationAuthor{
//如果没有获得定位授权,获取定位授权请求
self.locationM = [[CLLocationManager alloc] init];
if ([CLLocationManager locationServicesEnabled]) {
if ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse) {
[self.locationM requestWhenInUseAuthorization];
}
}
}
- (void)initMapView{
CGFloat x = 0;
CGFloat y = 20;
CGFloat width = self.view.frame.size.width;
CGFloat height = self.view.frame.size.height;
//创建MKMapView对象
MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(x, y, width, height)];
//设置地图类型
mapView.mapType = MKMapTypeStandard;
//设置用户跟踪模式
mapView.userTrackingMode = MKUserTrackingModeFollow;
mapView.delegate = self;
[self.view addSubview:mapView];
self.mapView = mapView;
}
#pragma mark - MKMapViewDelegate
/* 更新用户位置会调用 */
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
CLLocation *location = userLocation.location;
CLLocationCoordinate2D coordinate = location.coordinate;
NSLog(@"经度:%f,纬度:%f",coordinate.latitude,coordinate.longitude);
}添加大头针 MapKit没有自带的大头针,只有大头针协议MKAnnotation,我们需要自定义大头针:
建一个继承NSObject的类
实现MKAnnotation协议
必须创建一个属性,用于存储大头针位置
1
@property (nonatomic) CLLocationCoordinate2D coordinate;
简单创建的ZHYAnnotation类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface ZHYAnnotation : NSObject <MKAnnotation>
/* 必须创建的属性 */
@property (nonatomic) CLLocationCoordinate2D coordinate;
/* 可选的属性 */
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
/* 自定义的属性 */
@property (nonatomic, strong) UIImage *icon;
@end
@implementation ZHYAnnotation
@end实际的使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- (void)viewDidLoad {
[super viewDidLoad];
//请求定位授权
[self requestUserLocationAuthor];
//初始化MKMapView
[self initMapView];
//添加大头针
[self addAnnotationsToMapView];
}
- (void)addAnnotationsToMapView{
CLLocationCoordinate2D location1 = CLLocationCoordinate2DMake(22.54, 114.02);
//创建大头针
ZHYAnnotation *annotation = [[ZHYAnnotation alloc] init];
annotation.title = @"胡杨树";
annotation.subtitle = @"张湖扬开的店";
annotation.coordinate = location1;
annotation.icon = [UIImage imageNamed:@"red"];
//添加大头针
[self.mapView addAnnotation:annotation1];
}MKMapView的默认样式大头针视图MKAnnotationView,我们先来了解下它的常用属性:
1 | @property (nonatomic, strong) id<MKAnnotation> annotation;/*< 大头针数据 */ |
下面是通过设置MKAnnotationView的属性,自定义大头针视图:
1 | /* 每当大头针显示在可视界面上时,就会调用该方法,用户位置的蓝色点也是个大头针,也会调用 */ |