Skip to content

Commit 03141bc

Browse files
authored
Custom RoutingProvider example (#173)
added example of custom implementation of RoutingProvider.
1 parent f688935 commit 03141bc

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

Navigation-Examples.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
2B521D4E24090A3A00984CF8 /* CustomBarsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B521D4D24090A3A00984CF8 /* CustomBarsViewController.swift */; };
1414
2B521D502409240E00984CF8 /* CustomBottomBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B521D4F2409240E00984CF8 /* CustomBottomBannerView.swift */; };
1515
2B521D522409242A00984CF8 /* CustomBottomBannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2B521D512409242A00984CF8 /* CustomBottomBannerView.xib */; };
16+
2B5470632816C74A0068DA3F /* Custom-RoutingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5470622816C74A0068DA3F /* Custom-RoutingProvider.swift */; };
1617
8A0B182326553482005107DE /* CustomSegue.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8A0B182226553482005107DE /* CustomSegue.storyboard */; };
1718
8A2DFA57261650A60034A87E /* CustomCameraStateTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2DFA55261650A60034A87E /* CustomCameraStateTransition.swift */; };
1819
8A2DFA58261650A60034A87E /* CustomViewportDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A2DFA56261650A60034A87E /* CustomViewportDataSource.swift */; };
@@ -61,6 +62,7 @@
6162
2B521D4D24090A3A00984CF8 /* CustomBarsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBarsViewController.swift; sourceTree = "<group>"; };
6263
2B521D4F2409240E00984CF8 /* CustomBottomBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomBottomBannerView.swift; sourceTree = "<group>"; };
6364
2B521D512409242A00984CF8 /* CustomBottomBannerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomBottomBannerView.xib; sourceTree = "<group>"; };
65+
2B5470622816C74A0068DA3F /* Custom-RoutingProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Custom-RoutingProvider.swift"; sourceTree = "<group>"; };
6466
4EF74F60792B221A1A61A3CE /* Pods-Navigation-Examples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Navigation-Examples.debug.xcconfig"; path = "Target Support Files/Pods-Navigation-Examples/Pods-Navigation-Examples.debug.xcconfig"; sourceTree = "<group>"; };
6567
8A0B182226553482005107DE /* CustomSegue.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CustomSegue.storyboard; sourceTree = "<group>"; };
6668
8A2DFA55261650A60034A87E /* CustomCameraStateTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomCameraStateTransition.swift; sourceTree = "<group>"; };
@@ -254,6 +256,7 @@
254256
C52BAA772040D5030086EC6B /* Custom-Destination-Marker.swift */,
255257
C5C0D63720589422003A3B1D /* Custom-Server.swift */,
256258
C3893F1B25F96BDC0058A987 /* Custom-Waypoints.swift */,
259+
2B5470622816C74A0068DA3F /* Custom-RoutingProvider.swift */,
257260
8AE033212628EDFF000E7145 /* Route-Lines-Styling.swift */,
258261
8A96379A2492B366008DEF2A /* Route-Deserialization.swift */,
259262
8AC9129C2494106100B6941E /* Route-Initialization.swift */,
@@ -636,6 +639,7 @@
636639
C52BAA782040D5030086EC6B /* Custom-Destination-Marker.swift in Sources */,
637640
8DF510741FEB08F70049DB9C /* Embedded-Navigation.swift in Sources */,
638641
11B6E81726176F3600872E4D /* Upcoming-Intersection.swift in Sources */,
642+
2B5470632816C74A0068DA3F /* Custom-RoutingProvider.swift in Sources */,
639643
C58FB8711FE8A43000C4B491 /* Basic.swift in Sources */,
640644
8A2DFA57261650A60034A87E /* CustomCameraStateTransition.swift in Sources */,
641645
C58FB8731FE98AEB00C4B491 /* Styled-UI-Elements.swift in Sources */,

Navigation-Examples/Constants.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,12 @@ let listOfExamples: [NamedController] = [
174174
controller: OfflineRegionsViewController.self,
175175
storyboard: nil,
176176
pushExampleToViewController: true
177+
),
178+
(
179+
name: "Custom RoutingProvider",
180+
description: "Demonstrates how to implement and utilize custom `RoutingProvider`.",
181+
controller: CustomRoutingProviderViewController.self,
182+
storyboard: nil,
183+
pushExampleToViewController: true
177184
)
178185
]
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
This code example is part of the Mapbox Navigation SDK for iOS demo app,
3+
which you can build and run: https://github.com/mapbox/mapbox-navigation-ios-examples
4+
To learn more about each example in this app, including descriptions and links
5+
to documentation, see our docs: https://docs.mapbox.com/ios/navigation/examples/basic
6+
*/
7+
8+
import Foundation
9+
import UIKit
10+
import MapboxCoreNavigation
11+
import MapboxNavigation
12+
import MapboxDirections
13+
14+
/*
15+
This example demonstrates how users can control and customize the rerouting process.
16+
Unlike `Custom-Server` example, this one does not completely cancel SDK mechanism to do it
17+
separately, but instead controls just the part of new route calculation, preserving original
18+
workflow and events. In result, any related components will not know that rerouting was altered
19+
and will react as on usual reroute event.
20+
*/
21+
class CustomRoutingProviderViewController: UIViewController {
22+
override func viewDidLoad() {
23+
super.viewDidLoad()
24+
25+
let origin = CLLocationCoordinate2DMake(37.77440680146262, -122.43539772352648)
26+
let destination = CLLocationCoordinate2DMake(37.76556957793795, -122.42409811526268)
27+
let options = NavigationRouteOptions(coordinates: [origin, destination])
28+
29+
// This example is similar to `BasicViewController` except that we are using our custom `RoutingProvider` implementation for retrieving routes.
30+
let customRoutingProvider = CustomProvider()
31+
32+
_ = customRoutingProvider.calculateRoutes(options: options, completionHandler: { [weak self] (_, result) in
33+
switch result {
34+
case .failure(let error):
35+
print(error.localizedDescription)
36+
case .success(let response):
37+
guard let strongSelf = self else {
38+
return
39+
}
40+
41+
let navigationService = MapboxNavigationService(routeResponse: response,
42+
routeIndex: 0,
43+
routeOptions: options,
44+
routingProvider: customRoutingProvider, // passing `customRoutingProvider` to ensure it is used for re-routing and refreshing
45+
credentials: NavigationSettings.shared.directions.credentials,
46+
simulating: simulationIsEnabled ? .always : .onPoorGPS)
47+
48+
let navigationOptions = NavigationOptions(navigationService: navigationService)
49+
let navigationViewController = NavigationViewController(for: response,
50+
routeIndex: 0,
51+
routeOptions: options,
52+
navigationOptions: navigationOptions)
53+
navigationViewController.modalPresentationStyle = .fullScreen
54+
navigationViewController.routeLineTracksTraversal = true
55+
56+
strongSelf.present(navigationViewController, animated: true, completion: nil)
57+
}
58+
})
59+
}
60+
}
61+
62+
class CustomProvider: RoutingProvider {
63+
// This can encapsulate any route building engine we need. For simplicity let's use `MapboxRoutingProvider`.
64+
let routeCalculator = MapboxRoutingProvider()
65+
66+
// We can also modify the options used to calculate a route.
67+
func applyOptionsModification(_ options: DirectionsOptions) {
68+
options.attributeOptions = [.congestionLevel, .speed, .maximumSpeedLimit, .expectedTravelTime]
69+
}
70+
71+
// Here any manipulations on the reponse data can take place
72+
func applyMapMatchingModifications(_ response: MapMatchingResponse) {
73+
response.matches?.forEach { match in
74+
match.legs.forEach { leg in
75+
leg.incidents = fetchExternalIncidents(for: leg)
76+
}
77+
}
78+
}
79+
80+
// Let's say we have an external source of incidents data, we want to apply to the route.
81+
func fetchExternalIncidents(for leg: RouteLeg) -> [Incident] {
82+
return [Incident(identifier: "\(leg.name) incident",
83+
type: .otherNews,
84+
description: "Custom Incident",
85+
creationDate: Date(),
86+
startDate: Date(),
87+
endDate: Date().addingTimeInterval(60),
88+
impact: nil,
89+
subtype: nil,
90+
subtypeDescription: nil,
91+
alertCodes: [],
92+
lanesBlocked: nil,
93+
shapeIndexRange: 0..<1)]
94+
}
95+
96+
// MARK: RoutingProvider implementation
97+
98+
func calculateRoutes(options: RouteOptions, completionHandler: @escaping Directions.RouteCompletionHandler) -> NavigationProviderRequest? {
99+
applyOptionsModification(options)
100+
101+
// Using `MapboxRoutingProvider` also illustrates cases when we need to modify just a part of the route, or dynamically edit `RouteOptions` for each reroute.
102+
return routeCalculator.calculateRoutes(options: options,
103+
completionHandler: completionHandler)
104+
}
105+
106+
func calculateRoutes(options: MatchOptions, completionHandler: @escaping Directions.MatchCompletionHandler) -> NavigationProviderRequest? {
107+
applyOptionsModification(options)
108+
109+
return routeCalculator.calculateRoutes(options: options,
110+
completionHandler: { [weak self] (session, result) in
111+
switch result {
112+
case .failure(_):
113+
completionHandler(session, result)
114+
case .success(let response):
115+
guard let strongSelf = self else {
116+
return
117+
}
118+
strongSelf.applyMapMatchingModifications(response)
119+
120+
completionHandler(session, .success(response))
121+
}
122+
})
123+
}
124+
125+
// Let's make our custom routing provider prevent route refreshes.
126+
func refreshRoute(indexedRouteResponse: IndexedRouteResponse, fromLegAtIndex: UInt32, completionHandler: @escaping Directions.RouteCompletionHandler) -> NavigationProviderRequest? {
127+
128+
var options: DirectionsOptions!
129+
switch indexedRouteResponse.routeResponse.options {
130+
case.match(let matchOptions):
131+
options = matchOptions
132+
case .route(let routeOptions):
133+
options = routeOptions
134+
}
135+
136+
completionHandler((options, NavigationSettings.shared.directions.credentials),
137+
.failure(.unableToRoute))
138+
return nil
139+
}
140+
}

0 commit comments

Comments
 (0)