-
Notifications
You must be signed in to change notification settings - Fork 318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Offline routing #1768
Merged
Merged
Offline routing #1768
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
b9785d4
Outline offline routing
frederoni af69d03
Add test fixtures
frederoni 1c4151b
Add OfflineRoutingTests and clean up OfflineDirections
frederoni 233d335
Clean up error handling
frederoni a282566
Minify fixtures
frederoni 80f9548
Capture expection and return tile count
frederoni 300e775
Temporarily disable offline routing tests
frederoni a8942c7
Add documentation fix naming conventions
frederoni cae9ef3
Bump MapboxDirections
frederoni 43a3afb
Return number of tiles
frederoni 5352c5c
Refactor
frederoni 584228c
Don't use offline directions by default in the example app
frederoni b911dfb
Remove unneeded Obj-C category
frederoni 17f2763
Remove boilerplate code
frederoni ae4232d
Use URLs
frederoni cebe19a
Replace bundled translations with submodule
frederoni a3a0d33
Update submodules on CI
frederoni 8c2a4a9
Remove unused import
frederoni 3682e9f
Fix error bridging and description
frederoni d61dc25
Naming conventions
frederoni 6543704
Remove unused error
frederoni 1e67a08
Naming conventions
frederoni File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "thirdparty/osrm-text-instructions"] | ||
path = thirdparty/osrm-text-instructions | ||
url = https://github.com/Project-OSRM/osrm-text-instructions.git |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import Foundation | ||
import MapboxDirections | ||
import MapboxNavigationNative | ||
|
||
public typealias OfflineDirectionsCompletionHandler = (_ numberOfTiles: UInt64) -> Void | ||
|
||
enum OfflineRoutingError: Error, LocalizedError { | ||
case unexpectedRouteResult(String) | ||
case corruptRouteData(String) | ||
case responseError(String) | ||
|
||
public var localizedDescription: String { | ||
switch self { | ||
case .corruptRouteData(let value): | ||
return value | ||
case .unexpectedRouteResult(let value): | ||
return value | ||
case .responseError(let value): | ||
return value | ||
} | ||
} | ||
|
||
var errorDescription: String? { | ||
return localizedDescription | ||
} | ||
} | ||
|
||
struct OfflineDirectionsConstants { | ||
static let offlineSerialQueueLabel = Bundle.mapboxCoreNavigation.bundleIdentifier!.appending(".offline") | ||
static let serialQueue = DispatchQueue(label: OfflineDirectionsConstants.offlineSerialQueueLabel) | ||
} | ||
|
||
/** | ||
Defines additional functionality similar to `Directions` with support for offline routing. | ||
*/ | ||
@objc(MBOfflineDirectionsProtocol) | ||
public protocol OfflineRoutingProtocol { | ||
|
||
/** | ||
Initializes a newly created directions object with an optional access token and host. | ||
|
||
- parameter tilesPath: The location where the tiles has been sideloaded to. | ||
- parameter translationsPath: The location where the translations has been sideloaded to. | ||
- parameter accessToken: A Mapbox [access token](https://www.mapbox.com/help/define-access-token/). If an access token is not specified when initializing the directions object, it should be specified in the `MGLMapboxAccessToken` key in the main application bundle’s Info.plist. | ||
- parameter host: An optional hostname to the server API. The [Mapbox Directions API](https://www.mapbox.com/api-documentation/?language=Swift#directions) endpoint is used by default. | ||
*/ | ||
init(tilesURL: URL, translationsURL: URL, accessToken: String?, host: String?, completionHandler: @escaping OfflineDirectionsCompletionHandler) | ||
|
||
/** | ||
Begins asynchronously calculating the route or routes using the given options and delivers the results to a closure. | ||
|
||
This method retrieves the routes asynchronously via MapboxNavigationNative. | ||
|
||
Routes may be displayed atop a [Mapbox map](https://www.mapbox.com/maps/). They may be cached but may not be stored permanently. To use the results in other contexts or store them permanently, [upgrade to a Mapbox enterprise plan](https://www.mapbox.com/directions/#pricing). | ||
|
||
- parameter options: A `RouteOptions` object specifying the requirements for the resulting routes. | ||
- parameter completionHandler: The closure (block) to call with the resulting routes. This closure is executed on the application’s main thread. | ||
*/ | ||
func calculate(_ options: RouteOptions, offline: Bool, completionHandler: @escaping Directions.RouteCompletionHandler) | ||
} | ||
|
||
@objc(MBNavigationDirections) | ||
public class NavigationDirections: Directions, OfflineRoutingProtocol { | ||
|
||
public required init(tilesURL: URL, translationsURL: URL, accessToken: String?, host: String? = nil, completionHandler: @escaping OfflineDirectionsCompletionHandler) { | ||
|
||
super.init(accessToken: accessToken, host: host) | ||
|
||
OfflineDirectionsConstants.serialQueue.sync { | ||
let tilesPath = tilesURL.absoluteString.replacingOccurrences(of: "file://", with: "") | ||
let translationsPath = translationsURL.absoluteString.replacingOccurrences(of: "file://", with: "") | ||
let tileCount = self.navigator.configureRouter(forTilesPath: tilesPath, translationsPath: translationsPath) | ||
|
||
DispatchQueue.main.async { | ||
completionHandler(tileCount) | ||
} | ||
} | ||
} | ||
|
||
public func calculate(_ options: RouteOptions, offline: Bool = false, completionHandler: @escaping Directions.RouteCompletionHandler) { | ||
|
||
guard offline == true else { | ||
return calculate(options, completionHandler: completionHandler) | ||
} | ||
|
||
let url = self.url(forCalculating: options) | ||
|
||
OfflineDirectionsConstants.serialQueue.sync { [weak self] in | ||
|
||
guard let result = self?.navigator.getRouteForDirectionsUri(url.absoluteString) else { | ||
let error = OfflineRoutingError.unexpectedRouteResult("Unexpected routing result") | ||
return completionHandler(nil, nil, error as NSError) | ||
} | ||
|
||
guard let data = result.json.data(using: .utf8) else { | ||
let error = OfflineRoutingError.corruptRouteData("Corrupt route data") | ||
return completionHandler(nil, nil, error as NSError) | ||
} | ||
|
||
do { | ||
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] | ||
if let errorValue = json["error"] as? String { | ||
DispatchQueue.main.async { | ||
let error = OfflineRoutingError.responseError(errorValue) | ||
return completionHandler(nil, nil, error as NSError) | ||
} | ||
} else { | ||
let response = options.response(from: json) | ||
|
||
DispatchQueue.main.async { | ||
return completionHandler(response.0, response.1, nil) | ||
} | ||
} | ||
|
||
} catch { | ||
DispatchQueue.main.async { | ||
return completionHandler(nil, nil, error as NSError) | ||
} | ||
} | ||
} | ||
} | ||
|
||
var _navigator: MBNavigator! | ||
var navigator: MBNavigator { | ||
|
||
assert(currentQueueName() == OfflineDirectionsConstants.offlineSerialQueueLabel, | ||
"The offline navigator must be accessed from the dedicated serial queue") | ||
|
||
if _navigator == nil { | ||
self._navigator = MBNavigator() | ||
} | ||
|
||
return _navigator | ||
} | ||
} | ||
|
||
fileprivate func currentQueueName() -> String? { | ||
let name = __dispatch_queue_get_label(nil) | ||
return String(cString: name, encoding: .utf8) | ||
} |
2 changes: 1 addition & 1 deletion
2
MapboxCoreNavigationTests/CocoaPodsTest/PodInstall/Podfile.lock
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file added
BIN
+12.3 KB
MapboxCoreNavigationTests/Fixtures/routing/liechtenstein/0/003/107.gph.gz
Binary file not shown.
Binary file added
BIN
+83.4 KB
MapboxCoreNavigationTests/Fixtures/routing/liechtenstein/1/049/509.gph.gz
Binary file not shown.
Binary file added
BIN
+38.4 KB
MapboxCoreNavigationTests/Fixtures/routing/liechtenstein/2/000/789/877.gph.gz
Binary file not shown.
Binary file added
BIN
+635 KB
MapboxCoreNavigationTests/Fixtures/routing/liechtenstein/2/000/789/878.gph.gz
Binary file not shown.
Binary file added
BIN
+9.97 KB
MapboxCoreNavigationTests/Fixtures/routing/liechtenstein/2/000/791/318.gph.gz
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import XCTest | ||
import MapboxDirections | ||
@testable import MapboxCoreNavigation | ||
|
||
|
||
class OfflineRoutingTests: XCTestCase { | ||
|
||
func testOfflineDirections() { | ||
let bundle = Bundle(for: OfflineRoutingTests.self) | ||
let tilesURL = URL(fileURLWithPath: bundle.bundlePath.appending("/routing/liechtenstein")) | ||
let translationsURL = URL(fileURLWithPath: bundle.bundlePath.appending("/translations")) | ||
|
||
let setupExpectation = expectation(description: "Set up offline routing") | ||
|
||
let directions = NavigationDirections(tilesURL: tilesURL, translationsURL: translationsURL, accessToken: "foo") { (numberOfTiles) in | ||
XCTAssertEqual(numberOfTiles, 5) | ||
setupExpectation.fulfill() | ||
} | ||
|
||
wait(for: [setupExpectation], timeout: 2) | ||
|
||
// Coordinates within Liechtenstein | ||
let coordinates = [CLLocationCoordinate2D(latitude: 47.1192, longitude: 9.5412), | ||
CLLocationCoordinate2D(latitude: 47.1153, longitude: 9.5531)] | ||
|
||
let options = NavigationRouteOptions(coordinates: coordinates, profileIdentifier: .automobile) | ||
let calculateRouteExpectation = expectation(description: "Calculate route offline") | ||
var route: Route? | ||
|
||
directions.calculate(options, offline: true) { (waypoints, routes, error) in | ||
XCTAssertNil(error) | ||
XCTAssertNotNil(waypoints) | ||
XCTAssertNotNil(routes) | ||
route = routes!.first! | ||
calculateRouteExpectation.fulfill() | ||
} | ||
|
||
wait(for: [calculateRouteExpectation], timeout: 2) | ||
|
||
XCTAssertNotNil(route) | ||
XCTAssertEqual(route!.coordinates!.count, 239) | ||
} | ||
|
||
func testOfflineDirectionsError() { | ||
let bundle = Bundle(for: OfflineRoutingTests.self) | ||
let tilesURL = URL(fileURLWithPath: bundle.bundlePath).appendingPathComponent("/routing/liechtenstein") | ||
let translationsURL = URL(fileURLWithPath: bundle.bundlePath).appendingPathComponent("/translations") | ||
|
||
let setupExpectation = expectation(description: "Set up offline routing") | ||
|
||
let directions = NavigationDirections(tilesURL: tilesURL, translationsURL: translationsURL, accessToken: "foo") { (numberOfTiles) in | ||
XCTAssertEqual(numberOfTiles, 5) | ||
setupExpectation.fulfill() | ||
} | ||
|
||
wait(for: [setupExpectation], timeout: 2) | ||
|
||
// Coordinates in SF | ||
let coordinates = [CLLocationCoordinate2D(latitude: 37.7870, longitude: -122.4261), | ||
CLLocationCoordinate2D(latitude: 37.7805, longitude: -122.4073)] | ||
|
||
let options = NavigationRouteOptions(coordinates: coordinates, profileIdentifier: .automobile) | ||
let calculateRouteExpectation = expectation(description: "Calculate route offline") | ||
|
||
directions.calculate(options, offline: true) { (waypoints, routes, error) in | ||
XCTAssertNotNil(error) | ||
XCTAssertEqual(error!.localizedDescription, "No suitable edges near location") | ||
XCTAssertNil(routes) | ||
XCTAssertNil(waypoints) | ||
calculateRouteExpectation.fulfill() | ||
} | ||
|
||
wait(for: [calculateRouteExpectation], timeout: 2) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This comment was marked as resolved.
Sorry, something went wrong.