From ebe9bb5edbfc9ce3671b3f5d5b0f1a0e199db40e Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Mon, 12 Apr 2021 15:38:21 -0700 Subject: [PATCH 1/3] Add visible annotations to the map at intersecting roads along the active route as well as at maneuver points. --- .../NavigationMapView+RoadAnnotations.swift | 12 + Example/ViewController.swift | 2 + .../project.pbxproj | 6 + MapboxNavigation.xcodeproj/project.pbxproj | 11 + .../MapboxCoreNavigation/CoreConstants.swift | 2 + .../CoreNavigationNavigator.swift | 1 + .../MapboxNavigation/ElectronicHorizon.swift | 38 ++ ...ationMapView+IntersectionAnnotations.swift | 133 ++++++ .../MapboxNavigation/NavigationMapView.swift | 423 ++++++++++++++++++ .../NavigationMapViewIdentifiers.swift | 2 + .../NavigationViewController.swift | 19 + .../AnnotationCentered.png | Bin 0 -> 4850 bytes .../AnnotationCentered.imageset/Contents.json | 12 + .../Contents.json | 12 + .../RouteInfoAnnotationLeftHanded.png | Bin 0 -> 2391 bytes .../Contents.json | 12 + .../RouteInfoAnnotationRightHanded.png | Bin 0 -> 2463 bytes .../RouteLineController.swift | 5 + Sources/MapboxNavigation/UIColor.swift | 4 + Sources/MapboxNavigation/UIImage.swift | 2 + 20 files changed, 696 insertions(+) create mode 100644 Example/NavigationMapView+RoadAnnotations.swift create mode 100644 Sources/MapboxNavigation/ElectronicHorizon.swift create mode 100644 Sources/MapboxNavigation/NavigationMapView+IntersectionAnnotations.swift create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationCentered.imageset/AnnotationCentered.png create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationCentered.imageset/Contents.json create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationLeftHanded.imageset/Contents.json create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationLeftHanded.imageset/RouteInfoAnnotationLeftHanded.png create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/Contents.json create mode 100644 Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/RouteInfoAnnotationRightHanded.png diff --git a/Example/NavigationMapView+RoadAnnotations.swift b/Example/NavigationMapView+RoadAnnotations.swift new file mode 100644 index 00000000000..3aa07b631a8 --- /dev/null +++ b/Example/NavigationMapView+RoadAnnotations.swift @@ -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 { +} diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 3611be349fd..9c9e76ae0f0 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -573,6 +573,7 @@ class ViewController: UIViewController { completion: CompletionHandler? = nil) { navigationViewController.modalPresentationStyle = .fullScreen activeNavigationViewController = navigationViewController + activeNavigationViewController?.showIntersectionAnnotations = true present(navigationViewController, animated: true) { completion?() @@ -591,6 +592,7 @@ class ViewController: UIViewController { func dismissActiveNavigationViewController() { activeNavigationViewController?.dismiss(animated: true) { + self.activeNavigationViewController?.showIntersectionAnnotations = false self.activeNavigationViewController = nil } } diff --git a/MapboxNavigation-SPM.xcodeproj/project.pbxproj b/MapboxNavigation-SPM.xcodeproj/project.pbxproj index 48d2cbf8391..dcafaf8121c 100644 --- a/MapboxNavigation-SPM.xcodeproj/project.pbxproj +++ b/MapboxNavigation-SPM.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ DA303CA021B76B5C00F921DC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA303C9D21B76B5C00F921DC /* LaunchScreen.storyboard */; }; DA303CA421B76CB000F921DC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA303CA221B76CB000F921DC /* Localizable.stringsdict */; }; DA303CA521B76CB000F921DC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA303CA221B76CB000F921DC /* Localizable.stringsdict */; }; + DA493E282833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */; }; + DA493E292833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */; }; DA8805002316EAED00B54D87 /* ViewController+InstructionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED6285522CBE4CE00058A51 /* ViewController+InstructionsCard.swift */; }; DAF5BE4A26A1FD1200DD3F2B /* MapboxGeocoder in Frameworks */ = {isa = PBXBuildFile; productRef = DAF5BE4926A1FD1200DD3F2B /* MapboxGeocoder */; }; E27A2204265674E400AA935F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E27A2202265674E400AA935F /* Localizable.strings */; }; @@ -81,6 +83,7 @@ DA3327391F50C6DA00C5EE88 /* sl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Main.strings; sourceTree = ""; }; DA33273D1F50C7CA00C5EE88 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Main.strings; sourceTree = ""; }; DA3525712011435E0048DDFC /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; + DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NavigationMapView+RoadAnnotations.swift"; path = "Example/NavigationMapView+RoadAnnotations.swift"; sourceTree = ""; }; DA545ABA1FA993DF0090908E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; DA545ABE1FA9A1370090908E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Main.strings; sourceTree = ""; }; DA5AD03C1FEBA03700FC7D7B /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Main.strings; sourceTree = ""; }; @@ -152,6 +155,7 @@ C51FC31620F689F800400CE7 /* CustomStyles.swift */, C5D9800C1EFA8BA9006DBF2E /* CustomViewController.swift */, 8A0D5DB525DF2A86006F0919 /* StyledFeature.swift */, + DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */, ); name = Example; sourceTree = ""; @@ -388,6 +392,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA493E282833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */, 358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */, C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */, AED6285622CBE4CE00058A51 /* ViewController+InstructionsCard.swift in Sources */, @@ -405,6 +410,7 @@ files = ( C53F2EE420EBC95600D9798F /* ViewController.swift in Sources */, C53F2EE520EBC95600D9798F /* CustomViewController.swift in Sources */, + DA493E292833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */, C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */, 8A0D5DB725DF2A86006F0919 /* StyledFeature.swift in Sources */, DA8805002316EAED00B54D87 /* ViewController+InstructionsCard.swift in Sources */, diff --git a/MapboxNavigation.xcodeproj/project.pbxproj b/MapboxNavigation.xcodeproj/project.pbxproj index a9116d4af8f..01c2bfecaf2 100644 --- a/MapboxNavigation.xcodeproj/project.pbxproj +++ b/MapboxNavigation.xcodeproj/project.pbxproj @@ -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 */ @@ -1139,6 +1143,9 @@ E2DAFAB927BCF3C200BA12BD /* RoutesCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutesCoordinator.swift; sourceTree = ""; }; E2F08C6F269DB17C002EFDC5 /* AccessToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = ""; }; F46FF186260277F7007CC0E0 /* DateComponentsFormatter+NavigationAdditions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateComponentsFormatter+NavigationAdditions.swift"; sourceTree = ""; }; + F43EE328261F98DC0039D56F /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+RoadAnnotations.swift"; sourceTree = ""; }; + F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NavigationMapView+IntersectionAnnotations.swift"; sourceTree = ""; }; + F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElectronicHorizon.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1868,6 +1875,8 @@ 8A11FEEF27A3514C00285B6F /* CPRouteChoice.swift */, 8AD220AA27C091EE000734A5 /* Solar.swift */, 8AD220AE27C09544000734A5 /* Date.swift */, + F488A0BD26261C4600A4CC8C /* NavigationMapView+IntersectionAnnotations.swift */, + F488A0C726261D8100A4CC8C /* ElectronicHorizon.swift */, ); name = Extensions; sourceTree = ""; @@ -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 */, @@ -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 */, diff --git a/Sources/MapboxCoreNavigation/CoreConstants.swift b/Sources/MapboxCoreNavigation/CoreConstants.swift index 916f34e3c69..016e2c7056e 100644 --- a/Sources/MapboxCoreNavigation/CoreConstants.swift +++ b/Sources/MapboxCoreNavigation/CoreConstants.swift @@ -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. */ diff --git a/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift b/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift index b52004e07cd..7260a427ae2 100644 --- a/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift +++ b/Sources/MapboxCoreNavigation/CoreNavigationNavigator.swift @@ -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) } diff --git a/Sources/MapboxNavigation/ElectronicHorizon.swift b/Sources/MapboxNavigation/ElectronicHorizon.swift new file mode 100644 index 00000000000..9bc77f70783 --- /dev/null +++ b/Sources/MapboxNavigation/ElectronicHorizon.swift @@ -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 + } +} diff --git a/Sources/MapboxNavigation/NavigationMapView+IntersectionAnnotations.swift b/Sources/MapboxNavigation/NavigationMapView+IntersectionAnnotations.swift new file mode 100644 index 00000000000..b656a9e6ad1 --- /dev/null +++ b/Sources/MapboxNavigation/NavigationMapView+IntersectionAnnotations.swift @@ -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() + 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) + } + } +} diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index 826bffb6c2d..b857deaf1d6 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -113,6 +113,10 @@ open class NavigationMapView: UIView { @objc dynamic public var traversedRouteColor: UIColor = .defaultTraversedRouteColor @objc dynamic public var maneuverArrowColor: UIColor = .defaultManeuverArrow @objc dynamic public var maneuverArrowStrokeColor: UIColor = .defaultManeuverArrowStroke + @objc dynamic public var intersectionAnnotationDefaultBackgroundColor: UIColor = .intersectionAnnotationDefaultBackgroundColor + @objc dynamic public var intersectionAnnotationSelectedBackgroundColor: UIColor = .intersectionAnnotationSelectedBackgroundColor + @objc dynamic public var intersectionAnnotationDefaultLabelColor: UIColor = .intersectionAnnotationDefaultLabelColor + @objc dynamic public var intersectionAnnotationSelectedLabelColor: UIColor = .intersectionAnnotationSelectedLabelColor /** A pending user location coordinate, which is used to calculate the bottleneck distance for @@ -566,6 +570,10 @@ open class NavigationMapView: UIView { NSLog("Failed to add route layer \(layerIdentifier) with error: \(error.localizedDescription).") } } + + mapView.mapboxMap.onEvery(.mapLoaded) { [weak self] _ in + try? self?.addAnnotationSymbolImages() + } return layerIdentifier } @@ -1553,6 +1561,7 @@ open class NavigationMapView: UIView { makeGestureRecognizersResetFrameRate() setupGestureRecognizers() subscribeForNotifications() + annotationCache = AnnotationCache() setupUserLocation() // To prevent the lengthy animation from the Null Island to the current location use @@ -1563,6 +1572,7 @@ open class NavigationMapView: UIView { } deinit { + annotationCache = nil unsubscribeFromNotifications() } @@ -1899,6 +1909,419 @@ open class NavigationMapView: UIView { zoom: CGFloat(navigationViewportDataSource.options.followingCameraOptions.zoomRange.upperBound))) moveUserLocation(to: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)) } + + public var showIntersectionAnnotations: Bool = false { + didSet { + guard oldValue != showIntersectionAnnotations else { return } + intersectionsToAnnotate = nil + if showIntersectionAnnotations { + NotificationCenter.default.addObserver(self, + selector: #selector(didUpdateElectronicHorizonPosition), + name: .electronicHorizonDidUpdatePosition, + object: nil) + } else { + removeRouteAnnotationsLayerFromStyle() + NotificationCenter.default.removeObserver(self, name: .electronicHorizonDidUpdatePosition, object: nil) + } + } + } + + private func updateIntersectionAnnotationSet(tree: RoadGraph.Edge, roadGraph: RoadGraph) { + + guard let currentWayname = tree.edgeNames(roadGraph: roadGraph).first else { return } + + // grab the MPP from the Electronic Horizon + guard let edges = tree.mpp else { return } + + var intersections = [EdgeIntersection]() + + var intersectingWaynames = [String]() + + for mppEdge in edges { + let metadata = roadGraph.edgeMetadata(edgeIdentifier: mppEdge.identifier) + guard metadata?.names != nil else { continue } + // look through all the edges to filter out ones we don't want to consider + // These are ones that lack a name, are very short, or are not on-screen + let level1Edges = mppEdge.outletEdges.filter { outEdge -> Bool in + // Criteria for accepting an edge as a candidate intersecting road + // • Is not on the MPP + // • Is a named road + // • Is not the current road being travelled + // • Is not a road already accepted (happens since there will be more than one outlet edge if a road continues through the current one) + // • Is of a large enough road class + // • Is of a non-trivial length in meters + // • Intersection point is currently visible on screen + + guard outEdge.level != 0 else { return false } + guard let edgeMetadata = roadGraph.edgeMetadata(edgeIdentifier: outEdge.identifier), let geometry = roadGraph.edgeShape(edgeIdentifier: mppEdge.identifier) else { return false } + + let names = edgeMetadata.names.map { name -> String in + switch name { + case .name(let name): + return name + case .code(let code): + return "(\(code))" + } + } + + guard let firstName = names.first, firstName != "" else { + // edge has no name + return false + } + guard firstName != currentWayname else { + // edge is for the currently travelled road + return false + } + + guard !intersectingWaynames.contains(firstName) else { + // an edge for this road is already chosen + return false + } + + guard ![MapboxStreetsRoadClass.service, MapboxStreetsRoadClass.ferry, MapboxStreetsRoadClass.path, MapboxStreetsRoadClass.majorRail, MapboxStreetsRoadClass.minorRail, MapboxStreetsRoadClass.serviceRail, MapboxStreetsRoadClass.aerialway, MapboxStreetsRoadClass.golf].contains(edgeMetadata.mapboxStreetsRoadClass) else { + // edge is of type that we choose not to label + return false + } + + guard edgeMetadata.length >= 5 else { + // edge is at least 5 meters long + return false + } + + guard let length = geometry.distance() else { return false } + + let targetDistance = min(length / 2, Double.random(in: 15...30)) + guard let annotationPoint = geometry.coordinateFromStart(distance: targetDistance) else { + // unable to find a coordinate to label + return false + } + + let onscreenPoint = self.mapView.mapboxMap.point(for: annotationPoint) + + guard mapView.bounds.insetBy(dx: 20, dy: 20).contains(onscreenPoint) else { + // intersection coordinate is not visible on screen + return false + } + + // acceptable intersection to label + intersectingWaynames.append(firstName) + return true + } + + // record the edge information for use in creating the annotation Turf.Feature + let rootMetadata: RoadGraph.Edge.Metadata? = roadGraph.edgeMetadata(edgeIdentifier: mppEdge.identifier) + let rootShape: LineString? = roadGraph.edgeShape(edgeIdentifier: mppEdge.identifier) + for branch in level1Edges { + let branchMetadata: RoadGraph.Edge.Metadata? = roadGraph.edgeMetadata(edgeIdentifier: branch.identifier) + let branchShape: LineString? = roadGraph.edgeShape(edgeIdentifier: branch.identifier) + guard let rootMetadata = rootMetadata, let rootShape = rootShape, let branchInfo = branchMetadata, let branchGeometry = branchShape else { return } + + intersections.append(EdgeIntersection(root: mppEdge, branch: branch, rootMetadata: rootMetadata, rootShape: rootShape, branchMetadata: branchInfo, branchShape: branchGeometry)) + } + } + + // sort the edges by distance from the user + if let userCoordinate = mostRecentUserCourseViewLocation?.coordinate { + intersections.sort { (intersection1, intersection2) -> Bool in + if let edge1Start = intersection1.coordinate, let edge2Start = intersection2.coordinate { + return userCoordinate.distance(to: edge1Start) < userCoordinate.distance(to: edge2Start) + } + return true + } + } + + // form a set of the names of current intersections + // we will use this to check if any old intersections are no longer relevant or any additional ones have been picked + let currentNames = intersections.compactMap { return $0.intersectingWayName } + let currentNameSet = Set(currentNames) + + // if the road name set hasn't changed then we can just short-circuit out + guard previousNameSet != currentNameSet else { return } + + // go ahead and update our list of currently labelled intersections + previousNameSet = currentNameSet + + // take up to 4 intersections to annotate. Limit it to prevent cluttering the map with too many annotations + intersectionsToAnnotate = Array(intersections.prefix(4)) + } + + var previousNameSet: Set? + var intersectionsToAnnotate: [EdgeIntersection]? + + open func updateAnnotations(for routeProgress: RouteProgress) throws { + var features: [Feature] = [] + + // add an annotation for the next step + + if let upcomingStep = routeProgress.upcomingStep { + let maneuverLocation = upcomingStep.maneuverLocation + var labelText = upcomingStep.names?.first ?? "" + let currentLeg = routeProgress.currentLeg + + if upcomingStep == currentLeg.steps.last, let destination = currentLeg.destination?.name { + labelText = destination + } + + if labelText == "", let destinationCodes = upcomingStep.destinationCodes, destinationCodes.count > 0 { + labelText = destinationCodes[0] + + destinationCodes.dropFirst().forEach { destination in + labelText += " / " + destination + } + } + + if labelText == "", let exitCodes = upcomingStep.exitCodes, let code = exitCodes.first { + labelText = "Exit \(code)" + } + + if labelText == "", let destination = upcomingStep.destinations?.first { + labelText = destination + } + + if labelText == "", let exitName = upcomingStep.exitNames?.first { + labelText = exitName + } + + if labelText != "" { + var featurePoint: Feature + if let cachedEntry = cachedAnnotationFeature(for: labelText) { + featurePoint = cachedEntry.feature + } else { + featurePoint = Feature(geometry: Point(maneuverLocation)) + + let tailPosition = AnnotationTailPosition.center + + // set the feature attributes which will be used in styling the symbol style layer + featurePoint.properties = ["highlighted": true, "tailPosition": .number(Double(tailPosition.rawValue)), "text": .string(labelText), "imageName": "AnnotationCentered-Highlighted", "sortOrder": 0] + + annotationCache?.setValue(feature: featurePoint, coordinate: maneuverLocation, intersection: nil, for: labelText) + } + features.append(featurePoint) + } + } + + guard let intersectionsToAnnotate = intersectionsToAnnotate else { return } + for (index, intersection) in intersectionsToAnnotate.enumerated() { + guard let coordinate = intersection.annotationPoint else { continue } + var featurePoint: Feature + + if let intersectingWayName = intersection.intersectingWayName, let cachedEntry = cachedAnnotationFeature(for: intersectingWayName) { + featurePoint = cachedEntry.feature + } else { + featurePoint = Feature(geometry: Point(coordinate)) + + let tailPosition = intersection.incidentAngle < 180 ? AnnotationTailPosition.left : AnnotationTailPosition.right + + let imageName = tailPosition == .left ? "AnnotationLeftHanded" : "AnnotationRightHanded" + + // set the feature attributes which will be used in styling the symbol style layer + var properties: JSONObject = ["highlighted": false, "tailPosition": .number(Double(tailPosition.rawValue)), "imageName": .string(imageName), "sortOrder": .number(Double(-index))] + if let intersectingWayName = intersection.intersectingWayName { + properties["text"] = .string(intersectingWayName) + } + featurePoint.properties = properties + + if let intersectingWayName = intersection.intersectingWayName { + annotationCache?.setValue(feature: featurePoint, coordinate: coordinate, intersection: nil, for: intersectingWayName) + } + } + features.append(featurePoint) + } + + try updateAnnotationLayer(with: FeatureCollection(features: features)) + } + + private func addAnnotationSymbolImages() throws { + let style = mapView.mapboxMap.style + guard style.image(withId: "AnnotationLeftHanded") == nil, style.image(withId: "AnnotationRightHanded") == nil else { return } + + // Centered pin + if let image = UIImage(named: "AnnotationCentered", in: .mapboxNavigation, compatibleWith: nil) { + let stretchX = [ImageStretches(first: Float(20), second: Float(30)), ImageStretches(first: Float(90), second: Float(100))] + let stretchY = [ImageStretches(first: Float(26), second: Float(32))] + let imageContent = ImageContent(left: 20, top: 26, right: 100, bottom: 33) + + let regularAnnotationImage = image.tint(.intersectionAnnotationDefaultBackgroundColor) + + try style.addImage(regularAnnotationImage, + id: "AnnotationCentered", + stretchX: stretchX, + stretchY: stretchY, + content: imageContent) + + let highlightedAnnotationImage = image.tint(.intersectionAnnotationSelectedBackgroundColor) + try style.addImage(highlightedAnnotationImage, + id: "AnnotationCentered-Highlighted", + stretchX: stretchX, + stretchY: stretchY, + content: imageContent) + } + + let stretchX = [ImageStretches(first: Float(32), second: Float(42))] + let stretchY = [ImageStretches(first: Float(26), second: Float(32))] + let imageContent = ImageContent(left: 32, top: 26, right: 47, bottom: 33) + + // Right-hand pin + if let image = UIImage(named: "AnnotationRightHanded", in: .mapboxNavigation, compatibleWith: nil) { + let regularAnnotationImage = image.tint(.intersectionAnnotationDefaultBackgroundColor) + + try style.addImage(regularAnnotationImage, + id: "AnnotationRightHanded", + stretchX: stretchX, + stretchY: stretchY, + content: imageContent) + + let highlightedAnnotationImage = image.tint(.intersectionAnnotationSelectedBackgroundColor) + try style.addImage(highlightedAnnotationImage, + id: "AnnotationRightHanded-Highlighted", + stretchX: stretchX, + stretchY: stretchY, + content: imageContent) + } + + // Left-hand pin + if let image = UIImage(named: "AnnotationLeftHanded", in: .mapboxNavigation, compatibleWith: nil) { + let regularAnnotationImage = image.tint(.intersectionAnnotationDefaultBackgroundColor) + + try style.addImage(regularAnnotationImage, + id: "AnnotationLeftHanded", + stretchX: stretchX, + stretchY: stretchY, + content: imageContent) + + let highlightedAnnotationImage = image.tint(.intersectionAnnotationSelectedBackgroundColor) + try style.addImage(highlightedAnnotationImage, + id: "AnnotationLeftHanded-Highlighted", + stretchX: stretchX, + stretchY: stretchY, + content: imageContent) + } + } + + private func removeRouteAnnotationsLayerFromStyle() { + mapView.mapboxMap.style.removeLayers([LayerIdentifier.intersectionAnnotationsLayer]) + try? mapView.mapboxMap.style.removeSource(withId: SourceIdentifier.intersectionAnnotationsSource) + } + + var annotationCache: AnnotationCache? + + private func cachedAnnotationFeature(for labelText: String) -> AnnotationCacheEntry? { + if let existingFeature = annotationCache?.value(for: labelText) { + // ensure the cached feature is still visible on-screen. If it is not then remove the entry and return nil + let unprojectedCoordinate = self.mapView.mapboxMap.point(for: existingFeature.coordinate) + if mapView.bounds.contains(unprojectedCoordinate) { + return existingFeature + } else { + annotationCache?.remove(existingFeature) + } + } + + return nil + } + + private func updateAnnotationLayer(with features: FeatureCollection) throws { + let style = mapView.mapboxMap.style + let existingDataSource = try? style.source(withId: SourceIdentifier.intersectionAnnotationsSource, type: GeoJSONSource.self) + if existingDataSource != nil { + try style.updateGeoJSONSource(withId: SourceIdentifier.intersectionAnnotationsSource, geoJSON: .featureCollection(features)) + return + } else { + var dataSource = GeoJSONSource() + dataSource.data = .featureCollection(features) + try style.addSource(dataSource, id: SourceIdentifier.intersectionAnnotationsSource) + } + + try? style.removeLayer(withId: LayerIdentifier.intersectionAnnotationsLayer) + + var shapeLayer = SymbolLayer(id: LayerIdentifier.intersectionAnnotationsLayer) + shapeLayer.source = SourceIdentifier.intersectionAnnotationsSource + + shapeLayer.textField = .expression(Exp(.get) { + "text" + }) + + shapeLayer.iconImage = .expression(Exp(.get) { + "imageName" + }) + + shapeLayer.textColor = .expression(Exp(.switchCase) { + Exp(.any) { + Exp(.get) { + "highlighted" + } + } + self.intersectionAnnotationSelectedLabelColor + self.intersectionAnnotationDefaultLabelColor + }) + + shapeLayer.textSize = .constant(16) + shapeLayer.iconTextFit = .constant(.both) + shapeLayer.iconAllowOverlap = .constant(true) + shapeLayer.textAllowOverlap = .constant(true) + shapeLayer.textJustify = .constant(.center) + shapeLayer.symbolZOrder = .constant(.auto) + shapeLayer.textFont = .constant(["DIN Pro Medium"]) + shapeLayer.iconTextFitPadding = .constant([-4, 0, -3, 0]) + shapeLayer.symbolSortKey = .expression(Exp(.get) { "sortOrder" }) + + let anchorExpression = Exp(.match) { + Exp(.get) { "tailPosition" } + 0 + "bottom-left" + 1 + "bottom-right" + 2 + "bottom" + "center" + } + shapeLayer.iconAnchor = .expression(anchorExpression) + shapeLayer.textAnchor = .expression(anchorExpression) + + let offsetExpression = Exp(.match) { + Exp(.get) { "tailPosition" } + 0 + Exp(.literal) { [0.5, -1] } + 1 + Exp(.literal) { [-0.5, -1] } + 2 + Exp(.literal) { [0.0, -1] } + Exp(.literal) { [0.0, 0.0] } + } + shapeLayer.iconOffset = .expression(offsetExpression) + shapeLayer.textOffset = .expression(offsetExpression) + + try style.addLayer(shapeLayer, layerPosition: nil) + } + + @objc func didUpdateElectronicHorizonPosition(_ notification: Notification) { + guard let tree = notification.userInfo?[RoadGraph.NotificationUserInfoKey.treeKey] as? RoadGraph.Edge, let roadGraph = notification.userInfo?[RoadGraph.NotificationUserInfoKey.roadGraphIdentifierKey] as? RoadGraph else { + return + } + + DispatchQueue.main.async { + self.updateIntersectionAnnotationSet(tree: tree, roadGraph: roadGraph) + } + } + + func edgeNames(identifier: RoadGraph.Edge.Identifier, 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 + } } // MARK: - UIGestureRecognizerDelegate methods diff --git a/Sources/MapboxNavigation/NavigationMapViewIdentifiers.swift b/Sources/MapboxNavigation/NavigationMapViewIdentifiers.swift index 614db444fd1..95ffe16a1a4 100644 --- a/Sources/MapboxNavigation/NavigationMapViewIdentifiers.swift +++ b/Sources/MapboxNavigation/NavigationMapViewIdentifiers.swift @@ -15,6 +15,7 @@ extension NavigationMapView { static let waypointSymbolLayer = "\(identifier)_waypointSymbolLayer" static let buildingExtrusionLayer = "\(identifier)_buildingExtrusionLayer" static let routeDurationAnnotationsLayer: String = "\(identifier)_routeDurationAnnotationsLayer" + static let intersectionAnnotationsLayer = "\(identifier)_intersectionAnnotationsLayer" static let puck2DLayer: String = "puck" static let puck3DLayer: String = "puck-model-layer" } @@ -26,6 +27,7 @@ extension NavigationMapView { static let voiceInstructionSource = "\(identifier)_instructionSource" static let waypointSource = "\(identifier)_waypointSource" static let routeDurationAnnotationsSource: String = "\(identifier)_routeDurationAnnotationsSource" + static let intersectionAnnotationsSource = "\(identifier)_intersectionAnnotationsSource" static let puck3DSource: String = "puck-model-source" } diff --git a/Sources/MapboxNavigation/NavigationViewController.swift b/Sources/MapboxNavigation/NavigationViewController.swift index 06f69bdf579..94e3810d259 100644 --- a/Sources/MapboxNavigation/NavigationViewController.swift +++ b/Sources/MapboxNavigation/NavigationViewController.swift @@ -81,6 +81,22 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter } } + public var showIntersectionAnnotations: Bool { + get { + navigationMapView?.showIntersectionAnnotations ?? false + } + set { + navigationMapView?.showIntersectionAnnotations = newValue + if let routeController = router as? RouteController { + if newValue { + routeController.startUpdatingElectronicHorizon(with: nil) + } else { + routeController.stopUpdatingElectronicHorizon() + } + } + } + } + // MARK: Configuring Spoken Instructions /** @@ -328,6 +344,7 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter } deinit { + showIntersectionAnnotations = false navigationService?.stop() } @@ -417,6 +434,8 @@ open class NavigationViewController: UIViewController, NavigationStatusPresenter viewObservers.forEach { $0?.navigationViewDidDisappear(animated) } + + showIntersectionAnnotations = false } open override func viewDidLayoutSubviews() { diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationCentered.imageset/AnnotationCentered.png b/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationCentered.imageset/AnnotationCentered.png new file mode 100644 index 0000000000000000000000000000000000000000..2f35bbc6bce2d6a05869f21e39696473b4e3affa GIT binary patch literal 4850 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 JPDHLkV1jIPao_*| literal 0 HcmV?d00001 diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/Contents.json b/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/Contents.json new file mode 100644 index 00000000000..748a6ddd2a7 --- /dev/null +++ b/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "RouteInfoAnnotationRightHanded.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/RouteInfoAnnotationRightHanded.png b/Sources/MapboxNavigation/Resources/Assets.xcassets/AnnotationRightHanded.imageset/RouteInfoAnnotationRightHanded.png new file mode 100644 index 0000000000000000000000000000000000000000..4b9e2e7f0f85e1744af55d0a5c08be05916d8030 GIT binary patch literal 2463 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 T?9|?Ge~t(i_PFYkzbE_`06%_> literal 0 HcmV?d00001 diff --git a/Sources/MapboxNavigation/RouteLineController.swift b/Sources/MapboxNavigation/RouteLineController.swift index 9eadbdd7685..e02b47fc608 100644 --- a/Sources/MapboxNavigation/RouteLineController.swift +++ b/Sources/MapboxNavigation/RouteLineController.swift @@ -83,6 +83,11 @@ extension NavigationMapView { let stepIndex = progress.currentLegProgress.stepIndex navigationMapView.updatePreferredFrameRate(for: progress) + do { + try navigationMapView.updateAnnotations(for: progress) + } catch { + print(error) + } if currentLegIndexMapped != legIndex { navigationMapView.showWaypoints(on: route, legIndex: legIndex) navigationMapView.show([route], legIndex: legIndex) diff --git a/Sources/MapboxNavigation/UIColor.swift b/Sources/MapboxNavigation/UIColor.swift index 27995ffbe02..f5d4169357d 100644 --- a/Sources/MapboxNavigation/UIColor.swift +++ b/Sources/MapboxNavigation/UIColor.swift @@ -41,6 +41,10 @@ extension UIColor { class var alternativeTrafficSevere: UIColor { #colorLiteral(red: 0.71, green: 0.51, blue: 0.51, alpha: 1.0) } class var defaultBuildingColor: UIColor { #colorLiteral(red: 0.9833194452, green: 0.9843137255, blue: 0.9331936657, alpha: 0.8019049658) } class var defaultBuildingHighlightColor: UIColor { #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) } } class var defaultRouteRestrictedAreaColor: UIColor { #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) } diff --git a/Sources/MapboxNavigation/UIImage.swift b/Sources/MapboxNavigation/UIImage.swift index 1d526a36276..5977e4baa3f 100644 --- a/Sources/MapboxNavigation/UIImage.swift +++ b/Sources/MapboxNavigation/UIImage.swift @@ -74,6 +74,8 @@ extension UIImage { return scaledImage } + // Produce a copy of the image with tint color applied. + // Useful for deployment to iOS versions prior to 13 where tinting support was added to UIImage natively. func tint(_ tintColor: UIColor) -> UIImage { let imageSize = size let imageScale = scale From c02539f2623ddc8cce2c00b9140210876fb74a06 Mon Sep 17 00:00:00 2001 From: Avi Cieplinski Date: Mon, 19 Apr 2021 20:25:20 -0700 Subject: [PATCH 2/3] Add UIAppearance property for map style font list to be used for intersection annotation labels. Set defaults to include a couple of fallbacks that should catch a larger number of glyphs than before. --- Sources/MapboxNavigation/DayStyle.swift | 1 + Sources/MapboxNavigation/NavigationMapView.swift | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/MapboxNavigation/DayStyle.swift b/Sources/MapboxNavigation/DayStyle.swift index 045c8befeaf..07a77267e40 100644 --- a/Sources/MapboxNavigation/DayStyle.swift +++ b/Sources/MapboxNavigation/DayStyle.swift @@ -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) diff --git a/Sources/MapboxNavigation/NavigationMapView.swift b/Sources/MapboxNavigation/NavigationMapView.swift index b857deaf1d6..c7d8d2d6386 100755 --- a/Sources/MapboxNavigation/NavigationMapView.swift +++ b/Sources/MapboxNavigation/NavigationMapView.swift @@ -117,6 +117,7 @@ open class NavigationMapView: UIView { @objc dynamic public var intersectionAnnotationSelectedBackgroundColor: UIColor = .intersectionAnnotationSelectedBackgroundColor @objc dynamic public var intersectionAnnotationDefaultLabelColor: UIColor = .intersectionAnnotationDefaultLabelColor @objc dynamic public var intersectionAnnotationSelectedLabelColor: UIColor = .intersectionAnnotationSelectedLabelColor + @objc dynamic public var intersectionAnnotationFontNames: [String] = ["DIN Pro Medium", "Noto Sans CJK JP Medium", "Arial Unicode MS Regular"] /** A pending user location coordinate, which is used to calculate the bottleneck distance for @@ -2260,7 +2261,7 @@ open class NavigationMapView: UIView { shapeLayer.textAllowOverlap = .constant(true) shapeLayer.textJustify = .constant(.center) shapeLayer.symbolZOrder = .constant(.auto) - shapeLayer.textFont = .constant(["DIN Pro Medium"]) + shapeLayer.textFont = .constant(self.intersectionAnnotationFontNames) shapeLayer.iconTextFitPadding = .constant([-4, 0, -3, 0]) shapeLayer.symbolSortKey = .expression(Exp(.get) { "sortOrder" }) From b321c5150759599b7aa0234de3c358176b1696e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Tue, 17 May 2022 12:46:18 -0700 Subject: [PATCH 3/3] Deleted unused file --- Example/NavigationMapView+RoadAnnotations.swift | 12 ------------ MapboxNavigation-SPM.xcodeproj/project.pbxproj | 6 ------ 2 files changed, 18 deletions(-) delete mode 100644 Example/NavigationMapView+RoadAnnotations.swift diff --git a/Example/NavigationMapView+RoadAnnotations.swift b/Example/NavigationMapView+RoadAnnotations.swift deleted file mode 100644 index 3aa07b631a8..00000000000 --- a/Example/NavigationMapView+RoadAnnotations.swift +++ /dev/null @@ -1,12 +0,0 @@ -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 { -} diff --git a/MapboxNavigation-SPM.xcodeproj/project.pbxproj b/MapboxNavigation-SPM.xcodeproj/project.pbxproj index dcafaf8121c..48d2cbf8391 100644 --- a/MapboxNavigation-SPM.xcodeproj/project.pbxproj +++ b/MapboxNavigation-SPM.xcodeproj/project.pbxproj @@ -35,8 +35,6 @@ DA303CA021B76B5C00F921DC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA303C9D21B76B5C00F921DC /* LaunchScreen.storyboard */; }; DA303CA421B76CB000F921DC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA303CA221B76CB000F921DC /* Localizable.stringsdict */; }; DA303CA521B76CB000F921DC /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DA303CA221B76CB000F921DC /* Localizable.stringsdict */; }; - DA493E282833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */; }; - DA493E292833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */; }; DA8805002316EAED00B54D87 /* ViewController+InstructionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED6285522CBE4CE00058A51 /* ViewController+InstructionsCard.swift */; }; DAF5BE4A26A1FD1200DD3F2B /* MapboxGeocoder in Frameworks */ = {isa = PBXBuildFile; productRef = DAF5BE4926A1FD1200DD3F2B /* MapboxGeocoder */; }; E27A2204265674E400AA935F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E27A2202265674E400AA935F /* Localizable.strings */; }; @@ -83,7 +81,6 @@ DA3327391F50C6DA00C5EE88 /* sl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Main.strings; sourceTree = ""; }; DA33273D1F50C7CA00C5EE88 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Main.strings; sourceTree = ""; }; DA3525712011435E0048DDFC /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; - DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NavigationMapView+RoadAnnotations.swift"; path = "Example/NavigationMapView+RoadAnnotations.swift"; sourceTree = ""; }; DA545ABA1FA993DF0090908E /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; DA545ABE1FA9A1370090908E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Main.strings; sourceTree = ""; }; DA5AD03C1FEBA03700FC7D7B /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Main.strings; sourceTree = ""; }; @@ -155,7 +152,6 @@ C51FC31620F689F800400CE7 /* CustomStyles.swift */, C5D9800C1EFA8BA9006DBF2E /* CustomViewController.swift */, 8A0D5DB525DF2A86006F0919 /* StyledFeature.swift */, - DA493E272833FED4006D09AB /* NavigationMapView+RoadAnnotations.swift */, ); name = Example; sourceTree = ""; @@ -392,7 +388,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DA493E282833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */, 358D14681E5E3B7700ADE590 /* ViewController.swift in Sources */, C5D9800D1EFA8BA9006DBF2E /* CustomViewController.swift in Sources */, AED6285622CBE4CE00058A51 /* ViewController+InstructionsCard.swift in Sources */, @@ -410,7 +405,6 @@ files = ( C53F2EE420EBC95600D9798F /* ViewController.swift in Sources */, C53F2EE520EBC95600D9798F /* CustomViewController.swift in Sources */, - DA493E292833FED5006D09AB /* NavigationMapView+RoadAnnotations.swift in Sources */, C5DE4B6220F6B6B3007AFBE6 /* CustomStyles.swift in Sources */, 8A0D5DB725DF2A86006F0919 /* StyledFeature.swift in Sources */, DA8805002316EAED00B54D87 /* ViewController+InstructionsCard.swift in Sources */,