Fix dms and dmm negative latlon
This commit is contained in:
parent
81e1fa58ec
commit
110d562630
@ -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"
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user