-
Notifications
You must be signed in to change notification settings - Fork 1.3k
MGLDistanceFormatter #3331
Comments
Good idea. 💡 |
Hi guys, I'm sure you guys have your own code standards and way of writing things, but if you think that what I wrote could serve as a jumping off point for the real MGLDistanceFormatter, then I'd be happy to take what I wrote and convert it into Obj-C. Here is the Swift code :) Cheers, import Foundation
import CoreLocation
enum MGLDistanceFormatterUnits : UInt {
case Metric
case Imperial
case ImperialWithYards
}
enum MGLDistanceFormatterUnitStyle : UInt {
case Abbreviated
case Full
}
//Using ISO 3166-1 --> https://www.iso.org/obp/ui/
enum MGLImperialCountries: String {
case US //USA
case GB //United Kingdom
case MM //Myanmar
case BS //Bahamas
case BZ //Belize
}
public class MGLDistanceFormatter {
private let METERS_PER_KM = 1000.0
private let METERS_PER_MILE = 1609.34
private let FEET_PER_METER = 3.28084
private let FEET_PER_MILE = 5280.0
private let FEET_PER_YD = 3.0
private let YARDS_PER_METER = 1.09361
private let ABB_MILES = "mi"
private let ABB_METERS = "m"
private let ABB_YARDS = "yds"
private let ABB_KILOMETERS = "km"
private let ABB_FEET = "ft"
private let MILES = "miles"
private let METERS = "meters"
private let KILOMETERS = "kilometers"
private let FEET = "feet"
private let FOOT = "foot"
private let YARDS = "yards"
private var locale: NSLocale!
private var localeDeterminedUnits: MGLDistanceFormatterUnits!
var units: MGLDistanceFormatterUnits
var unitStyle: MGLDistanceFormatterUnitStyle
public init() {
locale = NSLocale.currentLocale()
unitStyle = .Abbreviated
if let countryCode = locale.objectForKey(NSLocaleCountryCode) as? String {
switch (countryCode) {
case MGLImperialCountries.US.rawValue:
localeDeterminedUnits = .Imperial
break
case MGLImperialCountries.GB.rawValue:
localeDeterminedUnits = .Imperial
break
case MGLImperialCountries.MM.rawValue:
localeDeterminedUnits = .Imperial
break
case MGLImperialCountries.BS.rawValue:
localeDeterminedUnits = .Imperial
break
case MGLImperialCountries.BZ.rawValue:
localeDeterminedUnits = .Imperial
break
default:
localeDeterminedUnits = .Metric
break
}
} else {
localeDeterminedUnits = .Metric
}
units = localeDeterminedUnits
}
public func stringFromDistance(distance: CLLocationDistance) -> String {
//CLLocationDistance is a typealias for Double in meters
guard distance >= 0.0 else {
return ""
}
var distanceString: String!
var convertedDistance: Double! //Distance after converting to user-specified units
//These two booleans are in reference to whether we should-- use feet as opposed to miles / use meters as opposed to kilometers
var useFeet = true
var useMeters = true
switch (units) {
case .Metric:
convertedDistance = distance //convertedDistance remains in meters
if convertedDistance > METERS_PER_KM {
convertedDistance = convertedDistance/METERS_PER_KM
useMeters = false
}
if unitStyle == .Abbreviated {
if useMeters {
distanceString = String(format: "%.1f \(ABB_METERS)", convertedDistance)
} else {
distanceString = String(format: "%.1f \(ABB_KILOMETERS)", convertedDistance)
}
} else {
if useMeters {
distanceString = String(format: "%.1f \(METERS)", convertedDistance)
} else {
distanceString = String(format: "%.1f \(KILOMETERS)", convertedDistance)
}
}
break
case .Imperial:
convertedDistance = distance * FEET_PER_METER //convertedDistance is now in feet
if convertedDistance > FEET_PER_MILE/2.0 {
convertedDistance = convertedDistance/FEET_PER_MILE
useFeet = false
}
if unitStyle == .Abbreviated {
if useFeet {
distanceString = String(format: "%.1f \(ABB_FEET)", convertedDistance)
} else {
distanceString = String(format: "%.1f \(ABB_MILES)", convertedDistance)
}
} else {
if useFeet {
if convertedDistance == 1.0 {
distanceString = String(format: "%.1f \(FOOT)", convertedDistance)
} else {
distanceString = String(format: "%.1f \(FEET)", convertedDistance)
}
} else {
distanceString = String(format: "%.1f \(MILES)", convertedDistance)
}
}
break
case .ImperialWithYards:
convertedDistance = distance * YARDS_PER_METER //convertedDistance is now in yards
if unitStyle == .Abbreviated {
distanceString = String(format: "%.1f \(ABB_YARDS)", convertedDistance)
} else {
distanceString = String(format: "%.1f \(YARDS)", convertedDistance)
}
break
}
return distanceString
}
public func distanceFromString(distance: String) -> CLLocationDistance {
//Split our distance string into an array with the first element being the actual distance and the second being the unit
let distanceComponents = distance.characters.split{$0 == " "}.map(String.init)
guard distanceComponents.count == 2 else {
return 0.0
}
let numericalDistance = Double(distanceComponents[0])
guard numericalDistance >= 0 else {
return 0.0
}
let unit = distanceComponents[1]
var distanceInMeters: Double!
switch (unit) {
case METERS, ABB_METERS:
distanceInMeters = numericalDistance
return distanceInMeters
case FEET, FOOT, ABB_FEET:
distanceInMeters = numericalDistance!/FEET_PER_METER
return distanceInMeters
case YARDS, ABB_YARDS:
distanceInMeters = numericalDistance!/YARDS_PER_METER
return distanceInMeters
case KILOMETERS, ABB_KILOMETERS:
distanceInMeters = numericalDistance!*METERS_PER_KM
return distanceInMeters
case MILES, ABB_MILES:
distanceInMeters = numericalDistance!*METERS_PER_MILE
return distanceInMeters
default:
return 0.0
}
}
} |
@1ec5 should this be implemented on the ios level in obj-c, or in c++? |
This and #3391 should be implemented at the SDK level. There's little to be gained by making the transformation cross-platform, especially if localization is supported. The SDK is currently entirely written in Objective-C/Objective-C++. We'd need some build system changes in order to support writing SDK classes in Swift, since one of the supported targets is a static framework that I don't think has the necessary build flags set for Swift modules. The sample implementation above is solid, although it doesn't support localization, which would be important for any formatter. I think I'd prefer an implementation based on NSLengthFormatter, with perhaps a special case to avoid outputting yards in the en-US locale. |
@1ec5 I didn't even think about the localization. Definitely something I should've done. Happy to help going forward |
We added some formatters with localization support in #4802. They might serve as an example of how to go about making this formatter respect the current system locale. Another point worth mentioning is that NSLengthFormatter already chooses between metric and Imperial units. The main thing MKDistanceFormatter provides over a raw NSLengthFormatter is unit thresholds that make more sense for navigational distances. Some time ago, we implemented something along these lines in an internal testbed application, also written in Swift. Here’s what it looks like. Most of the code is “an elaborate hack” for displaying mixed numbers with vulgar fractions instead of decimal miles, to match the convention established by U.S. and U.K. highway authorities. |
MapKit provides a handy subclass of NSFormatter, MKDistanceFormatter, for turning
CLLocationDistance
s into display-appropriate strings with units. The default unit is selected based on the system locale. Developers shouldn’t have to import MapKit for this functionality.This is a
starter-task
because it has no dependencies on MGLMapView or mbgl./ref #949
/cc @friedbunny
The text was updated successfully, but these errors were encountered: