POI(Point of Interest)
中文可以翻譯為“興趣點(diǎn)”档址。在地理信息系統(tǒng)中,一個(gè)POI可以是一棟房子邻梆、一個(gè)商鋪守伸、一個(gè)郵筒、一個(gè)公交站等浦妄。
更新:一些朋友使用Xcode10以上版本打開本Demo會(huì)報(bào)一個(gè)libstdc++的錯(cuò)誤尼摹,這是因?yàn)檫@個(gè)動(dòng)態(tài)庫(kù)已經(jīng)在10版本時(shí)過(guò)期,所以這里下載這個(gè)過(guò)期的動(dòng)態(tài)庫(kù)拖到項(xiàng)目里可臨時(shí)解決這個(gè)編譯問(wèn)題剂娄。在你的項(xiàng)目中蠢涝,建議你使用百度最新的SDK,然后按照要求去配置環(huán)境阅懦。
寫在前面:最近老是有朋友來(lái)問(wèn)我這個(gè)檢索怎么不行了和二,我今天看了下,果然耳胎,出了問(wèn)題惯吕,似乎是百度地圖的一個(gè)Bug。POI檢索后調(diào)POI詳情檢索怕午,但是詳情檢索出來(lái)的經(jīng)緯度全部是0废登,這樣自然是不能夠成功添加大頭針的。奇怪的是在POI檢索中經(jīng)緯度是有的郁惜,但是呢堡距,詳情中經(jīng)緯度竟然丟失了。這個(gè)只能等百度那邊修復(fù)了兆蕉,當(dāng)然我這里提供一個(gè)臨時(shí)解決這個(gè)辦法的方法羽戒。在文末我上一個(gè)截圖,有興趣的看下恨樟。
百度地圖iOS SDK
為開發(fā)者提供了公交
駕車
騎行
步行
4種類型的線路規(guī)劃方案半醉,同時(shí)根據(jù)不同的方案還可以選擇時(shí)間最短
距離最短
等策略來(lái)完成最終的線路規(guī)劃疚俱。開發(fā)者可根據(jù)自己實(shí)際的業(yè)務(wù)需求來(lái)自由使用劝术。
我想在看此博客之前你應(yīng)該去瀏覽下百度地圖開發(fā)者文檔,前面兩段都是廢話呆奕,但既然是博客的功能點(diǎn)养晋,還是寫出來(lái)。
請(qǐng)下載Demo
的朋友盡量更換百度appKey
和項(xiàng)目的boundID
為了方便部分朋友梁钾,我就不刪去了绳泉,項(xiàng)目可直接運(yùn)行。
無(wú)圖無(wú)真相姆泻!
POI檢索
路線規(guī)劃
UI是我上架項(xiàng)目中的四苇,為了方便博客和寫Demo
我就直接拖進(jìn)去了孝凌。
百度地圖的集成很簡(jiǎn)單,按照開發(fā)文檔幾分鐘就搞定了月腋,我就不抄寫了蟀架,但是記錄幾個(gè)可能會(huì)出問(wèn)題的地方吧。
- Privacy - Location Always Usage Description plist.info請(qǐng)求使用GPS
- LSApplicationQueriesSchemes 如果你需要調(diào)起百度地圖客戶端
- Bundle display name plist.info中需要加入榆骚,而且是必要的
- 這個(gè)文件用到了c++代碼片拍,請(qǐng)務(wù)必把文件后綴名改為.mm
POI詳情:
第一步:在Appdelegate.m
中設(shè)置AppKey
。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
HouseMapTootsViewController *vc = [[HouseMapTootsViewController alloc] init];
vc.latitude = 31.976;
vc.longitude = 118.71;
UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:vc];
//配置百度地圖
[self configurationBMKMap];
self.window.rootViewController = navi;
[self.window makeKeyAndVisible];
return YES;
}
#pragma mark -- 百度地圖
- (void)configurationBMKMap {
// 要使用百度地圖妓肢,請(qǐng)先啟動(dòng)BaiduMapManager
_mapManager = [[BMKMapManager alloc] init];
BOOL ret = [_mapManager start:@"appkey" generalDelegate:self];
if (!ret) {
NSLog(@"manager start failed!");
}
}
第二步:完成代理
#pragma mark -- BMKGeneralDelegate
- (void)onGetNetworkState:(int)iError {
if (0 == iError) {
NSLog(@"聯(lián)網(wǎng)成功");
}else {
NSLog(@"onGetNetworkState %d",iError);
}
}
- (void)onGetPermissionState:(int)iError {
if (0 == iError) {
NSLog(@"授權(quán)成功");
}else {
NSLog(@"onGetPermissionState %d",iError);
}
}
第三步:創(chuàng)建百度地圖捌省,開啟用戶定位(后面路線規(guī)劃需要)。并且添加一個(gè)大頭針职恳,這個(gè)大頭針就是你即將檢索的中心點(diǎn)所禀。
self.mapView = [[BMKMapView alloc] initWithFrame:CGRectMake(0, 40+64, kScreenWidth, kScreenHeight - 40 - 64)];
[self.view addSubview:self.mapView];
[self.view addSubview:self.planView];
self.locService = [[BMKLocationService alloc] init];
self.mapView.delegate = self;
self.locService.delegate = self;
//定位方向模式 不能使用跟隨,不然地圖中心就不是大頭針了
[self.mapView setZoomLevel:16];
self.mapView.showMapScaleBar = YES;
self.mapView.userTrackingMode = BMKUserTrackingModeNone;
self.mapView.showsUserLocation = YES;
[self.locService startUserLocationService];
CLLocationCoordinate2D coor;
coor.latitude = self.latitude;
coor.longitude = self.longitude;
if (self.pointAnnotation == nil) {
//自定義大頭針
self.pointAnnotation = [[YLAnnotationView alloc]init];
self.pointAnnotation.coordinate = coor;
self.pointAnnotation.title = @"房源位置";
self.pointAnnotation.subtitle = @"點(diǎn)擊到這里去";
self.pointAnnotation.image = [UIImage imageNamed:@"homelocation"];
}
[self.mapView addAnnotation:self.pointAnnotation];
//設(shè)置中心點(diǎn)
[self.mapView setCenterCoordinate:coor];
//檢索周邊設(shè)施
self.poiSearch.delegate = self;
//添加大頭針后添加周邊檢索
self.option.location = coor;
self.option.pageIndex = 0;
self.option.pageCapacity = 20;
self.option.radius = 1500;
第四步:在點(diǎn)擊事件中初始化檢索對(duì)象放钦,Demo中我自己定義了一個(gè)topView用來(lái)做不同點(diǎn)擊區(qū)分色徘。
#pragma mark -- YLSelectorItemViewDelegate
- (void)didSelectedItems:(NSInteger)item {
CLLocationCoordinate2D coor;
coor.latitude = self.latitude;
coor.longitude = self.longitude;
self.seletItem = item;
self.isFirst = YES;
if (item == 1) {
self.option.keyword = @"美食";
BOOL flag = [self.poiSearch poiSearchNearBy:self.option];
if(flag) {
NSLog(@"周邊檢索發(fā)送成功");
}else {
NSLog(@"周邊檢索發(fā)送失敗");
}
}else if (item == 2) {
self.option.keyword = @"超市";
BOOL flag1 = [self.poiSearch poiSearchNearBy:self.option];
if(flag1) {
NSLog(@"周邊檢索發(fā)送成功");
}else {
}
}else if (item == 3) {
self.option.keyword = @"ATM";
BOOL flag2 = [self.poiSearch poiSearchNearBy:self.option];
if(flag2) {
NSLog(@"周邊檢索發(fā)送成功");
}else {
NSLog(@"周邊檢索發(fā)送失敗");
}
}else if (item == 4) {
self.option.keyword = @"購(gòu)物";
BOOL flag3 = [self.poiSearch poiSearchNearBy:self.option];
if(flag3) {
NSLog(@"周邊檢索發(fā)送成功");
}else {
NSLog(@"周邊檢索發(fā)送失敗");
}
}
}
第五步:點(diǎn)擊后會(huì)進(jìn)入下面這個(gè)代理,首先刪除屏幕上的大頭針操禀,由于我這里還是需要顯示這個(gè)房源大頭針褂策,這里我做了一個(gè)處理保存下來(lái),在for循環(huán)
中拿到了所有的list
中的對(duì)象颓屑,這些對(duì)象就是我們要的周邊信息斤寂,但是并不是詳情,詳情是需要拿到這個(gè)目標(biāo)對(duì)象UID
再次去檢索(這里普通檢索和詳情檢索被百度強(qiáng)行分開了揪惦,可能處于流量或者模塊化的考慮吧)遍搞。那么就必須再次創(chuàng)建檢索對(duì)象了,這次for循環(huán)
每次都會(huì)出現(xiàn)一個(gè)詳情檢索器腋,于是我們可以到詳情代理中做事情了溪猿。
//實(shí)現(xiàn)PoiSearchDeleage處理回調(diào)結(jié)果
- (void)onGetPoiResult:(BMKPoiSearch *)searcher result:(BMKPoiResult *)poiResultList errorCode:(BMKSearchErrorCode)error {
// 清楚屏幕中除卻房源外的所有的annotation
NSMutableArray *array = [NSMutableArray arrayWithArray:self.mapView.annotations];
//把房源的保存下載
[array removeObjectAtIndex:0];
[self.mapView removeAnnotations:array];
array = [[NSArray arrayWithArray:self.mapView.overlays] mutableCopy];
[self.mapView removeOverlays:array];
if (error == BMK_SEARCH_NO_ERROR) {
for (int i = 0; i < poiResultList.poiInfoList.count; i++) {
BMKPoiInfo *poi = [poiResultList.poiInfoList objectAtIndex:i];
//自定義大頭針
BMKPoiDetailSearchOption *option = [[BMKPoiDetailSearchOption alloc] init];
option.poiUid = poi.uid;
BMKPoiSearch *se = [[BMKPoiSearch alloc] init];
se.delegate = self;
//把所有的POI存入數(shù)組,用于最終的釋放纫塌,避免循環(huán)引用诊县;
[self.poiDetails addObject:se];
[se poiDetailSearch:option];
}
} else if (error == BMK_SEARCH_AMBIGUOUS_KEYWORD){
NSLog(@"搜索詞有歧義");
} else {
// 各種情況的判斷。措左。依痊。
}
}
詳情代理,這里我需要不同的大頭針圖片怎披,做了一個(gè)處理
//周邊搜索的詳情代理
- (void)onGetPoiDetailResult:(BMKPoiSearch *)searcher result:(BMKPoiDetailResult *)poiDetailResult errorCode:(BMKSearchErrorCode)errorCode {
CLLocationCoordinate2D houseCoor;
houseCoor.latitude = self.latitude;
houseCoor.longitude = self.longitude;
if (errorCode == BMK_SEARCH_NO_ERROR) {
YLAnnotationView *item = [[YLAnnotationView alloc] init];
item.coordinate = poiDetailResult.pt;
switch (self.seletItem) {
case 1:
item.image = [UIImage imageNamed:@"food"];
item.subtitle = [NSString stringWithFormat:@"均價(jià):%.2f",poiDetailResult.price];
break;
case 2:
item.image = [UIImage imageNamed:@"supermarket"];
item.subtitle = poiDetailResult.address;
break;
case 3:
item.image = [UIImage imageNamed:@"ATM"];
item.subtitle = poiDetailResult.address;
break;
case 4:
item.image = [UIImage imageNamed:@"shoping"];
item.subtitle = poiDetailResult.address;
break;
default:
break;
}
item.title = poiDetailResult.name;
//加個(gè)判斷胸嘁,第一次進(jìn)來(lái)的時(shí)候設(shè)置中心點(diǎn)瓶摆,不能每次都移動(dòng)中心,不然POI期間會(huì)拖不動(dòng)地圖性宏。
if (self.isFirst) {
[self.mapView setCenterCoordinate:houseCoor animated:YES];
}
self.isFirst = NO;
[self.mapView addAnnotation:item];
}else if (errorCode == BMK_SEARCH_AMBIGUOUS_KEYWORD) {
NSLog(@"搜索詞有歧義");
}else {
}
}
到這里主要代碼就結(jié)束了赏壹。文末我會(huì)附上Demo
二:路徑規(guī)劃
點(diǎn)擊搜索,傳過(guò)來(lái)一種路線方式衔沼,并且傳來(lái)開始地與目的地蝌借。我本想直接寫出需要注意的地方,但是發(fā)現(xiàn)在代碼中不少都已經(jīng)注釋了指蚁,請(qǐng)大家注意菩佑,例如 //每次必須是一個(gè)新的對(duì)象,不然pt
和name
會(huì)混亂
下面代碼有很多邏輯上的處理凝化,為了一體性稍坯,我沒(méi)有刪去。
#pragma mark -- RoutePlanViewDelegate
//搜路線
- (void)didSelectorItems:(NSInteger)item withStartName:(NSString *)startName andEndName:(NSString *)endName {
CLLocationCoordinate2D houseCoor;
houseCoor.latitude = self.latitude;
houseCoor.longitude = self.longitude;
//每次必須是一個(gè)新的對(duì)象搓劫,不然pt和name會(huì)混亂
self.startNode = [[BMKPlanNode alloc] init];
self.endNode = [[BMKPlanNode alloc] init];
self.startNode.cityName = @"南京";
self.endNode.cityName = @"南京";
//設(shè)置出發(fā)點(diǎn)與終點(diǎn)
if ([startName isEqualToString:@""] || [endName isEqualToString:@""]) {
NSLog(@"搜索字段有歧義");
return;
}
if ([startName isEqualToString:@"當(dāng)前位置"] && [endName isEqualToString:@"房源位置"]) {
self.startNode.pt = self.userLocation.location.coordinate;
self.endNode.pt = houseCoor;
}else if ([endName isEqualToString:@"當(dāng)前位置"] && [startName isEqualToString:@"房源位置"]) {
self.startNode.pt = houseCoor;
self.endNode.pt = self.userLocation.location.coordinate;
}else if (![startName isEqualToString:@"當(dāng)前位置"] && [endName isEqualToString:@"房源位置"]) {
self.startNode.name = startName;
self.endNode.pt = houseCoor;
}else if([startName isEqualToString:@"當(dāng)前位置"] && ![endName isEqualToString:@"房源位置"]) {
self.startNode.pt = self.userLocation.location.coordinate;
self.endNode.name = endName;
}else if(![startName isEqualToString:@"當(dāng)前位置"] && [endName isEqualToString:@"當(dāng)前位置"]) {
self.startNode.name = startName;
self.endNode.pt = self.userLocation.location.coordinate;
}else if([startName isEqualToString:@"房源位置"] && ![endName isEqualToString:@"當(dāng)前位置"]) {
self.startNode.pt = houseCoor;
self.endNode.name = endName;
}else {
self.startNode.name = startName;
self.endNode.name = endName;
}
NSLog(@"%@ ---- %@",self.startNode.name,self.endNode.name);
if (item == 1) {
//駕車
BMKDrivingRoutePlanOption *driveRouteSearchOption =[[BMKDrivingRoutePlanOption alloc]init];
driveRouteSearchOption.from = self.startNode;
driveRouteSearchOption.to = self.endNode;
BOOL flag = [self.routeSearch drivingSearch:driveRouteSearchOption];
if (flag) {
}else {
}
}else if (item == 2) {
//公交
BMKMassTransitRoutePlanOption *option = [[BMKMassTransitRoutePlanOption alloc]init];
option.from = self.startNode;
option.to = self.endNode;
BOOL flag = [self.routeSearch massTransitSearch:option];
if(flag) {
NSLog(@"公交交通檢索(支持垮城)發(fā)送成功");
} else {
NSLog(@"公交交通檢索(支持垮城)發(fā)送失敗");
}
} else if (item == 3) {
//步行
BMKWalkingRoutePlanOption *walkingRouteSearchOption = [[BMKWalkingRoutePlanOption alloc] init];
walkingRouteSearchOption.from = self.startNode;
walkingRouteSearchOption.to = self.endNode;
BOOL flag = [self.routeSearch walkingSearch:walkingRouteSearchOption];
if(flag) {
NSLog(@"walk檢索發(fā)送成功");
}else {
NSLog(@"walk檢索發(fā)送失敗");
}
}else {
//騎車
BMKRidingRoutePlanOption *option = [[BMKRidingRoutePlanOption alloc]init];
option.from = self.startNode;
option.to = self.endNode;
BOOL flag = [self.routeSearch ridingSearch:option];
if (flag) {
NSLog(@"騎行規(guī)劃?rùn)z索發(fā)送成功");
}else {
NSLog(@"騎行規(guī)劃?rùn)z索發(fā)送失敗");
}
}
}
點(diǎn)擊后瞧哟,會(huì)進(jìn)入下面這個(gè)代理
#pragma mark -- BMKRouteSearchDelegate
//駕車
- (void)onGetDrivingRouteResult:(BMKRouteSearch *)searcher result:(BMKDrivingRouteResult *)result errorCode:(BMKSearchErrorCode)error {
NSMutableArray *array = [NSMutableArray arrayWithArray:self.mapView.annotations];
[array removeObjectAtIndex:0];
[self.mapView removeAnnotations:array];
array = [[NSArray arrayWithArray:self.mapView.overlays] mutableCopy];
[self.mapView removeOverlays:array];
if (error == BMK_SEARCH_NO_ERROR) {
//表示一條駕車路線
BMKDrivingRouteLine *plan = (BMKDrivingRouteLine *)[result.routes objectAtIndex:0];
// 計(jì)算路線方案中的路段數(shù)目
int size = (int)[plan.steps count];
int planPointCounts = 0;
for (int i = 0; i < size; i++) {
//表示駕車路線中的一個(gè)路段
BMKDrivingStep *transitStep = [plan.steps objectAtIndex:i];
if(i==0){
RouteAnnotation *item = [[RouteAnnotation alloc]init];
item.coordinate = plan.starting.location;
item.title = @"起點(diǎn)";
item.type = 0;
[self.mapView addAnnotation:item]; // 添加起點(diǎn)標(biāo)注
}else if(i==size-1){
RouteAnnotation *item = [[RouteAnnotation alloc]init];
item.coordinate = plan.terminal.location;
item.title = @"終點(diǎn)";
item.type = 1;
[self.mapView addAnnotation:item]; // 添加終點(diǎn)標(biāo)注
}
//添加annotation節(jié)點(diǎn)
RouteAnnotation *item = [[RouteAnnotation alloc]init];
item.coordinate = transitStep.entrace.location;
item.title = transitStep.entraceInstruction;
item.degree = transitStep.direction *30;
item.type = 4;
[self.mapView addAnnotation:item];
//軌跡點(diǎn)總數(shù)累計(jì)
planPointCounts += transitStep.pointsCount;
}
// 添加途經(jīng)點(diǎn)
if (plan.wayPoints) {
for (BMKPlanNode *tempNode in plan.wayPoints) {
RouteAnnotation *item = [[RouteAnnotation alloc]init];
item.coordinate = tempNode.pt;
item.type = 5;
item.title = tempNode.name;
[self.mapView addAnnotation:item];
}
}
//軌跡點(diǎn)
BMKMapPoint *temppoints = new BMKMapPoint[planPointCounts];
int i = 0;
for (int j = 0; j < size; j++) {
BMKDrivingStep *transitStep = [plan.steps objectAtIndex:j];
int k=0;
for(k=0;k<transitStep.pointsCount;k++) {
temppoints[i].x = transitStep.points[k].x;
temppoints[i].y = transitStep.points[k].y;
i++;
}
}
// 通過(guò)points構(gòu)建BMKPolyline
BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:temppoints count:planPointCounts];
[self.mapView addOverlay:polyLine]; // 添加路線overlay
delete []temppoints;
[self mapViewFitPolyLine:polyLine];
}
}
上面我僅僅放了一個(gè)駕車的代理,還有步行等沒(méi)有放上去枪向,太長(zhǎng)了勤揩,文末為了不想下載代碼的同學(xué)觀看,我會(huì)放上整頁(yè)代碼提供參考秘蛔。
言歸正傳陨亡,你們發(fā)現(xiàn)我有自定義了一個(gè)RouteAnnotation
類。這個(gè)路線需要字段比較多深员,我不想改動(dòng)之前大頭針類了负蠕,就直接重寫了一個(gè)。如下
/**
* 路線的標(biāo)注 自定義一個(gè)大頭針類 為了便捷倦畅,就直接放這里了
*/
@interface RouteAnnotation : BMKPointAnnotation {
int _type; ///<0:起點(diǎn) 1:終點(diǎn) 2:公交 3:地鐵 4:駕乘 5:途經(jīng)點(diǎn)
int _degree;//旋轉(zhuǎn)的角度
}
@property (nonatomic) int type;
@property (nonatomic) int degree;
@end
@implementation RouteAnnotation
@synthesize type = _type;
@synthesize degree = _degree;
@end
如果你也這樣做遮糖,那么就像我一樣在大頭針重用方法中做以下判斷,并且實(shí)現(xiàn)這個(gè)方法叠赐,如下:
if ([annotation isKindOfClass:[RouteAnnotation class]]) {
return [self getRouteAnnotationView:view viewForAnnotation:(RouteAnnotation *)annotation];
}
#pragma mark -- 獲取路線的標(biāo)注欲账,顯示到地圖(自定義的一個(gè)大頭針類實(shí)例方法)我只貼到case 0;其他的在文末查找燎悍,需要注意的地方我已寫注釋
- (BMKAnnotationView *)getRouteAnnotationView:(BMKMapView *)mapview viewForAnnotation:(RouteAnnotation *)routeAnnotation {
BMKAnnotationView *view = nil;
//根據(jù)大頭針類型判斷是什么圖標(biāo)
switch (routeAnnotation.type) {
case 0:
{ //開始點(diǎn)
view = [mapview dequeueReusableAnnotationViewWithIdentifier:@"start_node"];
if (view == nil) {
view = [[BMKAnnotationView alloc] initWithAnnotation:routeAnnotation reuseIdentifier:@"start_node"];
//從百度地圖資源文件中拿到需要的圖片
view.image = [UIImage imageWithContentsOfFile:[self getMyBundlePath1:@"images/icon_nav_start"]];
view.centerOffset = CGPointMake(0, -(view.frame.size.height * 0.5));
view.canShowCallout = true;
}
view.annotation = routeAnnotation;
}
在駕車路線的代理最后我們添加了一條線敬惦,就是如下代碼
// 通過(guò)points構(gòu)建BMKPolyline
BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:temppoints count:planPointCounts];
[self.mapView addOverlay:polyLine]; // 添加路線overlay
delete []temppoints;
[self mapViewFitPolyLine:polyLine];
這樣我們還需要實(shí)現(xiàn)一個(gè)劃線的代理盼理,這個(gè)是必須實(shí)現(xiàn)的谈山。還有一個(gè)地圖路線的范圍計(jì)算,文末的所有代碼中的最后一段宏怔,這些都是從百度地圖官方代碼拿來(lái)的奏路。
#pragma mark -- 路線線條繪制代理
- (BMKOverlayView *)mapView:(BMKMapView *)map viewForOverlay:(id<BMKOverlay>)overlay {
if ([overlay isKindOfClass:[BMKPolyline class]]) {
BMKPolylineView *polylineView = [[BMKPolylineView alloc] initWithOverlay:overlay];
//設(shè)置線條顏色
polylineView.fillColor = [[UIColor alloc] initWithRed:0 green:1 blue:1 alpha:1];
polylineView.strokeColor = [[UIColor alloc] initWithRed:0 green:0 blue:0.8 alpha:0.7];
polylineView.lineWidth = 3.0;
return polylineView;
}
return nil;
}
雖然上面大多都是復(fù)制粘貼把畴椰,但是 Demo
代碼都是我用心準(zhǔn)備的,這里也主要是說(shuō)個(gè)流程鸽粉。
Demo傳送門
整個(gè)打包斜脂,比較大,我也懶得放git触机,不想下載的看下面代碼
iOS技術(shù)交流群: 511860085
歡迎加入帚戳!
POI不能檢索問(wèn)題臨時(shí)解決辦法 不明白的加群來(lái)問(wèn)。
首先創(chuàng)建一個(gè)類