Skip to content

Commit

Permalink
Route Access Restrictions visualisation (#3603)
Browse files Browse the repository at this point in the history
* vk-844-access-restrictions: finalized drawing restrcited areas dash line; added constants for colors; added delegate methods for layer and shape configuration; included restrictions layer to layers handling pipeline; update calculating features, stops, road classes; added new route line types for layer identifier
* vk-844-access-restrictions: updated unit tests; improved restrictedRoadsFeatures calculations; refactored route line gradients stops calculation for congestion and restricted areas;
* vk-844-access-restrictions: CHANGELOG update.
  • Loading branch information
Udumft committed Dec 7, 2021
1 parent b9d9fb9 commit dd8ec59
Show file tree
Hide file tree
Showing 17 changed files with 504 additions and 74 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -83,6 +83,7 @@
* Added the `SpeechSynthesizing.managesAudioSession` property to control if the speech synthesizer is allowed to manage the shared `AVAudioSession`. Set this value to false if you want to enable and disable the `AVAudioSession` yourself, for example, if your app plays background music. ([#3572](https://github.com/mapbox/mapbox-navigation-ios/pull/3572))
* Fixed an issue when `SpeechSynthesizingDelegate.speechSynthesizer(_:willSpeak:)` callback was called at the wrong moment. ([#3572](https://github.com/mapbox/mapbox-navigation-ios/pull/3572))
* Fixed an issue where `RouteStepProgress.currentIntersection` was always returning invalid value, which in turn caused inability to correctly detect whether specific location along the route is in tunnel, or not. ([#3559](https://github.com/mapbox/mapbox-navigation-ios/pull/3559))
* Added `NavigationMapView.showsRestrictedAreasOnRoute` property which allows displaying on UI parts of a route which lie on restricted roads. This overlay is customisable through `NavigationMapView.routeRestrictedAreaColor`, `NavigationMapViewDelegate.navigationMapView(_:, restrictedAreasShapeFor:)` and `NavigationMapView.navigationMapView(_:, routeRestrictedAreasLineLayerWithIdentifier:, sourceIdentifier:)` methods. ([#3603](https://github.com/mapbox/mapbox-navigation-ios/pull/3603))
* Renamed the `Locale.usesMetric` property to `Locale.measuresDistancesInMetricUnits`. `Locale.usesMetric` is still available but deprecated. ([#3547](https://github.com/mapbox/mapbox-navigation-ios/pull/3547))
* Fixed an issue where the user interface did not necessarily display distances in the same units as the route by default. `NavigationRouteOptions` and `NavigationMatchOptions` now set `DirectionsOptions.distanceMeasurementSystem` to a default value matching the `NavigationSettings.distanceUnit` property. ([#3541](https://github.com/mapbox/mapbox-navigation-ios/pull/3541))
* Introduced `RoutingProvider` to parameterize routing fetching and refreshing during active guidance sessions. `Directions.calculateWithCache(options:completionHandler:)` and `Directions.calculateOffline(options:completionHandler)` functionality is deprecated by `MapboxRoutingProvider`. It is now recommended to use `MapboxRoutingProvider` to request or refresh routes instead of `Directions` object but you may also provide your own `RoutingProvider` implementation to `NavigationService`, `RouteController` or `LegacyRouteController`. Using `directions` property of listed above entities is discouraged, you should use corresponding `routingProvider` instead, albeit `Directions` also implements the protocol. ([#3261](https://github.com/mapbox/mapbox-navigation-ios/pull/3261))
Expand Down
1 change: 1 addition & 0 deletions Example/AppDelegate+CarPlay.swift
Expand Up @@ -94,6 +94,7 @@ extension AppDelegate: CarPlayManagerDelegate {

// Render part of the route that has been traversed with full transparency, to give the illusion of a disappearing route.
navigationViewController.routeLineTracksTraversal = true
navigationViewController.navigationMapView?.showsRestrictedAreasOnRoute = true

// Example of building highlighting in 3D.
navigationViewController.waypointStyle = .extrudedBuilding
Expand Down
2 changes: 2 additions & 0 deletions Example/ViewController.swift
Expand Up @@ -70,6 +70,7 @@ class ViewController: UIViewController {

// Show congestion levels on alternative route lines if there're multiple routes in the response.
navigationMapView.showsCongestionForAlternativeRoutes = true
navigationMapView.showsRestrictedAreasOnRoute = true
navigationMapView.show(prioritizedRoutes)
navigationMapView.showWaypoints(on: prioritizedRoutes.first!)
navigationMapView.showRouteDurations(along: prioritizedRoutes)
Expand Down Expand Up @@ -545,6 +546,7 @@ class ViewController: UIViewController {
present(navigationViewController, animated: true) {
completion?()
self.navigationMapView = nil
navigationViewController.navigationMapView?.showsRestrictedAreasOnRoute = true
}
}

Expand Down
38 changes: 26 additions & 12 deletions Sources/MapboxCoreNavigation/RouteLeg.swift
Expand Up @@ -8,24 +8,38 @@ extension RouteLeg {
}
}

/**
Returns an array of `MapboxStreetsRoadClass` objects for specific leg. `MapboxStreetsRoadClass` will be set to `nil` if it's not present in `Intersection`.
*/
public var streetsRoadClasses: [MapboxStreetsRoadClass?] {
func mapIntersectionsAttributes<T>(_ attributeTransform: (Intersection) -> T) -> [T] {
// Pick only valid segment indices for specific `Intersection` in `RouteStep`.
// Array of segment indexes can look like this: [0, 3, 24, 28, 48, 50, 51, 53].
let segmentIndices = steps.compactMap({ $0.segmentIndicesByIntersection?.compactMap({ $0 }) }).reduce([], +)

// Pick `MapboxStreetsRoadClass` in specific `Intersection` of `RouteStep`.
// It is expected that number of `segmentIndices` will be equal to number of `streetsRoadClassesInLeg`.
// Array of `MapboxStreetsRoadClass` can look like this: [Optional(motorway), ... , Optional(motorway), nil]
let streetsRoadClassesInLeg = steps.compactMap({ $0.intersections?.map({ $0.outletMapboxStreetsRoadClass }) }).reduce([], +)
// Pick selected attribute by `attributeTransform` in specific `Intersection` of `RouteStep`.
// It is expected that number of `segmentIndices` will be equal to number of `attributesInLeg`.
// Array may include optionals and nil values.
let attributesInLeg = steps.compactMap({ $0.intersections?.map(attributeTransform) }).reduce([], +)

// Map each `MapboxStreetsRoadClass` to the amount of two adjacent `segmentIndices`.
// At the end amount of `MapboxStreetsRoadClass` should be equal to the last segment index.
let streetsRoadClasses = segmentIndices.enumerated().map({ segmentIndices.indices.contains($0.offset + 1) && streetsRoadClassesInLeg.indices.contains($0.offset) ?
Array(repeating: streetsRoadClassesInLeg[$0.offset], count: segmentIndices[$0.offset + 1] - segmentIndices[$0.offset]) : [] }).reduce([], +)
// Map each selected attribute to the amount of two adjacent `segmentIndices`.
// At the end amount of attributes should be equal to the last segment index.
let streetsRoadClasses = segmentIndices.enumerated().map {
segmentIndices.indices.contains($0.offset + 1) && attributesInLeg.indices.contains($0.offset) ?
Array(repeating: attributesInLeg[$0.offset], count: segmentIndices[$0.offset + 1] - segmentIndices[$0.offset]) : []

}.reduce([], +)

return streetsRoadClasses
}

/**
Returns an array of `MapboxStreetsRoadClass` objects for specific leg. `MapboxStreetsRoadClass` will be set to `nil` if it's not present in `Intersection`.
*/
public var streetsRoadClasses: [MapboxStreetsRoadClass?] {
return mapIntersectionsAttributes { $0.outletMapboxStreetsRoadClass }
}

/**
Returns an array of `RoadClasses` objects for specific leg. `RoadClasses` will be set to `nil` if it's not present in `Intersection`.
*/
public var roadClasses: [RoadClasses?] {
return mapIntersectionsAttributes { $0.outletRoadClasses }
}
}
32 changes: 32 additions & 0 deletions Sources/MapboxNavigation/Array.swift
Expand Up @@ -100,6 +100,38 @@ extension Array where Iterator.Element == CLLocationCoordinate2D {
return segments
}

/**
Returns an array of road segments by associating road classes of corresponding line segments.
Adjacent segments with the same `combiningRoadClasses` will be merged together.
- parameter roadClasses: An array of `RoadClasses`along given segment. There should be one fewer congestion levels than coordinates.
- parameter combiningRoadClasses: `RoadClasses` which will be joined if they are neighbouring each other.
- returns: A list of `RoadClassesSegment` tuples with coordinate and road class.
*/
func combined(_ roadClasses: [RoadClasses?],
combiningRoadClasses: RoadClasses? = nil) -> [RoadClassesSegment] {
var segments: [RoadClassesSegment] = []
segments.reserveCapacity(roadClasses.count)

var index = 0
for (firstSegment, currentRoadClass) in zip(zip(self, self.suffix(from: 1)), roadClasses) {
let coordinates = [firstSegment.0, firstSegment.1]
var definedRoadClass = currentRoadClass ?? RoadClasses()
definedRoadClass = combiningRoadClasses?.intersection(definedRoadClass) ?? definedRoadClass

if segments.last?.1 == definedRoadClass {
segments[segments.count - 1].0 += [firstSegment.1]
} else {
segments.append((coordinates, definedRoadClass))
}

index += 1
}

return segments
}

func sliced(from: CLLocationCoordinate2D? = nil, to: CLLocationCoordinate2D? = nil) -> [CLLocationCoordinate2D] {
return LineString(self).sliced(from: from, to: to)?.coordinates ?? []
}
Expand Down
10 changes: 10 additions & 0 deletions Sources/MapboxNavigation/Constants.swift
Expand Up @@ -7,6 +7,11 @@ import MapboxDirections
*/
typealias CongestionSegment = ([CLLocationCoordinate2D], CongestionLevel)

/**
A tuple that pairs an array of coordinates with assigned road classes along these coordinates.
*/
typealias RoadClassesSegment = ([CLLocationCoordinate2D], RoadClasses)

/**
A stop dictionary representing the default line widths of the route line by zoom level when `NavigationMapViewDelegate.navigationMapView(_:routeLineLayerWithIdentifier:sourceIdentifier:)` is undefined.
Expand All @@ -26,6 +31,11 @@ public var RouteLineWidthByZoomLevel: [Double: Double] = [
@available(*, deprecated, message: "This value is no longer used.")
public var NavigationMapViewMinimumDistanceForOverheadZooming: CLLocationDistance = 200

/**
Attribute name for the route line that is used for identifying restricted areas along the route.
*/
public let RestrictedRoadClassAttribute = "isRestrictedRoad"

/**
Attribute name for the route line that is used for identifying whether a RouteLeg is the current active leg.
*/
Expand Down
2 changes: 2 additions & 0 deletions Sources/MapboxNavigation/DayStyle.swift
Expand Up @@ -36,6 +36,8 @@ extension UIColor {
class var alternativeTrafficSevere: UIColor { get { return #colorLiteral(red: 0.71, green: 0.51, blue: 0.51, alpha: 1.0) } }
class var defaultBuildingColor: UIColor { get { return #colorLiteral(red: 0.9833194452, green: 0.9843137255, blue: 0.9331936657, alpha: 0.8019049658) } }
class var defaultBuildingHighlightColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 0.949406036) } }

class var defaultRouteRestrictedAreaColor: UIColor { get { return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)} }

class var routeDurationAnnotationColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } }
class var selectedRouteDurationAnnotationColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } }
Expand Down

0 comments on commit dd8ec59

Please sign in to comment.