#[derive(Debug, Clone, PartialEq)]
pub struct DmsCoordinate {
pub degree: Option<String>,
pub minute: Option<String>,
pub second: Option<String>,
}
impl DmsCoordinate {
pub fn to_decimal(&self) -> Option<f64> {
let deg = self.degree.as_ref()?.parse::<f64>().ok()?;
let min = self.minute.as_ref()?.parse::<f64>().ok()?;
let sec = self.second.as_ref()?.parse::<f64>().ok()?;
Some(deg + min / 60.0 + sec / 3600.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeodeticDatum {
Tokyo,
Jgd2000,
Jgd2011,
Wgs84,
Unknown,
}
impl GeodeticDatum {
pub fn from_code(s: &str) -> Self {
match s.trim() {
"00" => GeodeticDatum::Tokyo,
"01" => GeodeticDatum::Jgd2000,
"02" => GeodeticDatum::Jgd2011,
_ => GeodeticDatum::Unknown,
}
}
pub fn epsg_code(&self) -> Option<&'static str> {
match self {
GeodeticDatum::Tokyo => Some("EPSG:4301"),
GeodeticDatum::Jgd2000 => Some("EPSG:4612"),
GeodeticDatum::Jgd2011 => Some("EPSG:6668"),
GeodeticDatum::Wgs84 => Some("EPSG:4326"),
GeodeticDatum::Unknown => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GeoLocation {
pub longitude: DmsCoordinate,
pub latitude: DmsCoordinate,
pub geodetic_system: Option<String>,
}
impl GeoLocation {
pub fn to_wgs84(&self) -> Option<(f64, f64)> {
let lng = self.longitude.to_decimal()?;
let lat = self.latitude.to_decimal()?;
let datum = self
.geodetic_system
.as_ref()
.map(|s| GeodeticDatum::from_code(s))
.unwrap_or(GeodeticDatum::Unknown);
let from_crs = datum.epsg_code()?;
let proj = proj::Proj::new_known_crs(from_crs, "EPSG:4326", None).ok()?;
proj.convert((lng, lat)).ok()
}
}
pub trait GeoCoordinate {
fn to_wgs84(&self) -> Option<(f64, f64)>;
}
impl GeoCoordinate for crate::boring_structs_400::Boring400 {
fn to_wgs84(&self) -> Option<(f64, f64)> {
let lng_lat = &self.title.longitude_latitude;
let loc = GeoLocation {
longitude: DmsCoordinate {
degree: lng_lat.longitude_degree.clone(),
minute: lng_lat.longitude_minute.clone(),
second: lng_lat.longitude_second.clone(),
},
latitude: DmsCoordinate {
degree: lng_lat.latitude_degree.clone(),
minute: lng_lat.latitude_minute.clone(),
second: lng_lat.latitude_second.clone(),
},
geodetic_system: lng_lat.geodetic_system.clone(),
};
loc.to_wgs84()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dms_to_decimal() {
let dms = DmsCoordinate {
degree: Some("139".to_string()),
minute: Some("45".to_string()),
second: Some("0".to_string()),
};
let decimal = dms.to_decimal().unwrap();
assert!((decimal - 139.75).abs() < 0.0001);
}
#[test]
fn test_dms_to_decimal_with_seconds() {
let dms = DmsCoordinate {
degree: Some("139".to_string()),
minute: Some("41".to_string()),
second: Some("30".to_string()),
};
let decimal = dms.to_decimal().unwrap();
assert!((decimal - 139.69166666).abs() < 0.0001);
}
#[test]
fn test_dms_to_decimal_none() {
let dms = DmsCoordinate {
degree: None,
minute: Some("45".to_string()),
second: Some("0".to_string()),
};
assert!(dms.to_decimal().is_none());
}
#[test]
fn test_geodetic_datum_from_code() {
assert_eq!(GeodeticDatum::from_code("00"), GeodeticDatum::Tokyo);
assert_eq!(GeodeticDatum::from_code("01"), GeodeticDatum::Jgd2000);
assert_eq!(GeodeticDatum::from_code("02"), GeodeticDatum::Jgd2011);
assert_eq!(GeodeticDatum::from_code("99"), GeodeticDatum::Unknown);
}
#[test]
fn test_to_wgs84_from_tokyo() {
let loc = GeoLocation {
longitude: DmsCoordinate {
degree: Some("139".to_string()),
minute: Some("46".to_string()),
second: Some("0".to_string()),
},
latitude: DmsCoordinate {
degree: Some("35".to_string()),
minute: Some("40".to_string()),
second: Some("0".to_string()),
},
geodetic_system: Some("00".to_string()),
};
let (lng, lat) = loc.to_wgs84().unwrap();
assert!((lng - 139.76).abs() < 0.02);
assert!((lat - 35.67).abs() < 0.02);
}
#[test]
fn test_to_wgs84_from_jgd2000() {
let loc = GeoLocation {
longitude: DmsCoordinate {
degree: Some("139".to_string()),
minute: Some("46".to_string()),
second: Some("0".to_string()),
},
latitude: DmsCoordinate {
degree: Some("35".to_string()),
minute: Some("40".to_string()),
second: Some("0".to_string()),
},
geodetic_system: Some("01".to_string()),
};
let (lng, lat) = loc.to_wgs84().unwrap();
assert!((lng - 139.76666666).abs() < 0.0001);
assert!((lat - 35.66666666).abs() < 0.0001);
}
}