1use crate::{Result, TrailgenError};
2use serde::{Deserialize, Serialize};
3
4const EARTH_R_M: f64 = 6_371_008.8;
5
6#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
7pub struct Coord {
8 pub lon: f64,
9 pub lat: f64,
10 #[serde(default, skip_serializing_if = "Option::is_none")]
11 pub ele: Option<f64>,
12}
13
14impl Coord {
15 #[must_use]
16 pub const fn new(lon: f64, lat: f64) -> Self {
17 Self {
18 lon,
19 lat,
20 ele: None,
21 }
22 }
23
24 #[must_use]
25 pub const fn with_ele(lon: f64, lat: f64, ele: f64) -> Self {
26 Self {
27 lon,
28 lat,
29 ele: Some(ele),
30 }
31 }
32
33 #[must_use]
34 pub fn haversine_m(self, rhs: Self) -> f64 {
35 let φ1 = self.lat.to_radians();
36 let φ2 = rhs.lat.to_radians();
37 let δφ = (rhs.lat - self.lat).to_radians();
38 let δλ = (rhs.lon - self.lon).to_radians();
39 let sin_half_lat = (δφ / 2.0).sin();
40 let sin_half_lon = (δλ / 2.0).sin();
41 let a =
42 (φ1.cos() * φ2.cos()).mul_add(sin_half_lon * sin_half_lon, sin_half_lat * sin_half_lat);
43 2.0 * EARTH_R_M * a.sqrt().asin()
44 }
45
46 #[must_use]
47 pub fn lerp(self, rhs: Self, t: f64) -> Self {
48 let ele = match (self.ele, rhs.ele) {
49 (Some(a), Some(b)) => Some((b - a).mul_add(t, a)),
50 _ => None,
51 };
52 Self {
53 lon: (rhs.lon - self.lon).mul_add(t, self.lon),
54 lat: (rhs.lat - self.lat).mul_add(t, self.lat),
55 ele,
56 }
57 }
58
59 #[must_use]
60 pub fn planar_distance2(self, rhs: Self) -> f64 {
61 let dx = self.lon - rhs.lon;
62 let dy = self.lat - rhs.lat;
63 dx.mul_add(dx, dy * dy)
64 }
65}
66
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68#[serde(transparent)]
69pub struct LineString {
70 pub points: Vec<Coord>,
71}
72
73impl LineString {
74 pub fn new(points: Vec<Coord>) -> Result<Self> {
75 if points.len() < 2 {
76 return Err(TrailgenError::InvalidGeometry(
77 "line string needs at least two coordinates".to_owned(),
78 ));
79 }
80 Ok(Self { points })
81 }
82
83 #[must_use]
84 pub fn unchecked(points: Vec<Coord>) -> Self {
85 debug_assert!(points.len() >= 2);
86 Self { points }
87 }
88
89 #[must_use]
90 pub fn length_m(&self) -> f64 {
91 self.points.windows(2).map(|w| w[0].haversine_m(w[1])).sum()
92 }
93
94 #[must_use]
95 pub fn ascent_descent_m(&self) -> (f64, f64) {
96 self.points
97 .windows(2)
98 .filter_map(|w| Some((w[0].ele?, w[1].ele?)))
99 .fold((0.0, 0.0), |(up, down), (a, b)| {
100 let d = b - a;
101 if d >= 0.0 {
102 (up + d, down)
103 } else {
104 (up, down - d)
105 }
106 })
107 }
108
109 #[must_use]
110 pub fn reversed(&self) -> Self {
111 let mut points = self.points.clone();
112 points.reverse();
113 Self { points }
114 }
115
116 #[must_use]
117 pub fn start(&self) -> Coord {
118 self.points[0]
119 }
120
121 #[must_use]
122 pub fn end(&self) -> Coord {
123 self.points[self.points.len() - 1]
124 }
125}