Skip to content
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 #1808

Merged
merged 72 commits into from
Dec 6, 2018
Merged

Offline routing #1808

merged 72 commits into from
Dec 6, 2018

Conversation

frederoni
Copy link
Contributor

@frederoni frederoni commented Nov 1, 2018

This PR brings offline routing to the Navigation SDK

cc @akitchen @1ec5 @JThramer @vincethecoder

@akitchen
Copy link
Contributor

akitchen commented Nov 2, 2018

Some thoughts on a few of the remaining checklist items:

Automatic failover

Should this be a gating feature at this point? I'm not entirely convinced but it should be open to debate. Also, if we are going to do this we should have a small group design discussion about the mechanism. I could see this being a candidate for a boolean delegate method or configuration option.

Wrap NavigationDirections.calculate(_:offline:completionHandler:) in a URLSessionDataTask or another operation that supports canceling

As we discussed, if we're going to use an NSURLSessionDataTask-like abstraction for this, perhaps the client-side routes should be loaded via a custom URL scheme filtered by an NSURLProtocol, e.g. mbx-offline:///route?options=values -- there is a bit of complexity there but the abstraction is close to what we need. And a route is technically a resource (which takes time to generate/load) so it feels kind of right.

Pause/Resume a download operation

I have a feeling this will punt to a further iteration. Especially considering that there are other potential changes to tile transport currently under debate which could change the scope of this.

@akitchen
Copy link
Contributor

akitchen commented Dec 4, 2018

I've given a 👍 - @mapbox/navigation-ios any other feedback?

Directions.shared.fetchAvailableOfflineVersions { [weak self] (versions, error) in

let nonEmptyVersions = versions?.filter { !$0.isEmpty }
guard let version = nonEmptyVersions?.first else { return }

This comment was marked as resolved.

return self / 2
}

func distance(_ to: CGFloat) -> CGFloat {

This comment was marked as resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calculations in the pan gesture recognizer were getting unreadable but it has been cleaned up since then so it makes sense to remove this simple distance.


let offlineItem = Item(title: "Download arbitrary region", viewControllerType: OfflineViewController.self, payload: nil)
let offlineSection = Section(title: "Offline Examples", items: [offlineItem])
let versionSection = Section(title: "Downloaded versions", items: versionDirectories())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use title case consistently for section titles:

Suggested change
let versionSection = Section(title: "Downloaded versions", items: versionDirectories())
let versionSection = Section(title: "Downloaded Versions", items: versionDirectories())

let tilesURL = Bundle.mapboxCoreNavigation.suggestedTilePathURL(for: selectedOfflineVersion)

Settings.directions.configureRouter(tilesURL: tilesURL!) { [weak self] (numberOfTiles) in
let message = "Router configured w/ \(numberOfTiles) tiles."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let message = "Router configured w/ \(numberOfTiles) tiles."
let formattedTileCount = NumberFormatter.localizedString(from: numberOfTiles as NSNumber, number: .decimal)
let message = "Router configured with \(formattedTileCount) tiles."

This comment was marked as resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that our Examples should only be in English while having empty languages added to test all supported languages in the SDK to mitigate translators fatigue. However, it's only a few localizable strings in the examples so far so I added this one as well.


func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool {

let shouldUseOfflineRouting = Settings.selectedOfflineVersion != nil

This comment was marked as resolved.

OfflineDirectionsConstants.offlineSerialQueue.async { [weak self] in

guard let result = self?.navigator.getRouteForDirectionsUri(url.absoluteString) else {
let error = OfflineRoutingError.unexpectedRouteResult("Unexpected routing result")

This comment was marked as resolved.

let calculateRouteExpectation = expectation(description: "Calculate route offline")
var route: Route?

directions.calculate(options, offline: true) { (waypoints, routes, error) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we verify somehow that nothing is fetched from an online source?


progressUpdates += 1

if (progressUpdates >= 2) {

This comment was marked as resolved.

@@ -1182,6 +1202,19 @@
name = Resources;
sourceTree = "<group>";
};
3529FCEE21A5C52C00AEA9AA /* Recovered References */ = {

This comment was marked as resolved.

}

/**
Defines additional functionality similar to `Directions` with support for offline routing.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this protocol differ from the OfflineDirectionsProtocol defined by MapboxDirections.swift? I think this could become a source of confusion among developers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it can cause confusion, however, NavigatorDirectionsProtocol depends on nav-native so it should be defined here, while OfflineDirectionsProtocol is a good fit for MapboxDirections since it's only adding additional directions related API calls. Do you have an idea how to simplify it? The protocol could be moved to MapboxDirections’s OfflineDirectionsProtocol but the implementation must reside in MapboxCoreNavigation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the confusion would stem from how this protocol is described as “similar to Directions with support for offline routing”. So if we need to keep it as a separate protocol, then can we frame it as a kind of NavigationService that has Directions functionality built in?

Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments about user-facing strings and localization fit-and-finish. Also, there needs to be at least one changelog entry about the new stuff. I can take it from here.

OfflineDirectionsConstants.offlineSerialQueue.async { [weak self] in

guard let result = self?.navigator.getRouteForDirectionsUri(url.absoluteString) else {
let message = NSLocalizedString("OFFLINE_NO_RESULT", bundle: .mapboxCoreNavigation, value: "No result", comment: "No result from offline route request")

This comment was marked as resolved.


Settings.directions.configureRouter(tilesURL: tilesURL!) { [weak self] (numberOfTiles) in
let formattedTileCount = NumberFormatter.localizedString(from: numberOfTiles as NSNumber, number: .decimal)
let message = String.localizedStringWithFormat(NSLocalizedString("Router configured with %@ tile(s).", comment: "Alert message when a router has been configured"), formattedTileCount)

This comment was marked as resolved.

}

guard let data = result.json.data(using: .utf8) else {
let message = NSLocalizedString("OFFLINE_CORRUPT_DATA", bundle: .mapboxCoreNavigation, value: "Corrupt route data", comment: "Unable to deserialize the offline route response")

This comment was marked as resolved.


func presentAlert(_ title: String? = nil, message: String? = nil) {
let controller = UIAlertController(title: title, message: message, preferredStyle: .alert)
controller.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in

This comment was marked as resolved.


view.addSubview(resizableView)

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Download", style: .done, target: self, action: #selector(downloadRegion))

This comment was marked as resolved.


view.addSubview(resizableView)

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Download", style: .done, target: self, action: #selector(downloadRegion))

This comment was marked as resolved.

NavigationDirections.unpackTilePack(at: url, outputDirectoryURL: outputDirectoryURL!, progressHandler: { (totalBytes, bytesRemaining) in

let progress = (Float(bytesRemaining) / Float(totalBytes)) * 100
self?.updateTitle("Unpacking \(Int(progress))%")

This comment was marked as resolved.

@1ec5
Copy link
Contributor

1ec5 commented Dec 5, 2018

I’m seeing tons of console spew along these lines, seemingly in a hot loop, while displaying a visual instruction that should include shields in the simulator. The shields never load, but more importantly, I think the level of spew would quickly consume battery and storage.

2018-12-04 18:53:40.639392-0800 Example[17124:9955719] Task <C932B850-0162-4891-94CC-D4135E89115F>.<7533> load failed with error Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL, NSErrorFailingURLStringKey=@2x.png, NSErrorFailingURLKey=@2x.png, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <C932B850-0162-4891-94CC-D4135E89115F>.<7533>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <C932B850-0162-4891-94CC-D4135E89115F>.<7533>, NSUnderlyingError=0x600003a123a0 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}} [-1002]
2018-12-04 18:53:40.644171-0800 Example[17124:9956578] Task <1DC0B9B7-FAFC-401C-96B1-11BCCE418505>.<7534> finished with error - code: -1002

Copyedited various strings in the example application and gave them all stable IDs. Made some additional strings localizable.

Removed a redundant Base.lproj folder from the example project structure.
Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks ready to go, but I’m concerned about the console spew in #1808 (comment).

These methods return file URLs, not path strings. Also added documentation and Objective-C compatibility.
Also fixed some copypasta in documentation comments.
public class NavigationDirections: Directions {

public typealias UnpackProgressHandler = (_ totalBytes: UInt64, _ remainingBytes: UInt64) -> ()
public typealias UnpackCompletionHandler = (_ result: UInt64, _ error: Error?) -> ()

This comment was marked as resolved.

public class NavigationDirections: Directions {

public typealias UnpackProgressHandler = (_ totalBytes: UInt64, _ remainingBytes: UInt64) -> ()
public typealias UnpackCompletionHandler = (_ result: UInt64, _ error: Error?) -> ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does result represent a status code? What codes can be returned, and under what circumstances?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to numberOfTiles since it corresponds to the number of unpacked tiles.

@1ec5
Copy link
Contributor

1ec5 commented Dec 6, 2018

I’m seeing tons of console spew along these lines, seemingly in a hot loop, while displaying a visual instruction that should include shields in the simulator.

This turned out to be infinite recursion: #1887.

@1ec5
Copy link
Contributor

1ec5 commented Dec 6, 2018

The infinite recursion will be fixed by #1888. Locally, with that patch applied, I see generic shields as expected, though I don’t see exit number tabs as I do with server-side routing. The exit number is included in spoken instructions, and the exit number tabs do show up when performing server-side routing on this same branch. Unless an obvious cause materializes very shortly, I think it’s fine to treat that issue as tail work.

@1ec5 1ec5 merged commit 7c305e0 into master Dec 6, 2018
@1ec5 1ec5 deleted the offline-routing branch December 6, 2018 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants