patternswiftMinor
Parsing and converting DMS Coordinates from String to Double
Viewed 0 times
coordinatesdmsparsingdoubleandconvertingfromstring
Problem
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
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.
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
}
}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
You have defined a value type (
you can omit the type annotations because the compiler can infer the
type automatically:
But actually I would go the other way around and replace the initial
values by an
your own init method – there is no default memberwise initializer anymore.
Therefore I would start with
which allows you to create a value not only from a string, but
also from a given latitude and longitude:
Your
string is in a valid format and can crash otherwise (by accesssing
out-of-bounds array elements or unwrapping
@Ashley already said in his answer, you should define a
failable initializer instead, which returns
invalid:
Alternatively, define a
which throws an error for invalid input.
Your helper methods
which is good. They do not use any properties of the value, which means
that they can be made
The conversion helper function is very lenient, it will for example
accept
as latitude. It does not check that the coordinate starts with
a valid direction, or that the proper separators are used.
I don't know what the final part
is ignored completely in your code.
There is also a flaw in your conversion, the fractional part
of the seconds is ignored, e.g.
I would suggest to use
easy to check for a valid input and simultaneously
parse the values into variables. To distinguish
between latitude and longitude, two arguments for the positive
and the negative direction are passed to the helper method.
The complete code then looks like this:
struct) and not a class, which is good. Invar latitude: Double = 0.0
var longitude: Double = 0.0you can omit the type annotations because the compiler can infer the
type automatically:
var latitude = 0.0
var longitude = 0.0But actually I would go the other way around and replace the initial
values by an
init() method. The reason is that – since you definedyour own init method – there is no default memberwise initializer anymore.
Therefore I would start with
struct PLNWaypointCoordinate {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
// ...
}which allows you to create a value not only from a string, but
also from a given latitude and longitude:
let c = PLNWaypointCoordinate(latitude: ..., longitude: ...)Your
init(coordinateString: String) method assumes that the givenstring is in a valid format and can crash otherwise (by accesssing
out-of-bounds array elements or unwrapping
nils). As@Ashley already said in his answer, you should define a
failable initializer instead, which returns
nil if the input isinvalid:
init?(coordinateString: String) { ... }Alternatively, define a
throwing initializer:init(coordinateString: String) throws { ... }which throws an error for invalid input.
Your helper methods
convertCoordinate and convert are private,which is good. They do not use any properties of the value, which means
that they can be made
static.The conversion helper function is very lenient, it will for example
accept
X47 15 36.75 instead of N47° 15' 36.75" as latitude. It does not check that the coordinate starts with
a valid direction, or that the proper separators are used.
I don't know what the final part
+001906.00 stands for, but thatis ignored completely in your code.
There is also a flaw in your conversion, the fractional part
of the seconds is ignored, e.g.
36.75" is taken as 36 seconds.I would suggest to use
Scanner instead, which makes it relativelyeasy to check for a valid input and simultaneously
parse the values into variables. To distinguish
between latitude and longitude, two arguments for the positive
and the negative direction are passed to the helper method.
The complete code then looks like this:
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)
}
}Code Snippets
var latitude: Double = 0.0
var longitude: Double = 0.0var latitude = 0.0
var longitude = 0.0struct PLNWaypointCoordinate {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
// ...
}let c = PLNWaypointCoordinate(latitude: ..., longitude: ...)init?(coordinateString: String) { ... }Context
StackExchange Code Review Q#153291, answer score: 5
Revisions (0)
No revisions yet.