1use crate::{
14 Coordinate, Error, Latitude, Longitude,
15 fmt::{FormatOptions, Formatter},
16};
17use core::hash::Hash;
18use std::{
19 fmt::{Display, Write},
20 str::FromStr,
21};
22use uom::{
23 fmt::DisplayStyle,
24 si::{f64::Length, length},
25};
26
27#[cfg(feature = "geojson")]
28use crate::Angle;
29#[cfg(feature = "geojson")]
30use crate::coord::{GEOJSON_COORDINATES_FIELD, GEOJSON_POINT_TYPE, GEOJSON_TYPE_FIELD};
31
32#[cfg(feature = "serde")]
33use serde::{Deserialize, Serialize};
34
35#[macro_export]
54macro_rules! elv {
55 ($value:expr; cm) => {
56 $crate::elevation::Elevation::centimeters($value)
57 };
58 ($value:expr; m) => {
59 $crate::elevation::Elevation::meters($value)
60 };
61 ($value:expr; km) => {
62 $crate::elevation::Elevation::kilometers($value)
63 };
64 ($value:expr) => {
65 $crate::elevation::Elevation::meters($value)
66 };
67}
68#[allow(clippy::derive_ord_xor_partial_ord)]
76#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
77#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
78pub struct Elevation(Length);
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
99pub struct CoordinateWithElevation {
100 point: Coordinate,
101 elevation: Elevation,
102}
103
104const ELEVATION_ZERO: f64 = 0.0;
109
110impl Display for Elevation {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 if f.alternate() {
113 match self.value() {
114 0.001..0.01 => self
115 .0
116 .into_format_args(length::millimeter, DisplayStyle::Description)
117 .fmt(f),
118 0.01..1.0 => self
119 .0
120 .into_format_args(length::centimeter, DisplayStyle::Description)
121 .fmt(f),
122 1_000.0.. => self
123 .0
124 .into_format_args(length::kilometer, DisplayStyle::Description)
125 .fmt(f),
126 _ => self
127 .0
128 .into_format_args(length::meter, DisplayStyle::Description)
129 .fmt(f),
130 }
131 } else {
132 self.0
133 .into_format_args(length::meter, DisplayStyle::Abbreviation)
134 .fmt(f)
135 }
136 }
137}
138
139impl FromStr for Elevation {
140 type Err = Error;
141
142 fn from_str(s: &str) -> Result<Self, Self::Err> {
143 let length_float =
144 f64::from_str(s).map_err(|_| Error::InvalidNumericFormat(s.to_string()))?;
145 Self::try_from(length_float)
146 }
147}
148
149impl TryFrom<Length> for Elevation {
150 type Error = Error;
151
152 fn try_from(value: Length) -> Result<Self, Self::Error> {
153 Self::try_from(value.value)
154 }
155}
156
157impl TryFrom<f64> for Elevation {
158 type Error = Error;
159
160 fn try_from(value: f64) -> Result<Self, Self::Error> {
161 if value.is_finite() && !value.is_nan() {
162 Ok(Self::meters(value))
163 } else {
164 Err(Error::InvalidNumericValue(value))
165 }
166 }
167}
168
169impl Eq for Elevation {}
170
171impl Hash for Elevation {
172 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
173 self.0.value.to_bits().hash(state);
174 }
175}
176
177impl Ord for Elevation {
178 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
179 self.0.value.total_cmp(&other.0.value)
180 }
181}
182
183impl From<Elevation> for Length {
184 fn from(value: Elevation) -> Self {
185 value.0
186 }
187}
188
189impl From<Elevation> for f64 {
190 fn from(value: Elevation) -> Self {
191 value.0.value
192 }
193}
194
195impl AsRef<Length> for Elevation {
196 fn as_ref(&self) -> &Length {
197 &self.0
198 }
199}
200
201impl Elevation {
202 pub fn zero() -> Self {
207 Self(Length::new::<length::meter>(ELEVATION_ZERO))
208 }
209
210 pub fn centimeters(value: f64) -> Self {
214 assert!(
215 value.is_finite() && !value.is_nan(),
216 "Invalid floating point value, `{value}`"
217 );
218 Self(Length::new::<length::centimeter>(value))
219 }
220
221 pub fn meters(value: f64) -> Self {
225 assert!(
226 value.is_finite() && !value.is_nan(),
227 "Invalid floating point value, `{value}`"
228 );
229 Self(Length::new::<length::meter>(value))
230 }
231
232 pub fn kilometers(value: f64) -> Self {
236 assert!(
237 value.is_finite() && !value.is_nan(),
238 "Invalid floating point value, `{value}`"
239 );
240 Self(Length::new::<length::kilometer>(value))
241 }
242
243 pub fn value(&self) -> f64 {
247 self.0.value
248 }
249
250 pub fn is_zero(&self) -> bool {
254 self.0.value == ELEVATION_ZERO
255 }
256}
257
258impl Display for CoordinateWithElevation {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 let format = if f.alternate() {
265 FormatOptions::dms()
266 } else {
267 FormatOptions::decimal()
268 };
269 self.format(f, &format)
270 }
271}
272
273impl Formatter for CoordinateWithElevation {
274 fn format<W: Write>(&self, f: &mut W, options: &FormatOptions) -> std::fmt::Result {
275 self.point.format(f, options)?;
276 write!(f, ", {}", self.elevation)
277 }
278}
279
280impl FromStr for CoordinateWithElevation {
281 type Err = Error;
282
283 fn from_str(s: &str) -> Result<Self, Self::Err> {
284 if let Some((coordinate, elevation)) = s.rsplit_once(',') {
285 Ok(Self::new(
286 Coordinate::from_str(coordinate.trim())?,
287 Elevation::from_str(elevation.trim())?,
288 ))
289 } else {
290 Err(Error::InvalidCoordinate)
291 }
292 }
293}
294
295impl CoordinateWithElevation {
296 #[must_use]
300 pub const fn new(point: Coordinate, elevation: Elevation) -> Self {
301 Self { point, elevation }
302 }
303
304 #[must_use]
307 pub const fn new_from(lat: Latitude, long: Longitude, elevation: Elevation) -> Self {
308 Self::new(Coordinate::new(lat, long), elevation)
309 }
310
311 #[must_use]
313 pub const fn with_point(mut self, point: Coordinate) -> Self {
314 self.point = point;
315 self
316 }
317
318 #[must_use]
320 pub const fn with_new_point(mut self, lat: Latitude, long: Longitude) -> Self {
321 self.point = Coordinate::new(lat, long);
322 self
323 }
324
325 #[must_use]
327 pub const fn with_elevation(mut self, elevation: Elevation) -> Self {
328 self.elevation = elevation;
329 self
330 }
331
332 #[must_use]
334 pub const fn point(&self) -> Coordinate {
335 self.point
336 }
337
338 #[must_use]
340 pub const fn elevation(&self) -> Elevation {
341 self.elevation
342 }
343
344 #[must_use]
346 pub fn is_on_equator(&self) -> bool {
347 self.point.latitude().is_on_equator()
348 }
349
350 #[must_use]
352 pub fn is_northern(&self) -> bool {
353 self.point.latitude().is_northern()
354 }
355
356 #[must_use]
358 pub fn is_southern(&self) -> bool {
359 self.point.latitude().is_southern()
360 }
361
362 #[must_use]
364 pub fn is_on_international_reference_meridian(&self) -> bool {
365 self.point
366 .longitude()
367 .is_on_international_reference_meridian()
368 }
369
370 #[must_use]
372 pub fn is_western(&self) -> bool {
373 self.point.longitude().is_western()
374 }
375
376 #[must_use]
378 pub fn is_eastern(&self) -> bool {
379 self.point.longitude().is_eastern()
380 }
381
382 #[must_use]
384 pub fn is_zero_elevation(&self) -> bool {
385 self.elevation.is_zero()
386 }
387}
388
389#[cfg(feature = "geojson")]
390impl From<CoordinateWithElevation> for serde_json::Value {
391 fn from(coord: CoordinateWithElevation) -> Self {
393 serde_json::json!({
394 GEOJSON_TYPE_FIELD: GEOJSON_POINT_TYPE,
395 GEOJSON_COORDINATES_FIELD: [
396 coord.point().latitude().as_float().0,
397 coord.point().longitude().as_float().0,
398 coord.elevation().value()
399 ]
400 })
401 }
402}
403
404#[cfg(feature = "geojson")]
405impl TryFrom<serde_json::Value> for CoordinateWithElevation {
406 type Error = crate::Error;
407
408 fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
409 if value[GEOJSON_TYPE_FIELD] != GEOJSON_POINT_TYPE {
410 return Err(crate::Error::InvalidCoordinate);
411 }
412 let coords = value[GEOJSON_COORDINATES_FIELD]
413 .as_array()
414 .ok_or(crate::Error::InvalidCoordinate)?;
415 if coords.len() != 3 {
416 return Err(crate::Error::InvalidCoordinate);
417 }
418 let lat_val: f64 = coords[0]
419 .as_f64()
420 .ok_or(crate::Error::InvalidNumericFormat(coords[0].to_string()))?;
421 let lon_val: f64 = coords[1]
422 .as_f64()
423 .ok_or(crate::Error::InvalidNumericFormat(coords[1].to_string()))?;
424 let alt_val: f64 = coords[2]
425 .as_f64()
426 .ok_or(crate::Error::InvalidNumericFormat(coords[2].to_string()))?;
427 let lat = Latitude::try_from(lat_val)?;
428 let lon = Longitude::try_from(lon_val)?;
429 let alt = Elevation::try_from(alt_val)?;
430 Ok(CoordinateWithElevation::new_from(lat, lon, alt))
431 }
432}