use crate::transform::JgdTransformer;
#[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, transformer: &JgdTransformer) -> Option<(f64, f64)> {
let lng = self.longitude.to_decimal()?;
let lat = self.latitude.to_decimal()?;
let system_code = self.geodetic_system.as_deref().unwrap_or("02");
transformer.to_wgs84(lat, lng, system_code)
}
pub fn to_decimal(&self) -> Option<(f64, f64)> {
let lng = self.longitude.to_decimal()?;
let lat = self.latitude.to_decimal()?;
Some((lng, lat))
}
pub fn datum(&self) -> GeodeticDatum {
self.geodetic_system
.as_ref()
.map(|s| GeodeticDatum::from_code(s))
.unwrap_or(GeodeticDatum::Unknown)
}
}
pub trait GeoCoordinate {
fn geo_location(&self) -> GeoLocation;
}
impl GeoCoordinate for crate::boring_structs_400::Boring400 {
fn geo_location(&self) -> GeoLocation {
let lng_lat = &self.title.longitude_latitude;
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(),
}
}
}
impl GeoCoordinate for crate::boring_structs_300::Boring300 {
fn geo_location(&self) -> GeoLocation {
let lng_lat = &self.title.longitude_latitude;
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(),
}
}
}
impl GeoCoordinate for crate::boring_structs_200::Boring200 {
fn geo_location(&self) -> GeoLocation {
let lng_lat = &self.title.longitude_latitude;
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(),
}
}
}
impl GeoCoordinate for crate::boring_structs_210::Boring210 {
fn geo_location(&self) -> GeoLocation {
let lng_lat = &self.title.longitude_latitude;
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(),
}
}
}
impl GeoCoordinate for crate::boring_structs_110::Boring110 {
fn geo_location(&self) -> GeoLocation {
let lng_lat = &self.title.longitude_latitude;
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(),
}
}
}
#[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() {
let workspace_root =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let tky_path = workspace_root.join("params/TKY2JGD.par");
let patch_dir = workspace_root.join("params/patch_files");
if !tky_path.exists() || !patch_dir.exists() {
println!("Skipping test: params not found");
return;
}
let transformer = crate::transform::JgdTransformer::new(&tky_path, &patch_dir)
.expect("Failed to create transformer");
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(&transformer).unwrap();
assert!((lng - 139.76).abs() < 0.02);
assert!((lat - 35.67).abs() < 0.02);
println!("Tokyo Datum → WGS84: ({}, {})", lng, lat);
}
}