Skip to main content

egui_map_view/layers/
mod.rs

1//! Layers for the map view that can handle input, and draw on top of the map view different kinds of data.
2//!
3use egui::{Painter, Pos2, Response};
4use std::any::Any;
5
6use crate::projection::MapProjection;
7
8/// GeoJSON serialization and deserialization for layers.
9#[cfg(feature = "geojson")]
10pub mod geojson;
11
12/// Drawing layer
13#[cfg(feature = "drawing-layer")]
14pub mod drawing;
15
16/// Text layer
17#[cfg(feature = "text-layer")]
18pub mod text;
19
20/// SVG layer
21#[cfg(feature = "svg-layer")]
22pub mod svg;
23
24/// Area layer
25#[cfg(feature = "area-layer")]
26pub mod area;
27
28// Tile layer
29#[cfg(feature = "tile-layer")]
30pub mod tile;
31
32/// A module for serializing and deserializing `Color32` to and from hex strings.
33pub(crate) mod serde_color32 {
34    use egui::Color32;
35    use serde::{self, Deserialize, Deserializer, Serializer};
36
37    /// Serializes a `Color32` to a hex string.
38    pub fn serialize<S>(color: &Color32, serializer: S) -> Result<S::Ok, S::Error>
39    where
40        S: Serializer,
41    {
42        serializer.serialize_str(&color.to_hex())
43    }
44
45    /// Deserializes a `Color32` from a hex string.
46    pub fn deserialize<'de, D>(deserializer: D) -> Result<Color32, D::Error>
47    where
48        D: Deserializer<'de>,
49    {
50        let s = <String as Deserialize>::deserialize(deserializer)?;
51        Color32::from_hex(&s).map_err(|err| serde::de::Error::custom(format!("{err:?}")))
52    }
53}
54
55/// A module for serializing and deserializing `egui::Stroke`.
56pub(crate) mod serde_stroke {
57    use crate::layers::serde_color32;
58    use egui::{Color32, Stroke};
59    use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
60
61    #[derive(Serialize, Deserialize)]
62    struct StrokeHelper {
63        width: f32,
64        #[serde(with = "serde_color32")]
65        color: Color32,
66    }
67
68    pub fn serialize<S>(stroke: &Stroke, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: Serializer,
71    {
72        let helper = StrokeHelper {
73            width: stroke.width,
74            color: stroke.color,
75        };
76        helper.serialize(serializer)
77    }
78
79    pub fn deserialize<'de, D>(deserializer: D) -> Result<Stroke, D::Error>
80    where
81        D: Deserializer<'de>,
82    {
83        let helper = StrokeHelper::deserialize(deserializer)?;
84        Ok(Stroke {
85            width: helper.width,
86            color: helper.color,
87        })
88    }
89}
90
91/// A trait for map layers.
92pub trait Layer: Any {
93    /// Handles user input for the layer. Returns `true` if the input was handled and should not be
94    /// processed further by the map.
95    fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool;
96
97    /// Draws the layer.
98    fn draw(&self, painter: &Painter, projection: &MapProjection);
99
100    /// Gets the layer as a `dyn Any`.
101    fn as_any(&self) -> &dyn Any;
102
103    /// Gets the layer as a mutable `dyn Any`.
104    fn as_any_mut(&mut self) -> &mut dyn Any;
105
106    /// The opacity of the layer, from 0.0 (completely transparent) to 1.0 (completely opaque).
107    fn opacity(&self) -> f32 {
108        1.0
109    }
110
111    /// Sets the opacity of the layer.
112    fn set_opacity(&mut self, _opacity: f32) {}
113}
114
115/// A helper function for `serde` to provide a default opacity of 1.0.
116pub fn default_opacity() -> f32 {
117    1.0
118}
119
120/// Calculates the squared distance from a point to a line segment.
121pub(crate) fn dist_sq_to_segment(p: Pos2, a: Pos2, b: Pos2) -> f32 {
122    let ab = b - a;
123    let ap = p - a;
124    let l2 = ab.length_sq();
125
126    if l2 == 0.0 {
127        // The segment is a point.
128        return ap.length_sq();
129    }
130
131    // Project point p onto the line defined by a and b.
132    // `t` is the normalized distance from a to the projection.
133    let t = (ap.dot(ab) / l2).clamp(0.0, 1.0);
134
135    // The closest point on the line segment.
136    let closest_point = a + t * ab;
137
138    p.distance_sq(closest_point)
139}
140
141/// Calculates the projection factor of a point onto a line segment.
142/// Returns a value `t` from 0.0 to 1.0.
143pub(crate) fn projection_factor(p: Pos2, a: Pos2, b: Pos2) -> f32 {
144    let ab = b - a;
145    let ap = p - a;
146    let l2 = ab.length_sq();
147
148    if l2 == 0.0 {
149        return 0.0;
150    }
151
152    // Project point p onto the line defined by a and b.
153    (ap.dot(ab) / l2).clamp(0.0, 1.0)
154}
155
156/// Checks if two line segments intersect.
157pub(crate) fn segments_intersect(p1: Pos2, q1: Pos2, p2: Pos2, q2: Pos2) -> bool {
158    fn orientation(p: Pos2, q: Pos2, r: Pos2) -> i8 {
159        let val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
160        if val.abs() < 1e-6 {
161            0 // Collinear
162        } else if val > 0.0 {
163            1 // Clockwise
164        } else {
165            -1 // Counter-clockwise
166        }
167    }
168
169    let o1 = orientation(p1, q1, p2);
170    let o2 = orientation(p1, q1, q2);
171    let o3 = orientation(p2, q2, p1);
172    let o4 = orientation(p2, q2, q1);
173
174    // General case: segments cross each other.
175    if o1 != o2 && o3 != o4 {
176        return true;
177    }
178
179    // Special cases for collinear points are ignored for simplicity,
180    // as they are less critical for this UI interaction.
181    false
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use egui::pos2;
188
189    const EPSILON: f32 = 1e-6;
190
191    #[test]
192    fn test_dist_sq_to_segment() {
193        let a = pos2(0.0, 0.0);
194        let b = pos2(10.0, 0.0);
195
196        // Point on the segment
197        let p1 = pos2(5.0, 0.0);
198        assert!((dist_sq_to_segment(p1, a, b) - 0.0).abs() < EPSILON);
199
200        // Point off the segment, projection is on the segment
201        let p2 = pos2(5.0, 5.0);
202        assert!((dist_sq_to_segment(p2, a, b) - 25.0).abs() < EPSILON); // 5*5
203
204        // Point off the segment, projection is before 'a'
205        let p3 = pos2(-5.0, 5.0);
206        assert!((dist_sq_to_segment(p3, a, b) - 50.0).abs() < EPSILON); // dist^2 from (-5,5) to (0,0) is 25+25 = 50
207
208        // Point off the segment, projection is after 'b'
209        let p4 = pos2(15.0, 5.0);
210        assert!((dist_sq_to_segment(p4, a, b) - 50.0).abs() < EPSILON); // dist^2 from (15,5) to (10,0) is 25+25 = 50
211
212        // Zero-length segment
213        let c = pos2(5.0, 5.0);
214        let p5 = pos2(10.0, 10.0);
215        assert!((dist_sq_to_segment(p5, c, c) - 50.0).abs() < EPSILON); // dist^2 from (10,10) to (5,5) is 25+25 = 50
216    }
217
218    #[test]
219    fn test_layer_opacity_defaults() {
220        struct MockLayer;
221        impl Layer for MockLayer {
222            fn handle_input(&mut self, _: &Response, _: &MapProjection) -> bool {
223                false
224            }
225            fn draw(&self, _: &Painter, _: &MapProjection) {}
226            fn as_any(&self) -> &dyn Any {
227                self
228            }
229            fn as_any_mut(&mut self) -> &mut dyn Any {
230                self
231            }
232        }
233
234        let mut layer = MockLayer;
235        assert_eq!(layer.opacity(), 1.0);
236        layer.set_opacity(0.5);
237        // Default implementation does nothing, so it should still be 1.0
238        assert_eq!(layer.opacity(), 1.0);
239    }
240
241    #[test]
242    fn test_projection_factor() {
243        let a = pos2(0.0, 0.0);
244        let b = pos2(10.0, 0.0);
245
246        // Point is 'a'
247        assert!((projection_factor(a, a, b) - 0.0).abs() < EPSILON);
248
249        // Point is 'b'
250        assert!((projection_factor(b, a, b) - 1.0).abs() < EPSILON);
251
252        // Point is midpoint
253        let p1 = pos2(5.0, 0.0);
254        assert!((projection_factor(p1, a, b) - 0.5).abs() < EPSILON);
255
256        // Point projects to midpoint
257        let p2 = pos2(5.0, 5.0);
258        assert!((projection_factor(p2, a, b) - 0.5).abs() < EPSILON);
259
260        // Point projects before 'a' (clamped)
261        let p3 = pos2(-5.0, 5.0);
262        assert!((projection_factor(p3, a, b) - 0.0).abs() < EPSILON);
263
264        // Point projects after 'b' (clamped)
265        let p4 = pos2(15.0, 5.0);
266        assert!((projection_factor(p4, a, b) - 1.0).abs() < EPSILON);
267
268        // Zero-length segment
269        let c = pos2(5.0, 5.0);
270        let p5 = pos2(10.0, 10.0);
271        assert!((projection_factor(p5, c, c) - 0.0).abs() < EPSILON);
272    }
273
274    #[test]
275    fn test_segments_intersect() {
276        // General intersection
277        let p1 = pos2(0.0, 0.0);
278        let q1 = pos2(10.0, 10.0);
279        let p2 = pos2(0.0, 10.0);
280        let q2 = pos2(10.0, 0.0);
281        assert!(segments_intersect(p1, q1, p2, q2), "General intersection");
282
283        // No intersection, parallel
284        let p1 = pos2(0.0, 0.0);
285        let q1 = pos2(10.0, 0.0);
286        let p2 = pos2(0.0, 5.0);
287        let q2 = pos2(10.0, 5.0);
288        assert!(
289            !segments_intersect(p1, q1, p2, q2),
290            "Parallel, no intersection"
291        );
292
293        // No intersection, not parallel
294        let p1 = pos2(0.0, 0.0);
295        let q1 = pos2(1.0, 1.0);
296        let p2 = pos2(2.0, 2.0);
297        let q2 = pos2(3.0, 0.0);
298        assert!(
299            !segments_intersect(p1, q1, p2, q2),
300            "Not parallel, no intersection"
301        );
302
303        // T-junction
304        let p1 = pos2(0.0, 5.0);
305        let q1 = pos2(10.0, 5.0);
306        let p2 = pos2(5.0, 0.0);
307        let q2 = pos2(5.0, 5.0);
308        assert!(segments_intersect(p1, q1, p2, q2), "T-junction");
309
310        // Segments meeting at an endpoint
311        let p1 = pos2(0.0, 0.0);
312        let q1 = pos2(5.0, 5.0);
313        let p2 = pos2(5.0, 5.0);
314        let q2 = pos2(10.0, 0.0);
315        assert!(segments_intersect(p1, q1, p2, q2), "Meeting at an endpoint");
316
317        // Collinear, overlapping
318        let p1 = pos2(0.0, 0.0);
319        let q1 = pos2(10.0, 0.0);
320        let p2 = pos2(5.0, 0.0);
321        let q2 = pos2(15.0, 0.0);
322        assert!(
323            !segments_intersect(p1, q1, p2, q2),
324            "Collinear, overlapping"
325        );
326
327        // Collinear, non-overlapping
328        let p1 = pos2(0.0, 0.0);
329        let q1 = pos2(10.0, 0.0);
330        let p2 = pos2(11.0, 0.0);
331        let q2 = pos2(20.0, 0.0);
332        assert!(
333            !segments_intersect(p1, q1, p2, q2),
334            "Collinear, non-overlapping"
335        );
336
337        // Collinear, one contains another
338        let p1 = pos2(0.0, 0.0);
339        let q1 = pos2(10.0, 0.0);
340        let p2 = pos2(2.0, 0.0);
341        let q2 = pos2(8.0, 0.0);
342        assert!(!segments_intersect(p1, q1, p2, q2), "Collinear, contained");
343
344        // One segment is a point on the other segment
345        let p1 = pos2(0.0, 0.0);
346        let q1 = pos2(10.0, 0.0);
347        let p2 = pos2(5.0, 0.0);
348        let q2 = pos2(5.0, 0.0);
349        assert!(!segments_intersect(p1, q1, p2, q2), "Point on segment");
350    }
351}