POI(Point of Interest)
中文可以翻译为“兴趣点”。在地理信息系统中,一个POI可以是一栋房子、一个商铺、一个邮筒、一个公交站等。
更新:一些朋友使用Xcode10以上版本打开本Demo会报一个libstdc++的错误,这是因为这个动态库已经在10版本时过期,所以这里下载这个过期的动态库拖到项目里可临时解决这个编译问题。在你的项目中,建议你使用百度最新的SDK,然后按照要求去配置环境。
写在前面:最近老是有朋友来问我这个检索怎么不行了,我今天看了下,果然,出了问题,似乎是百度地图的一个Bug。POI检索后调POI详情检索,但是详情检索出来的经纬度全部是0,这样自然是不能够成功添加大头针的。奇怪的是在POI检索中经纬度是有的,但是呢,详情中经纬度竟然丢失了。这个只能等百度那边修复了,当然我这里提供一个临时解决这个办法的方法。在文末我上一个截图,有兴趣的看下。
百度地图iOS SDK
为开发者提供了公交
驾车
骑行
步行
4种类型的线路规划方案,同时根据不同的方案还可以选择时间最短
距离最短
等策略来完成最终的线路规划。开发者可根据自己实际的业务需求来自由使用。
我想在看此博客之前你应该去浏览下百度地图开发者文档,前面两段都是废话,但既然是博客的功能点,还是写出来。
请下载Demo
的朋友尽量更换百度appKey
和项目的boundID
为了方便部分朋友,我就不删去了,项目可直接运行。
无图无真相!
POI检索
路线规划
UI是我上架项目中的,为了方便博客和写Demo
我就直接拖进去了。
百度地图的集成很简单,按照开发文档几分钟就搞定了,我就不抄写了,但是记录几个可能会出问题的地方吧。
- Privacy - Location Always Usage Description plist.info请求使用GPS
- LSApplicationQueriesSchemes 如果你需要调起百度地图客户端
- Bundle display name plist.info中需要加入,而且是必要的
- 这个文件用到了c++代码,请务必把文件后缀名改为.mm
POI详情:
第一步:在Appdelegate.m
中设置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 {
// 要使用百度地图,请先启动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(@"联网成功");
}else {
NSLog(@"onGetNetworkState %d",iError);
}
}
- (void)onGetPermissionState:(int)iError {
if (0 == iError) {
NSLog(@"授权成功");
}else {
NSLog(@"onGetPermissionState %d",iError);
}
}
第三步:创建百度地图,开启用户定位(后面路线规划需要)。并且添加一个大头针,这个大头针就是你即将检索的中心点。
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 = @"点击到这里去";
self.pointAnnotation.image = [UIImage imageNamed:@"homelocation"];
}
[self.mapView addAnnotation:self.pointAnnotation];
//设置中心点
[self.mapView setCenterCoordinate:coor];
//检索周边设施
self.poiSearch.delegate = self;
//添加大头针后添加周边检索
self.option.location = coor;
self.option.pageIndex = 0;
self.option.pageCapacity = 20;
self.option.radius = 1500;
第四步:在点击事件中初始化检索对象,Demo中我自己定义了一个topView用来做不同点击区分。
#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(@"周边检索发送成功");
}else {
NSLog(@"周边检索发送失败");
}
}else if (item == 2) {
self.option.keyword = @"超市";
BOOL flag1 = [self.poiSearch poiSearchNearBy:self.option];
if(flag1) {
NSLog(@"周边检索发送成功");
}else {
}
}else if (item == 3) {
self.option.keyword = @"ATM";
BOOL flag2 = [self.poiSearch poiSearchNearBy:self.option];
if(flag2) {
NSLog(@"周边检索发送成功");
}else {
NSLog(@"周边检索发送失败");
}
}else if (item == 4) {
self.option.keyword = @"购物";
BOOL flag3 = [self.poiSearch poiSearchNearBy:self.option];
if(flag3) {
NSLog(@"周边检索发送成功");
}else {
NSLog(@"周边检索发送失败");
}
}
}
第五步:点击后会进入下面这个代理,首先删除屏幕上的大头针,由于我这里还是需要显示这个房源大头针,这里我做了一个处理保存下来,在for循环
中拿到了所有的list
中的对象,这些对象就是我们要的周边信息,但是并不是详情,详情是需要拿到这个目标对象UID
再次去检索(这里普通检索和详情检索被百度强行分开了,可能处于流量或者模块化的考虑吧)。那么就必须再次创建检索对象了,这次for循环
每次都会出现一个详情检索,于是我们可以到详情代理中做事情了。
//实现PoiSearchDeleage处理回调结果
- (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存入数组,用于最终的释放,避免循环引用;
[self.poiDetails addObject:se];
[se poiDetailSearch:option];
}
} else if (error == BMK_SEARCH_AMBIGUOUS_KEYWORD){
NSLog(@"搜索词有歧义");
} else {
// 各种情况的判断。。。
}
}
详情代理,这里我需要不同的大头针图片,做了一个处理
//周边搜索的详情代理
- (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:@"均价:%.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;
//加个判断,第一次进来的时候设置中心点,不能每次都移动中心,不然POI期间会拖不动地图。
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 {
}
}
到这里主要代码就结束了。文末我会附上Demo
二:路径规划
点击搜索,传过来一种路线方式,并且传来开始地与目的地。我本想直接写出需要注意的地方,但是发现在代码中不少都已经注释了,请大家注意,例如 //每次必须是一个新的对象,不然pt
和name
会混乱
下面代码有很多逻辑上的处理,为了一体性,我没有删去。
#pragma mark -- RoutePlanViewDelegate
//搜路线
- (void)didSelectorItems:(NSInteger)item withStartName:(NSString *)startName andEndName:(NSString *)endName {
CLLocationCoordinate2D houseCoor;
houseCoor.latitude = self.latitude;
houseCoor.longitude = self.longitude;
//每次必须是一个新的对象,不然pt和name会混乱
self.startNode = [[BMKPlanNode alloc] init];
self.endNode = [[BMKPlanNode alloc] init];
self.startNode.cityName = @"南京";
self.endNode.cityName = @"南京";
//设置出发点与终点
if ([startName isEqualToString:@""] || [endName isEqualToString:@""]) {
NSLog(@"搜索字段有歧义");
return;
}
if ([startName isEqualToString:@"当前位置"] && [endName isEqualToString:@"房源位置"]) {
self.startNode.pt = self.userLocation.location.coordinate;
self.endNode.pt = houseCoor;
}else if ([endName isEqualToString:@"当前位置"] && [startName isEqualToString:@"房源位置"]) {
self.startNode.pt = houseCoor;
self.endNode.pt = self.userLocation.location.coordinate;
}else if (![startName isEqualToString:@"当前位置"] && [endName isEqualToString:@"房源位置"]) {
self.startNode.name = startName;
self.endNode.pt = houseCoor;
}else if([startName isEqualToString:@"当前位置"] && ![endName isEqualToString:@"房源位置"]) {
self.startNode.pt = self.userLocation.location.coordinate;
self.endNode.name = endName;
}else if(![startName isEqualToString:@"当前位置"] && [endName isEqualToString:@"当前位置"]) {
self.startNode.name = startName;
self.endNode.pt = self.userLocation.location.coordinate;
}else if([startName isEqualToString:@"房源位置"] && ![endName isEqualToString:@"当前位置"]) {
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(@"公交交通检索(支持垮城)发送成功");
} else {
NSLog(@"公交交通检索(支持垮城)发送失败");
}
} 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检索发送成功");
}else {
NSLog(@"walk检索发送失败");
}
}else {
//骑车
BMKRidingRoutePlanOption *option = [[BMKRidingRoutePlanOption alloc]init];
option.from = self.startNode;
option.to = self.endNode;
BOOL flag = [self.routeSearch ridingSearch:option];
if (flag) {
NSLog(@"骑行规划检索发送成功");
}else {
NSLog(@"骑行规划检索发送失败");
}
}
}
点击后,会进入下面这个代理
#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];
// 计算路线方案中的路段数目
int size = (int)[plan.steps count];
int planPointCounts = 0;
for (int i = 0; i < size; i++) {
//表示驾车路线中的一个路段
BMKDrivingStep *transitStep = [plan.steps objectAtIndex:i];
if(i==0){
RouteAnnotation *item = [[RouteAnnotation alloc]init];
item.coordinate = plan.starting.location;
item.title = @"起点";
item.type = 0;
[self.mapView addAnnotation:item]; // 添加起点标注
}else if(i==size-1){
RouteAnnotation *item = [[RouteAnnotation alloc]init];
item.coordinate = plan.terminal.location;
item.title = @"终点";
item.type = 1;
[self.mapView addAnnotation:item]; // 添加终点标注
}
//添加annotation节点
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];
//轨迹点总数累计
planPointCounts += transitStep.pointsCount;
}
// 添加途经点
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];
}
}
//轨迹点
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
上面我仅仅放了一个驾车的代理,还有步行等没有放上去,太长了,文末为了不想下载代码的同学观看,我会放上整页代码提供参考。
言归正传,你们发现我有自定义了一个RouteAnnotation
类。这个路线需要字段比较多,我不想改动之前大头针类了,就直接重写了一个。如下
/**
* 路线的标注 自定义一个大头针类 为了便捷,就直接放这里了
*/
@interface RouteAnnotation : BMKPointAnnotation {
int _type; ///<0:起点 1:终点 2:公交 3:地铁 4:驾乘 5:途经点
int _degree;//旋转的角度
}
@property (nonatomic) int type;
@property (nonatomic) int degree;
@end
@implementation RouteAnnotation
@synthesize type = _type;
@synthesize degree = _degree;
@end
如果你也这样做,那么就像我一样在大头针重用方法中做以下判断,并且实现这个方法,如下:
if ([annotation isKindOfClass:[RouteAnnotation class]]) {
return [self getRouteAnnotationView:view viewForAnnotation:(RouteAnnotation *)annotation];
}
#pragma mark -- 获取路线的标注,显示到地图(自定义的一个大头针类实例方法)我只贴到case 0;其他的在文末查找,需要注意的地方我已写注释
- (BMKAnnotationView *)getRouteAnnotationView:(BMKMapView *)mapview viewForAnnotation:(RouteAnnotation *)routeAnnotation {
BMKAnnotationView *view = nil;
//根据大头针类型判断是什么图标
switch (routeAnnotation.type) {
case 0:
{ //开始点
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;
}
在驾车路线的代理最后我们添加了一条线,就是如下代码
// 通过points构建BMKPolyline
BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:temppoints count:planPointCounts];
[self.mapView addOverlay:polyLine]; // 添加路线overlay
delete []temppoints;
[self mapViewFitPolyLine:polyLine];
这样我们还需要实现一个划线的代理,这个是必须实现的。还有一个地图路线的范围计算,文末的所有代码中的最后一段,这些都是从百度地图官方代码拿来的。
#pragma mark -- 路线线条绘制代理
- (BMKOverlayView *)mapView:(BMKMapView *)map viewForOverlay:(id)overlay {
if ([overlay isKindOfClass:[BMKPolyline class]]) {
BMKPolylineView *polylineView = [[BMKPolylineView alloc] initWithOverlay:overlay];
//设置线条颜色
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;
}
虽然上面大多都是复制粘贴把,但是 Demo
代码都是我用心准备的,这里也主要是说个流程。
Demo传送门
整个打包,比较大,我也懒得放git,不想下载的看下面代码
iOS技术交流群: 511860085
欢迎加入!
POI不能检索问题临时解决办法 不明白的加群来问。
首先创建一个类
然后仔细看POI两个代理的方法处理 数组自己声明,我就不多截图了