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 transformer.to_wgs84(lat, lng, system_code)
110 }
111
112 pub fn to_decimal(&self) -> Option<(f64, f64)> {
118 let lng = self.longitude.to_decimal()?;
119 let lat = self.latitude.to_decimal()?;
120 Some((lng, lat))
121 }
122
123 pub fn datum(&self) -> GeodeticDatum {
125 self.geodetic_system
126 .as_ref()
127 .map(|s| GeodeticDatum::from_code(s))
128 .unwrap_or(GeodeticDatum::Unknown)
129 }
130}
131
132pub trait GeoCoordinate {
134 fn geo_location(&self) -> GeoLocation;
143}
144
145impl GeoCoordinate for crate::boring_structs_400::Boring400 {
146 fn geo_location(&self) -> GeoLocation {
147 let lng_lat = &self.title.longitude_latitude;
148 GeoLocation {
149 longitude: DmsCoordinate {
150 degree: lng_lat.longitude_degree.clone(),
151 minute: lng_lat.longitude_minute.clone(),
152 second: lng_lat.longitude_second.clone(),
153 },
154 latitude: DmsCoordinate {
155 degree: lng_lat.latitude_degree.clone(),
156 minute: lng_lat.latitude_minute.clone(),
157 second: lng_lat.latitude_second.clone(),
158 },
159 geodetic_system: lng_lat.geodetic_system.clone(),
160 }
161 }
162}
163
164impl GeoCoordinate for crate::boring_structs_300::Boring300 {
165 fn geo_location(&self) -> GeoLocation {
166 let lng_lat = &self.title.longitude_latitude;
167 GeoLocation {
168 longitude: DmsCoordinate {
169 degree: lng_lat.longitude_degree.clone(),
170 minute: lng_lat.longitude_minute.clone(),
171 second: lng_lat.longitude_second.clone(),
172 },
173 latitude: DmsCoordinate {
174 degree: lng_lat.latitude_degree.clone(),
175 minute: lng_lat.latitude_minute.clone(),
176 second: lng_lat.latitude_second.clone(),
177 },
178 geodetic_system: lng_lat.geodetic_system.clone(),
179 }
180 }
181}
182
183impl GeoCoordinate for crate::boring_structs_200::Boring200 {
184 fn geo_location(&self) -> GeoLocation {
185 let lng_lat = &self.title.longitude_latitude;
186 GeoLocation {
187 longitude: DmsCoordinate {
188 degree: lng_lat.longitude_degree.clone(),
189 minute: lng_lat.longitude_minute.clone(),
190 second: lng_lat.longitude_second.clone(),
191 },
192 latitude: DmsCoordinate {
193 degree: lng_lat.latitude_degree.clone(),
194 minute: lng_lat.latitude_minute.clone(),
195 second: lng_lat.latitude_second.clone(),
196 },
197 geodetic_system: lng_lat.geodetic_system.clone(),
198 }
199 }
200}
201
202impl GeoCoordinate for crate::boring_structs_210::Boring210 {
203 fn geo_location(&self) -> GeoLocation {
204 let lng_lat = &self.title.longitude_latitude;
205 GeoLocation {
206 longitude: DmsCoordinate {
207 degree: lng_lat.longitude_degree.clone(),
208 minute: lng_lat.longitude_minute.clone(),
209 second: lng_lat.longitude_second.clone(),
210 },
211 latitude: DmsCoordinate {
212 degree: lng_lat.latitude_degree.clone(),
213 minute: lng_lat.latitude_minute.clone(),
214 second: lng_lat.latitude_second.clone(),
215 },
216 geodetic_system: lng_lat.geodetic_system.clone(),
217 }
218 }
219}
220
221impl GeoCoordinate for crate::boring_structs_110::Boring110 {
222 fn geo_location(&self) -> GeoLocation {
223 let lng_lat = &self.title.longitude_latitude;
224 GeoLocation {
225 longitude: DmsCoordinate {
226 degree: lng_lat.longitude_degree.clone(),
227 minute: lng_lat.longitude_minute.clone(),
228 second: lng_lat.longitude_second.clone(),
229 },
230 latitude: DmsCoordinate {
231 degree: lng_lat.latitude_degree.clone(),
232 minute: lng_lat.latitude_minute.clone(),
233 second: lng_lat.latitude_second.clone(),
234 },
235 geodetic_system: lng_lat.geodetic_system.clone(),
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_dms_to_decimal() {
246 let dms = DmsCoordinate {
247 degree: Some("139".to_string()),
248 minute: Some("45".to_string()),
249 second: Some("0".to_string()),
250 };
251 let decimal = dms.to_decimal().unwrap();
252 assert!((decimal - 139.75).abs() < 0.0001);
253 }
254
255 #[test]
256 fn test_dms_to_decimal_with_seconds() {
257 let dms = DmsCoordinate {
259 degree: Some("139".to_string()),
260 minute: Some("41".to_string()),
261 second: Some("30".to_string()),
262 };
263 let decimal = dms.to_decimal().unwrap();
264 assert!((decimal - 139.69166666).abs() < 0.0001);
266 }
267
268 #[test]
269 fn test_dms_to_decimal_none() {
270 let dms = DmsCoordinate {
271 degree: None,
272 minute: Some("45".to_string()),
273 second: Some("0".to_string()),
274 };
275 assert!(dms.to_decimal().is_none());
276 }
277
278 #[test]
279 fn test_geodetic_datum_from_code() {
280 assert_eq!(GeodeticDatum::from_code("00"), GeodeticDatum::Tokyo);
281 assert_eq!(GeodeticDatum::from_code("01"), GeodeticDatum::Jgd2000);
282 assert_eq!(GeodeticDatum::from_code("02"), GeodeticDatum::Jgd2011);
283 assert_eq!(GeodeticDatum::from_code("99"), GeodeticDatum::Unknown);
284 }
285
286 #[test]
287 fn test_to_wgs84() {
288 let workspace_root =
290 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
291 let tky_path = workspace_root.join("params/TKY2JGD.par");
292 let patch_dir = workspace_root.join("params/patch_files");
293
294 if !tky_path.exists() || !patch_dir.exists() {
295 println!("Skipping test: params not found");
296 return;
297 }
298
299 let transformer = crate::transform::JgdTransformer::new(&tky_path, &patch_dir)
300 .expect("Failed to create transformer");
301
302 let loc = GeoLocation {
304 longitude: DmsCoordinate {
305 degree: Some("139".to_string()),
306 minute: Some("46".to_string()),
307 second: Some("0".to_string()),
308 },
309 latitude: DmsCoordinate {
310 degree: Some("35".to_string()),
311 minute: Some("40".to_string()),
312 second: Some("0".to_string()),
313 },
314 geodetic_system: Some("00".to_string()),
315 };
316
317 let (lng, lat) = loc.to_wgs84(&transformer).unwrap();
318 assert!((lng - 139.76).abs() < 0.02);
320 assert!((lat - 35.67).abs() < 0.02);
321
322 println!("Tokyo Datum → WGS84: ({}, {})", lng, lat);
323 }
324}