boring_parser/
coordinate.rs1#[derive(Debug, Clone, PartialEq)]
8pub struct DmsCoordinate {
9 pub degree: Option<String>,
11 pub minute: Option<String>,
13 pub second: Option<String>,
15}
16
17impl DmsCoordinate {
18 pub fn to_decimal(&self) -> Option<f64> {
22 let deg = self.degree.as_ref()?.parse::<f64>().ok()?;
23 let min = self.minute.as_ref()?.parse::<f64>().ok()?;
24 let sec = self.second.as_ref()?.parse::<f64>().ok()?;
25 Some(deg + min / 60.0 + sec / 3600.0)
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum GeodeticDatum {
32 Tokyo,
34 Jgd2000,
36 Jgd2011,
38 Wgs84,
40 Unknown,
42}
43
44impl GeodeticDatum {
45 pub fn from_code(s: &str) -> Self {
52 match s.trim() {
53 "00" => GeodeticDatum::Tokyo,
54 "01" => GeodeticDatum::Jgd2000,
55 "02" => GeodeticDatum::Jgd2011,
56 _ => GeodeticDatum::Unknown,
57 }
58 }
59
60 pub fn epsg_code(&self) -> Option<&'static str> {
62 match self {
63 GeodeticDatum::Tokyo => Some("EPSG:4301"),
64 GeodeticDatum::Jgd2000 => Some("EPSG:4612"),
65 GeodeticDatum::Jgd2011 => Some("EPSG:6668"),
66 GeodeticDatum::Wgs84 => Some("EPSG:4326"),
67 GeodeticDatum::Unknown => None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq)]
74pub struct GeoLocation {
75 pub longitude: DmsCoordinate,
77 pub latitude: DmsCoordinate,
79 pub geodetic_system: Option<String>,
81}
82
83impl GeoLocation {
84 pub fn to_wgs84(&self) -> Option<(f64, f64)> {
90 let lng = self.longitude.to_decimal()?;
91 let lat = self.latitude.to_decimal()?;
92 let datum = self
93 .geodetic_system
94 .as_ref()
95 .map(|s| GeodeticDatum::from_code(s))
96 .unwrap_or(GeodeticDatum::Unknown);
97 let from_crs = datum.epsg_code()?;
98 let proj = proj::Proj::new_known_crs(from_crs, "EPSG:4326", None).ok()?;
99 proj.convert((lng, lat)).ok()
100 }
101}
102
103pub trait GeoCoordinate {
105 fn to_wgs84(&self) -> Option<(f64, f64)>;
107}
108
109impl GeoCoordinate for crate::boring_structs_400::Boring400 {
110 fn to_wgs84(&self) -> Option<(f64, f64)> {
111 let lng_lat = &self.title.longitude_latitude;
112 let loc = GeoLocation {
113 longitude: DmsCoordinate {
114 degree: lng_lat.longitude_degree.clone(),
115 minute: lng_lat.longitude_minute.clone(),
116 second: lng_lat.longitude_second.clone(),
117 },
118 latitude: DmsCoordinate {
119 degree: lng_lat.latitude_degree.clone(),
120 minute: lng_lat.latitude_minute.clone(),
121 second: lng_lat.latitude_second.clone(),
122 },
123 geodetic_system: lng_lat.geodetic_system.clone(),
124 };
125 loc.to_wgs84()
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_dms_to_decimal() {
135 let dms = DmsCoordinate {
136 degree: Some("139".to_string()),
137 minute: Some("45".to_string()),
138 second: Some("0".to_string()),
139 };
140 let decimal = dms.to_decimal().unwrap();
141 assert!((decimal - 139.75).abs() < 0.0001);
142 }
143
144 #[test]
145 fn test_dms_to_decimal_with_seconds() {
146 let dms = DmsCoordinate {
148 degree: Some("139".to_string()),
149 minute: Some("41".to_string()),
150 second: Some("30".to_string()),
151 };
152 let decimal = dms.to_decimal().unwrap();
153 assert!((decimal - 139.69166666).abs() < 0.0001);
155 }
156
157 #[test]
158 fn test_dms_to_decimal_none() {
159 let dms = DmsCoordinate {
160 degree: None,
161 minute: Some("45".to_string()),
162 second: Some("0".to_string()),
163 };
164 assert!(dms.to_decimal().is_none());
165 }
166
167 #[test]
168 fn test_geodetic_datum_from_code() {
169 assert_eq!(GeodeticDatum::from_code("00"), GeodeticDatum::Tokyo);
170 assert_eq!(GeodeticDatum::from_code("01"), GeodeticDatum::Jgd2000);
171 assert_eq!(GeodeticDatum::from_code("02"), GeodeticDatum::Jgd2011);
172 assert_eq!(GeodeticDatum::from_code("99"), GeodeticDatum::Unknown);
173 }
174
175 #[test]
176 fn test_to_wgs84_from_tokyo() {
177 let loc = GeoLocation {
179 longitude: DmsCoordinate {
180 degree: Some("139".to_string()),
181 minute: Some("46".to_string()),
182 second: Some("0".to_string()),
183 },
184 latitude: DmsCoordinate {
185 degree: Some("35".to_string()),
186 minute: Some("40".to_string()),
187 second: Some("0".to_string()),
188 },
189 geodetic_system: Some("00".to_string()),
190 };
191
192 let (lng, lat) = loc.to_wgs84().unwrap();
193 assert!((lng - 139.76).abs() < 0.02);
195 assert!((lat - 35.67).abs() < 0.02);
196 }
197
198 #[test]
199 fn test_to_wgs84_from_jgd2000() {
200 let loc = GeoLocation {
202 longitude: DmsCoordinate {
203 degree: Some("139".to_string()),
204 minute: Some("46".to_string()),
205 second: Some("0".to_string()),
206 },
207 latitude: DmsCoordinate {
208 degree: Some("35".to_string()),
209 minute: Some("40".to_string()),
210 second: Some("0".to_string()),
211 },
212 geodetic_system: Some("01".to_string()),
213 };
214
215 let (lng, lat) = loc.to_wgs84().unwrap();
216 assert!((lng - 139.76666666).abs() < 0.0001);
218 assert!((lat - 35.66666666).abs() < 0.0001);
219 }
220}