leptos_leaflet/components/
bounds.rs

1use leaflet::LatLng;
2use leaflet::LatLngBounds;
3
4use super::Position;
5
6/// Represents a geographical area defined by its northeast and southwest corners.
7///
8/// The `Bounds` struct is used to define rectangular areas on a map. It provides methods to calculate
9/// the center, size, and check for containment or intersection with other bounds.
10///
11/// # Fields
12///
13/// - `ne_corner`: The northeast corner of the bounds.
14/// - `sw_corner`: The southwest corner of the bounds.
15#[derive(Debug, Default, Clone, Copy, PartialEq)]
16pub struct Bounds {
17    pub ne_corner: Position,
18    pub sw_corner: Position,
19}
20
21impl Bounds {
22    /// Creates a new `Bounds` instance with the given northeast and southwest corners.
23    /// 
24    /// # Arguments
25    /// 
26    /// - `ne_corner`: The northeast corner of the bounds.
27    /// - `sw_corner`: The southwest corner of the bounds.
28    pub fn new(ne_corner: Position, sw_corner: Position) -> Self {
29        Self {
30            ne_corner,
31            sw_corner,
32        }
33    }
34
35    /// Gets the center of the bounds.
36    pub fn get_center(&self) -> Position {
37        Position {
38            lat: (self.ne_corner.lat + self.sw_corner.lat) / 2.0,
39            lng: (self.ne_corner.lng + self.sw_corner.lng) / 2.0,
40        }
41    }
42
43    /// Gets the southwest corner of the bounds.
44    pub fn get_bottom_left(&self) -> Position {
45        Position::new(self.sw_corner.lat, self.sw_corner.lng)
46    }
47
48    /// Gets the northeast corner of the bounds.
49    pub fn get_top_right(&self) -> Position {
50        Position::new(self.ne_corner.lat, self.ne_corner.lng)
51    }
52
53    /// Gets the northwest corner of the bounds.
54    pub fn get_top_left(&self) -> Position {
55        Position::new(self.ne_corner.lat, self.sw_corner.lng)
56    }
57
58    /// Gets the southeast corner of the bounds.
59    pub fn get_bottom_right(&self) -> Position {
60        Position::new(self.sw_corner.lat, self.ne_corner.lng)
61    }
62
63    /// Gets the size of the bounds.
64    pub fn get_size(&self) -> Position {
65        Position {
66            lat: (self.ne_corner.lat - self.sw_corner.lat).abs(),
67            lng: (self.ne_corner.lng - self.sw_corner.lng).abs(),
68        }
69    }
70
71    /// Returns true if the rectangle contains the given bounds. 
72    /// A rectangle contains another bounds if it contains all of its points.
73    /// 
74    /// # Arguments
75    /// 
76    /// - `position`: The position to check for containment.
77    pub fn contains(&self, position: Position) -> bool {
78        self.sw_corner.lat <= position.lat
79            && self.ne_corner.lat >= position.lat
80            && self.sw_corner.lng <= position.lng
81            && self.ne_corner.lng >= position.lng
82    }
83
84    /// Returns true if the rectangle intersects the given bounds.
85    /// Two bounds intersect if they have at least one point in common.
86    ///
87    /// # Arguments
88    /// 
89    /// - `other`: The bounds to check for intersection.
90    pub fn intersects(&self, other: Bounds) -> bool {
91        let lat_overlap =
92            self.ne_corner.lat >= other.sw_corner.lat && self.sw_corner.lat <= other.ne_corner.lat;
93        let lng_overlap =
94            self.ne_corner.lng >= other.sw_corner.lng && self.sw_corner.lng <= other.ne_corner.lng;
95
96        lat_overlap && lng_overlap
97    }
98
99    /// Returns true if the rectangle overlaps the given bounds.
100    /// Two bounds overlap if their intersection is an area.
101    /// 
102    /// # Arguments
103    /// 
104    /// - `other`: The bounds to check for overlap.
105    pub fn overlaps(&self, other: Bounds) -> bool {
106        let lat_overlap =
107            self.ne_corner.lat > other.sw_corner.lat && self.sw_corner.lat < other.ne_corner.lat;
108        let lng_overlap =
109            self.ne_corner.lng > other.sw_corner.lng && self.sw_corner.lng < other.ne_corner.lng;
110
111        lat_overlap && lng_overlap
112    }
113
114    /// Returns true if the bounds are valid.
115    pub fn is_valid(&self) -> bool {
116        self.ne_corner.lat <= 90.0
117            && self.ne_corner.lat >= -90.0
118            && self.sw_corner.lat <= 90.0
119            && self.sw_corner.lat >= -90.0
120            && self.ne_corner.lng <= 180.0
121            && self.ne_corner.lng >= -180.0
122            && self.sw_corner.lng <= 180.0
123            && self.sw_corner.lng >= -180.0
124            && self.ne_corner.lat >= self.sw_corner.lat
125            && self.ne_corner.lng >= self.sw_corner.lng
126    }
127
128    /// Returns a new bounds padded by the given ratio.
129    pub fn pad(&self, buffer_ratio: f64) -> Bounds {
130        let lat_diff = self.ne_corner.lat - self.sw_corner.lat;
131        let lng_diff = self.ne_corner.lng - self.sw_corner.lng;
132        let lat_pad = lat_diff * buffer_ratio;
133        let lng_pad = lng_diff * buffer_ratio;
134        Bounds {
135            ne_corner: Position::new(self.ne_corner.lat + lat_pad, self.ne_corner.lng - lng_pad),
136            sw_corner: Position::new(self.sw_corner.lat - lat_pad, self.sw_corner.lng + lng_pad),
137        }
138    }
139
140    /// Checks if the bounds are equal to the given bounds.
141    pub fn equals(&self, other: Bounds) -> bool {
142        self.ne_corner == other.ne_corner && self.sw_corner == other.sw_corner
143    }
144
145    pub fn as_lat_lng_bounds(&self) -> LatLngBounds {
146        LatLngBounds::new(
147            &LatLng::new(self.ne_corner.lat, self.ne_corner.lng),
148            &LatLng::new(self.sw_corner.lat, self.sw_corner.lng),
149        )
150    }
151}
152
153impl From<Bounds> for LatLngBounds {
154    fn from(value: Bounds) -> Self {
155        LatLngBounds::new(
156            &LatLng::new(value.ne_corner.lat, value.ne_corner.lng),
157            &LatLng::new(value.sw_corner.lat, value.sw_corner.lng),
158        )
159    }
160}
161
162impl From<&Bounds> for LatLngBounds {
163    fn from(value: &Bounds) -> Self {
164        LatLngBounds::new(
165            &LatLng::new(value.ne_corner.lat, value.ne_corner.lng),
166            &LatLng::new(value.sw_corner.lat, value.sw_corner.lng),
167        )
168    }
169}