Skip to content

Commit ebe9bb5

Browse files
avi-c1ec5
authored andcommitted
Add visible annotations to the map at intersecting roads along the active route as well as at maneuver points.
1 parent 111551d commit ebe9bb5

20 files changed

+696
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import UIKit
2+
import Turf
3+
import MapboxDirections
4+
import MapboxCoreNavigation
5+
import MapboxNavigation
6+
import MapboxCoreMaps
7+
import MapboxMaps
8+
9+
// MARK: - Visible annotations on the map about the current drive
10+
11+
extension NavigationMapView {
12+
}

Example/ViewController.swift

+2
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ class ViewController: UIViewController {
573573
completion: CompletionHandler? = nil) {
574574
navigationViewController.modalPresentationStyle = .fullScreen
575575
activeNavigationViewController = navigationViewController
576+
activeNavigationViewController?.showIntersectionAnnotations = true
576577

577578
present(navigationViewController, animated: true) {
578579
completion?()
@@ -591,6 +592,7 @@ class ViewController: UIViewController {
591592

592593
func dismissActiveNavigationViewController() {
593594
activeNavigationViewController?.dismiss(animated: true) {
595+
self.activeNavigationViewController?.showIntersectionAnnotations = false
594596
self.activeNavigationViewController = nil
595597
}
596598
}

MapboxNavigation-SPM.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
DA303CA021B76B5C00F921DC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA303C9D21B76B5C00F921DC /* LaunchScreen.storyboard */; };
3636
DA303CA421B76CB000F921DC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA303CA221B76CB000F921DC /* Localizable.stringsdict */; };
3737
DA303CA521B76CB000F921DC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA303CA221B76CB000F921DC /* Localizable.stringsdict */; };
38+
DA493E282833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */; };
39+
DA493E292833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */; };
3840
DA8805002316EAED00B54D87 /* ViewController+InstructionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED6285522CBE4CE00058A51 /* ViewController+InstructionsCard.swift */; };
3941
DAF5BE4A26A1FD1200DD3F2B /* MapboxGeocoder in Frameworks */ = {isa = PBXBuildFile; productRef = DAF5BE4926A1FD1200DD3F2B /* MapboxGeocoder */; };
4042
E27A2204265674E400AA935F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E27A2202265674E400AA935F /* Localizable.strings */; };
@@ -81,6 +83,7 @@
8183
DA3327391F50C6DA00C5EE88 /* sl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Main.strings; sourceTree = "<group>"; };
8284
DA33273D1F50C7CA00C5EE88 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Main.strings; sourceTree = "<group>"; };
8385
DA3525712011435E0048DDFC /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = "<group>"; };
86+
DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NavigationMapView+RoadAnnotations.swift"; path = "Example/NavigationMapView+RoadAnnotations.swift"; sourceTree = "<group>"; };
8487
DA545ABA1FA993DF0090908E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = "<group>"; };
8588
DA545ABE1FA9A1370090908E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Main.strings; sourceTree = "<group>"; };
8689
DA5AD03C1FEBA03700FC7D7B /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Main.strings; sourceTree = "<group>"; };
@@ -152,6 +155,7 @@
152155
C51FC31620F689F800400CE7 /* CustomStyles.swift */,
153156
C5D9800C1EFA8BA9006DBF2E /* CustomViewController.swift */,
154157
8A0D5DB525DF2A86006F0919 /* StyledFeature.swift */,
158+
DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */,
155159
);
156160
name = Example;
157161
sourceTree = "<group>";
@@ -388,6 +392,7 @@
388392
isa = PBXSourcesBuildPhase;
389393
buildActionMask = 2147483647;
390394
files = (
395+
DA493E282833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */,
391396
358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */,
392397
C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */,
393398
AED6285622CBE4CE00058A51 /* ViewController+InstructionsCard.swift in Sources */,
@@ -405,6 +410,7 @@
405410
files = (
406411
C53F2EE420EBC95600D9798F /* ViewController.swift in Sources */,
407412
C53F2EE520EBC95600D9798F /* CustomViewController.swift in Sources */,
413+
DA493E292833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */,
408414
C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */,
409415
8A0D5DB725DF2A86006F0919 /* StyledFeature.swift in Sources */,
410416
DA8805002316EAED00B54D87 /* ViewController+InstructionsCard.swift in Sources */,

MapboxNavigation.xcodeproj/project.pbxproj

+11
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,10 @@
485485
E2DAFABA27BCF3C200BA12BD /* RoutesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DAFAB927BCF3C200BA12BD /* RoutesCoordinator.swift */; };
486486
E2F08C70269DB17C002EFDC5 /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F08C6F269DB17C002EFDC5 /* AccessToken.swift */; };
487487
F46FF187260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FF186260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.swift */; };
488+
F43EE329261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */; };
489+
F43EE32A261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */; };
490+
F488A0BE26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */; };
491+
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */; };
488492
/* End PBXBuildFile section */
489493

490494
/* Begin PBXContainerItemProxy section */
@@ -1139,6 +1143,9 @@
11391143
E2DAFAB927BCF3C200BA12BD /* RoutesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutesCoordinator.swift; sourceTree = "<group>"; };
11401144
E2F08C6F269DB17C002EFDC5 /* AccessToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = "<group>"; };
11411145
F46FF186260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+NavigationAdditions.swift"; sourceTree = "<group>"; };
1146+
F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+RoadAnnotations.swift"; sourceTree = "<group>"; };
1147+
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+IntersectionAnnotations.swift"; sourceTree = "<group>"; };
1148+
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectronicHorizon.swift; sourceTree = "<group>"; };
11421149
/* End PBXFileReference section */
11431150

11441151
/* Begin PBXFrameworksBuildPhase section */
@@ -1868,6 +1875,8 @@
18681875
8A11FEEF27A3514C00285B6F /* CPRouteChoice.swift */,
18691876
8AD220AA27C091EE000734A5 /* Solar.swift */,
18701877
8AD220AE27C09544000734A5 /* Date.swift */,
1878+
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */,
1879+
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */,
18711880
);
18721881
name = Extensions;
18731882
sourceTree = "<group>";
@@ -2712,6 +2721,7 @@
27122721
8AD2210F27C434CD000734A5 /* TitleLabel.swift in Sources */,
27132722
8A50A3CB26EC09FB00894A8E /* FeedbackSubtypeCollectionViewCell.swift in Sources */,
27142723
8A50A3D326EC0AE100894A8E /* IdleTimerManager.swift in Sources */,
2724+
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */,
27152725
8D5DFFF1207C04840093765A /* NSAttributedString.swift in Sources */,
27162726
8AD2211F27C43D11000734A5 /* FloatingButton.swift in Sources */,
27172727
8A2081CB25E07CED00F9B8A6 /* NavigationMapViewIdentifiers.swift in Sources */,
@@ -2745,6 +2755,7 @@
27452755
2EBF20AE25D6F89000DB7BF2 /* Utils.swift in Sources */,
27462756
160D8279205996DA00D278D6 /* DataCache.swift in Sources */,
27472757
351BEBF21E5BCC63006FE110 /* Style.swift in Sources */,
2758+
F488A0BE26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift in Sources */,
27482759
43FB386923A202420064481E /* Route.swift in Sources */,
27492760
3EA937B1F4DF73EB004BA6BE /* InstructionPresenter.swift in Sources */,
27502761
5A1C075824BDEB44000A6330 /* PassiveLocationProvider.swift in Sources */,

Sources/MapboxCoreNavigation/CoreConstants.swift

+2
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,8 @@ extension RoadGraph {
483483
/**
484484
A key in the user info dictionary of a `Notification.Name.electronicHorizonDidEnterRoadObject` or `Notification.Name.electronicHorizonDidExitRoadObject` notification. The corresponding value is a `RoadObject.Identifier` identifying the road object that the user entered or exited. */
485485
public static let roadObjectIdentifierKey: NotificationUserInfoKey = .init(rawValue: "roadObjectIdentifier")
486+
487+
public static let roadGraphIdentifierKey: NotificationUserInfoKey = .init(rawValue: "roadGraph")
486488

487489
/**
488490
A key in the user info dictionary of a `Notification.Name.electronicHorizonDidEnterRoadObject` or `Notification.Name.electronicHorizonDidExitRoadObject` notification. The corresponding value is an `NSNumber` containing a Boolean value set to `true` if the user entered at the beginning or exited at the end of the road object, or `false` if they entered or exited somewhere along the road object. */

Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift

+1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ class NavigatorElectronicHorizonObserver: ElectronicHorizonObserver {
313313
.treeKey: RoadGraph.Edge(position.tree().start),
314314
.updatesMostProbablePathKey: position.type() == .update,
315315
.distancesByRoadObjectKey: distances.map(DistancedRoadObject.init),
316+
.roadGraphIdentifierKey: Navigator.shared.roadGraph,
316317
]
317318
NotificationCenter.default.post(name: .electronicHorizonDidUpdatePosition, object: nil, userInfo: userInfo)
318319
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import MapboxCoreNavigation
2+
3+
extension RoadGraph.Edge {
4+
var mpp: [RoadGraph.Edge]? {
5+
6+
guard level == 0 else { return nil }
7+
8+
var mostProbablePath = [self]
9+
10+
for child in outletEdges {
11+
if let childMPP = child.mpp {
12+
mostProbablePath.append(contentsOf: childMPP)
13+
}
14+
}
15+
16+
return mostProbablePath
17+
}
18+
19+
func edgeNames(roadGraph: RoadGraph) -> [String] {
20+
guard let metadata = roadGraph.edgeMetadata(edgeIdentifier: identifier) else {
21+
return []
22+
}
23+
let names = metadata.names.map { name -> String in
24+
switch name {
25+
case .name(let name):
26+
return name
27+
case .code(let code):
28+
return "(\(code))"
29+
}
30+
}
31+
32+
// If the road is unnamed, fall back to the road class.
33+
if names.isEmpty {
34+
return ["\(metadata.mapboxStreetsRoadClass.rawValue)"]
35+
}
36+
return names
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import CoreLocation
2+
import UIKit
3+
import MapboxDirections
4+
import MapboxCoreNavigation
5+
import Turf
6+
import MapboxMaps
7+
8+
extension NavigationMapView {
9+
10+
struct EdgeIntersection {
11+
var root: RoadGraph.Edge
12+
var branch: RoadGraph.Edge
13+
var rootMetadata: RoadGraph.Edge.Metadata
14+
var rootShape: LineString
15+
var branchMetadata: RoadGraph.Edge.Metadata
16+
var branchShape: LineString
17+
18+
var coordinate: CLLocationCoordinate2D? {
19+
rootShape.coordinates.first
20+
}
21+
22+
var annotationPoint: CLLocationCoordinate2D? {
23+
guard let length = branchShape.distance() else { return nil }
24+
let targetDistance = min(length / 2, Double.random(in: 15...30))
25+
guard let annotationPoint = branchShape.coordinateFromStart(distance: targetDistance) else { return nil }
26+
return annotationPoint
27+
}
28+
29+
var wayName: String? {
30+
guard let roadName = rootMetadata.names.first else { return nil }
31+
32+
switch roadName {
33+
case .name(let name):
34+
return name
35+
case .code(let code):
36+
return "(\(code))"
37+
}
38+
}
39+
var intersectingWayName: String? {
40+
guard let roadName = branchMetadata.names.first else { return nil }
41+
42+
switch roadName {
43+
case .name(let name):
44+
return name
45+
case .code(let code):
46+
return "(\(code))"
47+
}
48+
}
49+
50+
var incidentAngle: CLLocationDegrees {
51+
return (branchMetadata.heading - rootMetadata.heading).wrap(min: 0, max: 360)
52+
}
53+
54+
var description: String {
55+
return "EdgeIntersection: root: \(wayName ?? "") intersection: \(intersectingWayName ?? "") coordinate: \(String(describing: coordinate))"
56+
}
57+
}
58+
59+
enum AnnotationTailPosition: Int {
60+
case left
61+
case right
62+
case center
63+
}
64+
65+
class AnnotationCacheEntry: Equatable, Hashable {
66+
var wayname: String
67+
var coordinate: CLLocationCoordinate2D
68+
var intersection: EdgeIntersection?
69+
var feature: Feature
70+
var lastAccessTime: Date
71+
72+
init(coordinate: CLLocationCoordinate2D, wayname: String, intersection: EdgeIntersection? = nil, feature: Feature) {
73+
self.wayname = wayname
74+
self.coordinate = coordinate
75+
self.intersection = intersection
76+
self.feature = feature
77+
self.lastAccessTime = Date()
78+
}
79+
80+
static func == (lhs: AnnotationCacheEntry, rhs: AnnotationCacheEntry) -> Bool {
81+
return lhs.wayname == rhs.wayname
82+
}
83+
84+
func hash(into hasher: inout Hasher) {
85+
hasher.combine(wayname.hashValue)
86+
}
87+
}
88+
89+
class AnnotationCache {
90+
private let maxEntryAge = TimeInterval(30)
91+
var entries = Set<AnnotationCacheEntry>()
92+
var cachePruningTimer: Timer?
93+
94+
init() {
95+
// periodically prune the cache to remove entries that have been passed already
96+
cachePruningTimer = Timer.scheduledTimer(withTimeInterval: 15, repeats: true, block: { [weak self] _ in
97+
self?.prune()
98+
})
99+
}
100+
101+
deinit {
102+
cachePruningTimer?.invalidate()
103+
cachePruningTimer = nil
104+
}
105+
106+
func setValue(feature: Feature, coordinate: CLLocationCoordinate2D, intersection: EdgeIntersection?, for wayname: String) {
107+
entries.insert(AnnotationCacheEntry(coordinate: coordinate, wayname: wayname, intersection: intersection, feature: feature))
108+
}
109+
110+
func value(for wayname: String) -> AnnotationCacheEntry? {
111+
let matchingEntry = entries.first { entry -> Bool in
112+
entry.wayname == wayname
113+
}
114+
115+
if let matchingEntry = matchingEntry {
116+
// update the timestamp used for pruning the cache
117+
matchingEntry.lastAccessTime = Date()
118+
}
119+
120+
return matchingEntry
121+
}
122+
123+
private func prune() {
124+
let now = Date()
125+
126+
entries.filter { now.timeIntervalSince($0.lastAccessTime) > maxEntryAge }.forEach { remove($0) }
127+
}
128+
129+
public func remove(_ entry: AnnotationCacheEntry) {
130+
entries.remove(entry)
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)