iOS 地图开发-那些年我们遇到的坑~
一、前言
地图开发的重要性就不讲了,大家看一下自己手机里的App就知道了,10个App有9个包含了位置服务功能,而且发展到今天,精准度已经非常高了。balabala......
我是地图.png二、关于地图的Info.plist文件配置
iOS 10 和iOS 11 的设置分别是这样配置的(如下图)
注:每次Apple有大版本更新,都得检查一下plist文件的权限配置
如果配置不完整,在部分系统上是无法加载地图的(显示为一片蓝色),且在相应App的设置里都找不到『位置』这一项。反之,如果你发现设置里没有『位置』,大概就是权限配置有问题了。
『位置』.png三、我们用那种地图更好?
常用的地图:高德地图、百度地图、腾讯地图、Google地图、Apple地图
1.基于功能的强大,个人首推高德地图(如果不需要国际化)
我认为高德地图是我大天朝最好的地图供应商(仅限于国内),主要原因当然是里面的代理方法相当的丰富,文档也很清晰。
2.如果需要国际化,就只能考虑Apple地图和Google地图了
Google地图当然很好(可能是地球上最好的地图服务商),但免费版只提供了基础的定位服务,兴趣点等稍高级的服务都是收费的,所以,基于『勤俭节约』,就放弃吧!
3.重点来了,Apple地图
其实,Apple地图的数据并不是Apple公司提供的。大家打开自带的地图App,看看左下角,有一个『高德地图』的标志,那地处歪果的iPhone里地图App是什么呢?答案是『TomTom』,大家番羽墙出去,再打开看看就知道了。也就是说,Apple只拿着别人家的数据,优化了一下UI,就成了Apple地图了,它自己是没有地图数据的,就更不能提供地图服务了。Apple地图是根据当前网络所处的国家,自动请求不同服务商的数据(国内是高德,国外则是Tom Tom)。Apple地图的API完全没有高德地图的API丰富。
注:其实如果业务逻辑不那么复杂,直接用Apple地图就行了。毕竟原生,简单、好用。(文档中都有)
四、原生地图的坑
『附近的兴趣点』!!!(兴趣点 学名:POI - Point of Interest)
『关键字检索POI』就大家都有,就不说了。这里主要说一下『检索周边POI』。
1.什么叫『附近的兴趣点』?
在地图表达中,一个POI可代表一栋大厦、一家商铺、一处景点等等。通过POI搜索,完成找餐馆、找景点、找厕所等等的功能。
比如:附近的停车场、附近的建筑、附近的公司、附近的电影院。
2.高德地图如何实现?
高德地图的POI类别共20个大类,分别为:(InterestKey)
- 汽车服务、汽车销售、汽车维修、摩托车服务、餐饮服务、
- 购物服务、生活服务、体育休闲服务、医疗保健服务、住宿服务、
- 风景名胜、商务住宅、政府机构及社会团体、科教文化服务、交通设施服务、
- 金融保险服务、公司企业、道路附属设施、地名地址信息、公共设施
注:不设置POI的类别,默认返回“餐饮服务”、“商务住宅”、“生活服务”这三种类别的POI
- 高德地图的附近POI检索(AMapPOIAroundSearchRequest)
- (void)loadAroundPOIsInAMapWithCoordinate:(CLLocationCoordinate2D)coordinate {
AMapPOIAroundSearchRequest *request = [[AMapPOIAroundSearchRequest alloc]init];
request.location = [AMapGeoPoint locationWithLatitude:coordinate.latitude longitude:coordinate.longitude];
// exp : @"公司企业|政府机构及社会团体|道路附属设施|地名地址信息"
request.types = InterestKey;
request.sortrule = 0;
request.requireExtension = YES;
[self.search AMapPOIAroundSearch:request];
}
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response {
for (AMapPOI *poi in response.pois) {
// 检索的结果
}
}
- 高德地图的关键字检索是另一套方法(AMapInputTipsSearchRequest)
- (void)searchInAMapWith:(NSString *)searchKey {
AMapInputTipsSearchRequest *request = [[AMapInputTipsSearchRequest alloc]init];
request.keywords = searchKey;
request.types = @"公司企业|政府机构及社会团体";
[self.search AMapInputTipsSearch:request];
}
- (void)onInputTipsSearchDone:(AMapInputTipsSearchRequest *)request response:(AMapInputTipsSearchResponse *)response {
NSMutableArray *resultArrM = [NSMutableArray array];
for (AMapTip *tip in response.tips) {
// 检索的结果
}
}
3.Apple地图如何实现?
『臣妾』做不到!!!我也很无奈!!!
- 好吧,其实也有一个比较凑合的方式(非常凑合):
那就是有关键字检索的方式,固定检索事先设置好的关键字,比如:
Company|Community|cafe|supermarket|village|
Shop|Restaurant|School|hospital|Street|
Convenience store|Shopping Centre|Place names|Hotel|Grocery store
经过实测,有一定的效果(毕竟,有总比没有好~)
- (void)loadAroundPOIsInAppleMapWithCoordinate:(CLLocationCoordinate2D)coordinate {
MKCoordinateSpan span = {0.005, 0.005};
MKCoordinateRegion region = {coordinate, span}; //MKCoordinateRegionMakeWithDistance(coordinate,1000, 1000);
MKLocalSearchRequest *req = [[MKLocalSearchRequest alloc] init];
req.region = region;
req.naturalLanguageQuery = @"Company";
MKLocalSearch * ser = [[MKLocalSearch alloc] initWithRequest:req];
//开始检索,结果返回在block中
[ser startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (error) {
return ;
}
//兴趣点节点数组
for (MKMapItem *item in response.mapItems) {
}
}];
}
- 如果是真正的关键字检索呢?大家自行对比一下(其实是一模一样,唯一不同就是naturalLanguageQuery的不同使用,注:非官方做法,慎重!)
- (void)searchInAppleMapWithNaturaLanguage:(NSString *)searchKey {
MKCoordinateRegion region = self.mkMapView.region;
MKLocalSearchRequest *req = [[MKLocalSearchRequest alloc] init];
req.region = region;
req.naturalLanguageQuery = searchKey;
MKLocalSearch * ser = [[MKLocalSearch alloc] initWithRequest:req];
//开始检索,结果返回在block中
[ser startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (error) {
return ;
}
//兴趣点节点数组
for (MKMapItem *item in response.mapItems) {
}
}];
}
五、关于地图坐标系的问题
- 地球坐标(WGS84) - 是国际标准,GPS坐标(Google Earth使用、或者GPS模块)- 苹果的CLLocationManager
- 火星坐标(GCJ-02) - 中国坐标偏移标准,Google地图、高德、腾讯使用
- 百度坐标(BD-09) - 百度坐标偏移标准,Baidu地图使用
我们经常在常用的这几种地图中进行坐标转换,或用于第三方地图的导航,或用于后台下发地址的打点等等场景。虽然百度和高德都提供相应的API,但是他们都只提供向自家坐标系转化的API,需要连网请求才能得到转化后的结果。
提供一下坐标系的转换代码 👇👇👇
CLLocation+Sino.h
#import <CoreLocation/CoreLocation.h>
@interface CLLocation (Sino)
/**地球坐标转火星坐标*/
- (CLLocation*)locationMarsFromEarth;
/**火星坐标转百度坐标*/
- (CLLocation*)locationBearPawFromMars;
/**百度坐标转火星坐标*/
- (CLLocation*)locationMarsFromBearPaw;
@end
CLLocation+Sino.m
#import "CLLocation+Sino.h"
void transform_earth_2_mars(double lat, double lng, double* tarLat, double* tarLng);
void transform_mars_2_bear_paw(double lat, double lng, double* tarLat, double* tarLng);
void transform_bear_paw_2_mars(double lat, double lng, double* tarLat, double* tarLng);
@implementation CLLocation (Sino)
- (CLLocation*) locationMarsFromEarth {
double lat = 0.0;
double lng = 0.0;
transform_earth_2_mars(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
- (CLLocation*) locationBearPawFromMars {
double lat = 0.0;
double lng = 0.0;
transform_mars_2_bear_paw(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
- (CLLocation*) locationMarsFromBearPaw {
double lat = 0.0;
double lng = 0.0;
transform_bear_paw_2_mars(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat, lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}
const double a = 6378245.0;
const double ee = 0.00669342162296594323;
bool transform_sino_out_china(double lat, double lon) {
if (lon < 72.004 || lon > 137.8347)
return true;
if (lat < 0.8293 || lat > 55.8271)
return true;
return false;
}
double transform_earth_2_mars_lat(double x, double y) {
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0;
ret += (20.0 * sin(y * M_PI) + 40.0 * sin(y / 3.0 * M_PI)) * 2.0 / 3.0;
ret += (160.0 * sin(y / 12.0 * M_PI) + 320 * sin(y * M_PI / 30.0)) * 2.0 / 3.0;
return ret;
}
double transform_earth_2_mars_lng(double x, double y) {
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * M_PI) + 20.0 * sin(2.0 * x * M_PI)) * 2.0 / 3.0;
ret += (20.0 * sin(x * M_PI) + 40.0 * sin(x / 3.0 * M_PI)) * 2.0 / 3.0;
ret += (150.0 * sin(x / 12.0 * M_PI) + 300.0 * sin(x / 30.0 * M_PI)) * 2.0 / 3.0;
return ret;
}
void transform_earth_2_mars(double lat, double lng, double* tarLat, double* tarLng) {
if (transform_sino_out_china(lat, lng)) {
*tarLat = lat;
*tarLng = lng;
return;
}
double dLat = transform_earth_2_mars_lat(lng - 105.0, lat - 35.0);
double dLon = transform_earth_2_mars_lng(lng - 105.0, lat - 35.0);
double radLat = lat / 180.0 * M_PI;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * M_PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * M_PI);
*tarLat = lat + dLat;
*tarLng = lng + dLon;
}
const double x_pi = M_PI * 3000.0 / 180.0;
void transform_mars_2_bear_paw(double gg_lat, double gg_lon, double *bd_lat, double *bd_lon) {
double x = gg_lon, y = gg_lat;
double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
*bd_lon = z * cos(theta) + 0.0065;
*bd_lat = z * sin(theta) + 0.006;
}
void transform_bear_paw_2_mars(double bd_lat, double bd_lon, double *gg_lat, double *gg_lon) {
double x = bd_lon - 0.0065, y = bd_lat - 0.006;
double z = sqrt(x * x + y * y) - 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) - 0.000003 * cos(x * x_pi);
*gg_lon = z * cos(theta);
*gg_lat = z * sin(theta);
}
@end
六、写在最后
本文没有涉及详细开发步骤,主要就是『点一下狼坑』而已,常规的开发网上有很多,而且还有各自的官方开发文档。
目的是介绍地图开发中一些可能出现的问题,也都是我在开发中的『心路历程』。有不对的地方,欢迎指正!
就是想配个图.png