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

Respect heading while walking #2215

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## master
* Fixed an issue where swiping the banner down after the StepsTableViewController has already displayed could put the UI in an unstable state. ([#2197](https://github.com/mapbox/mapbox-navigation-ios/pull/2197))
* If the user walks away from the route, they may be rerouted onto a route that initially travels in the opposite direction. This is only the case along steps that require walking on foot. ([#2215](https://github.com/mapbox/mapbox-navigation-ios/pull/2215))
* While the user is walking, the map rotates according to the user’s heading instead of their course. ([#2215](https://github.com/mapbox/mapbox-navigation-ios/pull/2215))
* Added the `NavigationMapView.updateCourseTracking(location:heading:camera:animated:)` method for manually forcing the map view and user course view to update according to a location or heading. ([#2215](https://github.com/mapbox/mapbox-navigation-ios/pull/2215))
* Added `RouteControllerNotificationUserInfoKey.headingKey` to the user info dictionary of `Notification.Name.routeControllerWillReroute`, `Notification.Name.routeControllerDidReroute`, and `Notification.Name.routeControllerProgressDidChange` notifications. ([#2215](https://github.com/mapbox/mapbox-navigation-ios/pull/2215))
* Added a `Router.heading` property that may contain a heading from the location manager. ([#2215](https://github.com/mapbox/mapbox-navigation-ios/pull/2215))

## v0.36.0

Expand Down
3 changes: 2 additions & 1 deletion Example/CustomViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {

let routeProgress = notification.userInfo![RouteControllerNotificationUserInfoKey.routeProgressKey] as! RouteProgress
let location = notification.userInfo![RouteControllerNotificationUserInfoKey.locationKey] as! CLLocation
let heading = notification.userInfo![RouteControllerNotificationUserInfoKey.headingKey] as? CLHeading

// Add maneuver arrow
if routeProgress.currentLegProgress.followOnStep != nil {
Expand All @@ -104,7 +105,7 @@ class CustomViewController: UIViewController, MGLMapViewDelegate {
instructionsBannerView.isHidden = false

// Update the user puck
mapView.updateCourseTracking(location: location, animated: true)
mapView.updateCourseTracking(location: location, heading: heading, animated: true)
}

@objc func updateInstructionsBanner(notification: NSNotification) {
Expand Down
6 changes: 3 additions & 3 deletions MapboxCoreNavigation/CoreConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,21 @@ extension Notification.Name {
/**
Posted after the user diverges from the expected route, just before `RouteController` attempts to calculate a new route.

The user info dictionary contains the key `RouteControllerNotificationUserInfoKey.locationKey`.
The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.locationKey` and `RouteControllerNotificationUserInfoKey.headingKey`.
*/
public static let routeControllerWillReroute = MBRouteControllerWillReroute

/**
Posted when `RouteController` obtains a new route in response to the user diverging from a previous route.

The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.locationKey` and `RouteControllerNotificationUserInfoKey.isProactiveKey`.
The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.locationKey`, `RouteControllerNotificationUserInfoKey.headingKey`, and `RouteControllerNotificationUserInfoKey.isProactiveKey`.
*/
public static let routeControllerDidReroute = MBRouteControllerDidReroute

/**
Posted when `RouteController` receives a user location update representing movement along the expected route.

The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.routeProgressKey`, `RouteControllerNotificationUserInfoKey.locationKey`, and `RouteControllerNotificationUserInfoKey.rawLocationKey`.
The user info dictionary contains the keys `RouteControllerNotificationUserInfoKey.routeProgressKey`, `RouteControllerNotificationUserInfoKey.locationKey`, `RouteControllerNotificationUserInfoKey.headingKey`, and `RouteControllerNotificationUserInfoKey.rawLocationKey`.
*/
public static let routeControllerProgressDidChange = MBRouteControllerProgressDidChange

Expand Down
26 changes: 16 additions & 10 deletions MapboxCoreNavigation/LegacyRouteController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
return rawLocation?.snapped(to: routeProgress)
}

var heading: CLHeading?
public var heading: CLHeading?

/**
The most recently received user location.
Expand Down Expand Up @@ -224,6 +224,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa

@objc public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
heading = newHeading
// TODO: Cause a map view to update its camera.
}

@objc public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
Expand Down Expand Up @@ -300,11 +301,14 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
delegate?.router?(self, didUpdate: progress, with: location, rawLocation: rawLocation)

//Fire the notification (for now)
NotificationCenter.default.post(name: .routeControllerProgressDidChange, object: self, userInfo: [
RouteControllerNotificationUserInfoKey.routeProgressKey: progress,
RouteControllerNotificationUserInfoKey.locationKey: location, //guaranteed value
RouteControllerNotificationUserInfoKey.rawLocationKey: rawLocation //raw
])
var userInfo: [RouteControllerNotificationUserInfoKey: Any] = [
.routeProgressKey: progress,
.locationKey: location, //guaranteed value
.rawLocationKey: rawLocation, //raw
]
userInfo[.headingKey] = heading
userInfo[.rawHeadingKey] = heading // heading snapping not yet implemented (an unnecessary?)
Copy link
Contributor

Choose a reason for hiding this comment

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

YAGNI?

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 get rid of rawHeadingKey, but that could be problematic if we ever do need to implement heading snapping for some reason. We’d want the non-raw values to be snapped when a raw value is available. That said, the comment refers to the prospect that heading snapping may not be especially useful.

NotificationCenter.default.post(name: .routeControllerProgressDidChange, object: self, userInfo: userInfo)
}
}

Expand Down Expand Up @@ -353,13 +357,15 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
isRerouting = true

delegate?.router?(self, willRerouteFrom: location)
NotificationCenter.default.post(name: .routeControllerWillReroute, object: self, userInfo: [
RouteControllerNotificationUserInfoKey.locationKey: location
])
var userInfo: [RouteControllerNotificationUserInfoKey: Any] = [
.locationKey: location,
]
userInfo[.headingKey] = heading
NotificationCenter.default.post(name: .routeControllerWillReroute, object: self, userInfo: userInfo)

self.lastRerouteLocation = location

getDirections(from: location, along: progress) { [weak self] (route, error) in
getDirections(from: location, routeProgress: progress) { [weak self] (route, error) in
guard let strongSelf = self else {
return
}
Expand Down
10 changes: 10 additions & 0 deletions MapboxCoreNavigation/MBRouteController.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ extern const _Nonnull MBRouteControllerNotificationUserInfoKey MBRouteController
*/
extern const _Nonnull MBRouteControllerNotificationUserInfoKey MBRouteControllerRawLocationKey;

/**
A key in the user info dictionary of a `MBRouteControllerProgressDidChangeNotification` or `MBRouteControllerWillRerouteNotification` notification. The corresponding value is a `CLHeading` object representing the current idealized user heading. The value may be `nil`.
*/
extern const _Nonnull MBRouteControllerNotificationUserInfoKey MBRouteControllerHeadingKey;

/**
A key in the user info dictionary of a `MBRouteControllerProgressDidChangeNotification` or `MBRouteControllerWillRerouteNotification` notification. The corresponding value is a `CLHeading` object representing the current raw user heading. The value may be `nil`.
*/
extern const _Nonnull MBRouteControllerNotificationUserInfoKey MBRouteControllerRawHeadingKey;

/**
A key in the user info dictionary of a `MBRouteControllerDidFailToRerouteNotification` notification. The corresponding value is an `NSError` object indicating why `RouteController` was unable to calculate a new route.
*/
Expand Down
2 changes: 2 additions & 0 deletions MapboxCoreNavigation/MBRouteController.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
const MBRouteControllerNotificationUserInfoKey MBRouteControllerRouteProgressKey = @"progress";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerLocationKey = @"location";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerRawLocationKey = @"rawLocation";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerHeadingKey = @"heading";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerRawHeadingKey = @"rawHeading";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerRoutingErrorKey = @"error";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerVisualInstructionKey = @"visualInstruction";
const MBRouteControllerNotificationUserInfoKey MBRouteControllerSpokenInstructionKey = @"spokenInstruction";
Expand Down
34 changes: 23 additions & 11 deletions MapboxCoreNavigation/RouteController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ open class RouteController: NSObject {
return CLLocation(status.location)
}

var heading: CLHeading?
public var heading: CLHeading?

/**
The most recently received user location.
Expand Down Expand Up @@ -158,6 +158,11 @@ open class RouteController: NSObject {
navigator.setRouteForRouteResponse(jsonString, route: 0, leg: 0)
}

@objc public func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
heading = newHeading
// TODO: Cause a map view to update its camera.
}

public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

guard let location = locations.last else { return }
Expand All @@ -168,7 +173,9 @@ open class RouteController: NSObject {

rawLocation = locations.last

locations.forEach { navigator.updateLocation(for: MBFixLocation($0)) }
for location in locations {
navigator.updateLocation(for: MBFixLocation(location))
}

let status = navigator.getStatusForTimestamp(location.timestamp)

Expand Down Expand Up @@ -276,11 +283,14 @@ open class RouteController: NSObject {
delegate?.router?(self, didUpdate: progress, with: location, rawLocation: rawLocation)

//Fire the notification (for now)
NotificationCenter.default.post(name: .routeControllerProgressDidChange, object: self, userInfo: [
RouteControllerNotificationUserInfoKey.routeProgressKey: progress,
RouteControllerNotificationUserInfoKey.locationKey: location, //guaranteed value
RouteControllerNotificationUserInfoKey.rawLocationKey: rawLocation //raw
])
var userInfo: [RouteControllerNotificationUserInfoKey: Any] = [
.routeProgressKey: progress,
.locationKey: location, //guaranteed value
.rawLocationKey: rawLocation, //raw
]
userInfo[.headingKey] = heading
userInfo[.rawHeadingKey] = heading // heading snapping not yet implemented (an unnecessary?)
NotificationCenter.default.post(name: .routeControllerProgressDidChange, object: self, userInfo: userInfo)
}
}

Expand Down Expand Up @@ -358,17 +368,19 @@ extension RouteController: Router {
}

delegate?.router?(self, willRerouteFrom: location)
NotificationCenter.default.post(name: .routeControllerWillReroute, object: self, userInfo: [
RouteControllerNotificationUserInfoKey.locationKey: location
])
var userInfo: [RouteControllerNotificationUserInfoKey: Any] = [
.locationKey: location,
]
userInfo[.headingKey] = heading
NotificationCenter.default.post(name: .routeControllerWillReroute, object: self, userInfo: userInfo)

self.lastRerouteLocation = location

// Avoid interrupting an ongoing reroute
if isRerouting { return }
isRerouting = true

getDirections(from: location, along: progress) { [weak self] (route, error) in
getDirections(from: location, routeProgress: progress) { [weak self] (route, error) in
self?.isRerouting = false

guard let strongSelf: RouteController = self else {
Expand Down
10 changes: 6 additions & 4 deletions MapboxCoreNavigation/RouteProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,14 @@ open class RouteProgress: NSObject {
}
}

func reroutingOptions(with current: CLLocation) -> RouteOptions {
func reroutingOptions(from location: CLLocation) -> RouteOptions {
let oldOptions = route.routeOptions
let user = Waypoint(coordinate: current.coordinate)
let user = Waypoint(coordinate: location.coordinate)

if (current.course >= 0) {
user.heading = current.course
// A pedestrian can turn on a dime; there’s no problem with a route that starts out by turning the pedestrian around.
let transportType = currentLegProgress.currentStep.transportType
if transportType != .walking && location.course >= 0 {
user.heading = location.course
user.headingAccuracy = RouteProgress.reroutingAccuracy
}
let newWaypoints = [user] + remainingWaypointsForCalculatingRoute()
Expand Down
17 changes: 10 additions & 7 deletions MapboxCoreNavigation/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public protocol RouterDataSource {
*/
@objc var rawLocation: CLLocation? { get }

/**
The most recently received user heading, if any.
*/
@objc var heading: CLHeading? { get }

/**
If true, the `RouteController` attempts to calculate a more optimal route for the user on an interval defined by `RouteControllerProactiveReroutingInterval`.
Expand Down Expand Up @@ -135,7 +139,7 @@ extension InternalRouter where Self: Router {
if isRerouting { return }
isRerouting = true

getDirections(from: location, along: routeProgress) { [weak self] (route, error) in
getDirections(from: location, routeProgress: routeProgress) { [weak self] (route, error) in
self?.isRerouting = false

guard let route = route else { return }
Expand All @@ -156,9 +160,9 @@ extension InternalRouter where Self: Router {
}
}

func getDirections(from location: CLLocation, along progress: RouteProgress, completion: @escaping (_ route: Route?, _ error: Error?)->Void) {
func getDirections(from location: CLLocation, routeProgress: RouteProgress, completion: @escaping (_ route: Route?, _ error: Error?)->Void) {
routeTask?.cancel()
let options = progress.reroutingOptions(with: location)
let options = routeProgress.reroutingOptions(from: location)

lastRerouteLocation = location

Expand All @@ -168,7 +172,7 @@ extension InternalRouter where Self: Router {
return completion(nil, error)
}

let mostSimilar = routes.mostSimilar(to: progress.route)
let mostSimilar = routes.mostSimilar(to: routeProgress.route)
return completion(mostSimilar ?? routes.first, error)
}
}
Expand All @@ -188,9 +192,8 @@ extension InternalRouter where Self: Router {

func announce(reroute newRoute: Route, at location: CLLocation?, proactive: Bool) {
var userInfo = [RouteControllerNotificationUserInfoKey: Any]()
if let location = location {
userInfo[.locationKey] = location
}
userInfo[.locationKey] = location
userInfo[.headingKey] = heading
userInfo[.isProactiveKey] = proactive
NotificationCenter.default.post(name: .routeControllerDidReroute, object: self, userInfo: userInfo)
delegate?.router?(self, didRerouteAlong: routeProgress.route, at: location, proactive: proactive)
Expand Down
2 changes: 1 addition & 1 deletion MapboxNavigation/CarPlayNavigationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ public class CarPlayNavigationViewController: UIViewController, NavigationMapVie
// Update the user puck
mapView?.updatePreferredFrameRate(for: routeProgress)
let camera = MGLMapCamera(lookingAtCenter: location.coordinate, altitude: 120, pitch: 60, heading: location.course)
mapView?.updateCourseTracking(location: location, camera: camera, animated: true)
mapView?.updateCourseTracking(location: location, heading: nil, camera: camera, animated: true)

let congestionLevel = routeProgress.averageCongestionLevelRemainingOnLeg ?? .unknown
guard let maneuver = carSession.upcomingManeuvers.first else { return }
Expand Down
Loading