Skip to content

Commit

Permalink
2284: added basic implementation for calling Route Refresh. Experimen…
Browse files Browse the repository at this point in the history
…tal idea about RouteProgress route refreshing.
  • Loading branch information
Victor Kononov authored and 1ec5 committed Sep 3, 2020
1 parent bfed4ac commit 6103287
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 42 deletions.
2 changes: 1 addition & 1 deletion MapboxCoreNavigation/CoreConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public var RouteControllerNumberOfSecondsForRerouteFeedback: TimeInterval = 10
public var RouteControllerMinimumDurationRemainingForProactiveRerouting: TimeInterval = 600

/**
The number of seconds between attempts to automatically calculate a more optimal route while traveling.
The number of seconds between attempts to automatically calculate a more optimal route while traveling. During such attempt, if Route Refresh is enabled, route will also be refreshed to verify correct ETA and congestion.
*/
public var RouteControllerProactiveReroutingInterval: TimeInterval = 120

Expand Down
8 changes: 7 additions & 1 deletion MapboxCoreNavigation/LegacyRouteController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
public var waypointArrivalThreshold: TimeInterval = 5.0

public var reroutesProactively = true

public var refreshesRoute: Bool = true

var didFindFasterRoute = false

var lastProactiveRerouteDate: Date?

var lastRouteRefresh: Date?

public var routeProgress: RouteProgress {
get {
Expand Down Expand Up @@ -63,6 +67,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
}

var isRerouting = false
var isRefreshing = false
var lastRerouteLocation: CLLocation?

var routeTask: URLSessionDataTask?
Expand All @@ -82,6 +87,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
self.directions = directions
self._routeProgress = RouteProgress(route: route, options: options)
self.dataSource = source
self.refreshesRoute = options.profileIdentifier == .automobileAvoidingTraffic && options.refreshingEnabled
UIDevice.current.isBatteryMonitoringEnabled = true

super.init()
Expand Down Expand Up @@ -276,7 +282,7 @@ open class LegacyRouteController: NSObject, Router, InternalRouter, CLLocationMa
updateSpokenInstructionProgress()

// Check for faster route proactively (if reroutesProactively is enabled)
checkForFasterRoute(from: location, routeProgress: routeProgress)
refreshAndCheckForFasterRoute(from: location, routeProgress: routeProgress)
}

private func update(progress: RouteProgress, with location: CLLocation, rawLocation: CLLocation) {
Expand Down
9 changes: 8 additions & 1 deletion MapboxCoreNavigation/RouteController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ open class RouteController: NSObject {

var isRerouting = false

var isRefreshing = false

var userSnapToStepDistanceFromManeuver: CLLocationDistance?

var previousArrivalWaypoint: Waypoint?
Expand Down Expand Up @@ -110,6 +112,10 @@ open class RouteController: NSObject {

var lastProactiveRerouteDate: Date?

var lastRouteRefresh: Date?

public var refreshesRoute: Bool = true

/**
The route controller’s delegate.
*/
Expand Down Expand Up @@ -137,6 +143,7 @@ open class RouteController: NSObject {
self.directions = directions
self._routeProgress = RouteProgress(route: route, options: options)
self.dataSource = source
self.refreshesRoute = options.profileIdentifier == .automobileAvoidingTraffic && options.refreshingEnabled
UIDevice.current.isBatteryMonitoringEnabled = true

super.init()
Expand Down Expand Up @@ -211,7 +218,7 @@ open class RouteController: NSObject {
}

// Check for faster route proactively (if reroutesProactively is enabled)
checkForFasterRoute(from: location, routeProgress: routeProgress)
refreshAndCheckForFasterRoute(from: location, routeProgress: routeProgress)
}

func updateIndexes(status: NavigationStatus, progress: RouteProgress) {
Expand Down
86 changes: 51 additions & 35 deletions MapboxCoreNavigation/RouteProgress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ open class RouteProgress {
/**
Returns the current `Route`.
*/
public let route: Route
public private(set) var route: Route

public let routeOptions: RouteOptions

Expand Down Expand Up @@ -182,40 +182,7 @@ open class RouteProgress {
self.legIndex = legIndex
self.currentLegProgress = RouteLegProgress(leg: route.legs[legIndex], stepIndex: 0, spokenInstructionIndex: spokenInstructionIndex)

for (legIndex, leg) in route.legs.enumerated() {
var maneuverCoordinateIndex = 0

congestionTimesPerStep.append([])

/// An index into the route’s coordinates and congestionTravelTimesSegmentsByStep that corresponds to a step’s maneuver location.
var congestionTravelTimesSegmentsByLeg: [[TimedCongestionLevel]] = []

if let segmentCongestionLevels = leg.segmentCongestionLevels, let expectedSegmentTravelTimes = leg.expectedSegmentTravelTimes {
for step in leg.steps {
guard let coordinates = step.shape?.coordinates else { continue }
let stepCoordinateCount = step.maneuverType == .arrive ? Int(coordinates.count) : coordinates.dropLast().count
let nextManeuverCoordinateIndex = maneuverCoordinateIndex + stepCoordinateCount - 1

guard nextManeuverCoordinateIndex < segmentCongestionLevels.count else { continue }
guard nextManeuverCoordinateIndex < expectedSegmentTravelTimes.count else { continue }

let stepSegmentCongestionLevels = Array(segmentCongestionLevels[maneuverCoordinateIndex..<nextManeuverCoordinateIndex])
let stepSegmentTravelTimes = Array(expectedSegmentTravelTimes[maneuverCoordinateIndex..<nextManeuverCoordinateIndex])
maneuverCoordinateIndex = nextManeuverCoordinateIndex

let stepTimedCongestionLevels = Array(zip(stepSegmentCongestionLevels, stepSegmentTravelTimes))
congestionTravelTimesSegmentsByLeg.append(stepTimedCongestionLevels)
var stepCongestionValues: [CongestionLevel: TimeInterval] = [:]
for (segmentCongestion, segmentTime) in stepTimedCongestionLevels {
stepCongestionValues[segmentCongestion] = (stepCongestionValues[segmentCongestion] ?? 0) + segmentTime
}

congestionTimesPerStep[legIndex].append(stepCongestionValues)
}
}

congestionTravelTimesSegmentsByStep.append(congestionTravelTimesSegmentsByLeg)
}
self.calculateLegsCongestion()
}

public var averageCongestionLevelRemainingOnLeg: CongestionLevel? {
Expand Down Expand Up @@ -272,6 +239,55 @@ open class RouteProgress {

return newOptions
}

func calculateLegsCongestion() {
congestionTimesPerStep.removeAll()
congestionTravelTimesSegmentsByStep.removeAll()

for (legIndex, leg) in route.legs.enumerated() {
var maneuverCoordinateIndex = 0

congestionTimesPerStep.append([])

/// An index into the route’s coordinates and congestionTravelTimesSegmentsByStep that corresponds to a step’s maneuver location.
var congestionTravelTimesSegmentsByLeg: [[TimedCongestionLevel]] = []

if let segmentCongestionLevels = leg.segmentCongestionLevels, let expectedSegmentTravelTimes = leg.expectedSegmentTravelTimes {
for step in leg.steps {
guard let coordinates = step.shape?.coordinates else { continue }
let stepCoordinateCount = step.maneuverType == .arrive ? Int(coordinates.count) : coordinates.dropLast().count
let nextManeuverCoordinateIndex = maneuverCoordinateIndex + stepCoordinateCount - 1

guard nextManeuverCoordinateIndex < segmentCongestionLevels.count else { continue }
guard nextManeuverCoordinateIndex < expectedSegmentTravelTimes.count else { continue }

let stepSegmentCongestionLevels = Array(segmentCongestionLevels[maneuverCoordinateIndex..<nextManeuverCoordinateIndex])
let stepSegmentTravelTimes = Array(expectedSegmentTravelTimes[maneuverCoordinateIndex..<nextManeuverCoordinateIndex])
maneuverCoordinateIndex = nextManeuverCoordinateIndex

let stepTimedCongestionLevels = Array(zip(stepSegmentCongestionLevels, stepSegmentTravelTimes))
congestionTravelTimesSegmentsByLeg.append(stepTimedCongestionLevels)
var stepCongestionValues: [CongestionLevel: TimeInterval] = [:]
for (segmentCongestion, segmentTime) in stepTimedCongestionLevels {
stepCongestionValues[segmentCongestion] = (stepCongestionValues[segmentCongestion] ?? 0) + segmentTime
}

congestionTimesPerStep[legIndex].append(stepCongestionValues)
}
}

congestionTravelTimesSegmentsByStep.append(congestionTravelTimesSegmentsByLeg)
}
}

public func refreshRoute(with refreshedRoute: Route) {
route = refreshedRoute
currentLegProgress = RouteLegProgress(leg: route.legs[legIndex],
stepIndex: currentLegProgress.stepIndex,
spokenInstructionIndex: currentLegProgress.currentStepProgress.spokenInstructionIndex)

calculateLegsCongestion()
}
}

/**
Expand Down
72 changes: 68 additions & 4 deletions MapboxCoreNavigation/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,17 @@ public protocol Router: class, CLLocationManagerDelegate {
var rawLocation: CLLocation? { get }

/**
If true, the `RouteController` attempts to calculate a more optimal route for the user on an interval defined by `RouteControllerProactiveReroutingInterval`.
If true, the `RouteController` attempts to calculate a more optimal route for the user on an interval defined by `RouteControllerProactiveReroutingInterval`. If `refreshesRoute` is enabled too, reroute attempt will be fired after route refreshing.
*/
var reroutesProactively: Bool { get set }

/**
If true, the `RouteController` attempts to update ETA and route congestion on an interval defined by `RouteControllerProactiveReroutingInterval`.

Refreshing will be used only if route's mode of transportation profile is set to `.automobileAvoidingTraffic`. If `reroutesProactively` is enabled too, rerouting will be checked after route is refreshed.
*/
var refreshesRoute: Bool { get set }

/**
Advances the leg index.

Expand All @@ -83,6 +90,8 @@ public protocol Router: class, CLLocationManagerDelegate {
protocol InternalRouter: class {
var lastProactiveRerouteDate: Date? { get set }

var lastRouteRefresh: Date? { get set }

var routeTask: URLSessionDataTask? { get set }

var didFindFasterRoute: Bool { get set }
Expand All @@ -93,12 +102,68 @@ protocol InternalRouter: class {

var isRerouting: Bool { get set }

var isRefreshing: Bool { get set }

var directions: Directions { get }

var routeProgress: RouteProgress { get set }
}

extension InternalRouter where Self: Router {

func refreshAndCheckForFasterRoute(from location: CLLocation, routeProgress: RouteProgress) {
if refreshesRoute {
refreshRoute(from: location, legIndex: routeProgress.legIndex) {
self.checkForFasterRoute(from: location, routeProgress: routeProgress)
}
} else {
checkForFasterRoute(from: location, routeProgress: routeProgress)
}
}

func refreshRoute(from location: CLLocation, legIndex: Int, completion: @escaping ()->()) {
guard refreshesRoute else {
completion()
return
}

guard let lastRouteRefresh = lastRouteRefresh else {
self.lastRouteRefresh = location.timestamp
completion()
return
}

guard location.timestamp.timeIntervalSince(lastRouteRefresh) >= RouteControllerProactiveReroutingInterval else {
completion()
return
}

if isRefreshing {
completion()
return
}
isRefreshing = true

directions.refresh(route: route,
currentLegIndex: legIndex,
completionHandler: { [weak self] (session, result) in
defer {
self?.isRefreshing = false
self?.lastRouteRefresh = nil
completion()
}

guard case let .success(response) = result else {
return
}

guard let route = response.route else {
return
}
self?.routeProgress.refreshRoute(with: route)
})
}

func checkForFasterRoute(from location: CLLocation, routeProgress: RouteProgress) {
// Check for faster route given users current location
guard reroutesProactively else { return }
Expand All @@ -112,13 +177,13 @@ extension InternalRouter where Self: Router {
return
}

guard let lastProactiveRerouteDate = lastProactiveRerouteDate else {
guard let lastRouteValidationDate = lastProactiveRerouteDate else {
self.lastProactiveRerouteDate = location.timestamp
return
}

// Only check every so often for a faster route.
guard location.timestamp.timeIntervalSince(lastProactiveRerouteDate) >= RouteControllerProactiveReroutingInterval else {
guard location.timestamp.timeIntervalSince(lastRouteValidationDate) >= RouteControllerProactiveReroutingInterval else {
return
}

Expand Down Expand Up @@ -162,7 +227,6 @@ extension InternalRouter where Self: Router {
guard case let .success(response) = result else {
return completion(session, result)
}


guard let mostSimilar = response.routes?.mostSimilar(to: progress.route) else {
return completion(session, result)
Expand Down

0 comments on commit 6103287

Please sign in to comment.