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

Navigation Service, Part 1 #1602

Merged
merged 41 commits into from
Sep 26, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
57ed105
WIP: Building out scaffolding for `MapboxNavigationService`
Aug 9, 2018
ef09aac
WIP: It compiles! (Completed building out scaffolding around Navigati…
Aug 13, 2018
23818fc
WIP: It's Aliiiiive! (Hooking NavigationService up to consumer, movin…
Aug 13, 2018
a9611d1
WIP: PR Comments
Aug 13, 2018
8f47670
Update all the tests!
Aug 14, 2018
099ddc4
Cleaning up after myself
Aug 15, 2018
b24c8bf
Merge branch 'master' into jerrad/146-navigation-service
Aug 15, 2018
518a89e
Decoupling EventsManager from RouteController
Aug 16, 2018
8e44849
Merge branch 'master' into jerrad/146-navigation-service
Aug 16, 2018
d169b06
WIP: Temporarily disabling TunnelIntersectionTests for refactor
Aug 21, 2018
3a80e14
Decoupling the TunnelIntersectionManager (now TunnelAuthority) from t…
Aug 21, 2018
cedabcb
Gah, forgot about the OBJ-C interface!
Aug 21, 2018
230351f
Prevent yo-yoing by only passing synthetic location updates onto the …
Aug 21, 2018
8c04240
Fix naming conventions and coding style
frederoni Aug 21, 2018
7f5aa7f
Simplified TunnelAuthority and refactor accompanying tests
frederoni Aug 21, 2018
089a39a
Completely decoupling tunnel detection from route simulation. This si…
Aug 22, 2018
9c5b70d
Fixing issue where countdown timer payload was being executed on back…
Aug 22, 2018
950cd68
Fixing issue where we were using timer.cancel() (which is permanent) …
Aug 22, 2018
d49ebdc
Fixing Tests. Gah.
Aug 22, 2018
3e8edde
PR Comments, and preliminary polish.
Aug 22, 2018
d6f8498
Merge branch 'master' into jerrad/146-navigation-service
Aug 24, 2018
8a8fc79
Adding new `NavigationServiceDelegate` type and plumbing everything t…
Aug 27, 2018
7b8ed23
Merge branch 'master' into jerrad/146-navigation-service
Aug 27, 2018
e454202
Hooking up example
Aug 27, 2018
45795e4
cleanup, random comment fix
Aug 27, 2018
a139f89
Merge branch 'master' into jerrad/146-navigation-service
Aug 29, 2018
4fc2e2a
Fixing tests.
Aug 29, 2018
297f7b4
Consolidating directions reference into a single-point-of-truth.
Aug 29, 2018
ee3cd30
Merge branch 'master' into jerrad/146-navigation-service
Sep 7, 2018
cec16b6
Fixing merge clobber
Sep 7, 2018
b5c0c03
Merge branch 'epic/nav-native' into jerrad/146-navigation-service
Sep 21, 2018
fdcb350
WIP: Trying to fix test failures
Sep 24, 2018
9c7c9f9
WIP: Fixed crash, now working on the actual failures, which is "we di…
Sep 24, 2018
2049a30
Fixed issue where wrong type of location manager was used for core na…
Sep 24, 2018
41265b1
Fixing remaining tests
Sep 26, 2018
89c73cf
Final polish -- fixing issue where speed multiplier changes weren't b…
Sep 26, 2018
2e161df
Fixing test cruft where testing type is no longer extant on stubbed e…
Sep 26, 2018
6bccfd6
Reverting removal of ostensibly redundant XCTest availability macros
Sep 26, 2018
edbb56d
Merge branch 'epic/nav-native' into jerrad/146-navigation-service
Sep 26, 2018
0994b2b
Forgot this one
Sep 26, 2018
e9a7781
Fixing some post-merge testing issues
Sep 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 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;
JThramer marked this conversation as resolved.
Show resolved Hide resolved
@property (nonatomic) NSLengthFormatter *lengthFormatter;
@property (nonatomic) AVSpeechSynthesizer *speechSynth;
@end
Expand All @@ -37,7 +37,7 @@ - (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];

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

- (IBAction)didLongPress:(UILongPressGestureRecognizer *)sender {
Expand Down Expand Up @@ -121,10 +121,13 @@ -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"StartNavigation"]) {
MBNavigationViewController *controller = (MBNavigationViewController *)[segue destinationViewController];

MBSimulatedLocationManager *locationManager = [[MBSimulatedLocationManager alloc] initWithRoute:self.route];

controller.directions = [MBDirections sharedDirections];
controller.route = self.route;
controller.navigationService = [[MBNavigationService alloc] initWithRoute:self.route directions:controller.directions locationSource:locationManager eventsManager:nil];

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

}
}

Expand Down
13 changes: 7 additions & 6 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 @@ -27,7 +27,8 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
super.viewDidLoad()

let locationManager = simulateLocation ? SimulatedLocationManager(route: userRoute!) : NavigationLocationManager()
routeController = RouteController(along: userRoute!, locationManager: locationManager)
navigationService = MapboxNavigationService(route: userRoute!, locationSource: locationManager)


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

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

// Center map on user
mapView.recenterMap()
Expand All @@ -55,7 +56,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 @@ -65,7 +66,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 @@ -96,7 +97,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 Down
15 changes: 9 additions & 6 deletions Examples/Swift/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,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 }
Copy link
Contributor

Choose a reason for hiding this comment

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

This PR introduces some significant new concepts that will affect most non-trivial usage of the navigation SDK. So we’ll need an update to the changelog that enumerates the publicly visible changes (or links to a PR description that does so).


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

router.routeProgress.legIndex += 1
navService.start()
})
}
}
Expand All @@ -349,7 +352,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
53 changes: 24 additions & 29 deletions MapboxCoreNavigation/EventDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,21 @@ struct EventDetails: Encodable {
var newDurationRemaining: TimeInterval?
var newGeometry: String?

init(router: Router, session: SessionState) {

init(dataSource: EventsManagerDataSource, session: SessionState) {
coordinate = dataSource.location?.coordinate
startTimestamp = session.departureTimestamp ?? nil
sdkIdentifier = router.usesDefaultUserInterface ? "mapbox-navigation-ui-ios" : "mapbox-navigation-ios"
profile = router.routeProgress.route.routeOptions.profileIdentifier.rawValue
simulation = router.locationManager is ReplayLocationManager || router.locationManager is SimulatedLocationManager ? true : false
sdkIdentifier = dataSource.usesDefaultUserInterface ? "mapbox-navigation-ui-ios" : "mapbox-navigation-ios"
profile = dataSource.routeProgress.route.routeOptions.profileIdentifier.rawValue
simulation = dataSource.locationSource.isSimulated

sessionIdentifier = session.identifier.uuidString
originalRequestIdentifier = session.originalRoute.routeIdentifier
requestIdentifier = router.routeProgress.route.routeIdentifier

let location = router.locationManager.location
coordinate = location?.coordinate ?? nil

if let coordinates = router.routeProgress.route.coordinates, let lastCoord = coordinates.last {
userAbsoluteDistanceToDestination = location?.distance(from: CLLocation(latitude: lastCoord.latitude, longitude: lastCoord.longitude)) ?? nil
requestIdentifier = dataSource.routeProgress.route.routeIdentifier

if let location = dataSource.location,
let coordinates = dataSource.routeProgress.route.coordinates,
let lastCoord = coordinates.last {
userAbsoluteDistanceToDestination = location.distance(from: CLLocation(latitude: lastCoord.latitude, longitude: lastCoord.longitude))
} else {
userAbsoluteDistanceToDestination = nil
}
Expand All @@ -105,19 +104,15 @@ struct EventDetails: Encodable {
estimatedDuration = nil
}

distanceCompleted = round(session.totalDistanceCompleted + router.routeProgress.distanceTraveled)
distanceRemaining = round(router.routeProgress.distanceRemaining)
durationRemaining = round(router.routeProgress.durationRemaining)
distanceCompleted = round(session.totalDistanceCompleted + dataSource.routeProgress.distanceTraveled)
distanceRemaining = round(dataSource.routeProgress.distanceRemaining)
durationRemaining = round(dataSource.routeProgress.durationRemaining)

rerouteCount = session.numberOfReroutes

if let manager = router.locationManager {
locationEngine = String(describing: manager)
locationManagerDesiredAccuracy = manager.desiredAccuracy
} else {
locationEngine = nil
locationManagerDesiredAccuracy = nil
}
locationEngine = dataSource.locationSource.description
Copy link
Contributor

@frederoni frederoni Aug 20, 2018

Choose a reason for hiding this comment

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

Use the name of the class instead. LocationSource.description only supports simulation or device but you can also simulate on a device, or replay using the simulator, or use a custom location source. This information is useful when debugging events.

locationManagerDesiredAccuracy = dataSource.desiredAccuracy


var totalTimeInPortrait = session.timeSpentInPortrait
var totalTimeInLandscape = session.timeSpentInLandscape
Expand All @@ -138,11 +133,11 @@ struct EventDetails: Encodable {
percentTimeInForeground = totalTimeInPortrait + totalTimeInLandscape == 0 ? 100 : Int((totalTimeInPortrait / (totalTimeInPortrait + totalTimeInLandscape) * 100))


stepIndex = router.routeProgress.currentLegProgress.stepIndex
stepCount = router.routeProgress.currentLeg.steps.count
legIndex = router.routeProgress.legIndex
legCount = router.routeProgress.route.legs.count
totalStepCount = router.routeProgress.route.legs.map { $0.steps.count }.reduce(0, +)
stepIndex = dataSource.routeProgress.currentLegProgress.stepIndex
stepCount = dataSource.routeProgress.currentLeg.steps.count
legIndex = dataSource.routeProgress.legIndex
legCount = dataSource.routeProgress.route.legs.count
totalStepCount = dataSource.routeProgress.route.legs.map { $0.steps.count }.reduce(0, +)
}

private enum CodingKeys: String, CodingKey {
Expand Down Expand Up @@ -306,8 +301,8 @@ extension EventDetails {
}
}

static func defaultEvents(router: Router) -> EventDetails {
return EventDetails(router: router, session: router.eventsManager.sessionState)
static func defaultEvents(dataSource source: EventsManagerDataSource, session state: SessionState) -> EventDetails {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is redundant.

return EventDetails(dataSource: source, session: state)
}
}

Expand Down
62 changes: 49 additions & 13 deletions MapboxCoreNavigation/EventsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,41 @@ import MapboxDirections

`SessionState` is a struct that stores all memoized statistics that we later send to the telemetry engine.
*/

@objc public enum LocationSource: Int {
case device, simulated

var isSimulated: Bool { return self == .simulated }

var description: String {
switch self {
case .device:
return String(describing: CLLocationManager.self)
case .simulated:
return String(describing: SimulatedLocationManager.self)
}
}

}
@objc public protocol EventsManagerDataSource: class {
var routeProgress: RouteProgress { get }
var usesDefaultUserInterface: Bool { get set }
var location: CLLocation? { get }
var desiredAccuracy: CLLocationAccuracy { get }
var locationSource: LocationSource { get }
//todo: change MNS proto to `locationManager`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To-Do: remove this todo

}

@objc(MBEventsManager)
open class EventsManager: NSObject {

@objc public var manager = MMEEventsManager.shared()
@objc public var manager: MMEEventsManager = .shared()

var sessionState: SessionState!

var outstandingFeedbackEvents = [CoreFeedbackEvent]()

weak var routeController: Router!
unowned var dataSource: EventsManagerDataSource

/// :nodoc: This is used internally when the navigation UI is being used
var usesDefaultUserInterface = false
Expand All @@ -33,7 +58,8 @@ open class EventsManager: NSObject {
return token
}()

@objc public init(accessToken possibleToken: String? = nil) {
@objc public required init(dataSource source: EventsManagerDataSource, accessToken possibleToken: String? = nil) {
dataSource = source
super.init()
if let tokenOverride = possibleToken {
accessToken = tokenOverride
Expand Down Expand Up @@ -76,7 +102,7 @@ open class EventsManager: NSObject {

func navigationCancelEvent(rating potentialRating: Int? = nil, comment: String? = nil) -> EventDetails {
let rating = potentialRating ?? MMEEventsManager.unrated
var event = EventDetails.defaultEvents(router: routeController)
var event = EventDetails.defaultEvents(dataSource: dataSource, session: sessionState)
event.event = MMEEventTypeNavigationCancel
event.arrivalTimestamp = sessionState.arrivalTimestamp

Expand All @@ -91,13 +117,13 @@ open class EventsManager: NSObject {
}

func navigationDepartEvent() -> EventDetails {
var event = EventDetails.defaultEvents(router: routeController)
var event = EventDetails.defaultEvents(dataSource: dataSource, session: sessionState)
event.event = MMEEventTypeNavigationDepart
return event
}

func navigationArriveEvent() -> EventDetails {
var event = EventDetails.defaultEvents(router: routeController)
var event = EventDetails.defaultEvents(dataSource: dataSource, session: sessionState)
event.event = MMEEventTypeNavigationArrive
return event
}
Expand All @@ -111,7 +137,7 @@ open class EventsManager: NSObject {
}

func navigationFeedbackEvent(type: FeedbackType, description: String?) -> EventDetails {
var event = EventDetails.defaultEvents(router: routeController)
var event = EventDetails.defaultEvents(dataSource: dataSource, session: sessionState)
event.event = MMEEventTypeNavigationFeedback

event.userId = UIDevice.current.identifierForVendor?.uuidString
Expand All @@ -126,7 +152,7 @@ open class EventsManager: NSObject {
func navigationRerouteEvent(eventType: String = MMEEventTypeNavigationReroute) -> EventDetails {
let timestamp = Date()

var event = EventDetails.defaultEvents(router: routeController)
var event = EventDetails.defaultEvents(dataSource: dataSource, session: sessionState)
event.event = eventType
event.secondsSinceLastReroute = sessionState.lastRerouteDate != nil ? round(timestamp.timeIntervalSince(sessionState.lastRerouteDate!)) : -1

Expand Down Expand Up @@ -157,7 +183,6 @@ extension EventsManager {
}

func sendCancelEvent(rating: Int? = nil, comment: String? = nil) {
guard routeController != nil else { return }
guard let attributes = try? navigationCancelEvent(rating: rating, comment: comment).asDictionary() else { return }
manager.enqueueEvent(withName: MMEEventTypeNavigationCancel, attributes: attributes)
manager.flush()
Expand Down Expand Up @@ -185,6 +210,7 @@ extension EventsManager {
return event.id
}

@discardableResult
func enqueueRerouteEvent() -> String {
let timestamp = Date()
let eventDictionary = try! navigationRerouteEvent().asDictionary()
Expand All @@ -200,10 +226,11 @@ extension EventsManager {
}

func resetSession() {
let route = routeController.routeProgress.route
let route = dataSource.routeProgress.route
sessionState = SessionState(currentRoute: route, originalRoute: route)
}

@discardableResult
func enqueueFoundFasterRouteEvent() -> String {
let timestamp = Date()
let eventDictionary = try! navigationRerouteEvent(eventType: FasterRouteFoundEvent).asDictionary()
Expand Down Expand Up @@ -291,12 +318,21 @@ extension EventsManager {
sendOutstandingFeedbackEvents(forceAll: true)
}

func reportReroute(newRoute: Route, proactive: Bool) {
func reportReroute(progress: RouteProgress, proactive: Bool) {
let route = progress.route

// if the user has already arrived and a new route has been set, restart the navigation session
if sessionState.arrivalTimestamp != nil {
resetSession()
} else {
sessionState.currentRoute = route
}

if (proactive) {
_ = enqueueFoundFasterRouteEvent()
enqueueFoundFasterRouteEvent()
}
let latestReroute = outstandingFeedbackEvents.compactMap({ $0 as? RerouteEvent }).last
latestReroute?.update(newRoute: newRoute)
latestReroute?.update(newRoute: route)
}

@objc func update(progress: RouteProgress) {
Expand Down
8 changes: 8 additions & 0 deletions MapboxCoreNavigation/NavigationLocationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import UIKit
open class NavigationLocationManager: CLLocationManager {

var lastKnownLocation: CLLocation?
override open var delegate: CLLocationManagerDelegate? {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: no need to override the default implementation for delegate with the same implementation as the superclass already provides.

get {
return super.delegate
}
set {
super.delegate = newValue
}
}

override public init() {
super.init()
Expand Down