Skip to content

Commit 1763da3

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 c28e1e7 commit 1763da3

20 files changed

+751
-7
lines changed

Example/ViewController.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ class ViewController: UIViewController {
453453
func present(_ navigationViewController: NavigationViewController, completion: CompletionHandler? = nil) {
454454
navigationViewController.modalPresentationStyle = .fullScreen
455455
activeNavigationViewController = navigationViewController
456+
activeNavigationViewController?.navigationMapView?.showIntersectionAnnotations = true
456457

457458
present(navigationViewController, animated: true) {
458459
completion?()
@@ -467,6 +468,7 @@ class ViewController: UIViewController {
467468

468469
func dismissActiveNavigationViewController() {
469470
activeNavigationViewController?.dismiss(animated: true) {
471+
self.activeNavigationViewController?.navigationMapView?.showIntersectionAnnotations = false
470472
self.activeNavigationViewController = nil
471473
}
472474
}

MapboxNavigation.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@
395395
DAD903AF23E3DCC80057CF1F /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD903AE23E3DCC80057CF1F /* DateTests.swift */; };
396396
DADD82802161EC0300B8B47D /* UIViewAnimationOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADD827F2161EC0300B8B47D /* UIViewAnimationOptionsTests.swift */; };
397397
DAFA92071F01735000A7FB09 /* DistanceFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351BEC0B1E5BCC72006FE110 /* DistanceFormatter.swift */; };
398+
F43EE329261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */; };
399+
F43EE32A261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */; };
400+
F488A0BE26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */; };
401+
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */; };
398402
F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; };
399403
F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; };
400404
/* End PBXBuildFile section */
@@ -1021,6 +1025,9 @@
10211025
DAFEB36D2093A11F00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
10221026
DAFEB36E2093A3E000A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
10231027
DAFEB36F2093A3EF00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = Resources/ko.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
1028+
F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+RoadAnnotations.swift"; sourceTree = "<group>"; };
1029+
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+IntersectionAnnotations.swift"; sourceTree = "<group>"; };
1030+
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectronicHorizon.swift; sourceTree = "<group>"; };
10241031
F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = "<group>"; };
10251032
F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = "<group>"; };
10261033
/* End PBXFileReference section */
@@ -1352,6 +1359,7 @@
13521359
35002D721E5F6C830090E733 /* Supporting files */,
13531360
AED6285522CBE4CE00058A51 /* ViewController+GuidanceCards.swift */,
13541361
8A092F9A25DEE81900CA7CF5 /* ViewController+FreeDrive.swift */,
1362+
F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */,
13551363
3577B877214FF35800094294 /* FavoritesList.swift */,
13561364
358D14651E5E3B7700ADE590 /* AppDelegate.swift */,
13571365
35379CFB21480BFB00FD402E /* AppDelegate+CarPlay.swift */,
@@ -1658,6 +1666,8 @@
16581666
8AE9081125FAA53300F37077 /* Collection.swift */,
16591667
8A8C3D97260175D20071D274 /* CLLocationDirection.swift */,
16601668
8A446644260A7B24008BA55E /* BoundingBox.swift */,
1669+
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */,
1670+
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */,
16611671
);
16621672
name = Extensions;
16631673
sourceTree = "<group>";
@@ -2555,6 +2565,7 @@
25552565
8DEDEF3421E3FBE80049E114 /* NavigationViewControllerDelegate.swift in Sources */,
25562566
8A446645260A7B24008BA55E /* BoundingBox.swift in Sources */,
25572567
8AD866F625CA1BF10019A638 /* NavigationCamera.swift in Sources */,
2568+
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */,
25582569
8D5DFFF1207C04840093765A /* NSAttributedString.swift in Sources */,
25592570
35CF34B11F0A733200C2692E /* UIFont.swift in Sources */,
25602571
8AD866F925CA1BF10019A638 /* ViewportDataSource.swift in Sources */,
@@ -2579,6 +2590,7 @@
25792590
160D8279205996DA00D278D6 /* DataCache.swift in Sources */,
25802591
8AFF437125F847340053CBB1 /* CameraOptions.swift in Sources */,
25812592
351BEBF21E5BCC63006FE110 /* Style.swift in Sources */,
2593+
F488A0BE26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift in Sources */,
25822594
43FB386923A202420064481E /* Route.swift in Sources */,
25832595
3EA937B1F4DF73EB004BA6BE /* InstructionPresenter.swift in Sources */,
25842596
5A1C075824BDEB44000A6330 /* PassiveLocationManager.swift in Sources */,
@@ -2600,6 +2612,7 @@
26002612
buildActionMask = 2147483647;
26012613
files = (
26022614
358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */,
2615+
F43EE329261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */,
26032616
C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */,
26042617
AED6285622CBE4CE00058A51 /* ViewController+GuidanceCards.swift in Sources */,
26052618
8A092F9B25DEE81900CA7CF5 /* ViewController+FreeDrive.swift in Sources */,
@@ -2705,6 +2718,7 @@
27052718
C53F2EE720EBC95600D9798F /* WaypointConfirmationViewController.swift in Sources */,
27062719
C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */,
27072720
8A0D5DB725DF2A86006F0919 /* StyledFeature.swift in Sources */,
2721+
F43EE32A261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */,
27082722
DA8805002316EAED00B54D87 /* ViewController+GuidanceCards.swift in Sources */,
27092723
8A092F9C25DEE81900CA7CF5 /* ViewController+FreeDrive.swift in Sources */,
27102724
35379CFD21480C0500FD402E /* AppDelegate+CarPlay.swift in Sources */,
Lines changed: 12 additions & 0 deletions
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+
}

Sources/MapboxCoreNavigation/CoreConstants.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ extension ElectronicHorizon {
363363
/**
364364
A key in the user info dictionary of a `Notification.Name.electronicHorizonDidEnterRoadObject` or `Notification.Name.electronicHorizonDidExitRoadObject` notification. The corresponding value is a `RoadObjectIdentifier` identifying the road object that the user entered or exited. */
365365
public static let roadObjectIdentifierKey: NotificationUserInfoKey = .init(rawValue: "roadObjectIdentifier")
366+
367+
public static let roadGraphIdentifierKey: NotificationUserInfoKey = .init(rawValue: "roadGraph")
366368

367369
/**
368370
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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,15 @@ class Navigator {
128128

129129
extension Navigator: ElectronicHorizonObserver {
130130
public func onPositionUpdated(for position: ElectronicHorizonPosition, distances: [String : MapboxNavigationNative.RoadObjectDistanceInfo]) {
131-
let userInfo: [ElectronicHorizon.NotificationUserInfoKey: Any] = [
131+
var userInfo: [ElectronicHorizon.NotificationUserInfoKey: Any] = [
132132
.positionKey: RoadGraph.Position(try! position.position()),
133133
.treeKey: ElectronicHorizon(try! position.tree()),
134134
.updatesMostProbablePathKey: try! position.type() == .UPDATE,
135135
.distancesByRoadObjectKey: distances.mapValues(RoadObjectDistanceInfo.init),
136136
]
137+
if let roadGraph = roadGraph {
138+
userInfo.updateValue(roadGraph, forKey: .roadGraphIdentifierKey)
139+
}
137140
NotificationCenter.default.post(name: .electronicHorizonDidUpdatePosition, object: nil, userInfo: userInfo)
138141
}
139142

Sources/MapboxCoreNavigation/NavigationService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ public class MapboxNavigationService: NSObject, NavigationService {
249249
let routerType = routerType ?? DefaultRouter.self
250250
router = routerType.init(along: route, routeIndex: routeIndex, options: routeOptions, directions: self.directions, dataSource: self)
251251
NavigationSettings.shared.distanceUnit = routeOptions.locale.usesMetric ? .kilometer : .mile
252-
252+
253253
let eventType = eventsManagerType ?? NavigationEventsManager.self
254254
eventsManager = eventType.init(dataSource: self, accessToken: self.directions.credentials.accessToken)
255255
locationManager.activityType = routeOptions.activityType

Sources/MapboxNavigation/DayStyle.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ extension UIColor {
3636
class var alternativeTrafficSevere: UIColor { get { return #colorLiteral(red: 0.71, green: 0.51, blue: 0.51, alpha: 1.0) } }
3737
class var defaultBuildingColor: UIColor { get { return #colorLiteral(red: 0.9833194452, green: 0.9843137255, blue: 0.9331936657, alpha: 0.8019049658) } }
3838
class var defaultBuildingHighlightColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 0.949406036) } }
39+
class var intersectionAnnotationDefaultBackgroundColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } }
40+
class var intersectionAnnotationSelectedBackgroundColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } }
41+
class var intersectionAnnotationDefaultLabelColor: UIColor { get { return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } }
42+
class var intersectionAnnotationSelectedLabelColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } }
3943
}
4044

4145
extension UIColor {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import MapboxCoreNavigation
2+
3+
extension ElectronicHorizon.Edge {
4+
var mpp: [ElectronicHorizon.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+
}

Sources/MapboxNavigation/NavigationMapView+BuildingHighlighting.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,16 @@ extension NavigationMapView {
101101
highlightedBuildingsLayer.paint?.fillExtrusionColor = .constant(.init(color: buildingHighlightColor))
102102
highlightedBuildingsLayer.paint?.fillExtrusionHeightTransition = StyleTransition(duration: 0.8, delay: 0)
103103
highlightedBuildingsLayer.paint?.fillExtrusionOpacityTransition = StyleTransition(duration: 0.8, delay: 0)
104+
105+
#if false
106+
if let _ = try? mapView.style.getSource(identifier: NavigationMapView.intersectionAnnotations, type: GeoJSONSource.self).get() {
107+
mapView.style.addLayer(layer: highlightedBuildingsLayer, layerPosition: LayerPosition(above: nil, below: IdentifierString.intersectionAnnotationsLayer, at: nil))
108+
} else {
109+
mapView.style.addLayer(layer: highlightedBuildingsLayer)
110+
}
111+
#else
104112
mapView.style.addLayer(layer: highlightedBuildingsLayer)
113+
#endif
105114
}
106115

107116
}
Lines changed: 133 additions & 0 deletions
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: ElectronicHorizon.Edge
12+
var branch: ElectronicHorizon.Edge
13+
var rootMetadata: ElectronicHorizon.Edge.Metadata
14+
var rootShape: LineString
15+
var branchMetadata: ElectronicHorizon.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)