一、前言
地图开发的重要性就不讲了,大家看一下自己手机里的App就知道了,10个App有9个包含了位置服务功能,而且发展到今天,精准度已经非常高了。balabala……
二、关于地图的Info.plist文件配置
iOS 10 和iOS 11 的设置分别是这样配置的(如下图)
注:每次Apple有大版本更新,都得检查一下plist文件的权限配置
如果配置不完整,在部分系统上是无法加载地图的(显示为一片蓝色),且在相应App的设置里都找不到『位置』这一项。反之,如果你发现设置里没有『位置』,大概就是权限配置有问题了。
三、我们用那种地图更好?
常用的地图:高德地图、百度地图、腾讯地图、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)
1 | - (void)loadAroundPOIsInAMapWithCoordinate:(CLLocationCoordinate2D)coordinate { |
- 高德地图的关键字检索是另一套方法(AMapInputTipsSearchRequest)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- (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
经过实测,有一定的效果(毕竟,有总比没有好~)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19- (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的不同使用,注:非官方做法,慎重!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18- (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.h1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface CLLocation (Sino)
/**地球坐标转火星坐标*/
- (CLLocation*)locationMarsFromEarth;
/**火星坐标转百度坐标*/
- (CLLocation*)locationBearPawFromMars;
/**百度坐标转火星坐标*/
- (CLLocation*)locationMarsFromBearPaw;
@end
CLLocation+Sino.m1
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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
六、写在最后
本文没有涉及详细开发步骤,主要就是『点一下狼坑』而已,常规的开发网上有很多,而且还有各自的官方开发文档。
目的是介绍地图开发中一些可能出现的问题,也都是我在开发中的『心路历程』。有不对的地方,欢迎指正!