Skip to content

Commit

Permalink
Merge pull request #1602 from mapbox/jerrad/146-navigation-service
Browse files Browse the repository at this point in the history
Navigation Service, Part 1
  • Loading branch information
JThramer committed Sep 26, 2018
2 parents a07e37c + e9a7781 commit 8b67898
Show file tree
Hide file tree
Showing 44 changed files with 1,827 additions and 1,562 deletions.
9 changes: 0 additions & 9 deletions Examples/Objective-C/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@
<outlet property="howToBeginLabel" destination="Hk0-SM-2Wl" id="iU7-G1-DBc"/>
<outlet property="mapView" destination="A3N-JT-loC" id="iZS-hq-X5f"/>
<outlet property="toggleNavigationButton" destination="8Sl-bV-xyU" id="LwJ-MX-oWU"/>
<segue destination="dYn-UD-FHX" kind="presentation" identifier="StartNavigation" id="CX3-Eh-kHD"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
Expand All @@ -92,13 +91,5 @@
</objects>
<point key="canvasLocation" x="24.800000000000001" y="35.532233883058474"/>
</scene>
<!--Navigation-->
<scene sceneID="Cg8-0l-4UG">
<objects>
<viewControllerPlaceholder storyboardName="Navigation" bundleIdentifier="com.mapbox.MapboxNavigation" id="dYn-UD-FHX" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="RhK-y2-JnK" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="646" y="35"/>
</scene>
</scenes>
</document>
33 changes: 10 additions & 23 deletions Examples/Objective-C/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ @interface ViewController () <AVSpeechSynthesizerDelegate>
@property (nonatomic, assign) CLLocationCoordinate2D destination;
@property (nonatomic) MBDirections *directions;
@property (nonatomic) MBRoute *route;
@property (nonatomic) MBRouteController *navigation;
@property (nonatomic) MBNavigationService *navigation;
@property (nonatomic) NSLengthFormatter *lengthFormatter;
@end

Expand All @@ -26,19 +26,20 @@ - (void)viewDidLoad {

self.lengthFormatter = [[NSLengthFormatter alloc] init];
self.lengthFormatter.unitStyle = NSFormattingUnitStyleShort;
self.directions = [MBDirections sharedDirections];

[self resumeNotifications];
}
- (void)viewDidAppear:(BOOL)animated {
[self.navigation resume];
[self.navigation start];
[super viewDidAppear:animated];
}

- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];

[self suspendNotifications];
[self.navigation suspendLocationUpdates];
[self.navigation stop];
}

- (IBAction)didLongPress:(UILongPressGestureRecognizer *)sender {
Expand All @@ -65,7 +66,7 @@ - (void)progressDidChange:(NSNotification *)notification {
// If you are using MapboxCoreNavigation,
// this would be a good time to update UI elements.
// You can grab the current routeProgress like:
// let routeProgress = notification.userInfo![RouteControllerRouteProgressKey] as! RouteProgress
// MBRouteProgress *progress = notification.userInfo[MBRouteControllerRouteProgressKey];
}

- (void)willReroute:(NSNotification *)notification {
Expand Down Expand Up @@ -109,26 +110,12 @@ - (void)getRoute {
[task resume];
}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"StartNavigation"]) {
MBNavigationViewController *controller = (MBNavigationViewController *)[segue destinationViewController];

controller.directions = [MBDirections sharedDirections];
controller.route = self.route;

controller.routeController.locationManager = [[MBSimulatedLocationManager alloc] initWithRoute:self.route];
}
}

- (void)startNavigation:(MBRoute *)route {
MBSimulatedLocationManager *locationManager = [[MBSimulatedLocationManager alloc] initWithRoute:route];
MBNavigationViewController *controller = [[MBNavigationViewController alloc] initWithRoute:route
directions:[MBDirections sharedDirections]
styles:nil
routeController:nil
locationManager:locationManager
voiceController:nil
eventsManager:nil];
MBNavigationService *service = [[MBNavigationService alloc ] initWithRoute:route directions:self.directions locationSource:nil eventsManagerType:nil simulating:MBNavigationSimulationOptionsAlways];

self.navigation = service;
MBNavigationViewController *controller = [[MBNavigationViewController alloc] initWithRoute:route styles:nil navigationService:service voiceController: nil];

[self presentViewController:controller animated:YES completion:nil];

// Suspend notifications and let `MBNavigationViewController` handle all progress and voice updates.
Expand Down
7 changes: 4 additions & 3 deletions Examples/Swift/AppDelegate+CarPlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ extension AppDelegate: CPApplicationDelegate {
extension AppDelegate: CarPlayManagerDelegate {

// MARK: CarPlayManagerDelegate
func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith routeController: RouteController) {
guard let window = window else { return }
NavigationViewController.carPlayManager(carPlayManager, didBeginNavigationWith:routeController, window: window)
func carPlayManager(_ carPlayManager: CarPlayManager, didBeginNavigationWith service: NavigationService) {
guard let window = self.window else { return }
NavigationViewController.carPlayManager(carPlayManager, didBeginNavigationWith: service, window: window)
}


func carPlayManagerDidEndNavigation(_ carPlayManager: CarPlayManager) {
// Dismiss NavigationViewController if it's present in the navigation stack
Expand Down
19 changes: 10 additions & 9 deletions Examples/Swift/CustomViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {

var destination: MGLPointAnnotation!
let directions = Directions.shared
var routeController: RouteController!
var navigationService: NavigationService!
var simulateLocation = false

var userRoute: Route?
Expand All @@ -26,14 +26,15 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
@IBOutlet weak var instructionsBannerView: InstructionsBannerView!

lazy var feedbackViewController: FeedbackViewController = {
return FeedbackViewController(eventsManager: routeController.eventsManager)
return FeedbackViewController(eventsManager: navigationService.eventsManager)
}()

override func viewDidLoad() {
super.viewDidLoad()

let locationManager = simulateLocation ? SimulatedLocationManager(route: userRoute!) : NavigationLocationManager()
routeController = RouteController(along: userRoute!, locationManager: locationManager, eventsManager: EventsManager())
navigationService = MapboxNavigationService(route: userRoute!, locationSource: locationManager, simulating: simulateLocation ? .always : .onPoorGPS)


mapView.delegate = self
mapView.compassView.isHidden = true
Expand All @@ -44,7 +45,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
resumeNotifications()

// Start navigation
routeController.resume()
navigationService.start()

// Center map on user
mapView.recenterMap()
Expand All @@ -63,7 +64,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
func resumeNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_ :)), name: .routeControllerProgressDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(rerouted(_:)), name: .routeControllerDidReroute, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(updateInstructionsBanner(notification:)), name: .routeControllerDidPassVisualInstructionPoint, object: routeController)
NotificationCenter.default.addObserver(self, selector: #selector(updateInstructionsBanner(notification:)), name: .routeControllerDidPassVisualInstructionPoint, object: navigationService.router)
}

func suspendNotifications() {
Expand All @@ -73,7 +74,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
}

func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
self.mapView.showRoutes([routeController.routeProgress.route])
self.mapView.showRoutes([navigationService.route])
}

// Notifications sent on all location updates
Expand Down Expand Up @@ -104,7 +105,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
// Fired when the user is no longer on the route.
// Update the route on the map.
@objc func rerouted(_ notification: NSNotification) {
self.mapView.showRoutes([routeController.routeProgress.route])
self.mapView.showRoutes([navigationService.route])
}

@IBAction func cancelButtonPressed(_ sender: Any) {
Expand All @@ -124,9 +125,9 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
controller.dismiss()
stepsViewController = nil
} else {
guard let routeController = routeController else { return }
guard let service = navigationService else { return }

let controller = StepsViewController(routeProgress: routeController.routeProgress)
let controller = StepsViewController(routeProgress: service.routeProgress)
controller.delegate = self
addChildViewController(controller)
view.addSubview(controller.view)
Expand Down
32 changes: 19 additions & 13 deletions Examples/Swift/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ class ViewController: UIViewController, MGLMapViewDelegate {

let locationManager = ReplayLocationManager(locations: Array<CLLocation>.locations(from: filePath))

let navigationViewController = NavigationViewController(for: route, locationManager: locationManager)
let navigationService = MapboxNavigationService(route: route, locationSource: locationManager)
let navigationViewController = NavigationViewController(for: route, navigationService: navigationService)

present(navigationViewController, animated: true, completion: nil)
}
Expand Down Expand Up @@ -205,7 +206,7 @@ class ViewController: UIViewController, MGLMapViewDelegate {
func startBasicNavigation() {
guard let route = routes?.first else { return }

let navigationViewController = NavigationViewController(for: route, locationManager: navigationLocationManager())
let navigationViewController = NavigationViewController(for: route, navigationService: navigationService())
navigationViewController.delegate = self

presentAndRemoveMapview(navigationViewController)
Expand All @@ -214,7 +215,7 @@ class ViewController: UIViewController, MGLMapViewDelegate {
func startNavigation(styles: [Style]) {
guard let route = routes?.first else { return }

let navigationViewController = NavigationViewController(for: route, styles: styles, locationManager: navigationLocationManager())
let navigationViewController = NavigationViewController(for: route, styles: styles, navigationService: navigationService())
navigationViewController.delegate = self

presentAndRemoveMapview(navigationViewController)
Expand Down Expand Up @@ -243,15 +244,17 @@ class ViewController: UIViewController, MGLMapViewDelegate {

let styles = [CustomDayStyle(), CustomNightStyle()]

let navigationViewController = NavigationViewController(for: route, styles: styles, locationManager: navigationLocationManager())
let navigationViewController = NavigationViewController(for: route, styles: styles, navigationService: navigationService())
navigationViewController.delegate = self

presentAndRemoveMapview(navigationViewController)
}

func navigationLocationManager() -> NavigationLocationManager {
guard let route = routes?.first else { return NavigationLocationManager() }
return simulationButton.isSelected ? SimulatedLocationManager(route: route) : NavigationLocationManager()
func navigationService() -> NavigationService? {
guard let route = routes?.first else { return nil }
let simulate = simulationButton.isSelected
let option: SimulationOption = simulate ? .always : .onPoorGPS
return MapboxNavigationService(route: route, simulating: option)
}

func presentAndRemoveMapview(_ navigationViewController: NavigationViewController) {
Expand Down Expand Up @@ -346,11 +349,14 @@ extension ViewController: VoiceControllerDelegate {
extension ViewController: WaypointConfirmationViewControllerDelegate {
func confirmationControllerDidConfirm(_ confirmationController: WaypointConfirmationViewController) {
confirmationController.dismiss(animated: true, completion: {
guard let navigationViewController = self.presentedViewController as? NavigationViewController else { return }

guard navigationViewController.routeController.routeProgress.route.legs.count > navigationViewController.routeController.routeProgress.legIndex + 1 else { return }
navigationViewController.routeController.routeProgress.legIndex += 1
navigationViewController.routeController.resume()
guard let navigationViewController = self.presentedViewController as? NavigationViewController,
let navService = navigationViewController.navigationService else { return }

let router = navService.router!
guard router.route.legs.count > router.routeProgress.legIndex + 1 else { return }

router.routeProgress.legIndex += 1
navService.start()
})
}
}
Expand All @@ -366,7 +372,7 @@ extension ViewController: NavigationViewControllerDelegate {
// This type of screen could show information about a destination, pickup/dropoff confirmation, instructions upon arrival, etc.

//If we're not in a "Multiple Stops" demo, show the normal EORVC
if navigationViewController.routeController.routeProgress.isFinalLeg {
if navigationViewController.navigationService.router.routeProgress.isFinalLeg {
return true
}

Expand Down
8 changes: 0 additions & 8 deletions MapboxCoreNavigation/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,7 @@ public var RouteControllerMinNumberOfInCorrectCourses: Int = 4
*/
public var RouteControllerIncorrectCourseMultiplier: Int = 4

/**
Minimum distance to flag the proximity to an upcoming tunnel intersection on the route.
*/
public var RouteControllerMinimumDistanceToTunnelEntrance: CLLocationDistance = 15

/**
Minimum speed (mps) as the user enters the minimum radius of the tunnel entrance on the route.
*/
public var RouteControllerMinimumSpeedAtTunnelEntranceRadius: CLLocationSpeed = 5

/**
When calculating the user's snapped location, this constant will be used for deciding upon which step coordinates to include in the calculation.
Expand Down
70 changes: 70 additions & 0 deletions MapboxCoreNavigation/CountdownTimer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Foundation
import Dispatch

class CountdownTimer {
enum State {
case armed, disarmed
}

typealias Payload = DispatchSource.DispatchSourceHandler
static let defaultAccuracy: DispatchTimeInterval = .milliseconds(500)
let countdownInterval: DispatchTimeInterval
private var deadline: DispatchTime { return .now() + countdownInterval }
let accuracy: DispatchTimeInterval
let payload: Payload
let timerQueue = DispatchQueue(label: "com.mapbox.coreNavigation.timer.countdown", qos: DispatchQoS.background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
let executionQueue: DispatchQueue
let timer: DispatchSourceTimer

private(set) var state: State = .disarmed

init(countdown: DispatchTimeInterval, payload: @escaping Payload, accuracy: DispatchTimeInterval = defaultAccuracy, executingOn executionQueue: DispatchQueue = .main) {
countdownInterval = countdown
self.executionQueue = executionQueue
self.payload = payload
self.accuracy = accuracy
self.timer = DispatchSource.makeTimerSource(flags: [], queue: timerQueue)
}

deinit {
timer.setEventHandler {}
timer.cancel()
/*
If the timer is suspended, calling cancel without resuming
triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
*/
if state == .disarmed {
timer.resume()
}
}

private func scheduleTimer() {
timer.schedule(deadline: deadline, repeating: .never, leeway: accuracy)
}

private func fire() {
executionQueue.async(execute: payload)
}

func arm() {
guard state == .disarmed, !timer.isCancelled else { return }
state = .armed
scheduleTimer()
timer.setEventHandler(handler: fire)
timer.resume()
}

func reset() {
guard state == .armed, !timer.isCancelled else { return }
timer.suspend()
scheduleTimer()
timer.resume()
}

func disarm() {
guard state == .armed, !timer.isCancelled else { return }
state = .disarmed
timer.suspend()
timer.setEventHandler {}
}
}

0 comments on commit 8b67898

Please sign in to comment.