Skip to content

Routing

This section explains the use of route computing functionality including multiple waypoints and alternate routes within an Android app.

Car, Truck and Pedestrian Routing

The Sygic Maps SDK for Android supports route calculation with multiple waypoints, optimized for walking or driving. A route describes a path between at least two waypoints, the starting point and the destination, with optional intermediate waypoints in between. Most of the managers need to be called asynchronously.

Applications can provide route information to users in two ways:

  • A line rendered on a map that displays a connecting path between all waypoints
  • Turn-by-turn directions in text format

RoutePlan is a class containing all info about the route we want to calculate. It contains start, finish, via-points and another route options.

val startGeoCoordinates = GeoCoordinates(48.13308, 11.57249) // somewhere in Munich
val endGeoCoordinates = GeoCoordinates(47.54849, 9.68634) // the beautiful city of Lindau
val routePlan = RoutePlan()
routePlan.setStart(startGeoCoordinates)
routePlan.setDestination(endGeoCoordinates)

To adjust routing options, such as transport mode, dimensional restrictions, avoids and much more, create a RoutingOptions instance that you need to pass to the RoutePlan afterwards:

val routingOptions = RoutingOptions()
routingOptions.isUnpavedRoadAvoided = true
routingOptions.routingService = RoutingOptions.RoutingService.Online
routingOptions.transportMode = RoutingOptions.TransportMode.Car
routingOptions.tunnelRestriction = RoutingOptions.ADRTunnelType.B
routingOptions.addDimensionalRestriction(RoutingOptions.VehicleRestrictions.Height, 4500) // in mm, weight in kg

routePlan.setRoutingOptions(routingOptions);

Afterwards you can compute the route using an instance of Router. Please note that the onProgress callback gets continuous progress only in offline mode as the online routing's progress cannot be tracked (you only get 0 and then 100).

val router = RouterProvider.getInstance().get()
lateinit var route : Route
router.computeRoute(routePlan, object : Router.RouteComputeListener {
  override fun onComputeError(p0: Router?, p1: Int) {
    // handle the error
  }

  override fun onProgress(p0: Router?, p1: Int, p2: Long) {
    // show the progress to the user
  }

  override fun onComputeStarted(p0: Router?) {
    // notify the user that the compute has started / change the UI
  }

  override fun onPrimaryComputeFinished(p0: Router?, p1: Route?) {
    // add the primary route to the map
    p1?.let {
      mMapView.mapDataModel.addMapObject(
        MapRoute.from(it).setType(MapRoute.RouteType.Primary).build()
      )
      route = it
    }
  }

  override fun onAlternativeComputeFinished(p0: Router?, p1: Route?) {
    // add the alternative route(s) to the map
    // note that this callback might be called more than once
    p1?.let {
      mMapView.mapDataModel.addMapObject(
        MapRoute.from(it).setType(MapRoute.RouteType.Alternative)
          .build()
      )
    }
  }

  override fun onComputeFinished(p0: Router?) {
    // show the whole route to the user using its bounding box
    mMapView.cameraModel.setMapRectangle(
      MapRectangle(
        mRoute.boundingBox,
        0.15f,
        0.15f,
        0.15f,
        0.15f
      ),
      MapAnimation(
        500,
        MapAnimation.InterpolationCurve.AccelerateDecelerate
      )
    )
    // add a SmartLabel showing the primary route's duration
    mMapView.mapDataModel.addMapObject(
      MapSmartLabel
        .with(mRoute)
        .setText(
          (StyledText(
          // formatDate() is a function that transforms an integer into a stringified format
            formatDate(mRoute.routeInfo.durationWithSpeedProfileAndTraffic)
          ))
        )
        .build()
      )
      // add a MapMarker at the start
      mMapView.mapDataModel.addMapObject(
        MapMarker.at(start).withIcon(R.drawable.sygic_sdk_map_pin).build()
      )
      // add a MapMarker at the end
      mMapView.mapDataModel.addMapObject(
        MapMarker.at(end).withIcon(R.drawable.sygic_sdk_map_pin).build()
      )
      NavigationManagerProvider.getInstance().get().setRouteForNavigation(route)
    }
})

Sygic Maps SDK supports alternative routes between two waypoints. This allows more than one route to be returned after a route calculation.

Handling rerouting can be done using the NavigationManager's onRouteChanged listener. Setting the new route for navigation is handled internally, all you need to do is check if the new route is not null, remove the old MapRoute and add the new one. Of course, if you have stored the RoutePlan or the RouteInfo, you need to assign the new ones to your variables and also you can update the traffic data for route etc.

The MapRoute map object

The MapRoute class is a type of MapObject that displays a calculated route on a map. Typically, an application creates a MapRoute after a route calculation, passing the relevant Route object as a parameter to the MapRoute(Route) constructor before adding the MapRoute to the map by calling MapView.addMapObject(MapRoute).

For primary route:

MapRoute primaryRoute = MapRoute.from(route).setType(MapRoute.RouteType.Primary).build(); // route is obtained via onPrimaryComputeFinished() method in RouteComputeListener
map.GetMapDataModel().addMapObject(mapRoute);

For alternative route:

MapRoute alternativeRoute = MapRoute.from(route).setType(MapRoute.RouteType.Alternative).build(); 
// route is obtained via onAlternativeComputeFinished() method in RouteComputeListener
map.GetMapDataModel().addMapObject(mapRoute);

// removing
map.GetMapDataModel().removeMapObject(mapRoute);

You can also process the Route without using it in map, e.g. to get textual instructions:

StringBuilder instructionsList = new StringBuilder();
for (RouteManeuver maneuver : route.getManeuvers()) {
    switch (maneuver.getType()) {
        case RouteManeuver.Type.Left:
            instructionsList.append("Turn left onto ").append(maneuver.getNextRoadName()).append('\n');
            break;
        case RouteManeuver.Type.RoundaboutN:
            instructionsList.append("On the roundabout, continue straight with exit #")
                    .append(maneuver.getRoundaboutExit()).append('\n');
            break;
        // ...
    }
}

Route Serialization

If you wish to save a route you have computed, you can use Route.serializeToBriefJSON()

String json = route.serializeToBriefJSON();
// use the serialized route - save it to a file, send it over network, sky is the limit...

To deserialize the JSON and get the Route you can use Router.computeRouteFromJSONString().

mRouter.computeRouteFromJSONString(json, new Router.RouteComputeAdapter() {
    @Override
    public void onPrimaryComputeFinished(final Router router, final Route route) {
        // use route
    }
});

You can also extract RoutePlan first, e.g. if you want to edit it before computing:

RoutePlan routePlan = mRouter.createRoutePlanFronJSONString(json);
routePlan.addViaPoint(new GeoCoordinates(51.515436, -0.122045));
mRouter.computeRoute(routePlan, mRouteComputeListener);

Routing Options

You can edit parameters of route before computing using RoutingOptions.

RoutingOptions options = new RoutingOptions();
options.setTransportMode(RoutingOptions.TransportMode.Car);
options.setRoutingType(RoutingOptions.RoutingType.Economic);

routePlan.setRoutingOptions(options);
mRouter.computeRoute(routePlan, mRouteComputeListener);

By default, the route is computed at the instant. You can also set a departure time using mRoutingOptions.departureTime, but only set it when you want to override it as it computes the route without traffic and you wouldn't want that for a normal compute.

Avoids

To avoid certain elements on route like toll roads or unpaved roads, you can set flags on RoutingOptions.

val routingOptions = RoutingOptions() // this creates an object with default values
routingOptions.isTollRoadAvoided = true
routingOptions.isUnpavedRoadAvoided = true

val routePlan = RoutePlan()
routePlan.routingOptions = routingOptions
RouterProvider.getInstance().get().computeRoute(routePlan, ...)

After the route is computed, you can get RoutingOptions from Route in order to further edit the route. You can get countries on route and set avoids individually for each or you can avoid a whole country altogether as shown below.

val mRouter = RouterProvider.getInstance().get()
val routingOptions = mRoute.routingOptions
val avoidableCountries = setOf(routingOptions.avoidableCountries)

avoidableCountries.forEach {
    if (it.equals("fr")) {
        routingOptions.setTollRoadAvoided("fr", true)
    }
    if (it.equals("ch")) {
        routingOptions.setCountryAvoided("ch", true)
    }
}
// then assign the new routing options and compute the route again
routePlan.routingOptions = routingOptions
mRouter.computeRoute(routePlan, ...)

It is possible that you would like to see what can be avoided on the route that was computed. Imagine that the user computes a route from Bratislava, Slovakia to Saint-Malo, France. The route goes via Austria and Germany. After the route is computed, you can get the countries that can be avoided. In this case, Austria and Germany can be avoided.
Then, if you like, it's possible to avoid only certain types of roads in each country. For example, the route is computed via Toll roads and Motorways in each country (except for Germany, as there are no Toll roads). For example, the user should be able to avoid Toll roads in France, only. Therefore, you can let the user decide for each country just like this:

val routingOptions = mRoute.routingOptions
val transitCountries = mRoute.transitCountries
transitCountries.forEach {
    // ask for each country if the options are avoidable
    if (routingOptions.isHighwayAvoidable(it)) {
        // if highway is avoidable in the country, show it to the user
        // and then set isHighwayAvoided to true if the user chooses to avoid it
        routingOptions.isHighwayAvoided = true
    }
    if (routingOptions.isTollRoadAvoidable(it)) {
        // same here
        routingOptions.isTollRoadAvoided = true
    }
    // and for other avoids
}
// remember to recompute the route with the new options!
routePlan.routingOptions = routingOptions
mRouter.computeRoute(routePlan, ...)

Route Information

We will work with a route that we have computed in the previous chapters.

Some of the basic info can be received directly from the Route object:

val routingOptions = mRoute.routingOptions
val boundingBox = mRoute.boundingBox
val routeInfo = mRoute.routeInfo
val routeManeuvers = mRoute.maneuvers

The routingOptions can be used to show the routing options that have been used to compute the route. The boundingBox of the route can be passed into the setMapRectangle function to see just the whole route on the map:

mMapView.cameraModel.setMapRectangle(
  MapRectangle(
    mRoute.boundingBox,
    0.15f,
    0.15f,
    0.15f,
    0.15f
  ), MapAnimation(500, MapAnimation.InterpolationCurve.AccelerateDecelerate)
)

routeManeuvers can be used to show the all of the directions on the route beforehand. The routeInfo object holds information about the length of the route and three different durations - one includes speed profile information and traffic delays, the other includes only speed profile information and the last one doesn't use any of the additional information.

Route Explorer

After computing a route, you can get the traffic information, places along the route and more using the RouteExplorer.

Again, we will work with a pre-computed route mRoute. Let's see how to explore the traffic on route and its most important members:

RouteExplorer.exploreTrafficOnRoute(mRoute, object: RouteExplorer.OnExploreTrafficOnRouteListener{
  override fun onExploreTrafficLoaded(info: TrafficNotification) {
    info.delayOnRoute // total delay of all traffic events
    info.trafficLevel // from Low to High
    info.trafficInfoList.forEach { // as there can be more traffic events
      it.affectedArea // bounding box of the traffic
      it.affectedLength // length of the traffic event
      it.delay // delay in seconds
      it.distance // distance from the last callback position
      it.severity // Normal to Blocking
    }
  }

  override fun onExploreTrafficError(error: TrafficManager.ErrorCode) {
    // handle the error
  }
})

Next, let's see how to explore the Places on route. Please note that this time, you also receive progress of the exploring and the operation is fully completed when it reaches the value 100.

RouteExplorer.explorePlacesOnRoute(mRoute, listOf(PlaceCategories.PetrolStation), object: RouteExplorer.OnExplorePlacesOnRouteListener{
  override fun onExplorePlacesLoaded(
    info: List<PlaceInfo>,
    progress: Int
  ) {
    if (progress == 100){
      info.forEach {
        print(it.distance) // distance to the place in meters
        print(it.placeInfo.name) // name of the place
        print(it.placeInfo.category) // category of the place
      }
    }
  }
  override fun onExplorePlacesError(error: PlacesManager.ErrorCode) {
    // handle the error
  }
})

You can also explore various types of incidents on the route. Here we want to get only the instances of static radars on our route, if there are any :

RouteExplorer.exploreIncidentsOnRoute(mRoute, listOf(IncidentType.RadarStaticSpeed), object: RouteExplorer.OnExploreIncidentsOnRouteListener{
  override fun onExploreIncidentsLoaded(
    incidents: List<IncidentInfo>,
    progress: Int
  ) {
    if (progress == 100){
      incidents.forEach{
        print(it.distance) // distance to the incident in meters
        print(it.recommendedSpeed) // recommended speed if there's any
        it.incidentLink.location // GeoCoordinates of the incident
      }
    }
  }
  override fun onExploreIncidentsError(error: IncidentsManager.ErrorCode) {
    // handle the error
  }
})

Last but not least - if the maps that you use contain EV charging stations, you can explore those on your route.

This is most convenient when using the routing for electric vehicles. The function is similar to those above:

RouteExplorer.exploreChargingStationsOnRoute(mRoute, evProfile, object: RouteExplorer.OnExploreChargingStationsOnRouteListener{
  override fun onExploreChargingStationsLoaded(
    info: List<ChargingStation>,
    progress: Int
  ) {
    if (progress == 100) {
      info.forEach {
        print(it.distance) // distance to the ChargingStation in meters
        print(it.timeToFullCharge) // time in minutes needed to reach charging station and charge the battery
        print(it.link.name) // name of the ChargingStation
      }
    }
  }
  override fun onExploreChargingStationsError(error: PlacesManager.ErrorCode) {
    // handle the error
  }
})

Next Durations

If you would like to show the "ideal" time to leave, you can do that using the computeNextDurations() function.

It does not directly compute the Time to Leave, but you can achieve that on your own. Our engine can compute the next durations of the route if a list of times is passed. For example, let's see how to compute the next 30 durations with a span of 15 minutes. As always, we will work with an already computed route, mRoute. If you do not know how to compute a route, please refer to the top of this page.

val times = mutableListOf<Long>()
for (x in 0..30) {
  // the time that we pass has to be in seconds, therefore we divide by 1000, and 900 stands for 15 minutes
  times.add(System.currentTimeMillis() / 1000 + x * 900)
}

mRouter.computeNextDurations(mRoute, times, object: Router.RouteDurationListener{
  override fun onRouteDurations(p0: Route?, p1: MutableList<Int>?) {
    p1?.let {
      for (item in p1){
        // here you will get the durations as integers
      }
    }
  }
}

Routing for electric vehicles

As Electromobility is one of the strongest trends in the automotive industry and with the increasing number of electric vehicles, we recognize the necessity of introducing a routing engine for such vehicles. You can find the EV routing in the newest versions of SygicMapsSDK.

Our EV routing works in the offline mode only, yet. The route is computed according to the EV Profile that you set - if you wouldn't reach the destination with your current charge or capacity, the computing algorithm will try to find places where you can charge your vehicle so that you reach your destination without your vehicle running out of power. The time necessary to charge your vehicle is added to the final ETA and you can check the delay of each ChargingWaypoint.

You can (actually, you need to) define the Battery profile and a complete EV profile of your vehicle along with your preferences. You can choose from a wide range of connector types, according to your needs - e.g. CCS½, CHAdeMo, NEMA, Tesla etc.

Let's see what is required in order to compute an EV route:

val connectorTypeSet: Set<EVConnector.ConnectorType> =
  HashSet(Collections.singletonList(EVConnector.ConnectorType.Chademo)) // set of connector types
val powerTypeSet: Set<EVConnector.PowerType> =
  HashSet(Collections.singletonList(EVConnector.PowerType.DC)) // set of charging power types
val batteryProfile = BatteryProfile(
  30f, // Battery capacity in kWh
  4f, // Remaining battery capacity in kWh
  .2f, // Percentage of capacity when the battery should be charged
  .8f, // Percentage of capacity when the battery is considered as fully charged
  .05f //  Percentage of capacity that should never be used - for safety reasons
)

As seen above, we have configured the battery profile and our power and connector type sets. Naturally, you can add more connector types into the set.

Next, we need to set up an EVProfile in which we will use the information above and specify some new values.

val evProfile = EVProfile(
  batteryProfile, // the battery profile that has been
  100, // Max charging power in kW
  connectorTypeSet, // defined above
  powerTypeSet, // defined above
  1600.0, // Weight in kg
  2.5, // Frontal area in m2
  .25, // Aerodynamic coefficient 0.1 - 1.0
  .015, // Rolling resistance coefficient 0.01 - 0.03
  .85, // Battery to powertrain efficiency
  .8, // Recuperation
  .28, // Average kWh consumption per km generally
  -1.0, // Speed in km/h
  -1.0, // Average kwh consumption per km at speed V1
  -1.0, // Speed in km/h
  -1.0 // Average kwh consumption per km at speed V2
  )

Next, we need to configure the Router and RoutingOptions just like we would do in a normal compute and then we will call the computeEVRoute() method that is available via the Router.

mRouter = RouterProvider.getInstance().get() // get an instance of the Router
mRoutingOptions.isUnpavedRoadAvoided = true // use paved roads only
mRoutingOptions.routingService = RoutingOptions.RoutingService.Offline // as EV routing is available for Offline mode
mRoutingOptions.transportMode = RoutingOptions.TransportMode.Car
mRoutePlan.setStart(start) // instance of GeoCoordinates
mRoutePlan.setDestination(end) // instance of GeoCoordinates
mRoutePlan.routingOptions = mRoutingOptions

mRouter.computeEVRoute(mRoutePlan, evProfile, object: Router.RouteComputeListener { ..... }

Info

Please remember that the route that you obtain may contain ChargingWaypoint(s)! These are the waypoints that are necessary to visit in order to charge the vehicle. You will also receive alternative charging stations for each ChargingWaypoint.

One of the main purposes of our SDK is to be able to navigate to a point that the user selects from the map. Let's see the idea on how it's done (please note that this is a really simple example, you wouldn't normally want to call the routing after tapping into the map):

// once you got the mapView from the onMapReady()
var destination : GeoCoordinates
mMapView.addMapGestureListener(object: MapGestureAdapter() {
  override fun onMapClicked(
    e: MotionEvent?,
    isTwoFingers: Boolean
  ): Boolean {
    if (isTwoFingers){
      return false
    }
    e?.let { // request the objects at the point of the MotionEvent's x,y coordinates
      mMapView.requestObjectsAtPoint(e.x, e.y, object: RequestObjectCallback{
        override fun onRequestResult(
          p0: MutableList<ViewObject<ViewObjectData>>,
          p1: Float,
          p2: Float,
          p3: Int
        ) {
          destination = p0[0].position // here we work only with the position of the first result, but you can get more of them
          mRoutePlan.start = currentPosition // you can get your current position via the PositionManager
          mRoutePlan.destination = destination
          mRouter.computeRoute(mRoutePlan, ...) // compute the route just like in the examples above
        }
      })
    }
    return super.onMapClicked(e, isTwoFingers)
  }
})