@@ -264,6 +264,7 @@ open class NavigationMapView: UIView {
264264 customRouteLineLayerPosition = layerPosition
265265
266266 applyRoutesDisplay ( layerPosition: layerPosition)
267+ updateIntersectionSignalsAlongRouteOnMap ( styleType: styleType)
267268 }
268269
269270 func applyRoutesDisplay( layerPosition: MapboxMaps . LayerPosition ? = nil ) {
@@ -351,6 +352,7 @@ open class NavigationMapView: UIView {
351352
352353 routes = nil
353354 removeLineGradientStops ( )
355+ removeIntersectionSignals ( )
354356 }
355357
356358 /**
@@ -1125,8 +1127,107 @@ open class NavigationMapView: UIView {
11251127 }
11261128 }
11271129
1130+ /**
1131+ Shows the intersection signals on current route.
1132+ */
1133+ public var showsIntersectionSignalsOnRoutes : Bool = false {
1134+ didSet {
1135+ updateIntersectionSignalsAlongRouteOnMap ( styleType: styleType)
1136+ }
1137+ }
1138+
1139+ /**
1140+ The style type of `NavigationMapView` during active navigation.
1141+ */
1142+ var styleType : StyleType = . day {
1143+ didSet {
1144+ updateIntersectionSignalsAlongRouteOnMap ( styleType: styleType)
1145+ }
1146+ }
1147+
11281148 private let continuousAlternativeDurationAnnotationOffset : LocationDistance = 75
11291149
1150+ func updateIntersectionSignalsAlongRouteOnMap( styleType: StyleType = . day) {
1151+ guard showsIntersectionSignalsOnRoutes, let route = routes? . first else {
1152+ removeIntersectionSignals ( )
1153+ return
1154+ }
1155+
1156+ do {
1157+ try updateIntersectionSymbolImages ( )
1158+ } catch {
1159+ Log . error ( " Error occured while updating intersection signal images: \( error. localizedDescription) . " ,
1160+ category: . navigationUI)
1161+ }
1162+
1163+ updateIntersectionSignals ( along: route, styleType: styleType)
1164+ }
1165+
1166+ private func signalFeature( from intersection: Intersection , styleType: StyleType ) -> Feature ? {
1167+ var properties : JSONObject ? = nil
1168+ let styleTypeString = ( styleType == . day) ? " Day " : " Night "
1169+ if intersection. railroadCrossing == true {
1170+ properties = [ " imageName " : . string( " RailroadCrossing " + styleTypeString) ]
1171+ }
1172+ if intersection. trafficSignal == true {
1173+ properties = [ " imageName " : . string( " TrafficSignal " + styleTypeString) ]
1174+ }
1175+
1176+ guard let properties = properties else { return nil }
1177+
1178+ var feature = Feature ( geometry: . point( Point ( intersection. location) ) )
1179+ feature. properties = properties
1180+ return feature
1181+
1182+ }
1183+
1184+ private func updateIntersectionSignals( along route: Route , styleType: StyleType ) {
1185+ var featureCollection = FeatureCollection ( features: [ ] )
1186+ for leg in route. legs {
1187+ for step in leg. steps {
1188+ guard let intersections = step. intersections else { continue }
1189+ for intersection in intersections {
1190+ guard let feature = signalFeature ( from: intersection, styleType: styleType) else { continue }
1191+ featureCollection. features. append ( feature)
1192+ }
1193+ }
1194+ }
1195+
1196+ let style = mapView. mapboxMap. style
1197+
1198+ do {
1199+ let sourceIdentifier = NavigationMapView . SourceIdentifier. intersectionSignalSource
1200+ if style. sourceExists ( withId: sourceIdentifier) {
1201+ try style. updateGeoJSONSource ( withId: sourceIdentifier, geoJSON: . featureCollection( featureCollection) )
1202+ } else {
1203+ var source = GeoJSONSource ( )
1204+ source. data = . featureCollection( featureCollection)
1205+ try style. addSource ( source, id: sourceIdentifier)
1206+ }
1207+
1208+ let layerIdentifier = NavigationMapView . LayerIdentifier. intersectionSignalLayer
1209+ var shapeLayer : SymbolLayer
1210+ if style. layerExists ( withId: layerIdentifier) ,
1211+ let symbolLayer = try style. layer ( withId: layerIdentifier) as? SymbolLayer {
1212+ shapeLayer = symbolLayer
1213+ } else {
1214+ shapeLayer = SymbolLayer ( id: layerIdentifier)
1215+ }
1216+
1217+ shapeLayer. source = sourceIdentifier
1218+ shapeLayer. iconAllowOverlap = . constant( false )
1219+ shapeLayer. iconImage = . expression( Exp ( . get) {
1220+ " imageName "
1221+ } )
1222+
1223+ let layerPosition = layerPosition ( for: layerIdentifier)
1224+ try style. addPersistentLayer ( shapeLayer, layerPosition: layerPosition)
1225+ } catch {
1226+ Log . error ( " Failed to perform operation while adding intersection signals with error: \( error. localizedDescription) . " ,
1227+ category: . navigationUI)
1228+ }
1229+ }
1230+
11301231 func showContinuousAlternativeRoutesDurations( ) {
11311232 // Remove any existing route annotation.
11321233 removeContinuousAlternativeRoutesDurations ( )
@@ -1338,6 +1439,15 @@ open class NavigationMapView: UIView {
13381439 style. removeSources ( [ NavigationMapView . SourceIdentifier. routeDurationAnnotationsSource] )
13391440 }
13401441
1442+ /**
1443+ Removes all intersection signals on current route.
1444+ */
1445+ func removeIntersectionSignals( ) {
1446+ let style = mapView. mapboxMap. style
1447+ style. removeLayers ( [ NavigationMapView . LayerIdentifier. intersectionSignalLayer] )
1448+ style. removeSources ( [ NavigationMapView . SourceIdentifier. intersectionSignalSource] )
1449+ }
1450+
13411451 /**
13421452 Removes all visible continuous alternative routes duration callouts.
13431453 */
@@ -1628,6 +1738,33 @@ open class NavigationMapView: UIView {
16281738 return pointAnnotationManager? . annotations. filter ( { $0. id == identifier } ) ?? [ ]
16291739 }
16301740
1741+ /**
1742+ Updates the image assets in the map style for the route intersection signals.
1743+ */
1744+ private func updateIntersectionSymbolImages( ) throws {
1745+ let style = mapView. mapboxMap. style
1746+
1747+ if style. image ( withId: ImageIdentifier . trafficSignalDay) == nil ,
1748+ let trafficSignlaDay = Bundle . mapboxNavigation. image ( named: " TrafficSignalDay " ) {
1749+ try style. addImage ( trafficSignlaDay, id: ImageIdentifier . trafficSignalDay)
1750+ }
1751+
1752+ if style. image ( withId: ImageIdentifier . trafficSignalNight) == nil ,
1753+ let trafficSignalNight = Bundle . mapboxNavigation. image ( named: " TrafficSignalNight " ) {
1754+ try style. addImage ( trafficSignalNight, id: ImageIdentifier . trafficSignalNight)
1755+ }
1756+
1757+ if style. image ( withId: ImageIdentifier . railroadCrossingDay) == nil ,
1758+ let railroadCrossingDay = Bundle . mapboxNavigation. image ( named: " RailroadCrossingDay " ) {
1759+ try style. addImage ( railroadCrossingDay, id: ImageIdentifier . railroadCrossingDay)
1760+ }
1761+
1762+ if style. image ( withId: ImageIdentifier . railroadCrossingNight) == nil ,
1763+ let railroadCrossingNight = Bundle . mapboxNavigation. image ( named: " RailroadCrossingNight " ) {
1764+ try style. addImage ( railroadCrossingNight, id: ImageIdentifier . railroadCrossingNight)
1765+ }
1766+ }
1767+
16311768 /**
16321769 Updates the image assets in the map style for the route duration annotations. Useful when the
16331770 desired callout colors change, such as when transitioning between light and dark mode on iOS 13 and later.
@@ -1827,6 +1964,7 @@ open class NavigationMapView: UIView {
18271964 route? . identifier ( . restrictedRouteAreaRoute)
18281965 ] . compactMap { $0 }
18291966 let arrowLayers : [ String ] = [
1967+ LayerIdentifier . intersectionSignalLayer,
18301968 LayerIdentifier . arrowStrokeLayer,
18311969 LayerIdentifier . arrowLayer,
18321970 LayerIdentifier . arrowSymbolCasingLayer,
0 commit comments