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 Tile downloader #303

Merged
merged 15 commits into from
Nov 1, 2018
Merged

Offline Tile downloader #303

merged 15 commits into from
Nov 1, 2018

Conversation

frederoni
Copy link
Contributor

@frederoni frederoni commented Oct 23, 2018

Introduced Offline Tile downloader

Added two new public methods:

  • Directions.fetchAvailableVersions(completionHandler:)
  • Directions.downloadTiles(in:version:completionHandler:)

cc @1ec5 @JThramer @akitchen

}

@objc(MBOfflineDirections)
public class OfflineDirections: NSObject, URLSessionDownloadDelegate {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could probably fold this into Directions if we mention offline in the signature and/or documentation.
@1ec5 what’s your take on this?

@akitchen akitchen requested a review from 1ec5 October 23, 2018 18:38

return session.downloadTask(with: url) { [weak self] (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// TODO: Move file from temporary location to a provided location
Copy link
Contributor

Choose a reason for hiding this comment

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

To do...

}

public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
// TODO: resume download when range headers are supported
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 add support for this in advance of the feature availability on the server?

Fetches the available versions.
*/
@discardableResult
func availableOfflineVersions(completionHandler: @escaping OfflineVersionsHandler) -> URLSessionDataTask
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this method returns a task rather than the offline versions themselves, rename it to getAvailableOfflineVersions(completionHandler:).

Copy link
Contributor Author

@frederoni frederoni Oct 29, 2018

Choose a reason for hiding this comment

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

get is only used when getting values indirectly (such as UIColor.getRed(_:&green:&blue:&alpha:&)) in Cocoa but I get the point. How about fetch/list/find?

Copy link
Contributor

Choose a reason for hiding this comment

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

Fetch is fine.

A bounding box represents a geographic region.
*/
@objc(MBBoundingBox)
public class BoundingBox: NSObject, Codable {
Copy link
Contributor

Choose a reason for hiding this comment

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

The map SDK has a similar struct named MGLCoordinateBounds, and MapboxGeocoder.swift has a CLRegion subclass named RectangularRegion. It would be great if we could make this class a struct, for value semantics; otherwise, a subclass of CLRegion would at least make some of the capabilities more discoverable.

Copy link
Contributor Author

@frederoni frederoni Oct 29, 2018

Choose a reason for hiding this comment

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

Hm, yeah, it's unfortunate that MapboxDirections doesn't depend on those. Would be nice to share a core set of data types, similar to what gl-native is doing.

I went back and forth with CLRegion but didn't find it adding any value. CoordinateBounds makes sense for consistency. How about {MB}/CoordinateBounds?

Swift struct wouldn't bridge to Obj-C.

Copy link
Contributor Author

@frederoni frederoni Oct 30, 2018

Choose a reason for hiding this comment

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

Turf-swift has a BoundingBox but we are partially striving for turf-js/GeoJSON terminology there.

Copy link
Contributor

@1ec5 1ec5 Oct 31, 2018

Choose a reason for hiding this comment

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

GeoJSON terminology makes more sense for a library that’s very much about manipulating GeoJSON, such as Turf, but unless we have this library depend on Turf – not necessarily a bad idea – I kind of like CoordinateBounds for consistency with the map SDK. I agree that CLRegion isn’t adding much in this case; MapboxGeocoder.swift has that to be more similar to MapKit’s and Core Location’s geocoders.

Swift struct wouldn't bridge to Obj-C.

A C struct does bridge to Swift, so implement it in an Objective-C header. path could be a C function refined by a Swift-only extension method on this struct.

internal var offlineProgressHandler: OfflineDownloaderProgressHandler?

/// The handler that reports the completion of an offline tile pack
internal var offlineCompletionHandler: OfflineDownloaderCompletionHandler?
Copy link
Contributor

Choose a reason for hiding this comment

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

It’s a bit awkward that these properties are here in the main class definition because of an extension’s conformance to OfflineDirectionsProtocol.

More significantly, these properties make Directions stateful. Directions originally was stateful, going so far as to store the URLSessionTask for the API request, similar to MapKit’s MKDirections. But that made it impossible for a single Directions object to process multiple requests simultaneously, and it also meant that the object had to be held strongly for the duration of the request. #47 moved Directions to a stateless singleton model that has provided a good deal of flexibility to this library. If we’re going to return to the old model, Directions would need to encapsulate the URLSessionTask objects and provide a method for canceling the request.

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. Reverted back to a stateless version of Directions with an optional URLSession you can pass in to modify configurations and receive delegate callbacks such as progress updates.

Copy link
Contributor

Choose a reason for hiding this comment

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

MapboxDirections.swift takes the approach of exposing the request URL publicly, so you can hook the library into any networking library, not just URLSession but also AFNetworking etc.

}

@objc(MBVersion)
public class Version: NSObject, Codable {
Copy link
Contributor

Choose a reason for hiding this comment

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

There would be somewhat less boilerplate if we made Version a struct instead. It would have to be defined in Objective-C in order to support both languages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. The version should be represented as a String according to the api team.

func availableVersionsURL() -> URL {

let url = apiEndpoint.appendingPathComponent("route-tiles/v1").appendingPathComponent("versions")
var components = URLComponents(string: url.absoluteString)

This comment was marked as resolved.


static var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
Copy link
Contributor

Choose a reason for hiding this comment

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

On the off-chance that there needs to be two versions on the same day, would the API ever assign a more precise timestamp?

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.

Mostly stylistic nits remaining, but I do think we should try to use a C struct for the bounding box type, because reference semantics are unexpected for that concept (except for CLRegion).


extension Directions: OfflineDirectionsProtocol {

func availableVersionsURL() -> URL {
Copy link
Contributor

Choose a reason for hiding this comment

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

Turn this method into a computed variable if possible.

return components!.url!
}

func tilesURL(for boundingBox: BoundingBox, version: OfflineVersion) -> URL {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider replacing for with a more descriptive label, such as covering, or rename the method to url(forTilesIn:version:).


@discardableResult
@objc
public func downloadTiles(for boundingBox: BoundingBox,
Copy link
Contributor

Choose a reason for hiding this comment

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

downloadTiles(in:….

@objc
public func downloadTiles(for boundingBox: BoundingBox,
version: OfflineVersion,
session: URLSession? = URLSession.shared,
Copy link
Contributor

Choose a reason for hiding this comment

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

Even more flexible would be to expose url(forTilesIn:version:) publicly, so that developers can use the URL with either a custom URLSession or another networking library such as AFNetworking.

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