示例中心
功能在线体验
控制台

公交换乘

该示例将公交路线规划的换乘方案以Overlay和列表两种方式显示。
00:00 / 01:00
扫描二维码在手机端体验功能
体验移动端 扫码体验移动端

示例特点

公交换乘方案的多种表现形式(地图形式和列表展示),同时将 BusRouteOverlay 开源,帮助您做特殊样式的显示方案。

用到产品

Android 地图 SDK

核心类/接口

接口

说明

版本

RouteSearch.BusRouteQuery

BusRouteQuery(RouteSearch.FromAndTo fromAndTo, int mode, java.lang.String city, int nightFlag)

构造函数,构造公交路线规划查询参数对象。

V2.1.0版本起

RouteSearch

calculateBusRouteAsyn(RouteSearch.BusRouteQuery busQuery)

公交路线规划异步接口。

V2.1.0版本起

RouteSearch.OnRouteSearchListener

onBusRouteSearched(BusRouteResult busRouteResult, int errorCode)

公交换乘路径规划结果的回调方法。

V2.1.0版本起

BusRouteResult

getPaths()

返回公交路径规划方案。

V2.1.0版本起

BusPath

getSteps()

返回公交路径规划方案的路段列表。

V2.1.0版本起

BusStep

getBusLines()

返回此路段的公交导航信息。

V2.1.0版本起


getWalk()

返回此路段的步行信息。

V2.1.0版本起

核心难点

1、如何解析公交换乘结果对象?

a)通过 BusRouteResult.getPaths() 方法获取全部的公交换成方案的列表,列表中是 BusPath 对象。

b)BusPath 对象就是一个完整的公交换乘方案,每个方案都是由多个 BusStep 构成。

c)BusStep 对象由 RouteBusWalkItem(步行导航信息)和 RouteBusLineItem(公交导航信息)构成。

2、如何更改显示在地图上的 BusRouteOverlay 的样式(如:起终点图标、线路的颜色、宽度等)?

在本示例中,BusRouteOverlay 已对所有开发者开源,您可以参考源码修改 Overlay 的样式,其中:

 

  • 修改 getStartBitmapDescriptor() 方法的返回值,可以修改起点的图标。
  • 修改 getBusColor() 方法的返回值,可以修改公交路线的颜色。
00:00 / 01:00
扫描二维码在手机端体验功能
体验移动端 扫码体验移动端

使用场景

公交换乘方案的多种表现形式(地图形式和文字展示),同时将BusRouteOverlay开源,帮助您做特殊样式的显示方案。

用到产品

iOS 地图 SDK

核心类/接口

接口

说明

版本

MAMapView

addOverlay:

向地图窗口添加Overlay。

V2.0.0版本起


addAnnotation:

向地图窗口添加标注。

V2.0.0版本起

MAMapViewDelegate

mapView:viewForAnnotation:

地图上的起始点,终点,拐点的标注的回调,可以自定义图标展示等。

V2.0.0版本起


mapView:rendererForOverlay:

地图上覆盖物的渲染的回调,可以设置路径线路的宽度,颜色等。

V3.0.0版本起

AMapSearchAPI

AMapTransitRouteSearch:

公交路径规划查询接口。

V3.0.0版本起

AMapTransitRouteSearchRequest

city

查询城市。

V3.0.0版本起


requireExtension

是否返回扩展信息,默认NO。

V3.0.0版本起

AMapRouteSearchBaseRequest

origin

出发点坐标。

V3.0.0版本起


destination

目的地坐标。

V3.0.0版本起

AMapSearchDelegate

onRouteSearchDone:response:

路径规划查询完成回调。

V3.0.0版本起

核心难点

1、如何解析公交换乘结果对象。

a)通过response.route.transits 可以获取到 AMapTransit 列表。

b)AMapTransit 对象就是一个完整的公交换乘方案,每个方案都是由多个 AMapSegment 构成。

c)AMapSegment 对象由 AMapWalking(步行)和 AMapBusLine(公交线路)构成。

在本示例中,MANaviRoute 是一个生成路线 Polyline 和起终点以及中间节点的 Annotaion 的工具类,省去了您去解析 response 对象的成本。

- (void)presentCurrentRouteCourse {

    if (self.routeArray.count <= 0) {
        return;
    }

    [self.naviRoute removeFromMapView];  //清空地图上已有的路线

    AMapGeoPoint *startPoint = [AMapGeoPoint locationWithLatitude:self.startAnnotation.coordinate.latitude longitude:self.startAnnotation.coordinate.longitude]; //起点

    AMapGeoPoint *endPoint = [AMapGeoPoint locationWithLatitude:self.destinationAnnotation.coordinate.latitude longitude:self.destinationAnnotation.coordinate.longitude];  //终点

    //根据已经规划的换乘方案,起点,终点,生成显示方案
    self.naviRoute = [MANaviRoute naviRouteForTransit:self.route.transits[self.currentRouteIndex] startPoint:startPoint endPoint:endPoint];

    [self.naviRoute addToMapView:self.mapView];  //显示到地图上

    UIEdgeInsets edgePaddingRect = UIEdgeInsetsMake(RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge);

    //缩放地图使其适应polylines的展示
    [self.mapView setVisibleMapRect:[CommonUtility mapRectForOverlays:self.naviRoute.routePolylines] edgePadding:edgePaddingRect animated:YES];
}
//在地图上显示当前选择的路径
func presentCurrentRouteCourse() {

    if self.routeArray.count <= 0 {
        return
    }

    self.naviRoute?.removeFromMapView() //清空地图上已有的路线

    let startPoint = AMapGeoPoint.location(withLatitude: CGFloat(self.startAnnotation.coordinate.latitude), longitude: CGFloat(self.startAnnotation.coordinate.longitude)) //起点

    let endPoint = AMapGeoPoint.location(withLatitude: CGFloat(self.destinationAnnotation.coordinate.latitude), longitude: CGFloat(self.destinationAnnotation.coordinate.longitude))  //终点

    //根据已经规划的路径,起点,终点,规划类型,是否显示实时路况,生成显示方案
    self.naviRoute = MANaviRoute(for: self.route.transits[self.currentRouteIndex], start: startPoint, end: endPoint)
    self.naviRoute?.add(to: self.mapView)

    //显示到地图上
    let edgePaddingRect = UIEdgeInsetsMake(RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge, RoutePlanningPaddingEdge)

    //缩放地图使其适应polylines的展示
    self.mapView.setVisibleMapRect(CommonUtility.mapRect(forOverlays: self.naviRoute?.routePolylines), edgePadding: edgePaddingRect, animated: true)
}

2、如何更改显示在地图上的路线的的样式(包括:出发点和目的地的图标、路线的颜色、宽度等等)。

//地图上覆盖物的渲染,可以设置路径线路的宽度,颜色等
- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id<MAOverlay>)overlay {
    //虚线,如需要步行的
    if ([overlay isKindOfClass:[LineDashPolyline class]]) {
        MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:((LineDashPolyline *)overlay).polyline];
        polylineRenderer.lineWidth = 6;
        polylineRenderer.lineDash = YES;
        polylineRenderer.strokeColor = [UIColor redColor];
        
        return polylineRenderer;
    }
    
    //路径为单一颜色,比如公交线路目前为blueColor
    if ([overlay isKindOfClass:[MANaviPolyline class]]) {
        MANaviPolyline *naviPolyline = (MANaviPolyline *)overlay;
        MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:naviPolyline.polyline];
        
        polylineRenderer.lineWidth = 6;
        
        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;
    }
    
    //showTraffic为YES时,需要带实时路况,路径为多颜色渐变,多用于驾车路线规划,公交路线规划为单一颜色
    if ([overlay isKindOfClass:[MAMultiPolyline class]]) {
        MAMultiColoredPolylineRenderer * polylineRenderer = [[MAMultiColoredPolylineRenderer alloc] initWithMultiPolyline:overlay];
        
        polylineRenderer.lineWidth = 6;
        polylineRenderer.strokeColors = [self.naviRoute.multiPolylineColors copy];
        
        return polylineRenderer;
    }
    
    return nil;
}

//地图上的起始点,终点,拐点的标注,可以自定义图标展示等
- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation {
    if ([annotation isKindOfClass:[MAPointAnnotation class]]) {
        
        //标注的view的初始化和复用
        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:(NSString*)RoutePlanningViewControllerStartTitle]) {
                poiAnnotationView.image = [UIImage imageNamed:@"startPoint"];  //起点
            } else if ([[annotation title] isEqualToString:(NSString*)RoutePlanningViewControllerDestinationTitle]) {
                poiAnnotationView.image = [UIImage imageNamed:@"endPoint"];  //终点
            }
            
        }
        
        return poiAnnotationView;
    }
    
    return nil;
}
func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! {
    
    //虚线,如需要步行的
    if overlay.isKind(of: LineDashPolyline.self) {
        let naviPolyline: LineDashPolyline = overlay as! LineDashPolyline
        let renderer: MAPolylineRenderer = MAPolylineRenderer(overlay: naviPolyline.polyline)
        renderer.lineWidth = 6
        renderer.strokeColor = UIColor.red
        renderer.lineDash = true
        
        return renderer
    }
    
    //showTraffic为NO时,不需要带实时路况,路径为单一颜色,比如步行线路目前为海蓝色
    if overlay.isKind(of: MANaviPolyline.self) {
        
        let naviPolyline: MANaviPolyline = overlay as! MANaviPolyline
        let renderer: MAPolylineRenderer = MAPolylineRenderer(overlay: naviPolyline.polyline)
        renderer.lineWidth = 6
        
        if naviPolyline.type == MANaviAnnotationType.walking {
            renderer.strokeColor = naviRoute?.walkingColor
        }
        else if naviPolyline.type == MANaviAnnotationType.railway {
            renderer.strokeColor = naviRoute?.railwayColor;
        }
        else {
            renderer.strokeColor = naviRoute?.routeColor;
        }
        
        return renderer
    }
    
    //showTraffic为YES时,需要带实时路况,路径为多颜色渐变
    if overlay.isKind(of: MAMultiPolyline.self) {
        let renderer: MAMultiColoredPolylineRenderer = MAMultiColoredPolylineRenderer(multiPolyline: overlay as! MAMultiPolyline!)
        renderer.lineWidth = 6
        renderer.strokeColors = naviRoute?.multiPolylineColors
        
        return renderer
    }
    
    return nil
}

func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
    
    if annotation.isKind(of: MAPointAnnotation.self) {
        
        //标注的view的初始化和复用
        let pointReuseIndetifier = "RoutePlanningCellIdentifier"
        var annotationView: MAAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: pointReuseIndetifier)
        
        if annotationView == nil {
            annotationView = MAAnnotationView(annotation: annotation, reuseIdentifier: pointReuseIndetifier)
            annotationView!.canShowCallout = true
            annotationView!.isDraggable = false
        }
        
        annotationView!.image = nil
        
        if annotation.isKind(of: MANaviAnnotation.self) {
            let naviAnno = annotation as! MANaviAnnotation
            
            switch naviAnno.type {
            case MANaviAnnotationType.railway:
                annotationView!.image = UIImage(named: "railway_station")
                break
            case MANaviAnnotationType.drive:
                annotationView!.image = UIImage(named: "car")
                break
            case MANaviAnnotationType.riding:
                annotationView!.image = UIImage(named: "ride")
                break
            case MANaviAnnotationType.walking:
                annotationView!.image = UIImage(named: "man")
                break
            case MANaviAnnotationType.bus:
                annotationView!.image = UIImage(named: "bus")
                break
            }
        }
        else {
            if annotation.title == "起点" {
                annotationView!.image = UIImage(named: "startPoint")
            }
            else if annotation.title == "终点" {
                annotationView!.image = UIImage(named: "endPoint")
            }
        }
        return annotationView!
    }
    
    return nil
}

3、如何以列表的形式显示公交详情。

//根据transit的具体字段,显示信息
- (void)setUpViewsWithData {

    NSInteger hours = self.transit.duration / 3600;
    NSInteger minutes = (NSInteger)(self.transit.duration / 60) % 60;
    self.timeInfoLabel.text = [NSString stringWithFormat:@"%u小时%u分钟(%u公里)",(unsigned)hours,(unsigned)minutes,(unsigned)self.transit.distance / 1000];
    self.taxiCostInfoLabel.text = [NSString stringWithFormat:@"打车约%.0f元",self.route.taxiCost];

    self.routeDetailDataArray = @[].mutableCopy;
    [self.routeDetailDataArray addObject:@{RoutePathDetailStepInfoImageName : @"start",RoutePathDetailStepInfoText : @"开始出发"}]; // 图片的名字,具体步骤的文字信息

    for (AMapSegment *segment in self.transit.segments) {
        AMapRailway *railway = segment.railway; //火车
        AMapBusLine *busline = [segment.buslines firstObject];  // 地铁或者公交线路
        AMapWalking *walking = segment.walking;  //搭乘地铁或者公交前的步行信息

        if (walking.distance) {
            NSString *walkInfo = [NSString stringWithFormat:@"步行%u米",(unsigned)walking.distance];
            [self.routeDetailDataArray addObject:@{RoutePathDetailStepInfoImageName : @"walkRoute",RoutePathDetailStepInfoText : walkInfo}];
        }

        if (busline.name) {
            NSString *busImageName = @"busRoute";
            if ([busline.type isEqualToString:@"地铁线路"]) { //区分公交和地铁
                busImageName = @"underGround";
            }

            //viaBusStops途径的公交车站的数组,如需具体站名,可解析。
            NSString *busInfoText = [NSString stringWithFormat:@"乘坐%@,在 %@ 上车,途经 %u 站,在 %@ 下车",busline.name,busline.departureStop.name,(unsigned)(busline.viaBusStops.count + 1),busline.arrivalStop.name];
            [self.routeDetailDataArray addObject:@{RoutePathDetailStepInfoImageName : busImageName,RoutePathDetailStepInfoText : busInfoText}];

        } else if (railway.uid) {
            [self.routeDetailDataArray addObject:@{RoutePathDetailStepInfoImageName : @"railwayRoute",RoutePathDetailStepInfoText : railway.name}];
        }
    }

    [self.routeDetailDataArray addObject:@{RoutePathDetailStepInfoImageName : @"end",RoutePathDetailStepInfoText : @"抵达终点"}];

}
//根据transit的具体字段,显示信息
func setUpViewsWithData() {

    let hours = self.transit!.duration / 3600
    let minutes = Int(self.transit!.duration / 60) % 60
    self.timeInfoLabel.text = "\(UInt(hours))小时\(UInt(minutes))分钟(\(UInt(self.transit!.distance) / 1000)公里)"
    self.taxiCostInfoLabel.text = String(format: "打车约%.0f元", self.route.taxiCost)

    self.routeDetailDataArray = []

    self.routeDetailDataArray.add([RoutePathDetailStepInfoImageName : "start", RoutePathDetailStepInfoText : "开始出发"])

    for segment: AMapSegment in self.transit.segments as Array {

        if segment.walking != nil {
            let walkInfo = "步行\(segment.walking.distance)米"
            self.routeDetailDataArray.add([RoutePathDetailStepInfoImageName : "walkRoute", RoutePathDetailStepInfoText : walkInfo])
        }

        if (segment.buslines.first?.name != nil) {

            let busline: AMapBusLine = segment.buslines.first!

            var busImageName = "busRoute"
            if busline.type == "地铁线路" { //区分公交和地铁
                busImageName = "underGround"
            }

            //viaBusStops途径的公交车站的数组,如需具体站名,可解析。
            let busInfoText = String(format: "乘坐%@,在 %@ 上车,途经 %u 站,在 %@ 下车", busline.name,busline.departureStop.name,busline.viaBusStops.count + 1,busline.arrivalStop.name)

            self.routeDetailDataArray.add([RoutePathDetailStepInfoImageName : busImageName, RoutePathDetailStepInfoText : busInfoText])

        } else if segment.railway.uid != nil {
            self.routeDetailDataArray.add([RoutePathDetailStepInfoImageName : "railwayRoute", RoutePathDetailStepInfoText : segment.railway.name])
        }

    }

    self.routeDetailDataArray.add([RoutePathDetailStepInfoImageName : "end", RoutePathDetailStepInfoText : "抵达终点"])

}