在 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 来引用它。

收到https://codereview.stackexchange.com/questions/153291/parsing-and-converting-dms-coordinates-from-string-to-double

的回复时
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(&degrees),         // 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 的扩展。

它用正则表达式解析字符串并考虑

  • 参数之间的可选空格。
  • 方向在字符串的开头或结尾,EO 都表示东。
  • 分钟 ('′) 和秒 ("″) 的不同字符。

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])
    }
}