1use crate::transform::JgdTransformer;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct DmsCoordinate {
11 pub degree: Option<String>,
13 pub minute: Option<String>,
15 pub second: Option<String>,
17}
18
19impl DmsCoordinate {
20 pub fn to_decimal(&self) -> Option<f64> {
24 let deg = self.degree.as_ref()?.parse::<f64>().ok()?;
25 let min = self.minute.as_ref()?.parse::<f64>().ok()?;
26 let sec = self.second.as_ref()?.parse::<f64>().ok()?;
27 Some(deg + min / 60.0 + sec / 3600.0)
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum GeodeticDatum {
34 Tokyo,
36 Jgd2000,
38 Jgd2011,
40 Wgs84,
42 Unknown,
44}
45
46impl GeodeticDatum {
47 pub fn from_code(s: &str) -> Self {
54 match s.trim() {
55 "00" => GeodeticDatum::Tokyo,
56 "01" => GeodeticDatum::Jgd2000,
57 "02" => GeodeticDatum::Jgd2011,
58 _ => GeodeticDatum::Unknown,
59 }
60 }
61
62 pub fn epsg_code(&self) -> Option<&'static str> {
64 match self {
65 GeodeticDatum::Tokyo => Some("EPSG:4301"),
66 GeodeticDatum::Jgd2000 => Some("EPSG:4612"),
67 GeodeticDatum::Jgd2011 => Some("EPSG:6668"),
68 GeodeticDatum::Wgs84 => Some("EPSG:4326"),
69 GeodeticDatum::Unknown => None,
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub struct GeoLocation {
77 pub longitude: DmsCoordinate,
79 pub latitude: DmsCoordinate,
81 pub geodetic_system: Option<String>,
83}
84
85impl GeoLocation {
86 pub fn to_wgs84(&self, transformer: &JgdTransformer) -> Option<(f64, f64)> {
104 let lng = self.longitude.to_decimal()?;
105 let lat = self.latitude.to_decimal()?;
106 let system_code = self.geodetic_system.as_deref().unwrap_or("02");
107
108 let (lat_jgd2011, lng_jgd2011) =
110 transformer.transform_horizontal(lat, lng, system_code)?;
111
112 let proj = proj::Proj::new_known_crs("EPSG:6668", "EPSG:4326", None).ok()?;
114 proj.convert((lng_jgd2011, lat_jgd2011)).ok()
115 }
116
117 pub fn to_decimal(&self) -> Option<(f64, f64)> {
123 let lng = self.longitude.to_decimal()?;
124 let lat = self.latitude.to_decimal()?;
125 Some((lng, lat))
126 }
127
128 pub fn datum(&self) -> GeodeticDatum {
130 self.geodetic_system
131 .as_ref()
132 .map(|s| GeodeticDatum::from_code(s))
133 .unwrap_or(GeodeticDatum::Unknown)
134 }
135}
136
137pub trait GeoCoordinate {
139 fn geo_location(&self) -> GeoLocation;
148}
149
150impl GeoCoordinate for crate::boring_structs_400::Boring400 {
151 fn geo_location(&self) -> GeoLocation {
152 let lng_lat = &self.title.longitude_latitude;
153 GeoLocation {
154 longitude: DmsCoordinate {
155 degree: lng_lat.longitude_degree.clone(),
156 minute: lng_lat.longitude_minute.clone(),
157 second: lng_lat.longitude_second.clone(),
158 },
159 latitude: DmsCoordinate {
160 degree: lng_lat.latitude_degree.clone(),
161 minute: lng_lat.latitude_minute.clone(),
162 second: lng_lat.latitude_second.clone(),
163 },
164 geodetic_system: lng_lat.geodetic_system.clone(),
165 }
166 }
167}
168
169impl GeoCoordinate for crate::boring_structs_300::Boring300 {
170 fn geo_location(&self) -> GeoLocation {
171 let lng_lat = &self.title.longitude_latitude;
172 GeoLocation {
173 longitude: DmsCoordinate {
174 degree: lng_lat.longitude_degree.clone(),
175 minute: lng_lat.longitude_minute.clone(),
176 second: lng_lat.longitude_second.clone(),
177 },
178 latitude: DmsCoordinate {
179 degree: lng_lat.latitude_degree.clone(),
180 minute: lng_lat.latitude_minute.clone(),
181 second: lng_lat.latitude_second.clone(),
182 },
183 geodetic_system: lng_lat.geodetic_system.clone(),
184 }
185 }
186}
187
188impl GeoCoordinate for crate::boring_structs_210::Boring210 {
189 fn geo_location(&self) -> GeoLocation {
190 let lng_lat = &self.title.longitude_latitude;
191 GeoLocation {
192 longitude: DmsCoordinate {
193 degree: lng_lat.longitude_degree.clone(),
194 minute: lng_lat.longitude_minute.clone(),
195 second: lng_lat.longitude_second.clone(),
196 },
197 latitude: DmsCoordinate {
198 degree: lng_lat.latitude_degree.clone(),
199 minute: lng_lat.latitude_minute.clone(),
200 second: lng_lat.latitude_second.clone(),
201 },
202 geodetic_system: lng_lat.geodetic_system.clone(),
203 }
204 }
205}
206
207impl GeoCoordinate for crate::boring_structs_110::Boring110 {
208 fn geo_location(&self) -> GeoLocation {
209 let lng_lat = &self.title.longitude_latitude;
210 GeoLocation {
211 longitude: DmsCoordinate {
212 degree: lng_lat.longitude_degree.clone(),
213 minute: lng_lat.longitude_minute.clone(),
214 second: lng_lat.longitude_second.clone(),
215 },
216 latitude: DmsCoordinate {
217 degree: lng_lat.latitude_degree.clone(),
218 minute: lng_lat.latitude_minute.clone(),
219 second: lng_lat.latitude_second.clone(),
220 },
221 geodetic_system: lng_lat.geodetic_system.clone(),
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_dms_to_decimal() {
232 let dms = DmsCoordinate {
233 degree: Some("139".to_string()),
234 minute: Some("45".to_string()),
235 second: Some("0".to_string()),
236 };
237 let decimal = dms.to_decimal().unwrap();
238 assert!((decimal - 139.75).abs() < 0.0001);
239 }
240
241 #[test]
242 fn test_dms_to_decimal_with_seconds() {
243 let dms = DmsCoordinate {
245 degree: Some("139".to_string()),
246 minute: Some("41".to_string()),
247 second: Some("30".to_string()),
248 };
249 let decimal = dms.to_decimal().unwrap();
250 assert!((decimal - 139.69166666).abs() < 0.0001);
252 }
253
254 #[test]
255 fn test_dms_to_decimal_none() {
256 let dms = DmsCoordinate {
257 degree: None,
258 minute: Some("45".to_string()),
259 second: Some("0".to_string()),
260 };
261 assert!(dms.to_decimal().is_none());
262 }
263
264 #[test]
265 fn test_geodetic_datum_from_code() {
266 assert_eq!(GeodeticDatum::from_code("00"), GeodeticDatum::Tokyo);
267 assert_eq!(GeodeticDatum::from_code("01"), GeodeticDatum::Jgd2000);
268 assert_eq!(GeodeticDatum::from_code("02"), GeodeticDatum::Jgd2011);
269 assert_eq!(GeodeticDatum::from_code("99"), GeodeticDatum::Unknown);
270 }
271
272 #[test]
273 fn test_to_wgs84() {
274 let workspace_root =
276 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
277 let tky_path = workspace_root.join("params/TKY2JGD.par");
278 let patch_dir = workspace_root.join("params/patch_files");
279
280 if !tky_path.exists() || !patch_dir.exists() {
281 println!("Skipping test: params not found");
282 return;
283 }
284
285 let transformer = crate::transform::JgdTransformer::new(&tky_path, &patch_dir)
286 .expect("Failed to create transformer");
287
288 let loc = GeoLocation {
290 longitude: DmsCoordinate {
291 degree: Some("139".to_string()),
292 minute: Some("46".to_string()),
293 second: Some("0".to_string()),
294 },
295 latitude: DmsCoordinate {
296 degree: Some("35".to_string()),
297 minute: Some("40".to_string()),
298 second: Some("0".to_string()),
299 },
300 geodetic_system: Some("00".to_string()),
301 };
302
303 let (lng, lat) = loc.to_wgs84(&transformer).unwrap();
304 assert!((lng - 139.76).abs() < 0.02);
306 assert!((lat - 35.67).abs() < 0.02);
307
308 println!("Tokyo Datum → WGS84: ({}, {})", lng, lat);
309 }
310}