Skip to content

Transit#

Transit Directions

Our unique approach allows for anyone to integrate transit routing in full details instantly with just a few lines of code. Take advantage of our industry-leading router, built on top of our proprietary data factory, and refined for over a decade.

 


 

Get Transit Directions#

Transit Directions

Get transit routes between two locations using planTransitRoutes().

In the happy case, you will receive up to 5 transit Routes. If both endpoints are not in the same Citymapper coverage area, you'll receive a failure result, and no billable session will be recorded.

CitymapperDirections.shared.planTransitRoutes(
    start: CLLocationCoordinate(latitude: 51.504175, longitude: -0.105884),
    end: CLLocationCoordinate(latitude: 51.498255, longitude: -0.098251)) { (result) in
    switch result {
        case .failure(let error):
            print(error)
        case .success(let data):
            guard let route = data.routes.first else {
                return
            }
            // Display the Route
    }
}
//suspend function
coroutineScope.launch {
    val result = CitymapperDirections.getInstance(context).planTransitRoutes(
        start = Coords(51.504175, -0.105884),
        end = Coords(51.498255, -0.098251)
    ).execute()
    when (result) {
        is ApiResult.Failure -> {
            Log.e("DEBUG", "ApiResult failure", result.exception)
        }
        is ApiResult.Success -> {
            val route = result.data.routes.first()
            // Display the Route
        }
    }
}

//callback
CitymapperDirections.getInstance(context).planTransitRoutes(
    start = Coords(51.504175, -0.105884),
    end = Coords(51.498255, -0.098251)
).executeAsync { result ->
    when (result) {
        is ApiResult.Failure -> {
            Log.e("DEBUG", "ApiResult failure", result.exception)
        }
        is ApiResult.Success -> {
            val route = result.data.routes.first()
            // Display the Route
        }
    }
}
CitymapperDirections.getInstance(this).planTransitRoutes(
        new Coords(51.504175, -0.105884),
        new Coords(51.498255, -0.098251)
).executeAsync(result -> {
    if (result.isSuccess()) {
        DirectionsResults results = result.getSuccessData();
        Route route = results.getRoutes().get(0);
        // Display the Route
    } else {
        Log.e("DEBUG", "ApiResult failure", result.getFailureException());
    }
});

 

Display transit routes in a list#

Display a list of transit results

The Route List displays a list of possible Routes for your user to choose from.

To obtain a list of routes see Planning routes

Android#

Add a CitymapperRouteListView to your layout

<com.citymapper.sdk.ui.routelist.CitymapperRouteListView
    android:id="@+id/route_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Then pass the directions results to the view when they have loaded

val routeListView = requireViewById<CitymapperRouteListView>()
routeListView.setRoutes(routes) {
  // Handle result click (see below)
}

CitymapperRouteListView will take care of updating the Routes with the latest departure data as appropriate, while it is visible on screen.

The CitymapperRouteListView contains a RecyclerView internally. If custom content needs to be displayed as part of this list, and it can not be set up in a separate container using nested scrolling, an additional RecyclerView.Adapter can be passed to the view to supply items which will be appended to the start of the list.

routeListView.setHeaderAdapter(adapter)

Alternatively (since the number of routes is generally <=5) the whole view can be embedded within a larger scrolling view. In this case it may be necessary to disable nested scrolling.

routeListView.isNestedScrollingEnabled = false

Route selection#

The setResults method of CitymapperRouteListView takes a lambda which is called when a user clicks on a Route.

To display the route in detail see Display the route in details

iOS#

In iOS, the Route List is a UIViewController that you can add as a subcontroller to the main one for your screen. The width is intended to be the full-screen width, the height can be calculated via the contentHeight(forWidth: CGFloat) -> CGFloat method.

let vc = RouteListContainer.createWrappedRouteListController(routes: routes, delegate: self)
addChild(vc)

let width = self.view.frame.width
let requiredHeight = self.routeListViewController.contentHeight(forWidth: width)

vc.view.frame = CGRect(x: 0,
                       y: 300,
                       width: width,
                       height: requiredHeight)

view.addSubview(vc.view)
vc.didMove(toParent: self)

Handling Panel Height Updates#

Route List Controller is a UIViewController, that conforms to the protocol RouteListController

Above we see contentHeight(forWidth: CGFloat) -> CGFloat provides a mechanism to calculate the height required by the Route List controller once it's initialized, but before it's displayed on the screen.

However, the height required by Route List controller can also change whilst it's being displayed on the screen. This is due to route icon wrapping and live times changing.

In order to handle this 'in flight' height change RouteListDelegate has a required method listContentHeightUpdated(newHeight: CGFloat)

extension ViewController: RouteListDelegate {
    func listContentHeightUpdated(newHeight: CGFloat) {

            // Update layout constraints or directly manipulate the frame
            // on your RouteListViewController instance.

            DispatchQueue.main.async {
                var oldFrameWithAdjustments = self.routeListViewController.view.frame
                oldFrameWithAdjustments.size = CGSize(width: oldFrameWithAdjustments.width,
                                                      height: newHeight)
                self.routeListViewController.view.frame = oldFrameWithAdjustments
            }
    }

    // ...

}

Route selection#

Set the delegate: RouteListDelegate property on RouteListViewController then listen for calls.

protocol RouteListDelegate {
    func showRouteDetail(for route: Route)
}

public class MyViewController: UIViewController {
    // ...
}

extension MyViewController: RouteListDelegate {

    func showRouteDetail(for route: Route) {
        // Handle result click
    }
}
To display the route in detail see Display the route in details

 

Display transit route details#

Display transit result in details

Android#

Use the method RouteDetail.showStandaloneRouteDetailScreen(Context, Route, @StyleRes int) to display Route Details as a new Activity.

RouteDetail.showStandaloneRouteDetailScreen(requireContext(), route)

Theme#

You can pass a theme when you show a route detail. For more info on theming please refer to the doc

RouteDetail.showStandaloneRouteDetailScreen(
    context = requireContext(),
    route = route,
    theme = R.style.A_Citymapper_SDK_Theme
)

iOS#

let detail = RouteDetailsContainer
        .createWrappedRouteDetailsController(route: route)
navigationController?.pushViewController(detail,
                                         animated: true)

Config#

Decide if you want to show or hide the in-built back button. You can also pass nil for default back behaviour.

let config = RouteDetailsViewController.Configuration(backBehaviour: .hidden)
//or
let config = RouteDetailsViewController.Configuration(backBehaviour: .visible(actionOnTap: { [weak self] in
    self?.navigationController?.popViewController(animated: true)
}))

let detail = RouteDetailsContainer
        .createWrappedRouteDetailsController(route: route
                                             configuration: config)

Theme#

You can pass a theme when you show a route detail. For more info on theming please refer to the doc

let detail = RouteDetailsContainer
        .createWrappedRouteDetailsController(route: route
                                             theme: CitymapperSDKTheme())