Skip to content

Commit 611541a

Browse files
committed
Add the intersection signals along current route.
1 parent de2a854 commit 611541a

File tree

13 files changed

+239
-0
lines changed

13 files changed

+239
-0
lines changed

Sources/MapboxNavigation/NavigationMapView.swift

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
@@ -1639,6 +1749,33 @@ open class NavigationMapView: UIView {
16391749
return pointAnnotationManager?.annotations.filter({ $0.id == identifier }) ?? []
16401750
}
16411751

1752+
/**
1753+
Updates the image assets in the map style for the route intersection signals.
1754+
*/
1755+
private func updateIntersectionSymbolImages() throws {
1756+
let style = mapView.mapboxMap.style
1757+
1758+
if style.image(withId: ImageIdentifier.trafficSignalDay) == nil,
1759+
let trafficSignlaDay = Bundle.mapboxNavigation.image(named: "TrafficSignalDay") {
1760+
try style.addImage(trafficSignlaDay, id: ImageIdentifier.trafficSignalDay)
1761+
}
1762+
1763+
if style.image(withId: ImageIdentifier.trafficSignalNight) == nil,
1764+
let trafficSignalNight = Bundle.mapboxNavigation.image(named: "TrafficSignalNight") {
1765+
try style.addImage(trafficSignalNight, id: ImageIdentifier.trafficSignalNight)
1766+
}
1767+
1768+
if style.image(withId: ImageIdentifier.railroadCrossingDay) == nil,
1769+
let railroadCrossingDay = Bundle.mapboxNavigation.image(named: "RailroadCrossingDay") {
1770+
try style.addImage(railroadCrossingDay, id: ImageIdentifier.railroadCrossingDay)
1771+
}
1772+
1773+
if style.image(withId: ImageIdentifier.railroadCrossingNight) == nil,
1774+
let railroadCrossingNight = Bundle.mapboxNavigation.image(named: "RailroadCrossingNight") {
1775+
try style.addImage(railroadCrossingNight, id: ImageIdentifier.railroadCrossingNight)
1776+
}
1777+
}
1778+
16421779
/**
16431780
Updates the image assets in the map style for the route duration annotations. Useful when the
16441781
desired callout colors change, such as when transitioning between light and dark mode on iOS 13 and later.
@@ -1838,6 +1975,7 @@ open class NavigationMapView: UIView {
18381975
route?.identifier(.restrictedRouteAreaRoute)
18391976
].compactMap{ $0 }
18401977
let arrowLayers: [String] = [
1978+
LayerIdentifier.intersectionSignalLayer,
18411979
LayerIdentifier.arrowStrokeLayer,
18421980
LayerIdentifier.arrowLayer,
18431981
LayerIdentifier.arrowSymbolCasingLayer,

Sources/MapboxNavigation/NavigationMapViewIdentifiers.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extension NavigationMapView {
1111
static let arrowSymbolCasingLayer = "\(identifier)_arrowSymbolCasingLayer"
1212
static let voiceInstructionLabelLayer = "\(identifier)_voiceInstructionLabelLayer"
1313
static let voiceInstructionCircleLayer = "\(identifier)_voiceInstructionCircleLayer"
14+
static let intersectionSignalLayer = "\(identifier)_trafficLayer"
1415
static let waypointCircleLayer = "\(identifier)_waypointCircleLayer"
1516
static let waypointSymbolLayer = "\(identifier)_waypointSymbolLayer"
1617
static let buildingExtrusionLayer = "\(identifier)_buildingExtrusionLayer"
@@ -25,6 +26,7 @@ extension NavigationMapView {
2526
static let arrowStrokeSource = "\(identifier)_arrowStrokeSource"
2627
static let arrowSymbolSource = "\(identifier)_arrowSymbolSource"
2728
static let voiceInstructionSource = "\(identifier)_instructionSource"
29+
static let intersectionSignalSource = "\(identifier)_trafficSource"
2830
static let waypointSource = "\(identifier)_waypointSource"
2931
static let routeDurationAnnotationsSource: String = "\(identifier)_routeDurationAnnotationsSource"
3032
static let continuousAlternativeRoutesDurationAnnotationsSource: String = "\(identifier)_continuousAlternativeRoutesDurationAnnotationsSource"
@@ -36,6 +38,10 @@ extension NavigationMapView {
3638
static let markerImage = "default_marker"
3739
static let routeAnnotationLeftHanded = "RouteInfoAnnotationLeftHanded"
3840
static let routeAnnotationRightHanded = "RouteInfoAnnotationRightHanded"
41+
static let trafficSignalDay = "TrafficSignalDay"
42+
static let trafficSignalNight = "TrafficSignalNight"
43+
static let railroadCrossingDay = "RailroadCrossingDay"
44+
static let railroadCrossingNight = "RailroadCrossingNight"
3945
}
4046

4147
struct ModelKeyIdentifier {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"version" : 1,
4+
"author" : "xcode"
5+
}
6+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"filename" : "RailroadCrossingDay.pdf"
6+
}
7+
],
8+
"info" : {
9+
"version" : 1,
10+
"author" : "xcode"
11+
},
12+
"properties" : {
13+
"template-rendering-intent" : "template",
14+
"preserves-vector-representation" : true
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"filename" : "RailroadCrossingNight.pdf"
6+
}
7+
],
8+
"info" : {
9+
"version" : 1,
10+
"author" : "xcode"
11+
},
12+
"properties" : {
13+
"template-rendering-intent" : "template",
14+
"preserves-vector-representation" : true
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"filename" : "TrafficSignalDay.pdf"
6+
}
7+
],
8+
"info" : {
9+
"version" : 1,
10+
"author" : "xcode"
11+
},
12+
"properties" : {
13+
"template-rendering-intent" : "template",
14+
"preserves-vector-representation" : true
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"filename" : "TrafficSignalNight.pdf"
6+
}
7+
],
8+
"info" : {
9+
"version" : 1,
10+
"author" : "xcode"
11+
},
12+
"properties" : {
13+
"template-rendering-intent" : "template",
14+
"preserves-vector-representation" : true
15+
}
16+
}

0 commit comments

Comments
 (0)