Skip to content

Commit

Permalink
Add visible annotations to the map at intersecting roads along the ac…
Browse files Browse the repository at this point in the history
…tive route as well as at maneuver points.
  • Loading branch information
avi-c authored and 1ec5 committed May 17, 2022
1 parent c28e1e7 commit 1763da3
Show file tree
Hide file tree
Showing 20 changed files with 751 additions and 7 deletions.
2 changes: 2 additions & 0 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ class ViewController: UIViewController {
func present(_ navigationViewController: NavigationViewController, completion: CompletionHandler? = nil) {
navigationViewController.modalPresentationStyle = .fullScreen
activeNavigationViewController = navigationViewController
activeNavigationViewController?.navigationMapView?.showIntersectionAnnotations = true

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

func dismissActiveNavigationViewController() {
activeNavigationViewController?.dismiss(animated: true) {
self.activeNavigationViewController?.navigationMapView?.showIntersectionAnnotations = false
self.activeNavigationViewController = nil
}
}
Expand Down
14 changes: 14 additions & 0 deletions MapboxNavigation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@
DAD903AF23E3DCC80057CF1F /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD903AE23E3DCC80057CF1F /* DateTests.swift */; };
DADD82802161EC0300B8B47D /* UIViewAnimationOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADD827F2161EC0300B8B47D /* UIViewAnimationOptionsTests.swift */; };
DAFA92071F01735000A7FB09 /* DistanceFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 351BEC0B1E5BCC72006FE110 /* DistanceFormatter.swift */; };
F43EE329261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */; };
F43EE32A261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */; };
F488A0BE26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */; };
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */; };
F4BF512E24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */; };
F4C5A26F24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -1021,6 +1025,9 @@
DAFEB36D2093A11F00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
DAFEB36E2093A3E000A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
DAFEB36F2093A3EF00A86A83 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = Resources/ko.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+RoadAnnotations.swift"; sourceTree = "<group>"; };
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+IntersectionAnnotations.swift"; sourceTree = "<group>"; };
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectronicHorizon.swift; sourceTree = "<group>"; };
F4BF512D24EAD7A50066A49B /* FeedbackSubtypeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeCollectionViewCell.swift; sourceTree = "<group>"; };
F4C5A26E24EF1D16004ED0DD /* FeedbackSubtypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackSubtypeViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -1352,6 +1359,7 @@
35002D721E5F6C830090E733 /* Supporting files */,
AED6285522CBE4CE00058A51 /* ViewController+GuidanceCards.swift */,
8A092F9A25DEE81900CA7CF5 /* ViewController+FreeDrive.swift */,
F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */,
3577B877214FF35800094294 /* FavoritesList.swift */,
358D14651E5E3B7700ADE590 /* AppDelegate.swift */,
35379CFB21480BFB00FD402E /* AppDelegate+CarPlay.swift */,
Expand Down Expand Up @@ -1658,6 +1666,8 @@
8AE9081125FAA53300F37077 /* Collection.swift */,
8A8C3D97260175D20071D274 /* CLLocationDirection.swift */,
8A446644260A7B24008BA55E /* BoundingBox.swift */,
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */,
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */,
);
name = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -2555,6 +2565,7 @@
8DEDEF3421E3FBE80049E114 /* NavigationViewControllerDelegate.swift in Sources */,
8A446645260A7B24008BA55E /* BoundingBox.swift in Sources */,
8AD866F625CA1BF10019A638 /* NavigationCamera.swift in Sources */,
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */,
8D5DFFF1207C04840093765A /* NSAttributedString.swift in Sources */,
35CF34B11F0A733200C2692E /* UIFont.swift in Sources */,
8AD866F925CA1BF10019A638 /* ViewportDataSource.swift in Sources */,
Expand All @@ -2579,6 +2590,7 @@
160D8279205996DA00D278D6 /* DataCache.swift in Sources */,
8AFF437125F847340053CBB1 /* CameraOptions.swift in Sources */,
351BEBF21E5BCC63006FE110 /* Style.swift in Sources */,
F488A0BE26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift in Sources */,
43FB386923A202420064481E /* Route.swift in Sources */,
3EA937B1F4DF73EB004BA6BE /* InstructionPresenter.swift in Sources */,
5A1C075824BDEB44000A6330 /* PassiveLocationManager.swift in Sources */,
Expand All @@ -2600,6 +2612,7 @@
buildActionMask = 2147483647;
files = (
358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */,
F43EE329261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */,
C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */,
AED6285622CBE4CE00058A51 /* ViewController+GuidanceCards.swift in Sources */,
8A092F9B25DEE81900CA7CF5 /* ViewController+FreeDrive.swift in Sources */,
Expand Down Expand Up @@ -2705,6 +2718,7 @@
C53F2EE720EBC95600D9798F /* WaypointConfirmationViewController.swift in Sources */,
C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */,
8A0D5DB725DF2A86006F0919 /* StyledFeature.swift in Sources */,
F43EE32A261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift in Sources */,
DA8805002316EAED00B54D87 /* ViewController+GuidanceCards.swift in Sources */,
8A092F9C25DEE81900CA7CF5 /* ViewController+FreeDrive.swift in Sources */,
35379CFD21480C0500FD402E /* AppDelegate+CarPlay.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions NavigationMapView+RoadAnnotations.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import UIKit
import Turf
import MapboxDirections
import MapboxCoreNavigation
import MapboxNavigation
import MapboxCoreMaps
import MapboxMaps

// MARK: - Visible annotations on the map about the current drive

extension NavigationMapView {
}
2 changes: 2 additions & 0 deletions Sources/MapboxCoreNavigation/CoreConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ extension ElectronicHorizon {
/**
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. */
public static let roadObjectIdentifierKey: NotificationUserInfoKey = .init(rawValue: "roadObjectIdentifier")

public static let roadGraphIdentifierKey: NotificationUserInfoKey = .init(rawValue: "roadGraph")

/**
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. */
Expand Down
5 changes: 4 additions & 1 deletion Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,15 @@ class Navigator {

extension Navigator: ElectronicHorizonObserver {
public func onPositionUpdated(for position: ElectronicHorizonPosition, distances: [String : MapboxNavigationNative.RoadObjectDistanceInfo]) {
let userInfo: [ElectronicHorizon.NotificationUserInfoKey: Any] = [
var userInfo: [ElectronicHorizon.NotificationUserInfoKey: Any] = [
.positionKey: RoadGraph.Position(try! position.position()),
.treeKey: ElectronicHorizon(try! position.tree()),
.updatesMostProbablePathKey: try! position.type() == .UPDATE,
.distancesByRoadObjectKey: distances.mapValues(RoadObjectDistanceInfo.init),
]
if let roadGraph = roadGraph {
userInfo.updateValue(roadGraph, forKey: .roadGraphIdentifierKey)
}
NotificationCenter.default.post(name: .electronicHorizonDidUpdatePosition, object: nil, userInfo: userInfo)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/MapboxCoreNavigation/NavigationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public class MapboxNavigationService: NSObject, NavigationService {
let routerType = routerType ?? DefaultRouter.self
router = routerType.init(along: route, routeIndex: routeIndex, options: routeOptions, directions: self.directions, dataSource: self)
NavigationSettings.shared.distanceUnit = routeOptions.locale.usesMetric ? .kilometer : .mile

let eventType = eventsManagerType ?? NavigationEventsManager.self
eventsManager = eventType.init(dataSource: self, accessToken: self.directions.credentials.accessToken)
locationManager.activityType = routeOptions.activityType
Expand Down
4 changes: 4 additions & 0 deletions Sources/MapboxNavigation/DayStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ extension UIColor {
class var alternativeTrafficSevere: UIColor { get { return #colorLiteral(red: 0.71, green: 0.51, blue: 0.51, alpha: 1.0) } }
class var defaultBuildingColor: UIColor { get { return #colorLiteral(red: 0.9833194452, green: 0.9843137255, blue: 0.9331936657, alpha: 0.8019049658) } }
class var defaultBuildingHighlightColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 0.949406036) } }
class var intersectionAnnotationDefaultBackgroundColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } }
class var intersectionAnnotationSelectedBackgroundColor: UIColor { get { return #colorLiteral(red: 0.337254902, green: 0.6588235294, blue: 0.9843137255, alpha: 1) } }
class var intersectionAnnotationDefaultLabelColor: UIColor { get { return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } }
class var intersectionAnnotationSelectedLabelColor: UIColor { get { return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) } }
}

extension UIColor {
Expand Down
38 changes: 38 additions & 0 deletions Sources/MapboxNavigation/ElectronicHorizon.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import MapboxCoreNavigation

extension ElectronicHorizon.Edge {
var mpp: [ElectronicHorizon.Edge]? {

guard level == 0 else { return nil }

var mostProbablePath = [self]

for child in outletEdges {
if let childMPP = child.mpp {
mostProbablePath.append(contentsOf: childMPP)
}
}

return mostProbablePath
}

func edgeNames(roadGraph: RoadGraph) -> [String] {
guard let metadata = roadGraph.edgeMetadata(edgeIdentifier: identifier) else {
return []
}
let names = metadata.names.map { name -> String in
switch name {
case .name(let name):
return name
case .code(let code):
return "(\(code))"
}
}

// If the road is unnamed, fall back to the road class.
if names.isEmpty {
return ["\(metadata.mapboxStreetsRoadClass.rawValue)"]
}
return names
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,16 @@ extension NavigationMapView {
highlightedBuildingsLayer.paint?.fillExtrusionColor = .constant(.init(color: buildingHighlightColor))
highlightedBuildingsLayer.paint?.fillExtrusionHeightTransition = StyleTransition(duration: 0.8, delay: 0)
highlightedBuildingsLayer.paint?.fillExtrusionOpacityTransition = StyleTransition(duration: 0.8, delay: 0)

#if false
if let _ = try? mapView.style.getSource(identifier: NavigationMapView.intersectionAnnotations, type: GeoJSONSource.self).get() {
mapView.style.addLayer(layer: highlightedBuildingsLayer, layerPosition: LayerPosition(above: nil, below: IdentifierString.intersectionAnnotationsLayer, at: nil))
} else {
mapView.style.addLayer(layer: highlightedBuildingsLayer)
}
#else
mapView.style.addLayer(layer: highlightedBuildingsLayer)
#endif
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import CoreLocation
import UIKit
import MapboxDirections
import MapboxCoreNavigation
import Turf
import MapboxMaps

extension NavigationMapView {

struct EdgeIntersection {
var root: ElectronicHorizon.Edge
var branch: ElectronicHorizon.Edge
var rootMetadata: ElectronicHorizon.Edge.Metadata
var rootShape: LineString
var branchMetadata: ElectronicHorizon.Edge.Metadata
var branchShape: LineString

var coordinate: CLLocationCoordinate2D? {
rootShape.coordinates.first
}

var annotationPoint: CLLocationCoordinate2D? {
guard let length = branchShape.distance() else { return nil }
let targetDistance = min(length / 2, Double.random(in: 15...30))
guard let annotationPoint = branchShape.coordinateFromStart(distance: targetDistance) else { return nil }
return annotationPoint
}

var wayName: String? {
guard let roadName = rootMetadata.names.first else { return nil }

switch roadName {
case .name(let name):
return name
case .code(let code):
return "(\(code))"
}
}
var intersectingWayName: String? {
guard let roadName = branchMetadata.names.first else { return nil }

switch roadName {
case .name(let name):
return name
case .code(let code):
return "(\(code))"
}
}

var incidentAngle: CLLocationDegrees {
return (branchMetadata.heading - rootMetadata.heading).wrap(min: 0, max: 360)
}

var description: String {
return "EdgeIntersection: root: \(wayName ?? "") intersection: \(intersectingWayName ?? "") coordinate: \(String(describing: coordinate))"
}
}

enum AnnotationTailPosition: Int {
case left
case right
case center
}

class AnnotationCacheEntry: Equatable, Hashable {
var wayname: String
var coordinate: CLLocationCoordinate2D
var intersection: EdgeIntersection?
var feature: Feature
var lastAccessTime: Date

init(coordinate: CLLocationCoordinate2D, wayname: String, intersection: EdgeIntersection? = nil, feature: Feature) {
self.wayname = wayname
self.coordinate = coordinate
self.intersection = intersection
self.feature = feature
self.lastAccessTime = Date()
}

static func == (lhs: AnnotationCacheEntry, rhs: AnnotationCacheEntry) -> Bool {
return lhs.wayname == rhs.wayname
}

func hash(into hasher: inout Hasher) {
hasher.combine(wayname.hashValue)
}
}

class AnnotationCache {
private let maxEntryAge = TimeInterval(30)
var entries = Set<AnnotationCacheEntry>()
var cachePruningTimer: Timer?

init() {
// periodically prune the cache to remove entries that have been passed already
cachePruningTimer = Timer.scheduledTimer(withTimeInterval: 15, repeats: true, block: { [weak self] _ in
self?.prune()
})
}

deinit {
cachePruningTimer?.invalidate()
cachePruningTimer = nil
}

func setValue(feature: Feature, coordinate: CLLocationCoordinate2D, intersection: EdgeIntersection?, for wayname: String) {
entries.insert(AnnotationCacheEntry(coordinate: coordinate, wayname: wayname, intersection: intersection, feature: feature))
}

func value(for wayname: String) -> AnnotationCacheEntry? {
let matchingEntry = entries.first { entry -> Bool in
entry.wayname == wayname
}

if let matchingEntry = matchingEntry {
// update the timestamp used for pruning the cache
matchingEntry.lastAccessTime = Date()
}

return matchingEntry
}

private func prune() {
let now = Date()

entries.filter { now.timeIntervalSince($0.lastAccessTime) > maxEntryAge }.forEach { remove($0) }
}

public func remove(_ entry: AnnotationCacheEntry) {
entries.remove(entry)
}
}
}
Loading

0 comments on commit 1763da3

Please sign in to comment.