(文章末尾可下载源码)
目前在试着做一个旅游类app项目,自己想加入一个功能:定位当前用户位置,并能规划出到旅游目的地的不同类型的线路及展示,界面参照拉钩网app工作地址详情页,采用高德地图的接口,琢磨下高德地图的demo,实现起来挺容易,直接上图:
1.集成高德地图
首先要集成高德地图,需要在高德地图官网申请高德地图的开发者账号,然后导入其第三方库等步骤,可以参考http://www.jianshu.com/p/bc9462f9c1e9。
2.显示用户位置及目的地展示
通过第一步,地图应该能显示出来了(高兴),接下来就是在地图上显示出我们需要的信息,首先是目的地展示(这里我采用大头针来显示目的地),以及当前定位当前用户,使用高德地图的第三方库也都几句代码的事,官方文档也有说明这里也不多说直接上代码:
地图初始化完成后的方法中
_mapView.centerCoordinate=CLLocationCoordinate2DMake(self.ampDistancePoint.latitude,self.ampDistancePoint.longitude);//定位中心点
_mapView.zoomLevel=16.5;//地图缩放级别
_mapView.showsUserLocation = YES; //显示定位蓝点
在这里要说一下大头针,大头针也可以参考高德地图中的点标记绘制来进行自定义,在viewDidAppear中
MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
pointAnnotation.coordinate = CLLocationCoordinate2DMake(self.ampDistancePoint.latitude,self.ampDistancePoint.longitude);
pointAnnotation.title = self.model.name; //大头针
[_mapView addAnnotation:pointAnnotation];
然后 实现
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id )annotation
{
if ([annotation isKindOfClass:[MAPointAnnotation class]])
{
static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
MAPinAnnotationView*annotationView = (MAPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
if (annotationView == nil)
{
annotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
}
annotationView.canShowCallout= YES; //设置气泡可以弹出,默认为NO
annotationView.animatesDrop = YES; //设置标注动画显示,默认为NO
annotationView.draggable = YES; //设置标注可以拖动,默认为NO
annotationView.pinColor = MAPinAnnotationColorPurple;
return annotationView;
}
return nil;
}
3.不同类型路线规划
关于路线规划,主要参照了官方demo,使用AMapSearchAPI类,发起路线规划请求,驾车和步行的路线规划区别不大,公共交通出行路线倒是有点不同。
3.1驾车路线规划发起
在这里我自定义了一个路线类型属性,1为驾车,2为公共交通,3为步行。这样做的用意,大家后面就知道了。
-(void)driveNav{
routeType=1;//自定义的路线类型,1为驾车,2为公共交通,3为步行
AMapDrivingRouteSearchRequest *navi = [[AMapDrivingRouteSearchRequest alloc] init];
navi.requireExtension = YES;//是否返回扩展信息,默认为 NO
navi.strategy = 5;// 驾车导航策略([default = 0]) 0-速度优先(时间);1-费用优先(不走收费路段的最快道路);2-距离优先;3-不走快速路;4-结合实时交通(躲避拥堵);5-多策略(同时使用速度优先、费用优先、距离优先三个策略);6-不走高速;7-不走高速且避免收费;8-躲避收费和拥堵;9-不走高速且躲避收费和拥堵
/* 出发点. */
navi.origin = [AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude
longitude:_mapView.userLocation.location.coordinate.longitude];
/* 目的地. */
navi.destination =self.ampDistancePoint;
[self.search AMapDrivingRouteSearch:navi];//驾车路线规划
}
3.2公共交通路线规划发起
公共交通不一样的点主要是多了一个city属性,必须要让高德地图知道是哪个城市的公共交通路线。
-(void)busNav{
routeType=2;
AMapTransitRouteSearchRequest *navi = [[AMapTransitRouteSearchRequest alloc] init];
navi.requireExtension = YES;
navi.strategy = 4;//公交换乘策略([default = 0])0-最快捷模式;1-最经济模式;2-最少换乘模式;3-最少步行模式;4-最舒适模式;5-不乘地铁模式
navi.city =@"chongqing";
/* 出发点. */
navi.origin = [AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude
longitude:_mapView.userLocation.location.coordinate.longitude];
/* 目的地. */
navi.destination =self.ampDistancePoint;
[self.search AMapTransitRouteSearch:navi];//公共交通路线规
}
3.3步行路线规划发起
步行路线和驾车大致相同
-(void)WalkNav{
routeType=3;
AMapWalkingRouteSearchRequest *navi = [[AMapWalkingRouteSearchRequest alloc] init];
/* 出发点. */
navi.origin = [AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude
longitude:_mapView.userLocation.location.coordinate.longitude];
/* 目的地. */
navi.destination = self.ampDistancePoint;
[self.search AMapWalkingRouteSearch:navi];
}
4路径规划搜索回调
通过AMapSearchAPI类向服务器发起路线规划请求后,如规划成功则跳到onRouteSearchDone方法,response,包含类路线的所有信息耗时,距离,换乘等等,否则跳转到- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error方法中,并附带出错原因。
在这里我使用了预加载的模式,在用户跳转到本界面时,就自动发起多有类型路线的规划,等所有路线类型都规划完毕后,用户才能查看各种类型的导航路线。预加载可以让用户对三种路线规划耗时一目了然,增强用户体验。
在预加载这里有一个问题,如果同时发起三种类型的路线规划请求,将会收到三次回调,但是由于异步的关系,就不能知道哪次回调对应着的是哪种类型的路线规划请求。
所以在这里我用了一个笨方法,先发起步行路线规划,等收到回调后再发起公共交通路线规划,用routeType属性可以知道当前是哪种类型请求的回调,再发起驾车路线规划,等驾车路线规划收到回调,那么所有路线加载完毕,则隐藏小菊花,用户也就能查看到各种类型的导航路线了。
/* 路径规划搜索回调. */
- (void) onRouteSearchDone:(AMapRouteSearchBaseRequest *)request response:(AMapRouteSearchResponse *)response
{
if (response.route == nil)
{
return;
}
/* 预加载*/
if (routeType==1) {
self.driveRoute=response.route;
self.driveDration.text=[self timeFomart: response.route.paths[0].duration];//预计用时
[MBProgressHUD hideHUDForView:self.navView animated:YES];//隐藏菊花
self.distance.text =[self disFormat:self.driveRoute.paths[0].distance];//显示距离
[self isHideNavView:false];//显示控件
}
if (routeType==2) {
self.busRoute=response.route;
if (response.route.transits!=nil && response.route.transits.count!=0) {
if(response.route.transits.lastObject!=nil){
self.busDration.text=[self timeFomart:response.route.transits[0].duration];
}
}else{
self.busDration.text=@"暂无";
self.busBtn.enabled=false;
}
[self driveNav];
}
if (routeType==3) {
self.walkRoute=response.route;
if (response.route.paths!=nil) {
self.walkDration.text=[self timeFomart: response.route.paths[0].duration];
}
else{
self.walkDration.text=@"暂无";
self.walkBtn.enabled=false;
}
[self busNav];
}
self.route = response.route;
self.currentCourse = 0;
}
如若出错,应该就是网络不好访问超时,要么就是超过距离,如超过了市区的公交线路导航,或其他原因系统无法规划出路线,那么则在出错处理中根据不同的类型进行错误的处理,并在此发起下一个类型的线路规划请求,在这里为了方便,出错的类型直接显示暂无= =。
//出错处理
- (void)AMapSearchRequest:(id)request didFailWithError:(NSError *)error
{
if (routeType==1) {
self.driveDration.text=@"暂无";
self.carBtn.enabled=false;
}
if (routeType==2) {
self.busDration.text=@"暂无";
self.busBtn.enabled=false;
[self driveNav];
}
if (routeType==3) {
self.walkDration.text=@"暂无";
self.walkBtn.enabled=false;
[self busNav];
}
5解析路线及路线的绘制
路线的解析以及路线的绘制,如用户查看驾车路线时,直接解析已预加载好的路线规划,然后再进行路线的绘制即可,在这里我直接是用的官方demo里的代码,仅仅根据自己的需求及界面的美观小小的修改了一下。
路线的解析,注意有些类是demo中封装好的,非官方库中的。
注意:要先清楚地图上的所有绘制
//清理绘制路线
- (void)clear
{
[self.naviRoute removeFromMapView];
}
点击驾车路线规划图标:
- (IBAction)drivingBtn:(id)sender {
[self clear];
routeType=1;
self.distance.text =[self disFormat:self.driveRoute.paths[0].distance];
[self presentCurrentCourse];//路线解析
}
//根据不同导航类型解析不同路线
- (void)presentCurrentCourse
{
MANaviAnnotationType type = MANaviAnnotationTypeDrive;
if (routeType==1) {
type = MANaviAnnotationTypeDrive;
self.naviRoute = [MANaviRoute naviRouteForPath:self.driveRoute.paths[self.currentCourse] withNaviType:type showTraffic:YES startPoint:[AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude longitude:_mapView.userLocation.location.coordinate.longitude]
endPoint:self.ampDistancePoint];
[self.naviRoute addToMapView:self.mapView];
}
if (routeType==2) {
self.naviRoute = [MANaviRoute naviRouteForTransit:self.busRoute.transits[self.currentCourse] startPoint:[AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude longitude:_mapView.userLocation.location.coordinate.longitude]
endPoint:self.ampDistancePoint];
[self.naviRoute addToMapView:self.mapView];
}
if (routeType==3) {
type = MANaviAnnotationTypeWalking;
self.naviRoute = [MANaviRoute naviRouteForPath:self.walkRoute.paths[self.currentCourse] withNaviType:type showTraffic:YES startPoint:[AMapGeoPoint locationWithLatitude:_mapView.userLocation.location.coordinate.latitude longitude:_mapView.userLocation.location.coordinate.longitude]
endPoint:self.ampDistancePoint];
[self.naviRoute addToMapView:self.mapView];
}
/* 缩放地图使其适应polylines的展示. */
self.naviRoute.anntationVisible=YES;
[self.mapView showOverlays:self.naviRoute.routePolylines edgePadding:UIEdgeInsetsMake(RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge) animated:YES];
}
路线的绘制直接照搬官方demo中的(手动斜眼)
注意:地图的绘制分两个部分,路线的绘制以及路线节点(Annotation)的绘制
路线的绘制
//根据不同导航类型绘制路线
- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id )overlay// 任何遵循此协议的对象
{
if ([overlay isKindOfClass:[LineDashPolyline class]])
{
MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:((LineDashPolyline *)overlay).polyline];
polylineRenderer.lineWidth = 8;
polylineRenderer.lineDashPattern = @[@10, @15];
polylineRenderer.strokeColor = [UIColor redColor];
return polylineRenderer;
}
if ([overlay isKindOfClass:[MANaviPolyline class]])
{
MANaviPolyline *naviPolyline = (MANaviPolyline *)overlay;
MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:naviPolyline.polyline];
polylineRenderer.lineWidth = 8;
if (naviPolyline.type == MANaviAnnotationTypeWalking)
{
polylineRenderer.strokeColor = self.naviRoute.walkingColor;
}
else if (naviPolyline.type == MANaviAnnotationTypeRailway)
{
polylineRenderer.strokeColor = self.naviRoute.railwayColor;
}
else
{
polylineRenderer.strokeColor = self.naviRoute.routeColor;
}
return polylineRenderer;
}
if ([overlay isKindOfClass:[MAMultiPolyline class]])
{
MAMultiColoredPolylineRenderer * polylineRenderer = [[MAMultiColoredPolylineRenderer alloc] initWithMultiPolyline:(MAMultiPolyline *)overlay];
polylineRenderer.lineWidth = 8;
polylineRenderer.strokeColors = [self.naviRoute.multiPolylineColors copy];
polylineRenderer.gradient = YES;
return polylineRenderer;
}
return nil;
}
路线节点的绘制
//根据导航类型绘制覆盖物
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id )annotation
{
if ([annotation isKindOfClass:[MAPointAnnotation class]])
{
static NSString *routePlanningCellIdentifier = @"RoutePlanningCellIdentifier";
MAAnnotationView *poiAnnotationView = (MAAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:routePlanningCellIdentifier];
if (poiAnnotationView == nil)
{
poiAnnotationView = [[MAAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:routePlanningCellIdentifier];
}
poiAnnotationView.canShowCallout = YES;
poiAnnotationView.image = nil;
if ([annotation isKindOfClass:[MANaviAnnotation class]])
{
switch (((MANaviAnnotation*)annotation).type)
{
case MANaviAnnotationTypeRailway:
poiAnnotationView.image = [UIImage imageNamed:@"railway_station"];
break;
case MANaviAnnotationTypeBus:
poiAnnotationView.image = [UIImage imageNamed:@"bus"];
break;
case MANaviAnnotationTypeDrive:
poiAnnotationView.image = [UIImage imageNamed:@"car"];
break;
case MANaviAnnotationTypeWalking:
poiAnnotationView.image = [UIImage imageNamed:@"man"];
break;
default:
break;
}
}
else
{
/* 起点. */ //绘制起点的红色大头针在这里哦
if ([[annotation title] isEqualToString:self.model.name])
{
static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
MAPinAnnotationView *annotationView = (MAPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
if (annotationView == nil)
{
annotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
}
annotationView.canShowCallout= YES; //设置气泡可以弹出,默认为NO
annotationView.animatesDrop = YES; //设置标注动画显示,默认为NO
annotationView.draggable = YES; //设置标注可以拖动,默认为NO
annotationView.pinColor = MAPinAnnotationColorRed;
return annotationView;
}
}
return poiAnnotationView;
}
return nil;
}
6其他
还有些边角代码:如时间格式转换,距离格式转换,string类型的经纬度转换等也一并献上吧,
-(AMapGeoPoint *)pointFormat:(NSString *)point{
//经纬度格式转换
NSArray *arry= [point componentsSeparatedByString:@","];
double p2=((NSString *)arry[0]).doubleValue;
double p1=((NSString *)arry[1]).doubleValue;
return [AMapGeoPoint locationWithLatitude:CLLocationCoordinate2DMake(p1,p2).latitude
longitude:CLLocationCoordinate2DMake(p1,p2).longitude];
}
//距离格式转换
-(NSString *)disFormat:(double)meters {
double intDistance=(int)round(meters);
return [NSString stringWithFormat:@"距离:%0.2fKM",intDistance/1000 ];
}
//时间格式转换
-(NSString *)timeFomart:(double)duration{
return [NSString stringWithFormat:@"%0.0f分钟",duration/60];
}
//笨方法,隐藏加载中的控件
-(void)isHideNavView:(BOOL) ishide{
self.busBtn.hidden=ishide;
self.busDration.hidden=ishide;
self.driveDration.hidden=ishide;
self.carBtn.hidden=ishide;
self.walkDration.hidden=ishide;
self.walkBtn.hidden=ishide;
}
项目demo开源在github欢迎Star(害羞#)
注:需手动导入AmpFrameworks
https://github.com/calvinWen/AmpDemo