diff --git a/Sources/MapboxNavigation/Resources/en.lproj/Localizable.strings b/Sources/MapboxNavigation/Resources/en.lproj/Localizable.strings index 67e4aaf323..9a91955b4c 100644 --- a/Sources/MapboxNavigation/Resources/en.lproj/Localizable.strings +++ b/Sources/MapboxNavigation/Resources/en.lproj/Localizable.strings @@ -254,7 +254,7 @@ "SLOWER_ALTERNATIVE" = "%@ slower"; /* Label above the speed limit in an MUTCD-style speed limit sign. Keep as short as possible. */ -"SPEED_LIMIT_LEGEND" = "Max"; +"SPEED_LIMIT_LEGEND" = "Speed Limit"; /* The text of a banner that appears during turn-by-turn navigation when route simulation is enabled. */ "USER_IN_SIMULATION_MODE" = "Simulating Navigation at %@×"; diff --git a/Sources/MapboxNavigation/SpeedLimitStyleKit.swift b/Sources/MapboxNavigation/SpeedLimitStyleKit.swift index 175db5ff7a..85f8857dcf 100644 --- a/Sources/MapboxNavigation/SpeedLimitStyleKit.swift +++ b/Sources/MapboxNavigation/SpeedLimitStyleKit.swift @@ -17,24 +17,29 @@ public class SpeedLimitStyleKit : NSObject { //// Drawing Methods - @objc dynamic public class func drawMUTCD(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 56, height: 66), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "50", legend: String = "MAX") { + @objc dynamic public class func drawMUTCD(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 53, height: 77), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30", legend: String = "SPEED\nLIMIT", showLegend: Bool = true, unit: String = "MPH") { //// General Declarations let context = UIGraphicsGetCurrentContext()! //// Resize to Target Frame context.saveGState() - let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 56, height: 66), target: targetFrame) + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 53, height: 77), target: targetFrame) context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) - context.scaleBy(x: resizedFrame.width / 56, y: resizedFrame.height / 66) + context.scaleBy(x: resizedFrame.width / 53, y: resizedFrame.height / 77) + //// Color Declarations + let borderColor = UIColor(red: 0.775, green: 0.775, blue: 0.775, alpha: 1.000) //// Variable Declarations - let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 24 : 32 + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + let legendHeight: CGFloat = legend == "MAX" ? 25 : 35 + let legendFontSize: CGFloat = legend == "MAX" ? 17 : 12 + let limitY: CGFloat = showLegend ? 34 : 10 //// Sign //// Sign Back Drawing - let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 50, height: 60), cornerRadius: 4) + let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 71), cornerRadius: 4) signBackColor.setFill() signBackPath.fill() signBackColor.setStroke() @@ -44,8 +49,8 @@ public class SpeedLimitStyleKit : NSObject { //// Border Drawing - let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 50, height: 60), cornerRadius: 4) - strokeColor.setStroke() + let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 71), cornerRadius: 4) + borderColor.setStroke() borderPath.lineWidth = 2 borderPath.lineJoinStyle = .bevel borderPath.stroke() @@ -55,7 +60,7 @@ public class SpeedLimitStyleKit : NSObject { //// Group //// Limit Label Drawing - let limitLabelRect = CGRect(x: 3, y: 28, width: 50, height: 35) + let limitLabelRect = CGRect(x: 2, y: (limitY - 8), width: 50, height: 35) let limitLabelStyle = NSMutableParagraphStyle() limitLabelStyle.alignment = .center let limitLabelFontAttributes = [ @@ -71,43 +76,224 @@ public class SpeedLimitStyleKit : NSObject { context.restoreGState() - //// Legend Label Drawing - let legendLabelRect = CGRect(x: 3, y: 3, width: 50, height: 25) - let legendLabelStyle = NSMutableParagraphStyle() - legendLabelStyle.alignment = .center - let legendLabelFontAttributes = [ - .font: UIFont.boldSystemFont(ofSize: UIFont.labelFontSize), + if (showLegend) { + //// Legend Label Drawing + let legendLabelRect = CGRect(x: 2, y: 0, width: 50, height: legendHeight) + let legendLabelStyle = NSMutableParagraphStyle() + legendLabelStyle.alignment = .center + let legendLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: legendFontSize), + .foregroundColor: strokeColor, + .paragraphStyle: legendLabelStyle, + ] as [NSAttributedString.Key: Any] + + let legendLabelTextHeight: CGFloat = legend.boundingRect(with: CGSize(width: legendLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: legendLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: legendLabelRect) + legend.draw(in: CGRect(x: legendLabelRect.minX, y: legendLabelRect.minY + (legendLabelRect.height - legendLabelTextHeight) / 2, width: legendLabelRect.width, height: legendLabelTextHeight), withAttributes: legendLabelFontAttributes) + context.restoreGState() + + + //// Unit Label Drawing + let unitLabelRect = CGRect(x: 3, y: 49, width: 49, height: (legendHeight - 7)) + let unitLabelStyle = NSMutableParagraphStyle() + unitLabelStyle.alignment = .center + let unitLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: (legendFontSize + 1)), + .foregroundColor: strokeColor, + .paragraphStyle: unitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let unitLabelTextHeight: CGFloat = unit.boundingRect(with: CGSize(width: unitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: unitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: unitLabelRect) + unit.draw(in: CGRect(x: unitLabelRect.minX, y: unitLabelRect.minY + (unitLabelRect.height - unitLabelTextHeight) / 2, width: unitLabelRect.width, height: unitLabelTextHeight), withAttributes: unitLabelFontAttributes) + context.restoreGState() + } + + context.restoreGState() + + } + + @objc dynamic public class func drawMUTCDLegendOnly(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 54, height: 67), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30", legend: String = "SPEED\nLIMIT", showLegend: Bool = true) { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 54, height: 67), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 54, y: resizedFrame.height / 67) + + + //// Color Declarations + let borderColor = UIColor(red: 0.775, green: 0.775, blue: 0.775, alpha: 1.000) + + //// Variable Declarations + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + let legendHeight: CGFloat = legend == "MAX" ? 25 : 35 + let legendFontSize: CGFloat = legend == "MAX" ? 17 : 12 + let limitY: CGFloat = showLegend ? 34 : 10 + + //// Sign + //// Sign Back Drawing + let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 61), cornerRadius: 4) + signBackColor.setFill() + signBackPath.fill() + signBackColor.setStroke() + signBackPath.lineWidth = 4 + signBackPath.lineJoinStyle = .bevel + signBackPath.stroke() + + + //// Border Drawing + let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 61), cornerRadius: 4) + borderColor.setStroke() + borderPath.lineWidth = 2 + borderPath.lineJoinStyle = .bevel + borderPath.stroke() + + + + + //// Group + //// Limit Label Drawing + let limitLabelRect = CGRect(x: 2, y: (limitY - 2), width: 50, height: 35) + let limitLabelStyle = NSMutableParagraphStyle() + limitLabelStyle.alignment = .center + let limitLabelFontAttributes = [ + .font: UIFont.systemFont(ofSize: limitFontSize, weight: .bold), .foregroundColor: strokeColor, - .paragraphStyle: legendLabelStyle, + .paragraphStyle: limitLabelStyle, ] as [NSAttributedString.Key: Any] - let legendLabelTextHeight: CGFloat = legend.boundingRect(with: CGSize(width: legendLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: legendLabelFontAttributes, context: nil).height + let limitLabelTextHeight: CGFloat = limit.boundingRect(with: CGSize(width: limitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: limitLabelFontAttributes, context: nil).height context.saveGState() - context.clip(to: legendLabelRect) - legend.draw(in: CGRect(x: legendLabelRect.minX, y: legendLabelRect.minY + legendLabelRect.height - legendLabelTextHeight, width: legendLabelRect.width, height: legendLabelTextHeight), withAttributes: legendLabelFontAttributes) + context.clip(to: limitLabelRect) + limit.draw(in: CGRect(x: limitLabelRect.minX, y: limitLabelRect.minY + (limitLabelRect.height - limitLabelTextHeight) / 2, width: limitLabelRect.width, height: limitLabelTextHeight), withAttributes: limitLabelFontAttributes) context.restoreGState() + + + if (showLegend) { + //// Legend Label Drawing + let legendLabelRect = CGRect(x: 2, y: 3, width: 50, height: legendHeight) + let legendLabelStyle = NSMutableParagraphStyle() + legendLabelStyle.alignment = .center + let legendLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: legendFontSize), + .foregroundColor: strokeColor, + .paragraphStyle: legendLabelStyle, + ] as [NSAttributedString.Key: Any] + + let legendLabelTextHeight: CGFloat = legend.boundingRect(with: CGSize(width: legendLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: legendLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: legendLabelRect) + legend.draw(in: CGRect(x: legendLabelRect.minX, y: legendLabelRect.minY + (legendLabelRect.height - legendLabelTextHeight) / 2, width: legendLabelRect.width, height: legendLabelTextHeight), withAttributes: legendLabelFontAttributes) + context.restoreGState() + } context.restoreGState() } - @objc dynamic public class func drawVienna(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 70, height: 70), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), regulatoryColor: UIColor = UIColor(red: 0.800, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "50") { + @objc dynamic public class func drawMUTCDUnitOnly(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 53, height: 58), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30", legend: String = "SPEED\nLIMIT", showLegend: Bool = true, unit: String = "MPH") { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 53, height: 58), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 53, y: resizedFrame.height / 58) + + + //// Color Declarations + let borderColor = UIColor(red: 0.775, green: 0.775, blue: 0.775, alpha: 1.000) + + //// Variable Declarations + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + let legendHeight: CGFloat = legend == "MAX" ? 25 : 35 + let legendFontSize: CGFloat = legend == "MAX" ? 17 : 12 + let limitY: CGFloat = showLegend ? 34 : 10 + + //// Sign + //// Sign Back Drawing + let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 51), cornerRadius: 4) + signBackColor.setFill() + signBackPath.fill() + signBackColor.setStroke() + signBackPath.lineWidth = 4 + signBackPath.lineJoinStyle = .bevel + signBackPath.stroke() + + + //// Border Drawing + let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 51), cornerRadius: 4) + borderColor.setStroke() + borderPath.lineWidth = 2 + borderPath.lineJoinStyle = .bevel + borderPath.stroke() + + + + + //// Group + //// Limit Label Drawing + let limitLabelRect = CGRect(x: 3, y: (limitY - 34), width: 48, height: 33) + let limitLabelStyle = NSMutableParagraphStyle() + limitLabelStyle.alignment = .center + let limitLabelFontAttributes = [ + .font: UIFont.systemFont(ofSize: limitFontSize, weight: .bold), + .foregroundColor: strokeColor, + .paragraphStyle: limitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let limitLabelTextHeight: CGFloat = limit.boundingRect(with: CGSize(width: limitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: limitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: limitLabelRect) + limit.draw(in: CGRect(x: limitLabelRect.minX, y: limitLabelRect.minY + (limitLabelRect.height - limitLabelTextHeight) / 2, width: limitLabelRect.width, height: limitLabelTextHeight), withAttributes: limitLabelFontAttributes) + context.restoreGState() + + + if (showLegend) { + //// Unit Label Drawing + let unitLabelRect = CGRect(x: 2, y: 27, width: 49, height: (legendHeight - 7)) + let unitLabelStyle = NSMutableParagraphStyle() + unitLabelStyle.alignment = .center + let unitLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: (legendFontSize + 1)), + .foregroundColor: strokeColor, + .paragraphStyle: unitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let unitLabelTextHeight: CGFloat = unit.boundingRect(with: CGSize(width: unitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: unitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: unitLabelRect) + unit.draw(in: CGRect(x: unitLabelRect.minX, y: unitLabelRect.minY + (unitLabelRect.height - unitLabelTextHeight) / 2, width: unitLabelRect.width, height: unitLabelTextHeight), withAttributes: unitLabelFontAttributes) + context.restoreGState() + } + + context.restoreGState() + + } + + @objc dynamic public class func drawVienna(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 54, height: 54), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), regulatoryColor: UIColor = UIColor(red: 0.800, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30") { //// General Declarations let context = UIGraphicsGetCurrentContext()! //// Resize to Target Frame context.saveGState() - let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 70, height: 70), target: targetFrame) + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 54, height: 54), target: targetFrame) context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) - context.scaleBy(x: resizedFrame.width / 70, y: resizedFrame.height / 70) + context.scaleBy(x: resizedFrame.width / 54, y: resizedFrame.height / 54) //// Variable Declarations - let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 24 : 32 + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 //// Sign Back Drawing - let signBackPath = UIBezierPath(ovalIn: CGRect(x: 5, y: 5, width: 60, height: 60)) + let signBackPath = UIBezierPath(ovalIn: CGRect(x: 5, y: 5, width: 44, height: 44)) signBackColor.setFill() signBackPath.fill() signBackColor.setStroke() @@ -116,7 +302,7 @@ public class SpeedLimitStyleKit : NSObject { //// Inscription Drawing - let inscriptionRect = CGRect(x: 5, y: 5, width: 60, height: 60) + let inscriptionRect = CGRect(x: 5, y: 5, width: 44, height: 44) let inscriptionPath = UIBezierPath(ovalIn: inscriptionRect) regulatoryColor.setStroke() inscriptionPath.lineWidth = 6 @@ -139,6 +325,79 @@ public class SpeedLimitStyleKit : NSObject { } + @objc dynamic public class func drawViennaDerestriction(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 44, height: 44), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 44, height: 44), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 44, y: resizedFrame.height / 44) + + + //// Sign Back Drawing + let signBackPath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 44, height: 44)) + signBackColor.setFill() + signBackPath.fill() + + + //// Border Drawing + let borderPath = UIBezierPath(ovalIn: CGRect(x: 3.5, y: 3.5, width: 37, height: 37)) + strokeColor.setStroke() + borderPath.lineWidth = 1 + borderPath.stroke() + + + //// Group + //// Slash 4 Drawing + let slash4Path = UIBezierPath() + slash4Path.move(to: CGPoint(x: 12.5, y: 37.5)) + slash4Path.addLine(to: CGPoint(x: 37.5, y: 12.5)) + strokeColor.setStroke() + slash4Path.lineWidth = 1 + slash4Path.stroke() + + + //// Slash 5 Drawing + let slash5Path = UIBezierPath() + slash5Path.move(to: CGPoint(x: 5.5, y: 30.5)) + slash5Path.addLine(to: CGPoint(x: 30.5, y: 5.5)) + strokeColor.setStroke() + slash5Path.lineWidth = 1 + slash5Path.stroke() + + + //// Slash Drawing + let slashPath = UIBezierPath() + slashPath.move(to: CGPoint(x: 8.5, y: 34.5)) + slashPath.addLine(to: CGPoint(x: 34.5, y: 8.5)) + strokeColor.setStroke() + slashPath.lineWidth = 1 + slashPath.stroke() + + + //// Slash 2 Drawing + let slash2Path = UIBezierPath() + slash2Path.move(to: CGPoint(x: 10.5, y: 36)) + slash2Path.addLine(to: CGPoint(x: 36, y: 10.5)) + strokeColor.setStroke() + slash2Path.lineWidth = 1 + slash2Path.stroke() + + + //// Slash 3 Drawing + let slash3Path = UIBezierPath() + slash3Path.move(to: CGPoint(x: 6.5, y: 33)) + slash3Path.addLine(to: CGPoint(x: 32, y: 7.5)) + strokeColor.setStroke() + slash3Path.lineWidth = 1 + slash3Path.stroke() + + context.restoreGState() + + } + diff --git a/Sources/MapboxNavigation/SpeedLimitView.swift b/Sources/MapboxNavigation/SpeedLimitView.swift index eff8ed1eb6..b939945c4d 100644 --- a/Sources/MapboxNavigation/SpeedLimitView.swift +++ b/Sources/MapboxNavigation/SpeedLimitView.swift @@ -88,6 +88,13 @@ public class SpeedLimitView: UIView { } } + /** + Allows to show only the legend of the speed limit for MUTCD-style signs. + */ + public var onlyShowLegend: Bool = false + + public var legend: String? = "Speed Limit" + let measurementFormatter: MeasurementFormatter = { let formatter = MeasurementFormatter() // Mitigate rounding error when converting back and forth between kilometers per hour and miles per hour. @@ -95,6 +102,8 @@ public class SpeedLimitView: UIView { return formatter }() + let locale = Locale.current + override init(frame: CGRect) { super.init(frame: frame) @@ -141,10 +150,23 @@ public class SpeedLimitView: UIView { switch signStandard { case .mutcd: - let legend = NSLocalizedString("SPEED_LIMIT_LEGEND", bundle: .mapboxNavigation, value: "Max", comment: "Label above the speed limit in an MUTCD-style speed limit sign. Keep as short as possible.").uppercased() - SpeedLimitStyleKit.drawMUTCD(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor, limit: formattedSpeedLimit, legend: legend) + let unit: String = locale.usesMetricSystem ? "KM/H" : "MPH" + if let legend = legend { + let legendForSign = NSLocalizedString("SPEED_LIMIT_LEGEND", bundle: .mapboxNavigation, value: legend, comment: "Label above the speed limit in an MUTCD-style speed limit sign. Keep as short as possible.").uppercased() + if onlyShowLegend { + SpeedLimitStyleKit.drawMUTCDLegendOnly(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor, limit: formattedSpeedLimit, legend: legendForSign, showLegend: true) + } else { + SpeedLimitStyleKit.drawMUTCD(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor, limit: formattedSpeedLimit, legend: legendForSign) + } + } else { + SpeedLimitStyleKit.drawMUTCDUnitOnly(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor, limit: formattedSpeedLimit, legend: "", showLegend: false, unit: unit) + } case .viennaConvention: - SpeedLimitStyleKit.drawVienna(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor, regulatoryColor: regulatoryBorderColor, limit: formattedSpeedLimit) + if formattedSpeedLimit == "∞" { + SpeedLimitStyleKit.drawViennaDerestriction(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor) + } else { + SpeedLimitStyleKit.drawVienna(frame: bounds, resizing: .aspectFit, signBackColor: signBackColor, strokeColor: textColor, regulatoryColor: regulatoryBorderColor, limit: formattedSpeedLimit) + } } } diff --git a/Sources/MapboxNavigation/SpeedLimitsKit.swift b/Sources/MapboxNavigation/SpeedLimitsKit.swift new file mode 100644 index 0000000000..df468292cf --- /dev/null +++ b/Sources/MapboxNavigation/SpeedLimitsKit.swift @@ -0,0 +1,442 @@ +// +// SpeedLimitsKit.swift +// MapboxNavigation +// +// Created on 5/24/22. +// Copyright © 2022 Mapbox. All rights reserved. +// +// Generated by PaintCode +// http://www.paintcodeapp.com +// + + + +import UIKit + +public class SpeedLimitsKit : NSObject { + + //// Drawing Methods + + @objc dynamic public class func drawMUTCDLegendOnly(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 54, height: 67), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30", legend: String = "SPEED\nLIMIT", showLegend: Bool = true) { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 54, height: 67), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 54, y: resizedFrame.height / 67) + + + //// Color Declarations + let borderColor = UIColor(red: 0.775, green: 0.775, blue: 0.775, alpha: 1.000) + + //// Variable Declarations + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + let legendHeight: CGFloat = legend == "MAX" ? 25 : 35 + let legendFontSize: CGFloat = legend == "MAX" ? 17 : 12 + let limitY: CGFloat = showLegend ? 34 : 10 + + //// Sign + //// Sign Back Drawing + let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 61), cornerRadius: 4) + signBackColor.setFill() + signBackPath.fill() + signBackColor.setStroke() + signBackPath.lineWidth = 4 + signBackPath.lineJoinStyle = .bevel + signBackPath.stroke() + + + //// Border Drawing + let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 61), cornerRadius: 4) + borderColor.setStroke() + borderPath.lineWidth = 2 + borderPath.lineJoinStyle = .bevel + borderPath.stroke() + + + + + //// Group + //// Limit Label Drawing + let limitLabelRect = CGRect(x: 2, y: (limitY - 2), width: 50, height: 35) + let limitLabelStyle = NSMutableParagraphStyle() + limitLabelStyle.alignment = .center + let limitLabelFontAttributes = [ + .font: UIFont.systemFont(ofSize: limitFontSize, weight: .bold), + .foregroundColor: strokeColor, + .paragraphStyle: limitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let limitLabelTextHeight: CGFloat = limit.boundingRect(with: CGSize(width: limitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: limitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: limitLabelRect) + limit.draw(in: CGRect(x: limitLabelRect.minX, y: limitLabelRect.minY + (limitLabelRect.height - limitLabelTextHeight) / 2, width: limitLabelRect.width, height: limitLabelTextHeight), withAttributes: limitLabelFontAttributes) + context.restoreGState() + + + if (showLegend) { + //// Legend Label Drawing + let legendLabelRect = CGRect(x: 2, y: 3, width: 50, height: legendHeight) + let legendLabelStyle = NSMutableParagraphStyle() + legendLabelStyle.alignment = .center + let legendLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: legendFontSize), + .foregroundColor: strokeColor, + .paragraphStyle: legendLabelStyle, + ] as [NSAttributedString.Key: Any] + + let legendLabelTextHeight: CGFloat = legend.boundingRect(with: CGSize(width: legendLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: legendLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: legendLabelRect) + legend.draw(in: CGRect(x: legendLabelRect.minX, y: legendLabelRect.minY + (legendLabelRect.height - legendLabelTextHeight) / 2, width: legendLabelRect.width, height: legendLabelTextHeight), withAttributes: legendLabelFontAttributes) + context.restoreGState() + } + + context.restoreGState() + + } + + @objc dynamic public class func drawVienna(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 54, height: 54), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), regulatoryColor: UIColor = UIColor(red: 0.800, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30") { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 54, height: 54), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 54, y: resizedFrame.height / 54) + + + + //// Variable Declarations + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + + //// Sign Back Drawing + let signBackPath = UIBezierPath(ovalIn: CGRect(x: 5, y: 5, width: 44, height: 44)) + signBackColor.setFill() + signBackPath.fill() + signBackColor.setStroke() + signBackPath.lineWidth = 8 + signBackPath.stroke() + + + //// Inscription Drawing + let inscriptionRect = CGRect(x: 5, y: 5, width: 44, height: 44) + let inscriptionPath = UIBezierPath(ovalIn: inscriptionRect) + regulatoryColor.setStroke() + inscriptionPath.lineWidth = 6 + inscriptionPath.stroke() + let inscriptionStyle = NSMutableParagraphStyle() + inscriptionStyle.alignment = .center + let inscriptionFontAttributes = [ + .font: UIFont.systemFont(ofSize: limitFontSize, weight: .bold), + .foregroundColor: strokeColor, + .paragraphStyle: inscriptionStyle, + ] as [NSAttributedString.Key: Any] + + let inscriptionTextHeight: CGFloat = limit.boundingRect(with: CGSize(width: inscriptionRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: inscriptionFontAttributes, context: nil).height + context.saveGState() + context.clip(to: inscriptionRect) + limit.draw(in: CGRect(x: inscriptionRect.minX, y: inscriptionRect.minY + (inscriptionRect.height - inscriptionTextHeight) / 2, width: inscriptionRect.width, height: inscriptionTextHeight), withAttributes: inscriptionFontAttributes) + context.restoreGState() + + context.restoreGState() + + } + + @objc dynamic public class func drawViennaDerestriction(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 44, height: 44), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000)) { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 44, height: 44), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 44, y: resizedFrame.height / 44) + + + //// Sign Back Drawing + let signBackPath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 44, height: 44)) + signBackColor.setFill() + signBackPath.fill() + + + //// Border Drawing + let borderPath = UIBezierPath(ovalIn: CGRect(x: 3.5, y: 3.5, width: 37, height: 37)) + strokeColor.setStroke() + borderPath.lineWidth = 1 + borderPath.stroke() + + + //// Group + //// Slash 4 Drawing + let slash4Path = UIBezierPath() + slash4Path.move(to: CGPoint(x: 12.5, y: 37.5)) + slash4Path.addLine(to: CGPoint(x: 37.5, y: 12.5)) + strokeColor.setStroke() + slash4Path.lineWidth = 1 + slash4Path.stroke() + + + //// Slash 5 Drawing + let slash5Path = UIBezierPath() + slash5Path.move(to: CGPoint(x: 5.5, y: 30.5)) + slash5Path.addLine(to: CGPoint(x: 30.5, y: 5.5)) + strokeColor.setStroke() + slash5Path.lineWidth = 1 + slash5Path.stroke() + + + //// Slash Drawing + let slashPath = UIBezierPath() + slashPath.move(to: CGPoint(x: 8.5, y: 34.5)) + slashPath.addLine(to: CGPoint(x: 34.5, y: 8.5)) + strokeColor.setStroke() + slashPath.lineWidth = 1 + slashPath.stroke() + + + //// Slash 2 Drawing + let slash2Path = UIBezierPath() + slash2Path.move(to: CGPoint(x: 10.5, y: 36)) + slash2Path.addLine(to: CGPoint(x: 36, y: 10.5)) + strokeColor.setStroke() + slash2Path.lineWidth = 1 + slash2Path.stroke() + + + //// Slash 3 Drawing + let slash3Path = UIBezierPath() + slash3Path.move(to: CGPoint(x: 6.5, y: 33)) + slash3Path.addLine(to: CGPoint(x: 32, y: 7.5)) + strokeColor.setStroke() + slash3Path.lineWidth = 1 + slash3Path.stroke() + + context.restoreGState() + + } + + @objc dynamic public class func drawMUTCDUnitOnly(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 53, height: 58), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30", legend: String = "SPEED\nLIMIT", showLegend: Bool = true, unit: String = "MPH") { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 53, height: 58), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 53, y: resizedFrame.height / 58) + + + //// Color Declarations + let borderColor = UIColor(red: 0.775, green: 0.775, blue: 0.775, alpha: 1.000) + + //// Variable Declarations + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + let legendHeight: CGFloat = legend == "MAX" ? 25 : 35 + let legendFontSize: CGFloat = legend == "MAX" ? 17 : 12 + let limitY: CGFloat = showLegend ? 34 : 10 + + //// Sign + //// Sign Back Drawing + let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 51), cornerRadius: 4) + signBackColor.setFill() + signBackPath.fill() + signBackColor.setStroke() + signBackPath.lineWidth = 4 + signBackPath.lineJoinStyle = .bevel + signBackPath.stroke() + + + //// Border Drawing + let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 51), cornerRadius: 4) + borderColor.setStroke() + borderPath.lineWidth = 2 + borderPath.lineJoinStyle = .bevel + borderPath.stroke() + + + + + //// Group + //// Limit Label Drawing + let limitLabelRect = CGRect(x: 3, y: (limitY - 34), width: 48, height: 33) + let limitLabelStyle = NSMutableParagraphStyle() + limitLabelStyle.alignment = .center + let limitLabelFontAttributes = [ + .font: UIFont.systemFont(ofSize: limitFontSize, weight: .bold), + .foregroundColor: strokeColor, + .paragraphStyle: limitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let limitLabelTextHeight: CGFloat = limit.boundingRect(with: CGSize(width: limitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: limitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: limitLabelRect) + limit.draw(in: CGRect(x: limitLabelRect.minX, y: limitLabelRect.minY + (limitLabelRect.height - limitLabelTextHeight) / 2, width: limitLabelRect.width, height: limitLabelTextHeight), withAttributes: limitLabelFontAttributes) + context.restoreGState() + + + if (showLegend) { + //// Unit Label Drawing + let unitLabelRect = CGRect(x: 2, y: 27, width: 49, height: (legendHeight - 7)) + let unitLabelStyle = NSMutableParagraphStyle() + unitLabelStyle.alignment = .center + let unitLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: (legendFontSize + 1)), + .foregroundColor: strokeColor, + .paragraphStyle: unitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let unitLabelTextHeight: CGFloat = unit.boundingRect(with: CGSize(width: unitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: unitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: unitLabelRect) + unit.draw(in: CGRect(x: unitLabelRect.minX, y: unitLabelRect.minY + (unitLabelRect.height - unitLabelTextHeight) / 2, width: unitLabelRect.width, height: unitLabelTextHeight), withAttributes: unitLabelFontAttributes) + context.restoreGState() + } + + context.restoreGState() + + } + + @objc dynamic public class func drawMUTCD(frame targetFrame: CGRect = CGRect(x: 0, y: 0, width: 53, height: 77), resizing: ResizingBehavior = .aspectFit, signBackColor: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000), strokeColor: UIColor = UIColor(red: 0.000, green: 0.000, blue: 0.000, alpha: 1.000), limit: String = "30", legend: String = "SPEED\nLIMIT", showLegend: Bool = true, unit: String = "MPH") { + //// General Declarations + let context = UIGraphicsGetCurrentContext()! + + //// Resize to Target Frame + context.saveGState() + let resizedFrame: CGRect = resizing.apply(rect: CGRect(x: 0, y: 0, width: 53, height: 77), target: targetFrame) + context.translateBy(x: resizedFrame.minX, y: resizedFrame.minY) + context.scaleBy(x: resizedFrame.width / 53, y: resizedFrame.height / 77) + + + //// Color Declarations + let borderColor = UIColor(red: 0.775, green: 0.775, blue: 0.775, alpha: 1.000) + + //// Variable Declarations + let limitFontSize: CGFloat = CGFloat(limit.count) > 2 ? 21 : 27 + let legendHeight: CGFloat = legend == "MAX" ? 25 : 35 + let legendFontSize: CGFloat = legend == "MAX" ? 17 : 12 + let limitY: CGFloat = showLegend ? 34 : 10 + + //// Sign + //// Sign Back Drawing + let signBackPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 71), cornerRadius: 4) + signBackColor.setFill() + signBackPath.fill() + signBackColor.setStroke() + signBackPath.lineWidth = 4 + signBackPath.lineJoinStyle = .bevel + signBackPath.stroke() + + + //// Border Drawing + let borderPath = UIBezierPath(roundedRect: CGRect(x: 3, y: 3, width: 48, height: 71), cornerRadius: 4) + borderColor.setStroke() + borderPath.lineWidth = 2 + borderPath.lineJoinStyle = .bevel + borderPath.stroke() + + + + + //// Group + //// Limit Label Drawing + let limitLabelRect = CGRect(x: 2, y: (limitY - 8), width: 50, height: 35) + let limitLabelStyle = NSMutableParagraphStyle() + limitLabelStyle.alignment = .center + let limitLabelFontAttributes = [ + .font: UIFont.systemFont(ofSize: limitFontSize, weight: .bold), + .foregroundColor: strokeColor, + .paragraphStyle: limitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let limitLabelTextHeight: CGFloat = limit.boundingRect(with: CGSize(width: limitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: limitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: limitLabelRect) + limit.draw(in: CGRect(x: limitLabelRect.minX, y: limitLabelRect.minY + (limitLabelRect.height - limitLabelTextHeight) / 2, width: limitLabelRect.width, height: limitLabelTextHeight), withAttributes: limitLabelFontAttributes) + context.restoreGState() + + + if (showLegend) { + //// Legend Label Drawing + let legendLabelRect = CGRect(x: 2, y: 0, width: 50, height: legendHeight) + let legendLabelStyle = NSMutableParagraphStyle() + legendLabelStyle.alignment = .center + let legendLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: legendFontSize), + .foregroundColor: strokeColor, + .paragraphStyle: legendLabelStyle, + ] as [NSAttributedString.Key: Any] + + let legendLabelTextHeight: CGFloat = legend.boundingRect(with: CGSize(width: legendLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: legendLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: legendLabelRect) + legend.draw(in: CGRect(x: legendLabelRect.minX, y: legendLabelRect.minY + (legendLabelRect.height - legendLabelTextHeight) / 2, width: legendLabelRect.width, height: legendLabelTextHeight), withAttributes: legendLabelFontAttributes) + context.restoreGState() + + + //// Unit Label Drawing + let unitLabelRect = CGRect(x: 3, y: 49, width: 49, height: (legendHeight - 7)) + let unitLabelStyle = NSMutableParagraphStyle() + unitLabelStyle.alignment = .center + let unitLabelFontAttributes = [ + .font: UIFont.boldSystemFont(ofSize: (legendFontSize + 1)), + .foregroundColor: strokeColor, + .paragraphStyle: unitLabelStyle, + ] as [NSAttributedString.Key: Any] + + let unitLabelTextHeight: CGFloat = unit.boundingRect(with: CGSize(width: unitLabelRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: unitLabelFontAttributes, context: nil).height + context.saveGState() + context.clip(to: unitLabelRect) + unit.draw(in: CGRect(x: unitLabelRect.minX, y: unitLabelRect.minY + (unitLabelRect.height - unitLabelTextHeight) / 2, width: unitLabelRect.width, height: unitLabelTextHeight), withAttributes: unitLabelFontAttributes) + context.restoreGState() + } + + context.restoreGState() + + } + + + + + @objc(SpeedLimitsKitResizingBehavior) + public enum ResizingBehavior: Int { + case aspectFit /// The content is proportionally resized to fit into the target rectangle. + case aspectFill /// The content is proportionally resized to completely fill the target rectangle. + case stretch /// The content is stretched to match the entire target rectangle. + case center /// The content is centered in the target rectangle, but it is NOT resized. + + public func apply(rect: CGRect, target: CGRect) -> CGRect { + if rect == target || target == CGRect.zero { + return rect + } + + var scales = CGSize.zero + scales.width = abs(target.width / rect.width) + scales.height = abs(target.height / rect.height) + + switch self { + case .aspectFit: + scales.width = min(scales.width, scales.height) + scales.height = scales.width + case .aspectFill: + scales.width = max(scales.width, scales.height) + scales.height = scales.width + case .stretch: + break + case .center: + scales.width = 1 + scales.height = 1 + } + + var result = rect.standardized + result.size.width *= scales.width + result.size.height *= scales.height + result.origin.x = target.minX + (target.width - result.width) / 2 + result.origin.y = target.minY + (target.height - result.height) / 2 + return result + } + } +}