在 Swift 中解析 DMS 坐标并将其从 String 转换为 Double 的更好方法
Better way to parse and convert DMS Coordinates from String to Double in Swift
所以我有一些坐标看起来像
N47° 15' 36.75",E011° 20' 38.28",+001906.00
我创建了一个 class 来解析并将它们转换为 double
struct PLNWaypointCoordinate {
var latitude: Double = 0.0
var longitude: Double = 0.0
init(coordinateString: String) {
self.latitude = convertCoordinate(string: coordinateString.components(separatedBy: ",")[0])
self.longitude = convertCoordinate(string: coordinateString.components(separatedBy: ",")[1])
}
private func convertCoordinate(string: String) -> Double {
var separatedCoordinate = string.characters.split(separator: " ").map(String.init)
let direction = separatedCoordinate[0].components(separatedBy: CharacterSet.letters.inverted).first
let degrees = Double(separatedCoordinate[0].components(separatedBy: CharacterSet.decimalDigits.inverted)[1])
let minutes = Double(separatedCoordinate[1].components(separatedBy: CharacterSet.decimalDigits.inverted)[0])
let seconds = Double(separatedCoordinate[2].components(separatedBy: CharacterSet.decimalDigits.inverted)[0])
return convert(degrees: degrees!, minutes: minutes!, seconds: seconds!, direction: direction!)
}
private func convert(degrees: Double, minutes: Double, seconds: Double, direction: String) -> Double {
let sign = (direction == "W" || direction == "S") ? -1.0 : 1.0
return (degrees + (minutes + seconds/60.0)/60.0) * sign
}
}
我的问题是,如标题所说,是否有更好更安全的方法来执行此转换?
我在这里学到的最后一种方法。抱歉,我找不到 link 来引用它。
的回复时
struct PLNWaypointCoordinate {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
init?(coordinateString: String) {
let components = coordinateString.components(separatedBy: ",")
guard components.count >= 2,
let latitude = PLNWaypointCoordinate.convertCoordinate(coordinate: components[0],
positiveDirection: "N",
negativeDirection: "S"),
let longitude = PLNWaypointCoordinate.convertCoordinate(coordinate: components[1],
positiveDirection: "E",
negativeDirection: "W")
else {
return nil
}
self.init(latitude: latitude, longitude: longitude)
}
private static func convertCoordinate(coordinate: String,
positiveDirection: String,
negativeDirection: String) -> Double? {
// Determine the sign from the first character:
let sign: Double
let scanner = Scanner(string: coordinate)
if scanner.scanString(positiveDirection, into: nil) {
sign = 1.0
} else if scanner.scanString(negativeDirection, into: nil) {
sign = -1.0
} else {
return nil
}
// Parse degrees, minutes, seconds:
var degrees = 0
var minutes = 0
var seconds = 0.0
guard scanner.scanInt(°rees), // Degrees (integer),
scanner.scanString("°", into: nil), // followed by °,
scanner.scanInt(&minutes), // minutes (integer)
scanner.scanString("'", into: nil), // followed by '
scanner.scanDouble(&seconds), // seconds (floating point),
scanner.scanString("\"", into: nil), // followed by ",
scanner.isAtEnd // and nothing else.
else { return nil }
return sign * (Double(degrees) + Double(minutes)/60.0 + seconds/3600.0)
}
}
感谢大家的帮助!
这是一个更通用的版本,作为 CLLocationCoordinate2D
的扩展。
它用正则表达式解析字符串并考虑
- 参数之间的可选空格。
- 方向在字符串的开头或结尾,
E
和 O
都表示东。
- 分钟 (
'′
) 和秒 ("″
) 的不同字符。
extension CLLocationCoordinate2D {
init?(coordinateString: String) {
let directionIsPrefix = "NEOWS".contains(coordinateString.prefix(1))
let pattern = directionIsPrefix
? "([NOEWS])\s?(\d{1,2})°\s?(\d{1,2})['′]\s?(\d{1,2}\.?(\d+)?)[\"″]"
: "(\d{1,2})°\s?(\d{1,2})['′]\s?(\d{1,2}\.?(\d+)?)[\"″]\s?([NOEWS])"
var latlon = [Double]()
do {
let regex = try NSRegularExpression(pattern: pattern)
let matches = regex.matches(in: coordinateString, range: NSRange(coordinateString.startIndex..., in: coordinateString))
guard matches.count == 2 else { return nil }
for match in matches {
let m1 = coordinateString[Range(match.range(at:1), in: coordinateString)!]
let m2 = coordinateString[Range(match.range(at:2), in: coordinateString)!]
let m3 = coordinateString[Range(match.range(at:3), in: coordinateString)!]
let lastIndex = directionIsPrefix ? 4 : 5
let m4 = coordinateString[Range(match.range(at:lastIndex), in: coordinateString)!]
let value : Double
if directionIsPrefix {
let sign = "NEO".contains(m1) ? 1.0 : -1.0
value = sign * (Double(m2)! + Double(m3)!/60.0 + Double(m4)!/3600.0)
} else {
let sign = "NEO".contains(m4) ? 1.0 : -1.0
value = sign * (Double(m1)! + Double(m2)!/60.0 + Double(m3)!/3600.0)
}
latlon.append(value)
}
} catch {
print(error)
return nil
}
self.init(latitude: latlon[0], longitude: latlon[1])
}
}
所以我有一些坐标看起来像
N47° 15' 36.75",E011° 20' 38.28",+001906.00
我创建了一个 class 来解析并将它们转换为 double
struct PLNWaypointCoordinate {
var latitude: Double = 0.0
var longitude: Double = 0.0
init(coordinateString: String) {
self.latitude = convertCoordinate(string: coordinateString.components(separatedBy: ",")[0])
self.longitude = convertCoordinate(string: coordinateString.components(separatedBy: ",")[1])
}
private func convertCoordinate(string: String) -> Double {
var separatedCoordinate = string.characters.split(separator: " ").map(String.init)
let direction = separatedCoordinate[0].components(separatedBy: CharacterSet.letters.inverted).first
let degrees = Double(separatedCoordinate[0].components(separatedBy: CharacterSet.decimalDigits.inverted)[1])
let minutes = Double(separatedCoordinate[1].components(separatedBy: CharacterSet.decimalDigits.inverted)[0])
let seconds = Double(separatedCoordinate[2].components(separatedBy: CharacterSet.decimalDigits.inverted)[0])
return convert(degrees: degrees!, minutes: minutes!, seconds: seconds!, direction: direction!)
}
private func convert(degrees: Double, minutes: Double, seconds: Double, direction: String) -> Double {
let sign = (direction == "W" || direction == "S") ? -1.0 : 1.0
return (degrees + (minutes + seconds/60.0)/60.0) * sign
}
}
我的问题是,如标题所说,是否有更好更安全的方法来执行此转换?
我在这里学到的最后一种方法。抱歉,我找不到 link 来引用它。
struct PLNWaypointCoordinate {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
init?(coordinateString: String) {
let components = coordinateString.components(separatedBy: ",")
guard components.count >= 2,
let latitude = PLNWaypointCoordinate.convertCoordinate(coordinate: components[0],
positiveDirection: "N",
negativeDirection: "S"),
let longitude = PLNWaypointCoordinate.convertCoordinate(coordinate: components[1],
positiveDirection: "E",
negativeDirection: "W")
else {
return nil
}
self.init(latitude: latitude, longitude: longitude)
}
private static func convertCoordinate(coordinate: String,
positiveDirection: String,
negativeDirection: String) -> Double? {
// Determine the sign from the first character:
let sign: Double
let scanner = Scanner(string: coordinate)
if scanner.scanString(positiveDirection, into: nil) {
sign = 1.0
} else if scanner.scanString(negativeDirection, into: nil) {
sign = -1.0
} else {
return nil
}
// Parse degrees, minutes, seconds:
var degrees = 0
var minutes = 0
var seconds = 0.0
guard scanner.scanInt(°rees), // Degrees (integer),
scanner.scanString("°", into: nil), // followed by °,
scanner.scanInt(&minutes), // minutes (integer)
scanner.scanString("'", into: nil), // followed by '
scanner.scanDouble(&seconds), // seconds (floating point),
scanner.scanString("\"", into: nil), // followed by ",
scanner.isAtEnd // and nothing else.
else { return nil }
return sign * (Double(degrees) + Double(minutes)/60.0 + seconds/3600.0)
}
}
感谢大家的帮助!
这是一个更通用的版本,作为 CLLocationCoordinate2D
的扩展。
它用正则表达式解析字符串并考虑
- 参数之间的可选空格。
- 方向在字符串的开头或结尾,
E
和O
都表示东。 - 分钟 (
'′
) 和秒 ("″
) 的不同字符。
extension CLLocationCoordinate2D {
init?(coordinateString: String) {
let directionIsPrefix = "NEOWS".contains(coordinateString.prefix(1))
let pattern = directionIsPrefix
? "([NOEWS])\s?(\d{1,2})°\s?(\d{1,2})['′]\s?(\d{1,2}\.?(\d+)?)[\"″]"
: "(\d{1,2})°\s?(\d{1,2})['′]\s?(\d{1,2}\.?(\d+)?)[\"″]\s?([NOEWS])"
var latlon = [Double]()
do {
let regex = try NSRegularExpression(pattern: pattern)
let matches = regex.matches(in: coordinateString, range: NSRange(coordinateString.startIndex..., in: coordinateString))
guard matches.count == 2 else { return nil }
for match in matches {
let m1 = coordinateString[Range(match.range(at:1), in: coordinateString)!]
let m2 = coordinateString[Range(match.range(at:2), in: coordinateString)!]
let m3 = coordinateString[Range(match.range(at:3), in: coordinateString)!]
let lastIndex = directionIsPrefix ? 4 : 5
let m4 = coordinateString[Range(match.range(at:lastIndex), in: coordinateString)!]
let value : Double
if directionIsPrefix {
let sign = "NEO".contains(m1) ? 1.0 : -1.0
value = sign * (Double(m2)! + Double(m3)!/60.0 + Double(m4)!/3600.0)
} else {
let sign = "NEO".contains(m4) ? 1.0 : -1.0
value = sign * (Double(m1)! + Double(m2)!/60.0 + Double(m3)!/3600.0)
}
latlon.append(value)
}
} catch {
print(error)
return nil
}
self.init(latitude: latlon[0], longitude: latlon[1])
}
}