iOS开发iOS开发

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

2017-12-04  本文已影响728人  Leewins

一、前言

地图开发的重要性就不讲了,大家看一下自己手机里的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

- (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) {
        // 检索的结果
    }
}
- (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) {
            
        }
    }];
}
- (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) {

        }
    }];
}

五、关于地图坐标系的问题

我们经常在常用的这几种地图中进行坐标转换,或用于第三方地图的导航,或用于后台下发地址的打点等等场景。虽然百度和高德都提供相应的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
上一篇下一篇

猜你喜欢

热点阅读