Skip to content

Commit

Permalink
Merge pull request #1808 from mapbox/offline-routing
Browse files Browse the repository at this point in the history
Offline routing
  • Loading branch information
1ec5 authored Dec 6, 2018
2 parents 1b02e6f + 14b3c37 commit 7c305e0
Show file tree
Hide file tree
Showing 46 changed files with 1,122 additions and 50 deletions.
Empty file added .gitmodules
Empty file.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## v0.26.0

### Client-side routing

* Added a `NavigationDirections` class that manages offline tile packs and client-side route calculation. ([#1808](https://github.com/mapbox/mapbox-navigation-ios/pull/1808))
* Extended `Bundle` with `Bundle.suggestedTileURL` and other properties to facilitate offline downloads. ([#1808](https://github.com/mapbox/mapbox-navigation-ios/pull/1808))

### CarPlay

* When selecting a search result in CarPlay, the resulting routes lead to the search result’s routable location when available. Routes to a routable location are more likely to be passable. ([#1859](https://github.com/mapbox/mapbox-navigation-ios/pull/1859))
Expand Down
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
binary "https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK.json" ~> 4.3
binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" ~> 3.2
binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" ~> 3.4.6
github "mapbox/MapboxDirections.swift" ~> 0.25.1
github "mapbox/turf-swift" ~> 0.2
github "mapbox/mapbox-events-ios" ~> 0.6
Expand Down
4 changes: 2 additions & 2 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
binary "https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK.json" "4.6.0"
binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" "3.4.1"
binary "https://www.mapbox.com/ios-sdk/MapboxNavigationNative.json" "3.4.6"
github "CedarBDD/Cedar" "v1.0"
github "Quick/Nimble" "v7.3.1"
github "Quick/Quick" "v1.3.2"
Expand All @@ -10,4 +10,4 @@ github "mapbox/mapbox-events-ios" "v0.6.0"
github "mapbox/mapbox-speech-swift" "v0.1.0"
github "mapbox/turf-swift" "v0.2.1"
github "raphaelmor/Polyline" "v4.2.0"
github "uber/ios-snapshot-test-case" "4.0.0"
github "uber/ios-snapshot-test-case" "4.0.1"
1 change: 1 addition & 0 deletions Example/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if isRunningTests() {
window!.rootViewController = UIViewController()
}

return true
}

Expand Down
12 changes: 12 additions & 0 deletions Example/Assets.xcassets/ic_resize.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_resize.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.
12 changes: 12 additions & 0 deletions Example/Assets.xcassets/settings.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "baseline-settings-20px.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.
39 changes: 39 additions & 0 deletions Example/Base.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* Alert action */
"ALERT_OK" = "OK";

/* Title of button that downloads an offline region */
"OFFLINE_ITEM_DOWNLOAD" = "Download";

/* Status item while downloading an offline region */
"OFFLINE_TITLE_DOWNLOADING_TILES" = "Downloading Tiles…";

/* Status item while downloading an offline region */
"OFFLINE_TITLE_FETCHING_VERSIONS" = "Fetching Versions…";

/* Status item while downloading an offline region; 1 = percentage complete */
"OFFLINE_TITLE_UNPACKING_FMT" = "Unpacking… (%@)";

/* Title of action for dismissing waypoint removal confirmation sheet */
"REMOVE_WAYPOINT_CONFIRM_CANCEL" = "Cancel";

/* Message of sheet confirming waypoint removal */
"REMOVE_WAYPOINT_CONFIRM_MSG" = "Do you want to remove this waypoint?";

/* Title of alert sheet action for removing a waypoint */
"REMOVE_WAYPOINT_CONFIRM_REMOVE" = "Remove Waypoint";

/* Title of sheet confirming waypoint removal */
"REMOVE_WAYPOINT_CONFIRM_TITLE" = "Remove Waypoint?";

/* Alert message when a router has been configured; 1 = number of map tiles */
"ROUTER_CONFIGURED_MSG" = "Router configured with %ld tile(s).";

/* Title of table view item that downloads a new offline region */
"SETTINGS_ITEM_DOWNLOAD_REGION_TITLE" = "Download Region";

/* Section of offline settings table view */
"SETTINGS_SECTION_DOWNLOADED_VERSIONS" = "Downloaded Versions";

/* Section of offline settings table view */
"SETTINGS_SECTION_OFFLINE_EXAMPLES" = "Offline Examples";

92 changes: 92 additions & 0 deletions Example/OfflineViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import UIKit
import Mapbox
import MapboxDirections
import MapboxCoreNavigation

class OfflineViewController: UIViewController {

var mapView: MGLMapView!
var resizableView: ResizableView!
var backgroundLayer = CAShapeLayer()

override func viewDidLoad() {
super.viewDidLoad()

mapView = MGLMapView(frame: view.bounds)
view.addSubview(mapView)

backgroundLayer.frame = view.bounds
backgroundLayer.fillColor = #colorLiteral(red: 0.1450980392, green: 0.2588235294, blue: 0.3725490196, alpha: 0.196852993).cgColor
view.layer.addSublayer(backgroundLayer)

resizableView = ResizableView(frame: CGRect(origin: view.center, size: CGSize(width: 50, height: 50)),
backgroundLayer: backgroundLayer)

view.addSubview(resizableView)

navigationItem.rightBarButtonItem = UIBarButtonItem(title: NSLocalizedString("OFFLINE_ITEM_DOWNLOAD", value: "Download", comment: "Title of button that downloads an offline region"), style: .done, target: self, action: #selector(downloadRegion))
}

@objc func downloadRegion() {

// Hide the download button so we can't download the same region twice
navigationItem.rightBarButtonItem = nil

let northWest = mapView.convert(resizableView.frame.minXY, toCoordinateFrom: nil)
let southEast = mapView.convert(resizableView.frame.maxXY, toCoordinateFrom: nil)

let coordinateBounds = CoordinateBounds([northWest, southEast])

updateTitle(NSLocalizedString("OFFLINE_TITLE_FETCHING_VERSIONS", value: "Fetching Versions…", comment: "Status item while downloading an offline region"))

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

guard let version = versions?.first(where: { !$0.isEmpty } ) else { return }

self?.updateTitle(NSLocalizedString("OFFLINE_TITLE_DOWNLOADING_TILES", value: "Downloading Tiles…", comment: "Status item while downloading an offline region"))

Directions.shared.downloadTiles(in: coordinateBounds, version: version, completionHandler: { (url, response, error) in
guard let url = url else { return assert(false, "Unable to locate temporary file") }

let outputDirectoryURL = Bundle.mapboxCoreNavigation.suggestedTileURL(version: version)
outputDirectoryURL?.ensureDirectoryExists()

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

let progress = Float(bytesRemaining) / Float(totalBytes)
let formattedProgress = NumberFormatter.localizedString(from: progress as NSNumber, number: .percent)
let title = String.localizedStringWithFormat(NSLocalizedString("OFFLINE_TITLE_UNPACKING_FMT", value: "Unpacking… (%@)", comment: "Status item while downloading an offline region; 1 = percentage complete"), formattedProgress)
self?.updateTitle(title)

}, completionHandler: { (result, error) in

self?.navigationController?.popViewController(animated: true)
})
}).resume()
}.resume()
}

func updateTitle(_ string: String) {
DispatchQueue.main.async { [weak self] in
self?.navigationItem.title = string
}
}
}

extension CGRect {

var minXY: CGPoint {
return CGPoint(x: minX, y: minY)
}

var maxXY: CGPoint {
return CGPoint(x: maxX, y: maxY)
}
}

extension URL {

func ensureDirectoryExists() {
try? FileManager.default.createDirectory(at: self, withIntermediateDirectories: true, attributes: nil)
}
}
137 changes: 137 additions & 0 deletions Example/ResizableView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import UIKit


class ResizableView: UIControl {

let lineLayer = CAShapeLayer()
// Associated background layer will be masked by the frame of the resizable view
weak var backgroundLayer: CAShapeLayer?
let maskLayer = CAShapeLayer()
var imageView: UIImageView!
var panRecognizer: UIPanGestureRecognizer!
var resizePanRecognizer: UIPanGestureRecognizer!

convenience init(frame: CGRect, backgroundLayer: CAShapeLayer) {
self.init(frame: frame)
self.backgroundLayer = backgroundLayer
}

override init(frame: CGRect) {

super.init(frame: frame)

clipsToBounds = false
layer.masksToBounds = false
isUserInteractionEnabled = true
backgroundColor = .clear
isOpaque = false
layer.backgroundColor = UIColor.clear.cgColor

panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
resizePanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(resizePan(_:)))

let image = UIImage(named: "ic_resize")!.withPadding(x: 12, y: 12)!
imageView = UIImageView(image: image.withRenderingMode(.alwaysTemplate))
imageView.layer.cornerRadius = image.size.width.mid
imageView.backgroundColor = .white
imageView.tintColor = #colorLiteral(red: 0, green: 0.5490196078, blue: 1, alpha: 1)
imageView.isUserInteractionEnabled = true
imageView.layer.shadowColor = #colorLiteral(red: 0.1029271765, green: 0.08949588804, blue: 0.1094761982, alpha: 0.8005611796)
imageView.layer.shadowOffset = CGSize(width: 0, height: 1)
imageView.layer.shadowOpacity = 1
imageView.layer.shadowRadius = 1.0

panRecognizer.require(toFail: resizePanRecognizer)

addGestureRecognizer(panRecognizer)
imageView.addGestureRecognizer(resizePanRecognizer)
addSubview(imageView)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc func pan(_ sender: UIPanGestureRecognizer) {
if sender.state == .began || sender.state == .changed {
center = sender.location(in: superview)

layoutSubviews()
}
}

@objc func resizePan(_ sender: UIPanGestureRecognizer) {

let location = sender.location(in: superview)

if sender.state == .began || sender.state == .changed {

let origin = CGPoint(x: frame.minX, y: frame.minY)
frame = CGRect(origin: origin,
size: CGSize(width: location.x - origin.x,
height: location.y - origin.y))
layoutSubviews()
}
}

override func layoutSubviews() {
super.layoutSubviews()

if lineLayer.superlayer == nil {
lineLayer.strokeColor = #colorLiteral(red: 0, green: 0.5490196078, blue: 1, alpha: 1).cgColor
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.lineWidth = 1
lineLayer.lineDashPattern = [5, 5]
layer.addSublayer(lineLayer)
}

lineLayer.path = UIBezierPath(rect: bounds).cgPath

let clippedPath = UIBezierPath(rect: superview!.bounds)
clippedPath.append(UIBezierPath(rect: lineLayer.frame))

let superFrame = self.convert(superview!.bounds, to: self)

if let backgroundLayer = backgroundLayer {
backgroundLayer.path = UIBezierPath(rect: superFrame).cgPath
backgroundLayer.frame = superFrame

let path = UIBezierPath(rect: frame)
path.append(UIBezierPath(rect: backgroundLayer.bounds))

maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.path = path.cgPath
backgroundLayer.mask = maskLayer
}

imageView.center = CGPoint(x: bounds.maxX-5, y: bounds.maxY-5)

bringSubview(toFront: imageView)
}

}

fileprivate extension CGFloat {

var mid: CGFloat {
return self / 2
}
}

extension UIImage {

func withPadding(x: CGFloat, y: CGFloat) -> UIImage? {
let width: CGFloat = size.width + x
let height: CGFloat = size.height + y
UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 0)

defer {
UIGraphicsEndImageContext()
}

let origin: CGPoint = CGPoint(x: (width - size.width) / 2, y: (height - size.height) / 2)
draw(at: origin)

return UIGraphicsGetImageFromCurrentImageContext()
}
}
12 changes: 12 additions & 0 deletions Example/Settings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation
import MapboxCoreNavigation
import MapboxDirections


struct Settings {

static var directions: NavigationDirections = NavigationDirections()

static var selectedOfflineVersion: String? = nil

}
Loading

0 comments on commit 7c305e0

Please sign in to comment.