1use crate::{
7 Coordinate, Error, Latitude, Longitude,
8 fmt::{FormatOptions, Formatter},
9};
10use serde::{Deserialize, Serialize};
11use std::{
12 fmt::{Display, Write},
13 str::FromStr,
14};
15use uom::{
16 fmt::DisplayStyle,
17 si::{f64::Length, length},
18};
19
20#[cfg(feature = "geojson")]
21use crate::Angle;
22#[cfg(feature = "geojson")]
23use crate::coord::{GEOJSON_COORDINATES_FIELD, GEOJSON_POINT_TYPE, GEOJSON_TYPE_FIELD};
24
25#[macro_export]
44macro_rules! alt {
45 ($value:expr; cm) => {
46 $crate::alt::Altitude::centimeters($value)
47 };
48 ($value:expr; m) => {
49 $crate::alt::Altitude::meters($value)
50 };
51 ($value:expr; km) => {
52 $crate::alt::Altitude::kilometers($value)
53 };
54 ($value:expr) => {
55 $crate::alt::Altitude::meters($value)
56 };
57}
58#[allow(clippy::derive_ord_xor_partial_ord)]
64#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
65pub struct Altitude(Length);
66
67#[derive(Debug, Clone, Copy, PartialEq)]
84#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
85pub struct Coordinate3d {
86 point: Coordinate,
87 altitude: Altitude,
88}
89
90impl Display for Altitude {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 if f.alternate() {
97 match self.value() {
98 0.001..0.01 => self
99 .0
100 .into_format_args(length::millimeter, DisplayStyle::Description)
101 .fmt(f),
102 0.01..1.0 => self
103 .0
104 .into_format_args(length::centimeter, DisplayStyle::Description)
105 .fmt(f),
106 1_000.0.. => self
107 .0
108 .into_format_args(length::kilometer, DisplayStyle::Description)
109 .fmt(f),
110 _ => self
111 .0
112 .into_format_args(length::meter, DisplayStyle::Description)
113 .fmt(f),
114 }
115 } else {
116 self.0
117 .into_format_args(length::meter, DisplayStyle::Abbreviation)
118 .fmt(f)
119 }
120 }
121}
122
123impl FromStr for Altitude {
124 type Err = Error;
125
126 fn from_str(s: &str) -> Result<Self, Self::Err> {
127 let length_float =
128 f64::from_str(s).map_err(|_| Error::InvalidNumericFormat(s.to_string()))?;
129 Self::try_from(length_float)
130 }
131}
132
133impl TryFrom<Length> for Altitude {
134 type Error = Error;
135
136 fn try_from(value: Length) -> Result<Self, Self::Error> {
137 Self::try_from(value.value)
138 }
139}
140
141impl TryFrom<f64> for Altitude {
142 type Error = Error;
143
144 fn try_from(value: f64) -> Result<Self, Self::Error> {
145 if value.is_finite() && !value.is_nan() {
146 Ok(Self::meters(value))
147 } else {
148 Err(Error::InvalidNumericValue(value))
149 }
150 }
151}
152
153impl Eq for Altitude {}
154
155impl Ord for Altitude {
156 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
157 self.0.value.total_cmp(&other.0.value)
158 }
159}
160
161impl From<Altitude> for Length {
162 fn from(value: Altitude) -> Self {
163 value.0
164 }
165}
166
167impl From<Altitude> for f64 {
168 fn from(value: Altitude) -> Self {
169 value.0.value
170 }
171}
172
173impl AsRef<Length> for Altitude {
174 fn as_ref(&self) -> &Length {
175 &self.0
176 }
177}
178
179impl Altitude {
180 pub fn sea_level() -> Self {
182 Self::meters(0.0)
183 }
184
185 pub fn centimeters(value: f64) -> Self {
187 assert!(
188 value.is_finite() && !value.is_nan(),
189 "Invalid floating point value, `{value}`"
190 );
191 Self(Length::new::<length::centimeter>(value))
192 }
193
194 pub fn meters(value: f64) -> Self {
196 assert!(
197 value.is_finite() && !value.is_nan(),
198 "Invalid floating point value, `{value}`"
199 );
200 Self(Length::new::<length::meter>(value))
201 }
202
203 pub fn kilometers(value: f64) -> Self {
205 assert!(
206 value.is_finite() && !value.is_nan(),
207 "Invalid floating point value, `{value}`"
208 );
209 Self(Length::new::<length::kilometer>(value))
210 }
211
212 pub fn value(&self) -> f64 {
213 self.0.value
214 }
215}
216
217impl Display for Coordinate3d {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 let format = if f.alternate() {
224 FormatOptions::dms()
225 } else {
226 FormatOptions::decimal()
227 };
228 self.format(f, &format)
229 }
230}
231
232impl Formatter for Coordinate3d {
233 fn format<W: Write>(&self, f: &mut W, options: &FormatOptions) -> std::fmt::Result {
234 self.point.format(f, options)?;
235 write!(f, ", {}", self.altitude)
236 }
237}
238
239impl Coordinate3d {
240 #[must_use]
242 pub const fn new(point: Coordinate, altitude: Altitude) -> Self {
243 Self { point, altitude }
244 }
245
246 #[must_use]
249 pub const fn new_from(lat: Latitude, long: Longitude, altitude: Altitude) -> Self {
250 Self::new(Coordinate::new(lat, long), altitude)
251 }
252
253 #[must_use]
255 pub const fn with_point(mut self, point: Coordinate) -> Self {
256 self.point = point;
257 self
258 }
259
260 #[must_use]
262 pub const fn with_new_point(mut self, lat: Latitude, long: Longitude) -> Self {
263 self.point = Coordinate::new(lat, long);
264 self
265 }
266
267 #[must_use]
269 pub const fn with_altitude(mut self, altitude: Altitude) -> Self {
270 self.altitude = altitude;
271 self
272 }
273
274 #[must_use]
276 pub const fn point(&self) -> Coordinate {
277 self.point
278 }
279
280 #[must_use]
282 pub const fn altitude(&self) -> Altitude {
283 self.altitude
284 }
285
286 #[must_use]
288 pub fn is_on_equator(&self) -> bool {
289 self.point.latitude().is_on_equator()
290 }
291
292 #[must_use]
294 pub fn is_northern(&self) -> bool {
295 self.point.latitude().is_northern()
296 }
297
298 #[must_use]
300 pub fn is_southern(&self) -> bool {
301 self.point.latitude().is_southern()
302 }
303
304 #[must_use]
306 pub fn is_on_international_reference_meridian(&self) -> bool {
307 self.point
308 .longitude()
309 .is_on_international_reference_meridian()
310 }
311
312 #[must_use]
314 pub fn is_western(&self) -> bool {
315 self.point.longitude().is_western()
316 }
317
318 #[must_use]
320 pub fn is_eastern(&self) -> bool {
321 self.point.longitude().is_eastern()
322 }
323
324 #[must_use]
326 pub fn is_at_sea_level(&self) -> bool {
327 self.altitude.value() == 0.0
328 }
329}
330
331#[cfg(feature = "geojson")]
332impl From<Coordinate3d> for serde_json::Value {
333 fn from(coord: Coordinate3d) -> Self {
335 serde_json::json!({
336 GEOJSON_TYPE_FIELD: GEOJSON_POINT_TYPE,
337 GEOJSON_COORDINATES_FIELD: [
338 coord.point().latitude().as_float().0,
339 coord.point().longitude().as_float().0,
340 coord.altitude().value()
341 ]
342 })
343 }
344}
345
346#[cfg(feature = "geojson")]
347impl TryFrom<serde_json::Value> for Coordinate3d {
348 type Error = crate::Error;
349
350 fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
351 if value[GEOJSON_TYPE_FIELD] != GEOJSON_POINT_TYPE {
352 return Err(crate::Error::InvalidCoordinate);
353 }
354 let coords = value[GEOJSON_COORDINATES_FIELD]
355 .as_array()
356 .ok_or(crate::Error::InvalidCoordinate)?;
357 if coords.len() != 3 {
358 return Err(crate::Error::InvalidCoordinate);
359 }
360 let lat_val: f64 = coords[0]
361 .as_f64()
362 .ok_or(crate::Error::InvalidNumericFormat(coords[0].to_string()))?;
363 let lon_val: f64 = coords[1]
364 .as_f64()
365 .ok_or(crate::Error::InvalidNumericFormat(coords[1].to_string()))?;
366 let alt_val: f64 = coords[2]
367 .as_f64()
368 .ok_or(crate::Error::InvalidNumericFormat(coords[2].to_string()))?;
369 let lat = Latitude::try_from(lat_val)?;
370 let lon = Longitude::try_from(lon_val)?;
371 let alt = Altitude::try_from(alt_val)?;
372 Ok(Coordinate3d::new_from(lat, lon, alt))
373 }
374}