Skip to content

Comments

Update ETA text color to match congestion on route#403

Merged
bsudekum merged 20 commits intomasterfrom
eta-color
Aug 23, 2017
Merged

Update ETA text color to match congestion on route#403
bsudekum merged 20 commits intomasterfrom
eta-color

Conversation

@bsudekum
Copy link
Contributor

@bsudekum bsudekum commented Jul 20, 2017

todo:

  • update according to remaining segments on route
  • use color in route line
    simulator screen shot jul 20 2017 11 19 32 am

/cc @frederoni @1ec5

1ec5
1ec5 previously approved these changes Jul 20, 2017
Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

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))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@1ec5 how do you feel about this logic?

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Note, I'm looking at the overall route here but your point is still valid.)

@1ec5 1ec5 dismissed their stale review July 23, 2017 05:45

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@1ec5 how would this help this PR? We're not really too interested in the given step, rather the entire route.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Bobby Sudekum added 2 commits July 24, 2017 13:33
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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@1ec5 @frederoni It looks like this is only truthy once; on the first step. Could there be a rounding error?

Copy link
Contributor

Choose a reason for hiding this comment

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

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:).

@bsudekum
Copy link
Contributor Author

@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 }
Copy link
Contributor

Choose a reason for hiding this comment

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

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]
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: s/segements/segments/.

/**
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)]]] = []
Copy link
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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 coordinates and congestionTravelTimesSegmentsByStep that 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])
Copy link
Contributor

Choose a reason for hiding this comment

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

stepSegmentCongestionLevels and stepSegmentTravelTimes


/**
Sets an alternate color used throughout the UI to denote low traffic congestion. Not used to style the route line.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

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 }
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should assert that this is the case.


guard coordinatesRemainingOnStep <= segementsForStep.count else { return }

let currentStepCongestionTimes = segementsForStep.suffix(from: coordinatesRemainingOnStep)
Copy link
Contributor

Choose a reason for hiding this comment

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

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))
Copy link
Contributor

Choose a reason for hiding this comment

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

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()
Copy link
Contributor

Choose a reason for hiding this comment

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

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

@ericrwolfe ericrwolfe modified the milestone: v0.7.0-1 Jul 27, 2017
@bsudekum
Copy link
Contributor Author

Updated this, but I'm still off by 1 somewhere. The remaining step congestion times do not add up quite yet.

@ericrwolfe ericrwolfe modified the milestones: v0.7.0-1, v0.7.0-2 Aug 9, 2017
@bsudekum
Copy link
Contributor Author

Got this working 🎉 . @1ec5 ready for another round of review.

@ericrwolfe ericrwolfe removed this from the v0.7.0-2 milestone Aug 16, 2017
@ericrwolfe ericrwolfe modified the milestones: v0.7.0-3, v0.7.0-2 Aug 16, 2017
*/
public var currentLegProgress: RouteLegProgress!

public typealias TimedCongestionLevel = (CongestionLevel, TimeInterval)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this typealias is public and the method arguments that use it are also public, the typealias needs documentation.

Copy link
Contributor

@1ec5 1ec5 Aug 17, 2017

Choose a reason for hiding this comment

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

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])
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 answer this question?

let stepCoordinatesCount = Int(step.coordinateCount) + maneuverCoordinateIndex - 1

let congestionSegment = Array(segmentCongestionLevels[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex])
let travelTimeSegment = Array(expectedSegmentTravelTimes[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex])
Copy link
Contributor

Choose a reason for hiding this comment

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

let congestionSegment = Array(segmentCongestionLevels[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex])
let travelTimeSegment = Array(expectedSegmentTravelTimes[maneuverCoordinateIndex-stepIndex..<stepCoordinatesCount - stepIndex])

let zippedCongestionTimes = Array(zip(congestionSegment, travelTimeSegment))
Copy link
Contributor

Choose a reason for hiding this comment

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

@bsudekum
Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: “tupel”.

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`.
Copy link
Contributor

Choose a reason for hiding this comment

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

this array will contain an array of arrays of tuples of segmentCongestionLevels and expectedSegmentTravelTimes

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.

Bobby Sudekum added 2 commits August 23, 2017 09:16
@bsudekum bsudekum merged commit 5f38563 into master Aug 23, 2017
@bsudekum bsudekum deleted the eta-color branch August 23, 2017 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants