iOS 地图开发-那些年我们遇到的坑~

一、前言

地图开发的重要性就不讲了,大家看一下自己手机里的App就知道了,10个App有9个包含了位置服务功能,而且发展到今天,精准度已经非常高了。balabala......

我是地图.png

二、关于地图的Info.plist文件配置

iOS 10 和iOS 11 的设置分别是这样配置的(如下图)
注:每次Apple有大版本更新,都得检查一下plist文件的权限配置

iOS 10 位置服务权限配置.png
iOS 11 位置服务权限配置.png

如果配置不完整,在部分系统上是无法加载地图的(显示为一片蓝色),且在相应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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348