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, LatLon,
}; };
#[derive(Debug)]
// TODO: Derive serialize
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Coordinates { pub struct Coordinates {
pub latlon: LatLon, pub latlon: LatLon,
@ -66,3 +68,15 @@ impl FromStr for Coordinates {
Self::try_from(LatLon::from(&Coordinate::from_str(i)?)) 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 { 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> { pub fn parse(i: &str) -> IResult<&str, Self> {
map_res( map_res(
tuple((DMS::parse, optional_separator(','), space0, DMS::parse)), tuple((DMS::parse, optional_separator(','), space0, DMS::parse)),
|(lat, _, _, lon)| { |(lat, _, _, lon)| Self::from_components(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))
}
},
)(i) )(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 { pub fn get_lat(&self) -> DMS {
self.0 self.0
} }
@ -106,6 +109,17 @@ impl DMS {
)(i) )(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 { pub fn from_decimal_degrees(d: f64, is_latitude: bool) -> Self {
let degrees = d as i16; let degrees = d as i16;
let minutes = d.abs().fract() * 60_f64; let minutes = d.abs().fract() * 60_f64;
@ -130,7 +144,12 @@ impl DMS {
} }
pub fn to_decimal_degrees(&self) -> f64 { 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::{ use nom::{
bytes::complete::take, bytes::complete::take,
character::complete::{self, space0}, character::complete::{self, space0},
@ -30,34 +34,45 @@ impl Coordinate {
opt(Self::parse_n_digits::<2>), opt(Self::parse_n_digits::<2>),
parse_direction, parse_direction,
)), )),
|(lat_d, lat_m, lat_s, lat_direction, _, _, _, 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> {
// Ensure this is a north/south then east/west direction let lat = DMS {
if !lat_direction.is_lat() { degrees: lat_d as i16,
Err(Error::InvalidLatitudeBearing(lat_direction)) minutes: lat_m.unwrap_or(0) as i16,
} else if !lon_direction.is_lon() { seconds: lat_s.unwrap_or(0) as f64,
Err(Error::InvalidLongitudeBearing(lon_direction)) direction: lat_direction,
} else { };
let lat = Self::dms_to_f64_helper(lat_d, lat_m, lat_s, &lat_direction); let lon = DMS {
let lon = Self::dms_to_f64_helper(lon_d, lon_m, lon_s, &lon_direction); 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)?; let dms = dms::Coordinate::from_components(lat, lon)?;
Ok(Coordinate(Self::latlon_to_string(&latlon), latlon)) 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) )(i)
} }
/// Helper to convert u8, Option<u8>, Option<u8> into an f64 /// 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 { // 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) // (d as f64
+ s.map(|s| s as f64 / 10_000.0_f64).unwrap_or(0.0_f64)) // + m.map(|m| m as f64 / 100.0_f64).unwrap_or(0.0_f64)
* if direction.is_positive() { // + s.map(|s| s as f64 / 10_000.0_f64).unwrap_or(0.0_f64))
1.0_f64 // * if direction.is_positive() {
} else { // 1.0_f64
-1.0_f64 // } else {
} // -1.0_f64
} // }
// }
/// Takes n digits from the input and parses it as a u8 /// Takes n digits from the input and parses it as a u8
fn parse_n_digits<const C: u8>(i: &str) -> IResult<&str, 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 { impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 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 { mod tests {
use super::*; use super::*;
#[test] // #[test]
fn test_helpers() { // fn test_helpers() {
assert_eq!( // assert_eq!(
Coordinate::dms_to_f64_helper(90, None, None, &Direction::North), // Coordinate::dms_to_f64_helper(90, None, None, &Direction::North),
90.0000_f64 // 90.0000_f64
); // );
assert_eq!( // assert_eq!(
Coordinate::dms_to_f64_helper(90, None, None, &Direction::South), // Coordinate::dms_to_f64_helper(90, None, None, &Direction::South),
-90.0000_f64 // -90.0000_f64
); // );
assert_eq!( // assert_eq!(
Coordinate::dms_to_f64_helper(90, Some(12), Some(8), &Direction::North), // Coordinate::dms_to_f64_helper(90, Some(12), Some(8), &Direction::North),
90.1208_f64 // 90.1208_f64
); // );
assert_eq!( // assert_eq!(
Coordinate::dms_to_f64_helper(90, None, Some(8), &Direction::North), // Coordinate::dms_to_f64_helper(90, None, Some(8), &Direction::North),
90.0008_f64 // 90.0008_f64
); // );
} // }
#[test] #[test]
fn test_general() { fn test_general() {
@ -172,8 +187,9 @@ mod tests {
Ok(Coordinate::from(LatLon::new(-90.0_f64, 100.0_f64).unwrap())) Ok(Coordinate::from(LatLon::new(-90.0_f64, 100.0_f64).unwrap()))
); );
// TODO: Are these truncated or rounded?
assert_eq!( assert_eq!(
Coordinate::from_str("697926N1082388W").unwrap().0, Coordinate::from_str("694733N1081419W").unwrap().0,
Coordinate::from(LatLon::new(69.79268710495744, -108.23886036872865).unwrap()).0 Coordinate::from(LatLon::new(69.79268710495744, -108.23886036872865).unwrap()).0
) )
} }