Conversation
There was a problem hiding this comment.
Is it possible for an unknown to appear in a response that otherwise has known congestion values? Would it be better to try and account for the unknown slot than to bail?
Examples/Swift/ViewController.swift
Outdated
There was a problem hiding this comment.
Also add a comment explaining why .expectedTravelTime appears here.
| // From this, we can estimate what coordinates in the route remain. | ||
| // This color change does not need to be 100% accurate, | ||
| // just a rough estimation of remaining route congestion. | ||
| let estimatedCoordinatesRemaining = Int(floor(Double(coordinates.count) * routeProgress.fractionTraveled)) |
There was a problem hiding this comment.
Hard to say. The number of coordinates in a given step has no relation to its distance. For example, a short, windy stretch of road may have many more coordinates than a longer, straighter stretch of road.
There was a problem hiding this comment.
(Note, I'm looking at the overall route here but your point is still valid.)
The code has changed significantly to account for only the remaining portion of the route.
|
|
||
| guard let coordinates = routeProgress.route.coordinates else { return } | ||
|
|
||
| // To keep this cheap, use the fraction traveled along the current route. |
There was a problem hiding this comment.
It would be very convenient to know which segments correspond to a given step, just as we know the geometry along an individual step. When initializing NavigationViewController with a route or when a new route comes in, let’s build a dictionary mapping steps to their segments, which we can reuse each time this method is called.
There was a problem hiding this comment.
@1ec5 how would this help this PR? We're not really too interested in the given step, rather the entire route.
There was a problem hiding this comment.
Aren’t we considering the total of expected travel times along all the remaining segments of the route? We could recompute that every time this method is called, which you’ve studiously avoided, but one optimization would be to essentially index the segments by step so that it would be cheaper to get all the remaining segments starting from the current location. So when this method is called, we’d sum the expected travel times on all the remaining segments on the current step plus all the remaining steps.
| for step in leg.steps { | ||
| if let lastCoord = step.coordinates?.last, let coordinates = route.coordinates, let segmentCongestionLevels = leg.segmentCongestionLevels, let expectedSegmentTravelTimes = leg.expectedSegmentTravelTimes { | ||
| guard let nextIndex = coordinates.index(where: { | ||
| $0.latitude == lastCoord.latitude && $0.longitude == lastCoord.longitude |
There was a problem hiding this comment.
@1ec5 @frederoni It looks like this is only truthy once; on the first step. Could there be a rounding error?
There was a problem hiding this comment.
Yeah, comparing doubles is bound to fail. Instead, let's take advantage of the fact that nextIndex is always coordinateIndex + step.coordinates.count to avoid a somewhat expensive call to index(where:).
|
@1ec5 @frederoni this is ready for review |
| let remaingCongestionSegmentTimes = legSegments + currentStepCongestionTimes | ||
| let segementsForStep = routeProgress.congestionTravelTimesSegmentsByStep[routeProgress.legIndex][routeProgress.currentLegProgress.stepIndex] | ||
|
|
||
| guard coordinatesRemainingOnStep <= segementsForStep.count else { return } |
There was a problem hiding this comment.
Aren’t there always fewer coordinates remaining on the step than the total number of coordinates on the step?
| let currentStepCongestionTimes = routeProgress.congestionTravelTimesSegmentsByStep[routeProgress.legIndex][routeProgress.currentLegProgress.stepIndex].suffix(estimatedCoordinatesRemaining) | ||
| let legSegments = Array(Array(routeProgress.congestionTravelTimesSegmentsByStep.joined()).joined()) | ||
| let remaingCongestionSegmentTimes = legSegments + currentStepCongestionTimes | ||
| let segementsForStep = routeProgress.congestionTravelTimesSegmentsByStep[routeProgress.legIndex][routeProgress.currentLegProgress.stepIndex] |
| /** | ||
| If the route contains both `segmentCongestionLevels` and `expectedSegmentTravelTimes`, this array will contain an array of arrays of tuples of `segmentCongestionLevels` and `expectedSegmentTravelTimes`. | ||
| */ | ||
| public var congestionTravelTimesSegmentsByStep: [[[(CongestionLevel, TimeInterval)]]] = [] |
There was a problem hiding this comment.
Factor out (CongestionLevel, TimeInterval) into a typealias. Maybe call it TimedCongestionLevel or CongestedTimeInterval. 😛 Then use the rename this property and congestionTravelTimesSegmentsByLeg accordingly.
By the way, note that this property won’t bridge to Objective-C. I think that’s OK though; the only expected user of this property is MapboxNavigation.
| super.init() | ||
| currentLegProgress = RouteLegProgress(leg: currentLeg, stepIndex: 0, alertLevel: alertLevel) | ||
|
|
||
| var coordinateIndex = 0 |
There was a problem hiding this comment.
maneuverCoordinateIndex would be more descriptive. A documentation comment like this would help as well (you can give local variables documentation comments too):
An index into the route’s
coordinatesandcongestionTravelTimesSegmentsByStepthat corresponds to a step’s maneuver location.
| let stepCoordinatesCount = Int(step.coordinateCount) + coordinateIndex - 1 | ||
|
|
||
| let congestionSegment = Array(segmentCongestionLevels[coordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) | ||
| let travelTimeSegment = Array(expectedSegmentTravelTimes[coordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) |
There was a problem hiding this comment.
stepSegmentCongestionLevels and stepSegmentTravelTimes
|
|
||
| /** | ||
| Sets an alternate color used throughout the UI to denote low traffic congestion. Not used to style the route line. | ||
| */ |
There was a problem hiding this comment.
Given that this color is used to style text, not the route line, let’s call this property lowTrafficTextColor.
(It would be more grammatical to call the other properties lowTrafficColor, moderateTrafficColor, etc., but I guess that can be a separate PR.)
|
|
||
| let segementsForStep = routeProgress.congestionTravelTimesSegmentsByStep[routeProgress.legIndex][routeProgress.currentLegProgress.stepIndex] | ||
|
|
||
| guard coordinatesRemainingOnStep <= segementsForStep.count else { return } |
There was a problem hiding this comment.
I wonder if we should assert that this is the case.
|
|
||
| guard coordinatesRemainingOnStep <= segementsForStep.count else { return } | ||
|
|
||
| let currentStepCongestionTimes = segementsForStep.suffix(from: coordinatesRemainingOnStep) |
There was a problem hiding this comment.
This could be useful functionality to put on RouteStepProgress.
| } else { | ||
| headerView.timeRemaining.text = dateComponentsFormatter.string(from: routeProgress.durationRemaining) | ||
|
|
||
| let coordinatesRemainingOnStep = Int(floor((Double(routeProgress.currentLegProgress.currentStepProgress.step.coordinateCount)) * routeProgress.currentLegProgress.currentStepProgress.fractionTraveled)) |
There was a problem hiding this comment.
countOfCoordinatesLeftOnStep so it’s clear that the property is a number, not an array of coordinates.
| guard coordinatesRemainingOnStep <= segementsForStep.count else { return } | ||
|
|
||
| let currentStepCongestionTimes = segementsForStep.suffix(from: coordinatesRemainingOnStep) | ||
| let remainingStepSegments = Array(routeProgress.congestionTravelTimesSegmentsByStep[routeProgress.legIndex].suffix(from: routeProgress.currentLegProgress.stepIndex)).joined() |
There was a problem hiding this comment.
If I’m not mistaken, this could still be a huge array unless we find a way to flatten or precompute the expected travel time buckets for later steps. Can we add another dictionary to RouteProgress that would make it possible to change this code to something like the following sketch?
let timedCongestionLevelsLeftOnStep: [TimedCongestionLevel] = stepTimedCongestionLevels.suffix(from: countOfCoordinatesLeftOnStep)
// var travelTimesByCongestionLevelByStep: [[[CongestionLevel: TimeInterval]]]
let timesLeftOnLegByCongestionLevel: [[CongestionLevel: TimeInterval]] = routeProgress.travelTimesByCongestionLevelByStep[routeProgress.legIndex].suffix(from: legProgress.stepIndex)
// `sum(_:)` is really a call to `reduce(_:_:)`
// The for loop before is OK too – just wrote it this way for clarity.
let lowTimeLeftOnStep: TimeInterval = sum(timedCongestionLevelsLeftOnStep.filter { $0.0.low }.map { $0.1 })
let lowTimeLeftOnLeg: TimeInterval = sum(timesLeftOnLegByCongestionLevel.map { $0.low })
let remainingLowTime = lowTimeLeftOnStep + lowTimeLeftOnLeg|
Updated this, but I'm still off by 1 somewhere. The remaining step congestion times do not add up quite yet. |
|
Got this working 🎉 . @1ec5 ready for another round of review. |
| */ | ||
| public var currentLegProgress: RouteLegProgress! | ||
|
|
||
| public typealias TimedCongestionLevel = (CongestionLevel, TimeInterval) |
There was a problem hiding this comment.
Since this typealias is public and the method arguments that use it are also public, the typealias needs documentation.
There was a problem hiding this comment.
This will crash if congestionTimesPerStep[routeProgress.legIndex] has only two items and we’re on step 1 (the second step), right?
| for (stepIndex, step) in leg.steps.enumerated() { | ||
| let stepCoordinatesCount = Int(step.coordinateCount) + maneuverCoordinateIndex - 1 | ||
|
|
||
| let congestionSegment = Array(segmentCongestionLevels[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) |
| let stepCoordinatesCount = Int(step.coordinateCount) + maneuverCoordinateIndex - 1 | ||
|
|
||
| let congestionSegment = Array(segmentCongestionLevels[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) | ||
| let travelTimeSegment = Array(expectedSegmentTravelTimes[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) |
| let congestionSegment = Array(segmentCongestionLevels[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) | ||
| let travelTimeSegment = Array(expectedSegmentTravelTimes[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex]) | ||
|
|
||
| let zippedCongestionTimes = Array(zip(congestionSegment, travelTimeSegment)) |
|
This is ready for review if @frederoni or @1ec5 want to check this out. |
| public var currentLegProgress: RouteLegProgress! | ||
|
|
||
| /** | ||
| Tupel containing a `CongestionLevel` and a corresponding `TimeInterval` representing the expected travel time for this segment. |
| public typealias TimedCongestionLevel = (CongestionLevel, TimeInterval) | ||
|
|
||
| /** | ||
| If the route contains both `segmentCongestionLevels` and `expectedSegmentTravelTimes`, this array will contain an array of arrays of tuples of `segmentCongestionLevels` and `expectedSegmentTravelTimes`. |
There was a problem hiding this comment.
this array will contain an array of arrays of tuples of
segmentCongestionLevelsandexpectedSegmentTravelTimes
This isn’t very helpful; Quick Help could’ve told the developer that. On the other hand, it would be helpful to phrase the comment like:
…this property is set to a deeply nested array of
TimeCongestionLevels per segment per step per leg.
todo:
/cc @frederoni @1ec5