leptos_leaflet/components/
position.rs

1use leaflet::LatLng;
2
3use crate::core::IntoLatLng;
4
5/// A struct to represent a position on the map.
6///
7/// This allows to pass the positions around even on the server side, since LatLng is a client-side only struct.
8/// 
9/// This struct offers some utility methods to work with positions, and conversions to LatLng.
10/// It also supports passing positions as tuples or arrays.
11#[derive(Debug, Default, Clone, Copy, PartialEq)]
12pub struct Position {
13    pub lat: f64,
14    pub lng: f64,
15}
16
17impl Position {
18    /// Creates a new position
19    pub fn new(lat: f64, lng: f64) -> Self {
20        Self { lat, lng }
21    }
22
23    /// Determines the distance between two positions using the Haversine formula.
24    ///
25    /// The result is in meters
26    pub fn distance_haversine(&self, other: &Self) -> f64 {
27        const R: f64 = 6371e3; // Earth's radius in meters
28        let phi1 = self.lat.to_radians();
29        let phi2 = other.lat.to_radians();
30        let delta_phi = (other.lat - self.lat).to_radians();
31        let delta_lambda = (other.lng - self.lng).to_radians();
32
33        let a = (delta_phi / 2.0).sin().powi(2)
34            + phi1.cos() * phi2.cos() * (delta_lambda / 2.0).sin().powi(2);
35        let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
36        R * c
37    }
38
39    /// Checks if the position is inside a circle
40    ///
41    /// # Arguments
42    ///
43    /// * `center`: Center of the circle
44    /// * `radius`: Radius of the circle in meters
45    ///
46    /// returns: bool
47    #[inline]
48    pub fn inside_circle(&self, center: &Position, radius: f64) -> bool {
49        self.distance_haversine(center) < radius
50    }
51
52    /// Check if the position is inside a polygon
53    #[inline]
54    pub fn inside_polygon(&self, polygon: &[Position]) -> bool {
55        let x = self.lat;
56        let y = self.lng;
57
58        let mut inside = false;
59        for i in 0..polygon.len() {
60            let j = if i == 0 { polygon.len() - 1 } else { i - 1 };
61            let xi = polygon[i].lat;
62            let yi = polygon[i].lng;
63            let xj = polygon[j].lat;
64            let yj = polygon[j].lng;
65
66            let intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
67            if intersect {
68                inside = !inside;
69            }
70        }
71
72        inside
73    }
74
75    /// Distance between two positions using pytagore theorem
76    pub fn distance(&self, other: &Self) -> f64 {
77        ((self.lat - other.lat).powi(2) + (self.lng - other.lng).powi(2)).sqrt()
78    }
79
80    /// Check if the position is zero using the f64::EPSILON
81    pub fn is_zero(&self) -> bool {
82        self.lat.abs() <= f64::EPSILON && self.lng.abs() <= f64::EPSILON
83    }
84
85    pub fn as_lat_lng(&self) -> LatLng {
86        LatLng::new(self.lat, self.lng)
87    }
88}
89
90/// Winding number of a polygon
91#[allow(unused)]
92fn winding_number(poly: &[Position], point: &Position) -> i32 {
93    let mut wn = 0;
94    let n = poly.len();
95    poly.iter().enumerate().for_each(|(i, p0)| {
96        let p1 = &poly[(i + 1) % n];
97        if p0.lng <= point.lng {
98            if p1.lng > point.lng && is_left(p0, p1, point) > 0.0 {
99                wn += 1;
100            }
101        } else if p1.lng <= point.lng && is_left(p0, p1, point) < 0.0 {
102            wn -= 1;
103        }
104    });
105    wn
106}
107
108/// Check if the point is on the left of the line
109#[allow(unused)]
110fn is_left(p0: &Position, p1: &Position, p2: &Position) -> f64 {
111    (p1.lat - p0.lat) * (p2.lng - p0.lng) - (p2.lat - p0.lat) * (p1.lng - p0.lng)
112}
113
114impl From<Position> for LatLng {
115    fn from(value: Position) -> Self {
116        LatLng::new(value.lat, value.lng)
117    }
118}
119
120impl From<&Position> for LatLng {
121    fn from(value: &Position) -> Self {
122        LatLng::new(value.lat, value.lng)
123    }
124}
125
126impl From<Position> for (f64, f64) {
127    fn from(value: Position) -> Self {
128        (value.lat, value.lng)
129    }
130}
131
132impl From<Position> for [f64; 2] {
133    fn from(value: Position) -> Self {
134        [value.lat, value.lng]
135    }
136}
137
138impl From<(f64, f64)> for Position {
139    fn from(value: (f64, f64)) -> Self {
140        Self::new(value.0, value.1)
141    }
142}
143
144impl From<[f64; 2]> for Position {
145    fn from(value: [f64; 2]) -> Self {
146        Self::new(value[0], value[1])
147    }
148}
149
150impl IntoLatLng for Position {
151    fn into_lat_lng(self) -> LatLng {
152        LatLng::new(self.lat, self.lng)
153    }
154}
155
156#[macro_export]
157macro_rules! position {
158    ($lat: expr, $lng: expr) => {
159        {
160            use leptos::prelude::*;
161            $crate::prelude::JsSignal::derive_local(move || $crate::prelude::Position::new($lat, $lng))
162        }
163    };
164}
165
166pub fn positions(positions: &[(f64, f64)]) -> Vec<Position> {
167    positions
168        .iter()
169        .map(|&(lat, lng)| Position::new(lat, lng))
170        .collect()
171}