Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotate intersecting roads and maneuver points along the current route #2928

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ class ViewController: UIViewController {
completion: CompletionHandler? = nil) {
navigationViewController.modalPresentationStyle = .fullScreen
activeNavigationViewController = navigationViewController
activeNavigationViewController?.showIntersectionAnnotations = true

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

func dismissActiveNavigationViewController() {
activeNavigationViewController?.dismiss(animated: true) {
self.activeNavigationViewController?.showIntersectionAnnotations = false
self.activeNavigationViewController = nil
}
}
Expand Down
11 changes: 11 additions & 0 deletions MapboxNavigation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,10 @@
E2DAFABA27BCF3C200BA12BD /* RoutesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DAFAB927BCF3C200BA12BD /* RoutesCoordinator.swift */; };
E2F08C70269DB17C002EFDC5 /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F08C6F269DB17C002EFDC5 /* AccessToken.swift */; };
F46FF187260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FF186260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.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 */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -1139,6 +1143,9 @@
E2DAFAB927BCF3C200BA12BD /* RoutesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutesCoordinator.swift; sourceTree = "<group>"; };
E2F08C6F269DB17C002EFDC5 /* AccessToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = "<group>"; };
F46FF186260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+NavigationAdditions.swift"; 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>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -1868,6 +1875,8 @@
8A11FEEF27A3514C00285B6F /* CPRouteChoice.swift */,
8AD220AA27C091EE000734A5 /* Solar.swift */,
8AD220AE27C09544000734A5 /* Date.swift */,
F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */,
F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */,
);
name = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -2712,6 +2721,7 @@
8AD2210F27C434CD000734A5 /* TitleLabel.swift in Sources */,
8A50A3CB26EC09FB00894A8E /* FeedbackSubtypeCollectionViewCell.swift in Sources */,
8A50A3D326EC0AE100894A8E /* IdleTimerManager.swift in Sources */,
F488A0C826261D8100A4CC8C /* ElectronicHorizon.swift in Sources */,
8D5DFFF1207C04840093765A /* NSAttributedString.swift in Sources */,
8AD2211F27C43D11000734A5 /* FloatingButton.swift in Sources */,
8A2081CB25E07CED00F9B8A6 /* NavigationMapViewIdentifiers.swift in Sources */,
Expand Down Expand Up @@ -2745,6 +2755,7 @@
2EBF20AE25D6F89000DB7BF2 /* Utils.swift in Sources */,
160D8279205996DA00D278D6 /* DataCache.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 /* PassiveLocationProvider.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Sources/MapboxCoreNavigation/CoreConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ extension RoadGraph {
/**
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. */
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
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ class NavigatorElectronicHorizonObserver: ElectronicHorizonObserver {
.treeKey: RoadGraph.Edge(position.tree().start),
.updatesMostProbablePathKey: position.type() == .update,
.distancesByRoadObjectKey: distances.map(DistancedRoadObject.init),
.roadGraphIdentifierKey: Navigator.shared.roadGraph,
]
NotificationCenter.default.post(name: .electronicHorizonDidUpdatePosition, object: nil, userInfo: userInfo)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/MapboxNavigation/DayStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ open class DayStyle: Style {
NavigationMapView.appearance().routeDurationAnnotationFontNames = UIFont.defaultNavigationSymbolLayerFontNames
NavigationMapView.appearance().routeDurationAnnotationTextColor = #colorLiteral(red: 0.09803921569, green: 0.09803921569, blue: 0.09803921569, alpha: 1)
NavigationMapView.appearance().routeDurationAnnotationSelectedTextColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
NavigationMapView.appearance().intersectionAnnotationFontNames = ["DIN Pro Medium", "Noto Sans CJK JP Medium", "Arial Unicode MS Regular"]
NavigationView.appearance().backgroundColor = #colorLiteral(red: 0.764706, green: 0.752941, blue: 0.733333, alpha: 1)
NextBannerView.appearance().backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1)
NextBannerView.appearance(whenContainedInInstancesOf:[InstructionsCardContainerView.self]).backgroundColor = #colorLiteral(red: 0.9675388083, green: 0.9675388083, blue: 0.9675388083, alpha: 1)
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 RoadGraph.Edge {
var mpp: [RoadGraph.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
@@ -0,0 +1,133 @@
import CoreLocation
import UIKit
import MapboxDirections
import MapboxCoreNavigation
import Turf
import MapboxMaps

extension NavigationMapView {

struct EdgeIntersection {
var root: RoadGraph.Edge
var branch: RoadGraph.Edge
var rootMetadata: RoadGraph.Edge.Metadata
var rootShape: LineString
var branchMetadata: RoadGraph.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