Fix dms and dmm negative latlon

This commit is contained in:
Austen Adler 2023-04-22 14:40:16 -04:00
parent 81e1fa58ec
commit 110d562630
4 changed files with 111 additions and 57 deletions

View File

@ -14,6 +14,8 @@ use crate::{
LatLon,
};
#[derive(Debug)]
// TODO: Derive serialize
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Coordinates {
pub latlon: LatLon,
@ -66,3 +68,15 @@ impl FromStr for Coordinates {
Self::try_from(LatLon::from(&Coordinate::from_str(i)?))
}
}
// #[cfg(test)]
// mod tests {
// use super::*;
// #[test]
// fn test_general() {
// dbg!(Coordinates::from_str(
// "69.79268710495744, -108.23886036872865"
// ));
// }
// }

View File

@ -127,7 +127,12 @@ impl DMM {
}
pub fn to_decimal_degrees(&self) -> f64 {
self.degrees as f64 + self.minutes / 60_f64
(self.degrees as f64 + self.minutes / 60_f64)
* if self.direction.is_positive() {
1.0_f64
} else {
-1.0_f64
}
}
}

View File

@ -18,20 +18,23 @@ impl Coordinate {
pub fn parse(i: &str) -> IResult<&str, Self> {
map_res(
tuple((DMS::parse, optional_separator(','), space0, DMS::parse)),
|(lat, _, _, lon)| {
// Ensure this is a north/south then east/west direction
if !lat.direction.is_lat() {
Err(Error::InvalidLatitudeBearing(lat.direction))
} else if !lon.direction.is_lon() {
Err(Error::InvalidLongitudeBearing(lon.direction))
} else {
let latlon = LatLon::new(lat.to_decimal_degrees(), lon.to_decimal_degrees())?;
Ok(Coordinate(lat, lon, latlon))
}
},
|(lat, _, _, lon)| Self::from_components(lat, lon),
)(i)
}
pub fn from_components(lat: DMS, lon: DMS) -> Result<Self, Error> {
let (lat, lon) = (lat.normalize(), lon.normalize());
// Ensure this is a north/south then east/west direction
if !lat.direction.is_lat() {
Err(Error::InvalidLatitudeBearing(lat.direction))
} else if !lon.direction.is_lon() {
Err(Error::InvalidLongitudeBearing(lon.direction))
} else {
let latlon = LatLon::new(lat.to_decimal_degrees(), lon.to_decimal_degrees())?;
Ok(Coordinate(lat, lon, latlon))
}
}
pub fn get_lat(&self) -> DMS {
self.0
}
@ -106,6 +109,17 @@ impl DMS {
)(i)
}
pub fn normalize(&self) -> Self {
let (degrees, direction) = normalize_degrees_direction(self.degrees, self.direction);
Self {
degrees,
minutes: self.minutes,
seconds: self.seconds,
direction,
}
}
pub fn from_decimal_degrees(d: f64, is_latitude: bool) -> Self {
let degrees = d as i16;
let minutes = d.abs().fract() * 60_f64;
@ -130,7 +144,12 @@ impl DMS {
}
pub fn to_decimal_degrees(&self) -> f64 {
self.degrees as f64 + self.minutes as f64 / 60_f64 + self.seconds / 3_600_f64
(self.degrees as f64 + self.minutes as f64 / 60_f64 + self.seconds / 3_600_f64)
* if self.direction.is_positive() {
1.0_f64
} else {
-1.0_f64
}
}
}

View File

@ -1,4 +1,8 @@
use crate::{common::parse_direction, Direction, Error, LatLon};
use crate::{
common::parse_direction,
dms::{self, DMS},
Direction, Error, LatLon,
};
use nom::{
bytes::complete::take,
character::complete::{self, space0},
@ -30,34 +34,45 @@ impl Coordinate {
opt(Self::parse_n_digits::<2>),
parse_direction,
)),
|(lat_d, lat_m, lat_s, lat_direction, _, _, _, lon_d, lon_m, lon_s, lon_direction)| {
// Ensure this is a north/south then east/west direction
if !lat_direction.is_lat() {
Err(Error::InvalidLatitudeBearing(lat_direction))
} else if !lon_direction.is_lon() {
Err(Error::InvalidLongitudeBearing(lon_direction))
} else {
let lat = Self::dms_to_f64_helper(lat_d, lat_m, lat_s, &lat_direction);
let lon = Self::dms_to_f64_helper(lon_d, lon_m, lon_s, &lon_direction);
|(lat_d, lat_m, lat_s, lat_direction, _, _, _, lon_d, lon_m, lon_s, lon_direction)| -> Result<_, Error> {
let lat = DMS {
degrees: lat_d as i16,
minutes: lat_m.unwrap_or(0) as i16,
seconds: lat_s.unwrap_or(0) as f64,
direction: lat_direction,
};
let lon = DMS {
degrees: lon_d as i16,
minutes: lon_m.unwrap_or(0) as i16,
seconds: lon_s.unwrap_or(0) as f64,
direction: lon_direction,
};
let latlon = LatLon::new(lat, lon)?;
Ok(Coordinate(Self::latlon_to_string(&latlon), latlon))
}
let dms = dms::Coordinate::from_components(lat, lon)?;
let latlon = LatLon::from(&dms);
// let lat = Self::dms_to_f64_helper(lat_d, lat_m, lat_s, &lat_direction);
// let lon = Self::dms_to_f64_helper(lon_d, lon_m, lon_s, &lon_direction);
// let latlon = DMS {};
// LatLon::new(lat, lon)?;
Ok(Coordinate(Self::latlon_to_string(&latlon), latlon))
},
)(i)
}
/// Helper to convert u8, Option<u8>, Option<u8> into an f64
fn dms_to_f64_helper(d: u8, m: Option<u8>, s: Option<u8>, direction: &Direction) -> f64 {
(d as f64
+ m.map(|m| m as f64 / 100.0_f64).unwrap_or(0.0_f64)
+ s.map(|s| s as f64 / 10_000.0_f64).unwrap_or(0.0_f64))
* if direction.is_positive() {
1.0_f64
} else {
-1.0_f64
}
}
// fn dms_to_f64_helper(d: u8, m: Option<u8>, s: Option<u8>, direction: &Direction) -> f64 {
// (d as f64
// + m.map(|m| m as f64 / 100.0_f64).unwrap_or(0.0_f64)
// + s.map(|s| s as f64 / 10_000.0_f64).unwrap_or(0.0_f64))
// * if direction.is_positive() {
// 1.0_f64
// } else {
// -1.0_f64
// }
// }
/// Takes n digits from the input and parses it as a u8
fn parse_n_digits<const C: u8>(i: &str) -> IResult<&str, u8> {
@ -96,7 +111,7 @@ impl FromStr for Coordinate {
impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, {}", self.0, self.1)
write!(f, "{}", self.0)
}
}
@ -117,25 +132,25 @@ impl From<LatLon> for Coordinate {
mod tests {
use super::*;
#[test]
fn test_helpers() {
assert_eq!(
Coordinate::dms_to_f64_helper(90, None, None, &Direction::North),
90.0000_f64
);
assert_eq!(
Coordinate::dms_to_f64_helper(90, None, None, &Direction::South),
-90.0000_f64
);
assert_eq!(
Coordinate::dms_to_f64_helper(90, Some(12), Some(8), &Direction::North),
90.1208_f64
);
assert_eq!(
Coordinate::dms_to_f64_helper(90, None, Some(8), &Direction::North),
90.0008_f64
);
}
// #[test]
// fn test_helpers() {
// assert_eq!(
// Coordinate::dms_to_f64_helper(90, None, None, &Direction::North),
// 90.0000_f64
// );
// assert_eq!(
// Coordinate::dms_to_f64_helper(90, None, None, &Direction::South),
// -90.0000_f64
// );
// assert_eq!(
// Coordinate::dms_to_f64_helper(90, Some(12), Some(8), &Direction::North),
// 90.1208_f64
// );
// assert_eq!(
// Coordinate::dms_to_f64_helper(90, None, Some(8), &Direction::North),
// 90.0008_f64
// );
// }
#[test]
fn test_general() {
@ -172,8 +187,9 @@ mod tests {
Ok(Coordinate::from(LatLon::new(-90.0_f64, 100.0_f64).unwrap()))
);
// TODO: Are these truncated or rounded?
assert_eq!(
Coordinate::from_str("697926N1082388W").unwrap().0,
Coordinate::from_str("694733N1081419W").unwrap().0,
Coordinate::from(LatLon::new(69.79268710495744, -108.23886036872865).unwrap()).0
)
}