Importing, exporting, and modifying routes

VERSION 0.13.0
PUBLIC PREVIEW

In addition to generating routes, the SDK allows developers to manipulate them to a limited degree. Specifically, you can:

  • Import routes from GPX files.
  • Export routes to GPX files.
  • Split one route into shorter routes.
  • Combine multiple routes into one larger route.

These manipulations are done by treating a route as a series of points. A Route in the SDK can contain two kinds of points:

  • Waypoints are locations along the route that can be planned to go past. They generate arrival events when the driver reaches them. For example, they can be places a driver wants to visit on a road trip or delivery locations for a cargo van.
  • Supporting points are geographical points that define the geometry of an existing route.

Before the SDK can use a series of points as a route (e.g., to show it on a map), it must be turned into a TomTom route. There are two ways to do this:

  • Planning a new route that passes through the route’s waypoints.
  • Reconstructing the route from its supporting points. This results in a route with a similar geometry to the original. The result can be used in the SDK like any other route. To include waypoints when reconstructing a route in this way, divide the geometry into legs that connect the waypoints, and pass them as a list of RouteLegOptions instances.

This guide contains code snippets to do all of these things except for planning a new route, which is explained in the Waypoints and custom routes guide.

Constructing a Route from the contents of a GPX file

To construct the route from a GPX file, a GPX parser from TomTomSDKCommon can be used.

1let parser = TomTomSDKCommon.makeGPXParser()
2let filePath = Bundle(for: RouteModifications.self).path(forResource: "test", ofType: "gpx")!
3let coordinates = parser.parseCoordinates(fromGpxFile: filePath)
4
5print("coordinates: \(coordinates)")

Then a route can be created, either by reconstructing it by using the track points in the imported geometry as supporting points, or by planning a new one that passes through the imported waypoints.

Exporting a Route into a GPX file

Knowing the structure of a GPX file, it is possible to export a Route into one.

1private func locationsToGpx(locations: [CLLocationCoordinate2D], waypoints: [Waypoint]) -> String {
2 var builder = TomTomSDKCommon.makeGPXBuilder()
3
4 builder.locations = locations
5 builder.waypoints = waypoints.map { waypoint in
6 waypoint.navigableCoordinates.first ?? waypoint.place.coordinate
7 }
8
9 let gpx = builder.build()
10
11 return gpx
12}

The returned String can then be stored in a file.

Splitting a Route from one of its points into two Routes

Every leg of a Route is self-contained so it can be split easily into several Routes.

1private func splitRoute(route: Route, waypointIndex: Int) {
2 assert(route.legs.count > waypointIndex + 1)
3
4 let firstHalf = route.legs[..<waypointIndex]
5 let secondHalf = route.legs[waypointIndex...]
6
7 let firstGeometry = firstHalf.flatMap { $0.geometry }
8 let secondGeometry = secondHalf.flatMap { $0.geometry }
9
10 print("firstGeometry: \(firstGeometry)")
11 print("secondGeometry: \(secondGeometry)")
12}

After splitting the legs, each of the geometries can be used to reconstruct a route.

Combining two Routes into one

Two Routes can be combined into a single one by combining their geometries.

1private func joinRoutes(route1: Route, route2: Route) {
2 let geometry1 = route1.geometry
3 let geometry2 = route2.geometry
4
5 let joinedGeometry = geometry1 + geometry2
6
7 print("joinedGeometry: \(joinedGeometry)")
8}

After combining, the geometry can be used to reconstruct the route.

Route reconstruction using the TomTom Routing Service

To build a Route from the coordinates of the passed points we call planRoute method on RoutePlanner

1private func geometryToRoute(
2 geometry: [CLLocationCoordinate2D],
3 completion: @escaping (Result<RoutePlanningResponse, Error>) -> ()
4) {
5 guard let origin = (geometry.first.map { ItineraryPoint(coordinate: $0, name: nil, address: nil) }) else { return }
6 guard let destination = (geometry.last.map { ItineraryPoint(coordinate: $0, name: nil, address: nil) }) else { return }
7
8 let itineraryPoints = (geometry.map { ItineraryPoint(coordinate: $0, name: nil, address: nil) })
9
10 let routingOptions: RoutePlanningOptions
11 do {
12 routingOptions = try RoutePlanningOptions(
13 itinerary: Itinerary(origin: origin, destination: destination, waypoints: itineraryPoints)
14 )
15 } catch {
16 print("Invalid planning options: \(error.localizedDescription)")
17 return
18 }
19
20 routePlanner.planRoute(options: routingOptions, onRouteReady: nil) { result in
21 completion(result)
22
23 switch result {
24 case let .success(response):
25 print(response.routes)
26 case let .failure(error):
27 print(error)
28 }
29 }
30}

The imported route can then be navigated like a regular route. Note that on deviation of such routes, currently the given geometry will be ignored when the SDK queries to get a new route plan. This behavior will be improved soon.

Next steps

Since you have learned how to modify a route, here are recommendations for the next steps: