///|
pub(all) enum CoordinateAxis {
Latitude
Longitude
} derive(Eq, Debug)
///|
pub(all) struct Coordinate {
axis_value : CoordinateAxis
degrees_value : Double
wire_value : ExactDecimal
hemisphere_value : UInt16
} derive(Eq, Debug)
///|
fn coordinate_error(source : SourceRef, message : String) -> NmeaError {
NmeaError::from_diagnostic(
Diagnostic::new(CoordinateInvalid, Error, message, source),
)
}
///|
pub fn Coordinate::axis(self : Coordinate) -> CoordinateAxis {
self.axis_value
}
///|
pub fn Coordinate::degrees(self : Coordinate) -> Double {
self.degrees_value
}
///|
pub fn Coordinate::wire(self : Coordinate) -> ExactDecimal {
self.wire_value
}
///|
pub fn Coordinate::hemisphere(self : Coordinate) -> UInt16 {
self.hemisphere_value
}
///|
/// Renders the axis-specific degree width used by NMEA degree-minute fields.
pub fn coordinate_wire_text(value : Coordinate) -> String {
let text = value.wire_value.to_string()
let required_whole_digits = match value.axis_value {
Latitude => 4
Longitude => 5
}
let mut dot = text.length()
for index = 0; index < text.length(); index = index + 1 {
if text[index].to_int() == 46 {
dot = index
break
}
}
if dot >= required_whole_digits {
text
} else {
String::repeat("0", required_whole_digits - dot) + text
}
}
///|
fn parse_coordinate(
value : String,
hemisphere : String,
axis : CoordinateAxis,
source : SourceRef,
) -> Result[Coordinate, NmeaError] {
if hemisphere.length() != 1 {
return Err(
coordinate_error(source, "coordinate hemisphere must be one letter"),
)
}
let hemi = hemisphere[0]
let valid_hemisphere = match axis {
Latitude => hemi == 78 || hemi == 83
Longitude => hemi == 69 || hemi == 87
}
if !valid_hemisphere {
return Err(
coordinate_error(source, "coordinate hemisphere is invalid for axis"),
)
}
let degree_digits = match axis {
Latitude => 2
Longitude => 3
}
if value.length() <= degree_digits + 2 {
return Err(
coordinate_error(source, "coordinate is missing degree-minute digits"),
)
}
let degree_text = value[:degree_digits].to_owned()
let minute_text = value[degree_digits:].to_owned()
let degrees = match
FieldCursor::new([degree_text]).integer(0, min=0, max=180) {
Ok(value) => value
Err(_) =>
return Err(coordinate_error(source, "coordinate degrees are invalid"))
}
let minutes = match ExactDecimal::parse(minute_text, max_scale=9) {
Ok(value) => value.to_double()
Err(_) =>
return Err(coordinate_error(source, "coordinate minutes are invalid"))
}
if minutes < 0.0 || minutes >= 60.0 {
return Err(coordinate_error(source, "coordinate minutes must be below 60"))
}
if (axis is Latitude && degrees > 90) || (axis is Longitude && degrees > 180) {
return Err(
coordinate_error(source, "coordinate degrees exceed axis bounds"),
)
}
if (
(axis is Latitude && degrees == 90) ||
(axis is Longitude && degrees == 180)
) &&
minutes != 0.0 {
return Err(
coordinate_error(source, "a pole or antimeridian must have zero minutes"),
)
}
let magnitude = degrees.to_double() + minutes / 60.0
let signed = if hemi == 83 || hemi == 87 { -magnitude } else { magnitude }
Ok({
axis_value: axis,
degrees_value: signed,
wire_value: match ExactDecimal::parse(value) {
Ok(value) => value
Err(_) =>
return Err(coordinate_error(source, "coordinate wire value is invalid"))
},
hemisphere_value: hemi,
})
}
///|
pub fn parse_latitude(
value : String,
hemisphere : String,
source : SourceRef,
) -> Result[Coordinate, NmeaError] {
parse_coordinate(value, hemisphere, Latitude, source)
}
///|
pub fn parse_longitude(
value : String,
hemisphere : String,
source : SourceRef,
) -> Result[Coordinate, NmeaError] {
parse_coordinate(value, hemisphere, Longitude, source)
}