Skip to content

Commit

Permalink
add the ability to generate route layer based on featurecollection an…
Browse files Browse the repository at this point in the history
…d overide route color based on leg
  • Loading branch information
ShanMa1991 committed Mar 4, 2021
1 parent 59e7ba4 commit f17996b
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 23 deletions.
4 changes: 2 additions & 2 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -558,11 +558,11 @@ extension ViewController: NavigationMapViewDelegate {
return lineLayer
}

func navigationMapView(_ navigationMapView: NavigationMapView, shapeFor route: Route) -> LineString? {
func navigationMapView(_ navigationMapView: NavigationMapView, shapeFor route: Route) -> FeatureCollection? {
return nil
}

func navigationMapView(_ navigationMapView: NavigationMapView, casingShapeFor route: Route) -> LineString? {
func navigationMapView(_ navigationMapView: NavigationMapView, casingShapeFor route: Route) -> FeatureCollection? {
return nil
}
}
Expand Down
94 changes: 85 additions & 9 deletions Sources/MapboxNavigation/NavigationMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,53 @@ open class NavigationMapView: UIView {
initPrimaryRoutePoints(route: route)
}

parentLayerIdentifier = addRouteLayer(route, below: parentLayerIdentifier, isMainRoute: index == 0)
parentLayerIdentifier = addRouteCasingLayer(route, below: parentLayerIdentifier, isMainRoute: index == 0)
parentLayerIdentifier = addRouteLayer(route, below: parentLayerIdentifier, isMainRoute: index == 0, legIndex: legIndex)
parentLayerIdentifier = addRouteCasingLayer(route, below: parentLayerIdentifier, isMainRoute: index == 0, legIndex: legIndex)
}
}

@discardableResult func addRouteLayer(_ route: Route, below parentLayerIndentifier: String? = nil, isMainRoute: Bool = true) -> String? {
guard let shape = route.shape else { return nil }
func shape(for route: Route, legIndex: Int?, isMainRoute: Bool = true) -> FeatureCollection? {
if isMainRoute {
let routeFeatures = route.congestionFeatures(legIndex: legIndex, roadClassesWithOverriddenCongestionLevels: roadClassesWithOverriddenCongestionLevels)
return FeatureCollection(features: routeFeatures)
} else {
if let shape = route.shape {
let routeFeature = Feature(shape)
return FeatureCollection(features: [routeFeature])
}
}
return nil
}

func shape(forCasingOf route: Route, legIndex: Int?, isMainRoute: Bool = true) -> FeatureCollection? {
if isMainRoute {
var casingFeatures = [Feature]()
for (index, leg) in route.legs.enumerated() {
let legCoordinates: [CLLocationCoordinate2D] = leg.steps.enumerated().reduce([]) { allCoordinates, current in
let index = current.offset
let step = current.element
let stepCoordinates = step.shape!.coordinates
return index == 0 ? stepCoordinates : allCoordinates + stepCoordinates.suffix(from: 1)
}
var feature = Feature(LineString(legCoordinates))
feature.properties = [CurrentLegAttribute: (legIndex != nil) ? index == legIndex : index == 0]
casingFeatures.append(feature)
}
return FeatureCollection(features:casingFeatures)
} else {
if let shape = route.shape {
let routeFeature = Feature(shape)
return FeatureCollection(features: [routeFeature])
}
}
return nil
}

@discardableResult func addRouteLayer(_ route: Route, below parentLayerIndentifier: String? = nil, isMainRoute: Bool = true, legIndex: Int) -> String? {
guard let routeFeatures = delegate?.navigationMapView(self, shapeFor: route) ?? self.shape(for: route, legIndex: legIndex, isMainRoute: isMainRoute) else { return nil }

var geoJSONSource = GeoJSONSource()
geoJSONSource.data = .geometry(.lineString(shape))
geoJSONSource.data = .featureCollection(routeFeatures)
geoJSONSource.lineMetrics = true

let sourceIdentifier = route.identifier(.source(isMainRoute: isMainRoute, isSourceCasing: true))
Expand All @@ -476,17 +513,36 @@ open class NavigationMapView: UIView {
if lineLayer == nil {
lineLayer = LineLayer(id: layerIdentifier)
lineLayer?.source = sourceIdentifier
lineLayer?.filter = Exp(.eq) {
"$type"
"LineString"
}
lineLayer?.paint?.lineColor = .constant(.init(color: trafficUnknownColor))
lineLayer?.paint?.lineWidth = .expression(Expression.routeLineWidthExpression())
lineLayer?.layout?.lineJoin = .round
lineLayer?.layout?.lineCap = .round

if isMainRoute {
let gradientStops = routeLineGradient(route.congestionFeatures(roadClassesWithOverriddenCongestionLevels: roadClassesWithOverriddenCongestionLevels),
let gradientStops = routeLineGradient(routeFeatures.features,
fractionTraveled: routeLineTracksTraversal ? fractionTraveled : 0.0)
lineLayer?.paint?.lineGradient = .expression((Expression.routeLineGradientExpression(gradientStops)))
let opacityExpression = Exp(.switchCase) {
Exp(.eq) {
Exp(.get) { CurrentLegAttribute }
true
}
1.0
Exp(.eq) {
Exp(.get) { CurrentLegAttribute }
false
}
0.0
1.0
}
lineLayer?.paint?.lineOpacity = .expression(opacityExpression)
} else {
lineLayer?.paint?.lineColor = .constant(.init(color: routeAlternateColor))
lineLayer?.paint?.lineOpacity = .constant(1.0)
}
}

Expand All @@ -500,11 +556,11 @@ open class NavigationMapView: UIView {
return layerIdentifier
}

@discardableResult func addRouteCasingLayer(_ route: Route, below parentLayerIndentifier: String? = nil, isMainRoute: Bool = true) -> String? {
guard let shape = route.shape else { return nil }
@discardableResult func addRouteCasingLayer(_ route: Route, below parentLayerIndentifier: String? = nil, isMainRoute: Bool = true, legIndex: Int) -> String? {
guard let casingFeatures = delegate?.navigationMapView(self, shapeFor: route) ?? self.shape(forCasingOf: route, legIndex: legIndex, isMainRoute: isMainRoute) else { return nil }

var geoJSONSource = GeoJSONSource()
geoJSONSource.data = .geometry(.lineString(shape))
geoJSONSource.data = .featureCollection(casingFeatures)
geoJSONSource.lineMetrics = true

let sourceIdentifier = route.identifier(.source(isMainRoute: isMainRoute, isSourceCasing: isMainRoute))
Expand All @@ -518,6 +574,11 @@ open class NavigationMapView: UIView {
if lineLayer == nil {
lineLayer = LineLayer(id: layerIdentifier)
lineLayer?.source = sourceIdentifier
lineLayer?.filter = Exp(.eq){
"$type"
"LineString"
}

lineLayer?.paint?.lineColor = .constant(.init(color: routeCasingColor))
lineLayer?.paint?.lineWidth = .expression(Expression.routeLineWidthExpression(1.5))
lineLayer?.layout?.lineJoin = .round
Expand All @@ -526,8 +587,23 @@ open class NavigationMapView: UIView {
if isMainRoute {
let gradientStops = routeLineGradient(fractionTraveled: routeLineTracksTraversal ? fractionTraveled : 0.0)
lineLayer?.paint?.lineGradient = .expression(Expression.routeLineGradientExpression(gradientStops))
let opacityExpression = Exp(.switchCase) {
Exp(.eq) {
Exp(.get) { CurrentLegAttribute }
true
}
1.0
Exp(.eq) {
Exp(.get) { CurrentLegAttribute }
false
}
0.85
1.0
}
lineLayer?.paint?.lineOpacity = .expression(opacityExpression)
} else {
lineLayer?.paint?.lineColor = .constant(.init(color: routeAlternateCasingColor))
lineLayer?.paint?.lineOpacity = .constant(1.0)
}
}

Expand Down
18 changes: 9 additions & 9 deletions Sources/MapboxNavigation/NavigationMapViewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ public protocol NavigationMapViewDelegate: class, UnimplementedLogging {
/**
Asks the receiver to return an `LineString` that describes the geometry of the route.
Resulting `LineString` will then be styled using `NavigationMapView.navigationMapView(_:routeLineLayerWithIdentifier:sourceIdentifier:)` provided style or a default congestion style if above delegate method was not implemented.
Resulting `FeatureCollection` will then be styled using `NavigationMapView.navigationMapView(_:routeLineLayerWithIdentifier:sourceIdentifier:)` provided style or a default congestion style if above delegate method was not implemented.
- parameter navigationMapView: The NavigationMapView.
- parameter route: The route that the sender is asking about.
- returns: A `LineString` object that defines the shape of the route, or `nil` in case of default behavior.
- returns: A `FeatureCollection` object that defines the shape and properties of the route, or `nil` in case of default behavior.
*/
func navigationMapView(_ navigationMapView: NavigationMapView, shapeFor route: Route) -> LineString?
func navigationMapView(_ navigationMapView: NavigationMapView, shapeFor route: Route) -> FeatureCollection?

/**
Asks the receiver to return an `LineString` that describes the geometry of the route casing.
Asks the receiver to return an `FeatureCollection` that describes the geometry of the route casing.
Resulting `LineString` will then be styled using `NavigationMapView.navigationMapView(_:routeCasingLineLayerWithIdentifier:sourceIdentifier:)` provided style or a default style if above delegate method was not implemented.
Resulting `FeatureCollection` will then be styled using `NavigationMapView.navigationMapView(_:routeCasingLineLayerWithIdentifier:sourceIdentifier:)` provided style or a default style if above delegate method was not implemented.
- parameter navigationMapView: The NavigationMapView.
- parameter route: The route that the sender is asking about.
- returns: A `LineString` object that defines the shape of the route casing, or `nil` in case of default behavior.
- returns: A `FeatureCollection` object that defines the shape and properties of the route casing, or `nil` in case of default behavior.
*/
func navigationMapView(_ navigationMapView: NavigationMapView, casingShapeFor route: Route) -> LineString?
func navigationMapView(_ navigationMapView: NavigationMapView, casingShapeFor route: Route) -> FeatureCollection?

/**
Asks the receiver to return a `CircleLayer` for waypoints, given an identifier and source.
Expand Down Expand Up @@ -145,15 +145,15 @@ public extension NavigationMapViewDelegate {
/**
`UnimplementedLogging` prints a warning to standard output the first time this method is called.
*/
func navigationMapView(_ navigationMapView: NavigationMapView, shapeFor route: Route) -> LineString? {
func navigationMapView(_ navigationMapView: NavigationMapView, shapeFor route: Route) -> FeatureCollection? {
logUnimplemented(protocolType: NavigationMapViewDelegate.self, level: .debug)
return nil
}

/**
`UnimplementedLogging` prints a warning to standard output the first time this method is called.
*/
func navigationMapView(_ navigationMapView: NavigationMapView, casingShapeFor route: Route) -> LineString? {
func navigationMapView(_ navigationMapView: NavigationMapView, casingShapeFor route: Route) -> FeatureCollection? {
logUnimplemented(protocolType: NavigationMapViewDelegate.self, level: .debug)
return nil
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/MapboxNavigation/NavigationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,14 @@ extension NavigationViewController: RouteMapViewControllerDelegate {
delegate?.navigationViewController(self, shapeFor: waypoints, legIndex: legIndex)
}

public func navigationMapView(_ mapView: NavigationMapView, shapeFor route: Route) -> FeatureCollection? {
return delegate?.navigationViewController(self, shapeFor: route)
}

public func navigationMapView(_ mapView: NavigationMapView, casingShapeFor route: Route) -> FeatureCollection? {
return delegate?.navigationViewController(self, casingShapeFor: route)
}

public func navigationMapView(_ navigationMapView: NavigationMapView, didSelect route: Route) {
delegate?.navigationViewController(self, didSelect: route)
}
Expand Down
30 changes: 30 additions & 0 deletions Sources/MapboxNavigation/NavigationViewControllerDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ public protocol NavigationViewControllerDelegate: VisualInstructionDelegate {
*/
func navigationViewController(_ navigationViewController: NavigationViewController, routeCasingLineLayerWithIdentifier identifier: String, sourceIdentifier: String) -> LineLayer?

/**
Returns an `FeatureCollection` that represents the path of the routes line.
If this method is unimplemented, the navigation view controller’s map view represents the route line using an `FeatureCollection` based on `route`’s `coordinates` property.
*/
func navigationViewController(_ navigationViewController: NavigationViewController, shapeFor route: Route) -> FeatureCollection?

/**
Returns an `FeatureCollection` that represents the path of the route line’s casing.
If this method is unimplemented, the navigation view controller’s map view represents the route line’s casing using an `FeatureCollection` identical to the one returned by `navigationViewController(_:shapeFor:)`.
*/
func navigationViewController(_ navigationViewController: NavigationViewController, casingShapeFor route: Route) -> FeatureCollection?

/**
Called when the user taps to select a route on the navigation view controller’s map view.
- parameter navigationViewController: The navigation view controller presenting the route that the user selected.
Expand Down Expand Up @@ -280,6 +294,22 @@ public extension NavigationViewControllerDelegate {
logUnimplemented(protocolType: NavigationViewControllerDelegate.self, level: .debug)
return nil
}

/**
`UnimplementedLogging` prints a warning to standard output the first time this method is called.
*/
func navigationViewController(_ navigationViewController: NavigationViewController, shapeFor route: Route) -> FeatureCollection? {
logUnimplemented(protocolType: NavigationViewControllerDelegate.self, level: .debug)
return nil
}

/**
`UnimplementedLogging` prints a warning to standard output the first time this method is called.
*/
func navigationViewController(_ navigationViewController: NavigationViewController, casingShapeFor route: Route) -> FeatureCollection? {
logUnimplemented(protocolType: NavigationViewControllerDelegate.self, level: .debug)
return nil
}

/**
`UnimplementedLogging` prints a warning to standard output the first time this method is called.
Expand Down
3 changes: 0 additions & 3 deletions Sources/MapboxNavigation/Route.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ extension Route {
}

func congestionFeatures(legIndex: Int? = nil,
isAlternativeRoute: Bool = false,
roadClassesWithOverriddenCongestionLevels: Set<MapboxStreetsRoadClass>? = nil) -> [Feature] {
guard let coordinates = shape?.coordinates, let shape = shape else { return [] }
var features: [Feature] = []
Expand All @@ -70,7 +69,6 @@ extension Route {
var feature = Feature(LineString(congestionSegment.0))
feature.properties = [
CongestionAttribute: String(describing: congestionSegment.1),
"isAlternativeRoute": isAlternativeRoute,
CurrentLegAttribute: (legIndex != nil) ? index == legIndex : index == 0
]

Expand All @@ -79,7 +77,6 @@ extension Route {
} else {
var feature = Feature(LineString(shape.coordinates))
feature.properties = [
"isAlternativeRoute": isAlternativeRoute,
CurrentLegAttribute: (legIndex != nil) ? index == legIndex : index == 0
]
legFeatures = [feature]
Expand Down
8 changes: 8 additions & 0 deletions Sources/MapboxNavigation/RouteMapViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,14 @@ extension RouteMapViewController: NavigationViewDelegate {
return delegate?.navigationMapView(navigationMapView, routeCasingLineLayerWithIdentifier: identifier, sourceIdentifier: sourceIdentifier)
}

func navigationMapView(_ mapView: NavigationMapView, shapeFor route: Route) -> FeatureCollection? {
return delegate?.navigationMapView(mapView, shapeFor: route)
}

func navigationMapView(_ mapView: NavigationMapView, casingShapeFor route: Route) -> FeatureCollection? {
return delegate?.navigationMapView(mapView, casingShapeFor: route)
}

func navigationMapView(_ navigationMapView: NavigationMapView, didSelect route: Route) {
delegate?.navigationMapView(navigationMapView, didSelect: route)
}
Expand Down

0 comments on commit f17996b

Please sign in to comment.