POI(Point of Interest)
中文可以翻译为“兴趣点”。在地理信息系统中,一个POI可以是一栋房子、一个商铺、一个邮筒、一个公交站等。写在前面:最近老是有朋友来问我这个检索怎么不行了,我今天看了下,果然,出了问题,似乎是百度地图的一个Bug。POI检索后调POI详情检索,但是详情检索出来的经纬度全部是0,这样自然是不能够成功添加大头针的。奇怪的是在POI检索中经纬度是有的,但是呢,详情中经纬度竟然丢失了。这个只能等百度那边修复了,当然我这里提供一个临时解决这个办法的方法。在文末我上一个截图,有兴趣的看下。
iOS SDK
为开发者提供了公交
驾车
骑行
步行
4种类型的线路规划方案,同时根据不同的方案还可以选择时间最短
距离最短
等策略来完成最终的线路规划。开发者可根据自己实际的业务需求来自由使用。Demo
的朋友尽量更换百度appKey
和项目的boundID
为了方便部分朋友,我就不删去了,项目可直接运行。Untitled,,.gif
Untitle,.gif
UI是我上架项目中的,为了方便博客和写Demo
我就直接拖进去了。
百度地图的集成很简单,按照开发文档几分钟就搞定了,我就不抄写了,但是记录几个可能会出问题的地方吧。
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<transitStep.pointsCount;k++) {
temppoints[i].x = transitStep.points[k].x;
temppoints[i].y = transitStep.points[k].y;
i++;
}
}
// 通过points构建BMKPolyline
BMKPolyline *polyLine = [BMKPolyline polylineWithPoints:temppoints count:planPointCounts];
[self.mapView addOverlay:polyLine]; // 添加路线overlay
delete []temppoints;
[self mapViewFitPolyLine:polyLine];
}
}
上面我仅仅放了一个驾车的代理,还有步行等没有放上去,太长了,文末为了不想下载代码的同学观看,我会放上整页代码提供参考。
言归正传,你们发现我有自定义了一个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<BMKOverlay>)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
欢迎加入!
首先创建一个类
55F75204-98A3-4B68-BBD8-0AC519CCAF18.png
然后仔细看POI两个代理的方法处理 数组自己声明,我就不多截图了
3B08BB92-38A3-430F-B358-798546AB883F.png
CFAD4F73-D921-422F-B5AB-9C932A651D9C.png