swiftcoordinatesdmscllocationcoordinate2d

Better way to parse and convert DMS Coordinates from String to Double in Swift


So I have some coordinates looking like

N47° 15' 36.75",E011° 20' 38.28",+001906.00

and i've created a class to parse and convert them to 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
    }

}

My question is, as the title says, is there a better and safer way to perform this conversion?

Last method I've picked up here. Sorry, but I can't find the link to reference it.


Solution

  • When ahead with the reply from 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)
        }
    }
    

    Thanks for the help everybody!