Skip to content

Commit

Permalink
Merge pull request #1576 from mapbox/navigation/reliable_current_road…
Browse files Browse the repository at this point in the history
…name_label

Retrieve additional map attributes to display reliable current road name.
  • Loading branch information
vincethecoder authored Aug 2, 2018
2 parents 5d746e1 + efbbb5a commit f185b56
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 10 deletions.
4 changes: 4 additions & 0 deletions MapboxNavigation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
AE5F8771209A082500F58FDB /* route-with-banner-instructions.json in Resources */ = {isa = PBXBuildFile; fileRef = AE5F8770209A082500F58FDB /* route-with-banner-instructions.json */; };
AE8B1B95207BFAEF003050F6 /* TunnelIntersectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE09EC13207BD88200782A33 /* TunnelIntersectionManager.swift */; };
AE8B1B97207D2B2B003050F6 /* TunnelIntersectionManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8B1B96207D2B2B003050F6 /* TunnelIntersectionManagerTests.swift */; };
AEC3AC9A2106703100A26F34 /* HighwayShield.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEC3AC992106703100A26F34 /* HighwayShield.swift */; };
AEF2C8F22072B603007B061F /* routeWithTunnels_9thStreetDC.json in Resources */ = {isa = PBXBuildFile; fileRef = AEF2C8F12072B603007B061F /* routeWithTunnels_9thStreetDC.json */; };
C51245F21F19471C00E33B52 /* MapboxMobileEvents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C549F8311F17F2C5001A0A2D /* MapboxMobileEvents.framework */; };
C51245F31F19471C00E33B52 /* MapboxMobileEvents.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C549F8311F17F2C5001A0A2D /* MapboxMobileEvents.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -610,6 +611,7 @@
AE46F95420EA735B00537AC2 /* VisualInstruction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualInstruction.swift; sourceTree = "<group>"; };
AE5F8770209A082500F58FDB /* route-with-banner-instructions.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "route-with-banner-instructions.json"; sourceTree = "<group>"; };
AE8B1B96207D2B2B003050F6 /* TunnelIntersectionManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelIntersectionManagerTests.swift; sourceTree = "<group>"; };
AEC3AC992106703100A26F34 /* HighwayShield.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighwayShield.swift; sourceTree = "<group>"; };
AED2156E208F7FEA009AA673 /* NavigationViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewControllerTests.swift; sourceTree = "<group>"; };
AEF2C8F12072B603007B061F /* routeWithTunnels_9thStreetDC.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = routeWithTunnels_9thStreetDC.json; sourceTree = "<group>"; };
C51DF8651F38C31C006C6A15 /* Locale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Locale.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -950,6 +952,7 @@
353AA55F1FCEF583009F0384 /* StyleManager.swift */,
359D1B271FFE70D30052FA42 /* NavigationView.swift */,
C54C655120336F2600D338E0 /* Constants.swift */,
AEC3AC992106703100A26F34 /* HighwayShield.swift */,
);
path = MapboxNavigation;
sourceTree = "<group>";
Expand Down Expand Up @@ -1825,6 +1828,7 @@
35D428291FA0B61F00176028 /* InstructionsBannerViewLayout.swift in Sources */,
C54C655220336F2600D338E0 /* Constants.swift in Sources */,
353610CE1FAB6A8F00FB1746 /* BottomBannerView.swift in Sources */,
AEC3AC9A2106703100A26F34 /* HighwayShield.swift in Sources */,
C58822001FB0F0D7008B0A2D /* Error.swift in Sources */,
35C9973F1E732C1B00544D1C /* RouteVoiceController.swift in Sources */,
8D24A2FA20449B430098CBF8 /* Dictionary.swift in Sources */,
Expand Down
183 changes: 183 additions & 0 deletions MapboxNavigation/HighwayShield.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
struct HighwayShield {

enum RoadClass: String {
case alternate, duplex, business, truck, bypass, b
case oneB = "1b", twoA = "2a", twoB = "2b"
}

enum RoadType: RawRepresentable {
typealias RawValue = String
typealias RoadTypeForLocaleRoadClassClosure = (Locale, RoadClass?) -> RoadType
typealias RoadTypeForLocaleClosure = (Locale) -> RoadType

var rawValue: String {
switch self {
case .generic:
return "generic"
case let .motorway(locale):
return "\(locale.rawValue)-motorway"
case let .expressway(locale):
return "\(locale.rawValue)-expressway"
case let .state(locale, roadClass):
guard let currentRoadClass = roadClass else { return "\(locale)-state" }
return "\(locale.rawValue)-state-\(currentRoadClass.rawValue)"
case let .highway(locale, roadClass):
guard let currentRoadClass = roadClass else { return "\(locale)-highway" }
return "\(locale.rawValue)-highway-\(currentRoadClass.rawValue)"
case let .national(locale):
return "\(locale.rawValue)-national"
case let .federal(locale):
return "\(locale.rawValue)-federal"
case let .main(locale):
return "\(locale.rawValue)-main"
case let .road(locale):
return "\(locale.rawValue)-road"
case let .primary(locale):
return "\(locale.rawValue)-primary"
case let .secondary(locale):
return "\(locale.rawValue)-secondary"
case let .trunk(locale):
return "\(locale.rawValue)-trunk"
case let .regional(locale):
return "\(locale.rawValue)-regional"
case let .voivodeship(locale):
return "\(locale.rawValue)-voivodeship"
case let .county(locale):
return "\(locale.rawValue)-county"
case let .communal(locale):
return "\(locale)-communal"
case let .interstate(locale, roadClass):
guard let currentRoadClass = roadClass else { return "\(locale.rawValue)-interstate"}
return "\(locale.rawValue)-interstate-\(currentRoadClass.rawValue)"
case let .metropolitan(locale):
return "\(locale.rawValue)-metropolitan"
case let .provincial(locale):
return "\(locale.rawValue)-provincial"
}
}

init?(rawValue: RawValue) {
let fields = rawValue.split(separator: "-").compactMap(String.init(_:))
switch fields.count {
case 1 where rawValue == "default":
self = .generic
case 2:
guard let roadType = RoadType.type(for: fields.last!),
let locale = Locale(rawValue: fields.first!) else {
return nil
}
self = roadType(locale, nil)
case 3:
guard let roadType = RoadType.type(for: fields[1]),
let locale = Locale(rawValue: fields[0]), let roadClass = RoadClass(rawValue: fields[2]) else {
return nil
}
self = roadType(locale, roadClass)
default:
return nil
}
}

private static func type(for identifier: String) -> RoadTypeForLocaleRoadClassClosure? {
switch identifier {
case "motorway":
return localeOnlyTransform(RoadType.motorway)
case "expressway":
return localeOnlyTransform(RoadType.expressway)
case "national":
return localeOnlyTransform(RoadType.national)
case "federal":
return localeOnlyTransform(RoadType.federal)
case "main":
return localeOnlyTransform(RoadType.main)
case "road":
return localeOnlyTransform(RoadType.road)
case "primary":
return localeOnlyTransform(RoadType.primary)
case "secondary":
return localeOnlyTransform(RoadType.secondary)
case "trunk":
return localeOnlyTransform(RoadType.trunk)
case "regional":
return localeOnlyTransform(RoadType.regional)
case "voivodeship":
return localeOnlyTransform(RoadType.voivodeship)
case "county":
return localeOnlyTransform(RoadType.county)
case "communal":
return localeOnlyTransform(RoadType.communal)
case "provincial":
return localeOnlyTransform(RoadType.provincial)
case "metropolitan":
return localeOnlyTransform(RoadType.metropolitan)
case "state":
return RoadType.state
case "highway":
return RoadType.highway
case "interstate":
return RoadType.interstate
default:
return nil
}
}

static func localeOnlyTransform(_ closure: @escaping RoadTypeForLocaleClosure) -> RoadTypeForLocaleRoadClassClosure {
return { locale, _ in
return closure(locale)
}
}

var textColor: UIColor? {
switch self {
case let .highway(locale, _):
if locale == .slovakia {
return .white
}
return .black
case .generic, .communal, .voivodeship, .trunk, .primary, .secondary:
return .black
case .motorway, .expressway, .road, .interstate:
return .white
case let .state(locale, roadClass):
switch locale {
case .austria, .croatia, .newZealand,
.serbia where roadClass == RoadClass.oneB:
return .white
default:
return .black
}
case .regional, .metropolitan, .provincial:
return .yellow
case let .county(locale):
if locale == .romania {
return .white
}
return .black
case let .main(locale):
if locale == .slovenia {
return .black
}
return .white
case let .national(locale):
switch locale {
case .southAfrica:
return .yellow
case .poland, .romania, .greece, .bulgeria:
return .white
default:
return .black
}
default:
return nil
}
}

case generic, motorway(Locale), expressway(Locale), state(Locale, RoadClass?), highway(Locale, RoadClass?)
case national(Locale), federal(Locale), main(Locale), road(Locale), primary(Locale), secondary(Locale), trunk(Locale), regional(Locale)
case voivodeship(Locale), county(Locale), communal(Locale), interstate(Locale, RoadClass?), metropolitan(Locale), provincial(Locale)
}

enum Locale: String {
case austria = "at", bulgeria = "bg", brazil = "br", switzerland = "ch", czech = "cz", germany = "de", denmark = "dk", finland = "fi", greece = "gr", croatia = "hr", hungary = "hu", india = "in", mexico = "mx", newZealand = "nz", peru = "pe", poland = "pl", romania = "ro", serbia = "rs", sweden = "se", slovenia = "si", slovakia = "sk", usa = "us", southAfrica = "za", e
}
}
36 changes: 34 additions & 2 deletions MapboxNavigation/InstructionPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,18 @@ class InstructionPresenter {

}

protocol ImagePresenter {
protocol ImagePresenter: TextPresenter {
var image: UIImage? { get }
}

protocol TextPresenter {
var text: String? { get }
var font: UIFont { get }
}

class ImageInstruction: NSTextAttachment, ImagePresenter {
var font: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize)
var text: String?

override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
guard let image = image else {
Expand All @@ -269,12 +274,39 @@ class ImageInstruction: NSTextAttachment, ImagePresenter {
let yOrigin = (font.capHeight - image.size.height).rounded() / 2
return CGRect(x: 0, y: yOrigin, width: image.size.width, height: image.size.height)
}

}

class TextInstruction: ImageInstruction {}
class ShieldAttachment: ImageInstruction {}
class GenericShieldAttachment: ShieldAttachment {}
class ExitAttachment: ImageInstruction {}
class RoadNameLabelAttachment: TextInstruction {
var scale: CGFloat?
var color: UIColor?

var compositeImage: UIImage? {
guard let image = image, let text = text, let color = color, let scale = scale else {
return nil
}

var currentImage: UIImage?
let textHeight = font.lineHeight
let pointY = (image.size.height - textHeight) / 2
currentImage = image.insert(text: text as NSString, color: color, font: font, atPoint: CGPoint(x: 0, y: pointY), scale: scale)

return currentImage
}

convenience init(image: UIImage, text: String, color: UIColor, font: UIFont, scale: CGFloat) {
self.init()
self.image = image
self.font = font
self.text = text
self.color = color
self.scale = scale
self.image = compositeImage ?? image
}
}

extension CGSize {
fileprivate static var greatestFiniteSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
Expand Down
77 changes: 69 additions & 8 deletions MapboxNavigation/RouteMapViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ extension RouteMapViewController: NavigationViewDelegate {
let features = mapView.visibleFeatures(at: userPuck, styleLayerIdentifiers: Set([roadLabelLayerIdentifier]))
var smallestLabelDistance = Double.infinity
var currentName: String?
var currentShieldName: NSAttributedString?

for feature in features {
var allLines: [MGLPolyline] = []
Expand Down Expand Up @@ -801,25 +802,85 @@ extension RouteMapViewController: NavigationViewDelegate {
if minDistanceBetweenPoints < smallestLabelDistance {
smallestLabelDistance = minDistanceBetweenPoints

if let line = feature as? MGLPolylineFeature, let name = line.attribute(forKey: "name") as? String {
currentName = name
} else if let line = feature as? MGLMultiPolylineFeature, let name = line.attribute(forKey: "name") as? String {
currentName = name
} else {
currentName = nil
if let line = feature as? MGLPolylineFeature {
let roadNameRecord = roadFeature(for: line)
currentShieldName = roadNameRecord.shieldName
currentName = roadNameRecord.roadName
} else if let line = feature as? MGLMultiPolylineFeature {
let roadNameRecord = roadFeature(for: line)
currentShieldName = roadNameRecord.shieldName
currentName = roadNameRecord.roadName
}
}
}
}

if smallestLabelDistance < 5 && currentName != nil {
navigationView.wayNameView.text = currentName
let hasWayName = currentName != nil || currentShieldName != nil
if smallestLabelDistance < 5 && hasWayName {
if let currentShieldName = currentShieldName {
navigationView.wayNameView.attributedText = currentShieldName
} else if let currentName = currentName {
navigationView.wayNameView.text = currentName
}
navigationView.wayNameView.isHidden = false
} else {
navigationView.wayNameView.isHidden = true
}
}

private func roadFeature(for line: MGLPolylineFeature) -> (roadName: String?, shieldName: NSAttributedString?) {
let roadNameRecord = roadFeatureHelper(ref: line.attribute(forKey: "ref"),
shield: line.attribute(forKey: "shield"),
reflen: line.attribute(forKey: "reflen"),
name: line.attribute(forKey: "name"))

return (roadName: roadNameRecord.roadName, shieldName: roadNameRecord.shieldName)
}

private func roadFeature(for line: MGLMultiPolylineFeature) -> (roadName: String?, shieldName: NSAttributedString?) {
let roadNameRecord = roadFeatureHelper(ref: line.attribute(forKey: "ref"),
shield: line.attribute(forKey: "shield"),
reflen: line.attribute(forKey: "reflen"),
name: line.attribute(forKey: "name"))

return (roadName: roadNameRecord.roadName, shieldName: roadNameRecord.shieldName)
}

private func roadFeatureHelper(ref: Any?, shield: Any?, reflen: Any?, name: Any?) -> (roadName: String?, shieldName: NSAttributedString?) {
var currentShieldName: NSAttributedString?, currentRoadName: String?

if let text = ref as? String, let shieldID = shield as? String, let reflenDigit = reflen as? Int {
currentShieldName = roadShieldName(for: text, shield: shieldID, reflen: reflenDigit)
}

if let roadName = name as? String {
currentRoadName = roadName
}

if let compositeShieldImage = currentShieldName, let roadName = currentRoadName {
let compositeShield = NSMutableAttributedString(string: " \(roadName)")
compositeShield.insert(compositeShieldImage, at: 0)
currentShieldName = compositeShield
}

return (roadName: currentRoadName, shieldName: currentShieldName)
}

private func roadShieldName(for text: String?, shield: String?, reflen: Int?) -> NSAttributedString? {
guard let text = text, let shield = shield, let reflen = reflen else { return nil }

let currentShield = HighwayShield.RoadType(rawValue: shield)
let textColor = currentShield?.textColor ?? .black
let imageName = "\(shield)-\(reflen)"

guard let image = mapView.style?.image(forName: imageName) else {
return nil
}

let attachment = RoadNameLabelAttachment(image: image, text: text, color: textColor, font: UIFont.boldSystemFont(ofSize: UIFont.systemFontSize), scale: UIScreen.main.scale)
return NSAttributedString(attachment: attachment)
}

@objc func updateETA() {
guard isViewLoaded, routeController != nil else { return }
navigationView.bottomBannerView.updateETA(routeProgress: routeController.routeProgress)
Expand Down
9 changes: 9 additions & 0 deletions MapboxNavigation/Style.swift
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,15 @@ open class WayNameView: UIView {
}
}

var attributedText: NSAttributedString? {
get {
return label.attributedText
}
set {
label.attributedText = newValue
}
}

@objc dynamic public var borderColor: UIColor? {
get {
guard let color = layer.borderColor else { return nil }
Expand Down
Loading

0 comments on commit f185b56

Please sign in to comment.