iOS开发:采用URI方式跳转到各类地图进行导航

  • 最近在做导航,所以把自己找到的资料总结一下!
  • 无论是百度地图、高德地图、谷歌地图还是腾讯地图它们都有自己的SDK,我们只需要在自己的工程中导入SDK并查看相应的官方文档,基本上就可以实现导航。但是这样每个地图的SDK都导入不但麻烦而且占用APP的内存。最关键的是我们上传到AppStore的包文件是有限制的。所以我的原则是能不导入的SDK 就不导入。
  • 还有一种方式就是是以URI跳转的方式(在iOS中就是以URL Scheme的方式),直接跳到对应的地图APP中,直接利用对方的功能来导航。缺点是用户没有安装对应的APP就不能使用其进行导航。 点击导航按钮会出现如下的弹窗, 当然手机上未安装的地图 其名称就不会出现在弹窗上。

1439521824220516.png

  • 在 iOS9之后 若想用URI方式跳转到百度地图、高德地图、腾讯地图、谷歌地图,需要你在info.plist加入这些东西。(ps:LSApplicationQueriesSchemes,短的自己手打吧,另外注意类型!)
    20160422164514276.png

以下出行的默认方式都是驾车

一、百度地图

  1. 说到百度地图,就不得不说它很坑爹。因为百度地图获取的经纬度,是在GCJ-02(火星坐标)进行偏移得到的经纬度,而高德、谷歌、腾讯都是使用GCJ-02坐标体系得到的经纬度。这样使用百度地图获取到的经纬度在高德、谷歌、腾讯上导航都会出现很大的偏差。所以自己做的APP中需要地图功能最好不要导入百度地图的SDK(使用上面三个中任何一个地图获取到的经纬度都可以很容易的转换成百度地图需要的经纬度),如果你是像我这样中途接手的项目,百度地图的相应功能已经做好了,那你可以用下面的方式换算一下经纬度(最下方)。
  2. 代码如下 :需传入起点和终点的经纬度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"baidumap://map/"]]) {
    UIAlertAction *baiduMapAction = [UIAlertAction actionWithTitle:@"百度地图" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    NSString *baiduParameterFormat = @"baidumap://map/direction?origin=latlng:%f,%f|name:我的位置&destination=latlng:%f,%f|name:终点&mode=driving";
    NSString *urlString = [[NSString stringWithFormat:
    baiduParameterFormat,
    userLocation.location.coordinate.latitude,
    userLocation.location.coordinate.longitude,
    self.destinationCoordinate.latitude,
    self.destinationCoordinate.longitude]
    stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
    }];
    [actionSheet addAction:baiduMapAction];
    }
  3. 各个参数代表的含义可参考百度地图官方文档

二、高德地图

  1. 只需传入终点经纬度 高德地图能够跳转回你的APP,前提是backScheme=%@(你的APP的URL)要填写。代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"iosamap://map/"]]) {
    UIAlertAction *gaodeMapAction = [UIAlertAction actionWithTitle:@"高德地图" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    NSString *gaodeParameterFormat = @"iosamap://navi?sourceApplication=%@&backScheme=%@&poiname=%@&lat=%f&lon=%f&dev=1&style=2";
    NSString *urlString = [[NSString stringWithFormat:
    gaodeParameterFormat,
    @"yourAppName",
    @"yourAppUrlSchema",
    @"终点",
    self.destinationCoordinate.latitude,
    self.destinationCoordinate.longitude]
    stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
    }];
    [actionSheet addAction:gaodeMapAction];
    }
  2. 各个参数的含义可参考高德地图官方文档

三、苹果地图

  1. 需传入起点和终点的经纬度,并导入头文件#import MapKit/MKMapItem.h>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [actionSheet addAction:[UIAlertAction actionWithTitle:@"苹果地图" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    //起点
    MKMapItem *currentLocation = [MKMapItem mapItemForCurrentLocation];
    CLLocationCoordinate2D desCorrdinate = CLLocationCoordinate2DMake(self.destinationCoordinate.latitude, self.destinationCoordinate.longitude);
    //终点
    MKMapItem *toLocation = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithCoordinate:desCorrdinate addressDictionary:nil]];
    //默认驾车
    [MKMapItem openMapsWithItems:@[currentLocation, toLocation]
    launchOptions:@{MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeDriving,
    MKLaunchOptionsMapTypeKey:[NSNumber numberWithInteger:MKMapTypeStandard],
    MKLaunchOptionsShowsTrafficKey:[NSNumber numberWithBool:YES]}];
    }]];
  2. 各个参数的含义可参考苹果地图官方文档

四、谷歌地图

  1. 只需传入终点的经纬度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"comgooglemaps://map/"]]) {
    [actionSheet addAction:[UIAlertAction actionWithTitle:@"苹果地图"style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    NSString *urlString = [[NSString stringWithFormat:@"comgooglemaps://?x-source=%@&x-success=%@&saddr=&daddr=%f,%f&directionsmode=driving",
    appName,
    urlScheme,
    coordinate.latitude,
    coordinate.longitude]
    stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
    }]];
    }
  2. 各个参数的含义可参考谷歌地图官方文档

五、腾讯地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"qqmap://map/"]]) {        
[actionSheet addAction:[UIAlertAction actionWithTitle:@"腾讯地图" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSString *QQParameterFormat = @"qqmap://map/routeplan?type=drive&fromcoord=%f, %f&tocoord=%f,%f&coord_type=1&policy=0&refer=%@";
NSString *urlString = [[NSString stringWithFormat:
QQParameterFormat,
userLocation.location.coordinate.latitude,
userLocation.location.coordinate.longitude,
self.destinationCoordinate.latitude,
self.destinationCoordinate.longitude,
@"yourAppName"]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];
}]];
}

GCJ-02坐标转换成BD-09坐标 和逆转换

  • GCJ-02坐标转换为BD-09坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** *  将GCJ-02坐标转换为BD-09坐标 即将高德地图上获取的坐标转换成百度坐标 */
- (CLLocationCoordinate2D)gcj02CoordianteToBD09:(CLLocationCoordinate2D)gdCoordinate
{
double x_PI = M_PI * 3000.0 /180.0;

double gd_lat = gdCoordinate.latitude;

double gd_lon = gdCoordinate.longitude;

double z = sqrt(gd_lat * gd_lat + gd_lon * gd_lon) + 0.00002 * sin(gd_lat * x_PI);

double theta = atan2(gd_lat, gd_lon) + 0.000003 * cos(gd_lon * x_PI);

return CLLocationCoordinate2DMake(z * sin(theta) + 0.006, z * cos(theta) + 0.0065);
}
  • BD-09坐标转换为GCJ-02坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** *  将BD-09坐标转换为GCJ-02坐标 即将百度地图上获取的坐标转换成高德地图的坐标 */
- (CLLocationCoordinate2D)bd09CoordinateToGCJ02:(CLLocationCoordinate2D)bdCoordinate
{
double x_PI = M_PI * 3000.0 /180.0;

double bd_lat = bdCoordinate.latitude - 0.006;

double bd_lon = bdCoordinate.longitude - 0.0065;

double z = sqrt(bd_lat * bd_lat + bd_lon * bd_lon) - 0.00002 * sin(bd_lat * x_PI);

double theta = atan2(bd_lat, bd_lon) - 0.000003 * cos(bd_lon * x_PI);

return CLLocationCoordinate2DMake(z * sin(theta), z * cos(theta));
}

地图坐标系转换

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
39
40
41
42
#import <CoreLocation/CoreLocation.h>
/*
从 CLLocationManager 取出来的经纬度放到 mapView 上显示,是错误的!
从 CLLocationManager 取出来的经纬度去 Google Maps API 做逆地址解析,当然是错的!
从 MKMapView 取出来的经纬度去 Google Maps API 做逆地址解析终于对了。去百度地图API做逆地址解析,依旧是错的!
从上面两处取的经纬度放到百度地图上显示都是错的!错的!的!

分为 地球坐标,火星坐标(iOS mapView 高德 , 国内google ,搜搜、阿里云 都是火星坐标),百度坐标(百度地图数据主要都是四维图新提供的)

火星坐标: MKMapView
地球坐标: CLLocationManager

当用到CLLocationManager 得到的数据转化为火星坐标, MKMapView不用处理


API 坐标系
百度地图API 百度坐标
腾讯搜搜地图API 火星坐标
搜狐搜狗地图API 搜狗坐标
阿里云地图API 火星坐标
图吧MapBar地图API 图吧坐标
高德MapABC地图API 火星坐标
灵图51ditu地图API 火星坐标
*/
@interface CLLocation (Location)

//从地图坐标转化到火星坐标
- (CLLocation *)locationMarsFromEarth;

//从火星坐标转化到百度坐标
- (CLLocation *)locationBaiduFromMars;

//从百度坐标到火星坐标
- (CLLocation *)locationMarsFromBaidu;

//从火星坐标到地图坐标
- (CLLocation *)locationEarthFromMars;

//从百度坐标到地图坐标
- (CLLocation *)locationEarthFromBaidu;

@end
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#import "CLLocation+Location.h"

void transform_earth_from_mars(double lat, double lng, double* tarLat, double* tarLng);
void transform_mars_from_baidu(double lat, double lng, double* tarLat, double* tarLng);
void transform_baidu_from_mars(double lat, double lng, double* tarLat, double* tarLng);

@implementation CLLocation (Location)

- (CLLocation*)locationMarsFromEarth
{
double lat = 0.0;
double lng = 0.0;
transform_earth_from_mars(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(lat+self.coordinate.latitude, lng+self.coordinate.longitude)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
}

- (CLLocation*)locationEarthFromMars
{
double lat = 0.0;
double lng = 0.0;
transform_earth_from_mars(self.coordinate.latitude, self.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(self.coordinate.latitude-lat, self.coordinate.longitude-lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
return nil;
}

- (CLLocation*)locationBaiduFromMars
{
double lat = 0.0;
double lng = 0.0;
transform_mars_from_baidu(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*)locationMarsFromBaidu
{
double lat = 0.0;
double lng = 0.0;
transform_baidu_from_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*)locationEarthFromBaidu
{
double lat = 0.0;
double lng = 0.0;
CLLocation *Mars = [self locationMarsFromBaidu];

transform_earth_from_mars(Mars.coordinate.latitude, Mars.coordinate.longitude, &lat, &lng);
return [[CLLocation alloc] initWithCoordinate:CLLocationCoordinate2DMake(Mars.coordinate.latitude-lat, Mars.coordinate.longitude-lng)
altitude:self.altitude
horizontalAccuracy:self.horizontalAccuracy
verticalAccuracy:self.verticalAccuracy
course:self.course
speed:self.speed
timestamp:self.timestamp];
return nil;
}

@end


// --- transform_earth_from_mars ---
// 参考来源:https://on4wp7.codeplex.com/SourceControl/changeset/view/21483#353936
// Krasovsky 1940
//
// a = 6378245.0, 1/f = 298.3
// b = a * (1 - f)
// ee = (a^2 - b^2) / a^2;
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_from_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_from_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_from_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_from_mars_lat(lng - 105.0, lat - 35.0);
double dLon = transform_earth_from_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 = dLat;
*tarLng = dLon;
}

// --- transform_earth_from_mars end ---
// --- transform_mars_vs_bear_paw ---
// 参考来源:http://blog.woodbunny.com/post-68.html
const double x_pi = M_PI * 3000.0 / 180.0;

void transform_mars_from_baidu(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_baidu_from_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);
}
  • Tips:无论导入的是百度SDK还是高德SDK,他们内部都封装了将目标经纬度转换为高德坐标系或百度坐标系(文档上的接口可能被弃用没有及时更新,是不是很坑爹),但是没有将高德或百度坐标转换为别的坐标系下的坐标的接口。
  • 设置URL Scheme:http://blog.csdn.net/wm9028/article/details/49995329