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

Enable simulated navigation for tunnel segments. #1218

Merged
merged 101 commits into from
May 4, 2018

Conversation

vincethecoder
Copy link
Contributor

@vincethecoder vincethecoder commented Mar 12, 2018

This will enable the simulated navigation whenever the commuter enters a tunnel until they exit the tunnel.

Closes #1275

TODO:

  • Accompanied Issue Ticket
  • Tests
  • Documentation
  • Class dedicated to Simulated Tunnel Navigation

@vincethecoder vincethecoder added this to the v0.16.x milestone Mar 12, 2018
@bsudekum bsudekum modified the milestone: v0.16.x Mar 12, 2018
if let currentIntersection = routeProgress.currentLegProgress.currentStepProgress.currentIntersection,
let classes = currentIntersection.outletRoadClasses {
if classes.contains(.tunnel) {
startTunnelAnimation(for: manager, routeProgress: routeProgress, distanceTraveled: distanceTraveled)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is distanceTraveled being passed in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When location simulation is needed in special cases such as dead reckoning, the distanceTraveled on the given route determines where the SimulatedLocationManager needs to begin the simulation.

… SimulatedLocationManager. Additional logic to stop simulated location updates when user halts navigation.
- parameter distanceTraveled: The distance traveled on the current route.
- returns: A `SimulatedLocationManager`
*/
@objc public init(route: Route, distanceTraveled: CLLocationDistance) {
Copy link
Contributor

@frederoni frederoni Mar 13, 2018

Choose a reason for hiding this comment

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

Would it be more versatile if we would pass a from:CLLocationCoordinate2D,to:CLLocationCoordinate2D?
As it stands now, we don't stop simulation after simulating through a tunnel if you would get stuck or slow down in the middle, right? Slicing the route could possibly fix that.

Copy link
Contributor Author

@vincethecoder vincethecoder Mar 13, 2018

Choose a reason for hiding this comment

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

@frederoni a couple of things.

  1. Yes, we stop simulation after simulating through a tunnel
    stopTunnelAnimation(for: manager)
  2. We don't need to slice, as the SimulatedLocationManager provides us with a robust data based on the currentDistance (a.k.a. distanceTraveled). Given the currentDistance, the manager can determine the current location on the given route - which is used to determine factors such as expectedSegmentTravelTimes, closestCoordinateOnRoute, nextCoordinateOnRoute and time.

guard let newCoordinate = polyline.coordinateFromStart(distance: currentDistance) else {

Copy link
Contributor

Choose a reason for hiding this comment

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

👍
I overlooked this part:

} else {
stopTunnelAnimation(for: manager)
}

and thought we would just continue simulating to the destination.

simulatedLocationManager?.delegate = self
simulatedLocationManager?.routeProgress = routeProgress

let dispatchGroup = DispatchGroup()
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain the reasoning behind using this dispatch group?

Copy link
Contributor Author

@vincethecoder vincethecoder Mar 13, 2018

Choose a reason for hiding this comment

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

@frederoni Sure.

This ensures we don't encounter any concurrency (dispatch) race between the local locationManager and the simulatedLocationManager. Basically, when we dispatch using a group, we are assured that our local locationManager will stop stopUpdatingLocation before the simulatedLocationManager starts startUpdatingLocation.

This facilitates a smooth handshake between using our RouteController.locationManager and RouteController.simulatedLocationManager where we will not have both running at the same time or vice versa.

@frederoni
Copy link
Contributor

refactor to use a delegate approach in NavigationLocationManager. This will allow for ReplayLocationManager and SimulatedLocationManager to utilize this feature.

Would replaying using ReplayLocationManager also make use of SimulatedLocationManager?

@vincethecoder
Copy link
Contributor Author

vincethecoder commented Mar 13, 2018

@frederoni Yes, I've considered using a delegate approach in NavigationLocationManager but it should be tracked in another PR. The delegate approach will make this PR bloated. NavigationLocationManager is used by several difference sub classes. It makes more sense to just simulate navigation using this approach and get some field tests going to address all possible tunnel simulation conditions.

Also, ReplayLocationManager and SimulatedLocationManager use 2 different approaches in simulating navigation as well. We can find some common ground, however I don't think it should be tracked in this PR. A follow up PR would be ideal.


cc @bsudekum @1ec5

@@ -232,7 +232,9 @@ open class RouteController: NSObject {
}

var userSnapToStepDistanceFromManeuver: CLLocationDistance?


var simulatedLocationManager: SimulatedLocationManager?
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 rename this to tunnelLocationManager or similar to avoid confusion?

Copy link
Contributor Author

@vincethecoder vincethecoder Mar 13, 2018

Choose a reason for hiding this comment

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

@frederoni this simulatedLocationManager is intentional. It can be used for other context of dead reckoning (such as highways, motorways, tolls etc.) not just tunnel navigation. I had followed up with @brsbl regarding this simulated navigation effort for dead reckoning.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, how about animatedLocationManager then? simulator should be reserved for the actual simulator in the sdk I think.

Copy link
Contributor

@JThramer JThramer Mar 13, 2018

Choose a reason for hiding this comment

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

🤔 maybe we should have a MapboxLocationManager to manage all of these real/faux CLLocationManager objects? Something to combine the (three now?) native managers (simulated, native, and tunnel) into one concern/interface. There would also be some advantages here in testability, dynamically enabling/disabling route sim, and handoff between one location manager and another, say when entering/exiting a tunnel.

It doesn't have to happen now (or ever), just an idea.
/cc @akitchen

Copy link
Contributor Author

@vincethecoder vincethecoder Mar 14, 2018

Choose a reason for hiding this comment

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

@JThramer I concur. We should have a LocationManager to handle the 3 variations of location manager. @frederoni, @1ec5 and I had a chat and we concluded there is definitely candidacy for ReplayLocationManager, SimulatedLocationManager to be tied to the location manager, MapboxLocationManager. I intend to track this in a new issue ticket for an upcoming PR.

/cc @bsudekum

Copy link
Contributor

Choose a reason for hiding this comment

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

I still disagree about this. Names are cheap, and this variable name tips off the developer that this SimulatedLocationManager is used for simulation. While we are simulating, this is particular kind of simulation that we should distinguish from others.

Even if this variable will be used for other types of animation in the future, we can still come up with a better name now that does not lead to confusion.

@@ -353,6 +355,11 @@ open class RouteController: NSObject {
@objc public func suspendLocationUpdates() {
locationManager.stopUpdatingLocation()
locationManager.stopUpdatingHeading()

// In case simulated navigation is abruptly stopped before completion,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this sentence is missing a few the.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@frederoni Haha yeah - facepalm (an old habit of eliminating a few prepositions to keep the comments concise 😸) Good looks. Thanks 👍

@vincethecoder vincethecoder added the feature New feature request. label Mar 15, 2018
@bsudekum bsudekum changed the base branch from tunnel/dark_mode_style to master March 19, 2018 21:45
@mapbox mapbox deleted a comment from vincethecoder Apr 30, 2018

var tunnelIntersectionManagerCompletionHandler: RouteControllerSimulationCompletionBlock?

public var tunnelSimulationFeatureEnabled: Bool = false
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say either make these private if they can be or document them.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, rename this to tunnelSimulationEnabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bsudekum we need to make it public. The developer needs a way to enable the tunnel simulation feature.

currentDistance = 0
currentSpeed = 30
currentSpeed = 0
currentDistance = distance + (currentSpeed * speedMultiplier)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add a new function here for these changes. Also, the units here do not match. We're adding a distance (meters) to a speed (meters per second).

Copy link
Contributor Author

@vincethecoder vincethecoder May 3, 2018

Choose a reason for hiding this comment

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

@bsudekum this is an already existing logic. Remember we had a prior discussion: #1218 (comment)

Should we probably leave some comment in the code for it, just to prevent a confusion in the future?

}

updateDistanceToIntersection(from: location)
updateRouteStepProgress(for: location)
updateRouteLegProgress(for: location)

guard userIsOnRoute(location) || !(delegate?.routeController?(self, shouldRerouteFrom: location) ?? true) else {
guard tunnelAnimationEnabled || userIsOnRoute(location) || !(delegate?.routeController?(self, shouldRerouteFrom: location) ?? true) else {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm slightly concerned with this. If the tunnel animation taking place, and the user is obviously outside of the tunnel, someplace else, we won't reroute them.

Copy link
Contributor Author

@vincethecoder vincethecoder May 3, 2018

Choose a reason for hiding this comment

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

@bsudekum We will reroute. Basically, whenever we get a valid location update we suspend tunnel animation demonstrated here: https://github.com/mapbox/mapbox-navigation-ios/pull/1218/files#diff-987c4bbd828e654bd63d59215f890022R129
Hence, the default navigation location manager will kick in and reroute if needed.


guard isAnimationEnabled else { return }

// Disable the tunnel animation after at least 3 bad location updates.
Copy link
Contributor

Choose a reason for hiding this comment

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

It think it might make more sense to disable tunnel animation after 3 good location updates. If we get 3 bad location updates in a row, we should not do anything IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bsudekum just for the record, this might delay style switches and delay in puck movements in case the driver exits the tunnel very quickly. (PS: I will still proceed with the suggested changes.)

tunnelIntersectionManager?.delegate = self
tunnelIntersectionManagerCompletionHandler = { enabled, _ in
self.tunnelIntersectionManager?.isAnimationEnabled = enabled
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be cleaner to wrap this in an initTunnelManager function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// Allow the user puck to advance. A stationary puck is not great.
self.rawLocation = lastLocation

// Check for a tunnel intersection at the current step we found the bad location update.
checkForTunnelIntersection(at: lastLocation, for: manager)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd move this this out of this if statement and put it below. This function should use location.

Copy link
Contributor Author

@vincethecoder vincethecoder May 3, 2018

Choose a reason for hiding this comment

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

@bsudekum shouldn't the simulation move along with the puck as indicated in the comment above

Allow the user puck to advance. A stationary puck is not great.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bsudekum also when you say "I'd move this out of this if statement and put it below."... do you mean, moving this out of the conditional statement:
if let lastLocation = locations.last, delegate?.routeController?(self, shouldDiscard: lastLocation) ?? true ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Move to at least here:

Copy link
Contributor Author

@vincethecoder vincethecoder May 3, 2018

Choose a reason for hiding this comment

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

@bsudekum It already resides here:

checkForTunnelIntersection(at: location, for: manager)

(Thus where step’s remaining distance has changed.)

The reason the check for tunnel lives here is to ensure whenever:

filteredLocations does not contain good locations and we have found at least one good location previously.

and... to follow suit as we advance the puck as indicated here:
https://github.com/mapbox/mapbox-navigation-ios/pull/1218/files#diff-1a2149692b3d7c58b6a0392120d6011aL549

... unless I'm missing something, here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Per discussion via slack, we decided to keep this function here.
/cc @bsudekum

callback: callback)
}

public func tunnelIntersectionManager(_ manager: CLLocationManager,
Copy link
Contributor

Choose a reason for hiding this comment

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

These functions need documentation since they are public.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bsudekum Hey, it's already documented in the TunnelIntersectionManagerDelegate class: https://github.com/mapbox/mapbox-navigation-ios/pull/1218/files#diff-987c4bbd828e654bd63d59215f890022R20

@vincethecoder vincethecoder merged commit e753711 into master May 4, 2018
@vincethecoder vincethecoder deleted the tunnel/simulate_navigation branch May 4, 2018 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature request.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants