egui_map_view/layers/
drawing.rs

1//! A layer for freeform drawing on the map.
2//!
3//! # Example
4//!
5//! ```no_run
6//! use eframe::egui;
7//! use egui_map_view::{layers::drawing::DrawingLayer, layers::drawing::DrawMode, Map, config::OpenStreetMapConfig};
8//!
9//! struct MyApp {
10//!     map: Map,
11//! }
12//!
13//! impl Default for MyApp {
14//!   fn default() -> Self {
15//!     let mut map = Map::new(OpenStreetMapConfig::default());
16//!      map.add_layer("drawing", DrawingLayer::default());
17//!      if let Some(drawing_layer) = map.layer_mut::<DrawingLayer>("drawing") {
18//!        drawing_layer.draw_mode = DrawMode::Draw;
19//!      }
20//!      Self { map }
21//!    }
22//! }
23//!
24//! impl eframe::App for MyApp {
25//!     fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
26//!         egui::CentralPanel::default().show(ctx, |ui| {
27//!             ui.add(&mut self.map);
28//!         });
29//!     }
30//! }
31//! ```
32use crate::layers::Layer;
33use crate::projection::{GeoPos, MapProjection};
34use egui::{Color32, Painter, Pos2, Response, Stroke};
35use serde::{Deserialize, Serialize};
36use std::any::Any;
37
38/// The mode of the `DrawingLayer`.
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
40pub enum DrawMode {
41    /// The layer is not interactive.
42    #[default]
43    Disabled,
44    /// The user can draw on the map.
45    Draw,
46    /// The user can erase drawings.
47    Erase,
48}
49
50/// Layer implementation that allows the user to draw polylines on the map.
51#[derive(Clone, Serialize, Deserialize)]
52#[serde(default)]
53pub struct DrawingLayer {
54    polylines: Vec<Vec<GeoPos>>,
55
56    #[serde(skip)]
57    /// The stroke style for drawing aka line width and color.
58    pub stroke: Stroke,
59
60    #[serde(skip)]
61    /// The current drawing mode.
62    pub draw_mode: DrawMode,
63}
64
65impl DrawingLayer {
66    /// Creates a new `DrawingLayer`.
67    pub fn new(stroke: Stroke) -> Self {
68        Self {
69            polylines: Vec::new(),
70            stroke,
71            draw_mode: DrawMode::default(),
72        }
73    }
74}
75
76impl Default for DrawingLayer {
77    fn default() -> Self {
78        Self {
79            polylines: Vec::new(),
80            stroke: Stroke::new(2.0, Color32::RED),
81            draw_mode: DrawMode::default(),
82        }
83    }
84}
85
86impl DrawingLayer {
87    fn handle_draw_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
88        if response.hovered() {
89            response.ctx.set_cursor_icon(egui::CursorIcon::Crosshair);
90        }
91
92        if response.clicked() {
93            if let Some(pointer_pos) = response.interact_pointer_pos() {
94                let geo_pos = projection.unproject(pointer_pos);
95                if let Some(last_line) = self.polylines.last_mut()
96                    && response.ctx.input(|i| i.modifiers.shift)
97                {
98                    last_line.push(geo_pos);
99                } else {
100                    // No polylines exist yet, so create a new one.
101                    let geo_pos2 = projection.unproject(pointer_pos + egui::vec2(1.0, 0.0));
102                    self.polylines.push(vec![geo_pos, geo_pos2]);
103                }
104            }
105        }
106
107        if response.drag_started() {
108            self.polylines.push(Vec::new());
109        }
110
111        if response.dragged() {
112            if let Some(pointer_pos) = response.interact_pointer_pos() {
113                if let Some(last_line) = self.polylines.last_mut() {
114                    let geo_pos = projection.unproject(pointer_pos);
115                    last_line.push(geo_pos);
116                }
117            }
118        }
119
120        // When drawing, we consume all interactions over the map,
121        // so that the map does not pan or zoom.
122        response.hovered()
123    }
124
125    fn handle_erase_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
126        if response.hovered() {
127            response.ctx.set_cursor_icon(egui::CursorIcon::NotAllowed);
128        }
129
130        if response.dragged() || response.clicked() {
131            if let Some(pointer_pos) = response.interact_pointer_pos() {
132                self.erase_at(pointer_pos, projection);
133            }
134        }
135        response.hovered()
136    }
137
138    fn erase_at(&mut self, pointer_pos: Pos2, projection: &MapProjection) {
139        let erase_radius_screen = self.stroke.width;
140        let erase_radius_sq = erase_radius_screen * erase_radius_screen;
141
142        let old_polylines = std::mem::take(&mut self.polylines);
143        self.polylines = old_polylines
144            .into_iter()
145            .flat_map(|polyline| {
146                split_polyline_by_erase_circle(&polyline, pointer_pos, erase_radius_sq, projection)
147            })
148            .collect();
149    }
150}
151
152impl Layer for DrawingLayer {
153    fn as_any(&self) -> &dyn Any {
154        self
155    }
156
157    fn as_any_mut(&mut self) -> &mut dyn Any {
158        self
159    }
160
161    fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
162        match self.draw_mode {
163            DrawMode::Disabled => false,
164            DrawMode::Draw => self.handle_draw_input(response, projection),
165            DrawMode::Erase => self.handle_erase_input(response, projection),
166        }
167    }
168
169    fn draw(&self, painter: &Painter, projection: &MapProjection) {
170        for polyline in &self.polylines {
171            if polyline.len() > 1 {
172                let screen_points: Vec<egui::Pos2> =
173                    polyline.iter().map(|p| projection.project(*p)).collect();
174                painter.add(egui::Shape::line(screen_points, self.stroke));
175            }
176        }
177    }
178}
179
180/// Splits a polyline into multiple polylines based on whether segments are within the erase radius.
181fn split_polyline_by_erase_circle(
182    polyline: &[GeoPos],
183    pointer_pos: Pos2,
184    erase_radius_sq: f32,
185    projection: &MapProjection,
186) -> Vec<Vec<GeoPos>> {
187    if polyline.len() < 2 {
188        return vec![];
189    }
190
191    let screen_points: Vec<Pos2> = polyline.iter().map(|p| projection.project(*p)).collect();
192
193    let mut new_polylines = Vec::new();
194    let mut current_line = Vec::new();
195    let mut in_visible_part = true;
196
197    // Check if the first segment is erased to correctly set initial state.
198    if dist_sq_to_segment(pointer_pos, screen_points[0], screen_points[1]) < erase_radius_sq {
199        in_visible_part = false;
200    } else {
201        current_line.push(polyline[0]);
202    }
203
204    for i in 0..(polyline.len() - 1) {
205        let p2_geo = polyline[i + 1];
206        let p1_screen = screen_points[i];
207        let p2_screen = screen_points[i + 1];
208
209        let segment_is_erased =
210            dist_sq_to_segment(pointer_pos, p1_screen, p2_screen) < erase_radius_sq;
211
212        if in_visible_part {
213            if segment_is_erased {
214                // Transition from visible to erased.
215                let t = projection_factor(pointer_pos, p1_screen, p2_screen);
216                let split_point_screen = p1_screen.lerp(p2_screen, t);
217                let split_point_geo = projection.unproject(split_point_screen);
218                current_line.push(split_point_geo);
219
220                if current_line.len() > 1 {
221                    new_polylines.push(std::mem::take(&mut current_line));
222                }
223                in_visible_part = false;
224            } else {
225                // Continue visible part.
226                current_line.push(p2_geo);
227            }
228        } else {
229            // In erased part
230            if !segment_is_erased {
231                // Transition from erased to visible.
232                let t = projection_factor(pointer_pos, p1_screen, p2_screen);
233                let split_point_screen = p1_screen.lerp(p2_screen, t);
234                let split_point_geo = projection.unproject(split_point_screen);
235
236                // Start new line.
237                current_line.push(split_point_geo);
238                current_line.push(p2_geo);
239                in_visible_part = true;
240            }
241            // Continue in erased part, do nothing.
242        }
243    }
244
245    if current_line.len() > 1 {
246        new_polylines.push(current_line);
247    }
248
249    new_polylines
250}
251
252/// Calculates the squared distance from a point to a line segment.
253fn dist_sq_to_segment(p: Pos2, a: Pos2, b: Pos2) -> f32 {
254    let ab = b - a;
255    let ap = p - a;
256    let l2 = ab.length_sq();
257
258    if l2 == 0.0 {
259        // The segment is a point.
260        return ap.length_sq();
261    }
262
263    // Project point p onto the line defined by a and b.
264    // `t` is the normalized distance from a to the projection.
265    let t = (ap.dot(ab) / l2).clamp(0.0, 1.0);
266
267    // The closest point on the line segment.
268    let closest_point = a + t * ab;
269
270    p.distance_sq(closest_point)
271}
272
273/// Calculates the projection factor of a point onto a line segment.
274/// Returns a value `t` from 0.0 to 1.0.
275fn projection_factor(p: Pos2, a: Pos2, b: Pos2) -> f32 {
276    let ab = b - a;
277    let ap = p - a;
278    let l2 = ab.length_sq();
279
280    if l2 == 0.0 {
281        return 0.0;
282    }
283
284    // Project point p onto the line defined by a and b.
285    (ap.dot(ab) / l2).clamp(0.0, 1.0)
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn drawing_layer_new() {
294        let layer = DrawingLayer::default();
295        assert_eq!(layer.draw_mode, DrawMode::Disabled);
296        assert!(layer.polylines.is_empty());
297    }
298
299    #[test]
300    fn drawing_layer_as_any() {
301        let layer = DrawingLayer::default();
302        assert!(layer.as_any().is::<DrawingLayer>());
303    }
304
305    #[test]
306    fn drawing_layer_as_any_mut() {
307        let mut layer = DrawingLayer::default();
308        assert!(layer.as_any_mut().is::<DrawingLayer>());
309    }
310
311    #[test]
312    fn drawing_layer_serde() {
313        let mut layer = DrawingLayer::default();
314        layer.draw_mode = DrawMode::Draw; // This should not be serialized.
315        layer.polylines.push(vec![
316            GeoPos { lon: 1.0, lat: 2.0 },
317            GeoPos { lon: 3.0, lat: 4.0 },
318        ]);
319        layer.stroke = Stroke::new(5.0, Color32::BLUE); // This should not be serialized.
320
321        let json = serde_json::to_string(&layer).unwrap();
322
323        // The serialized string should only contain polylines.
324        assert!(json.contains(r#""polylines":[[{"lon":1.0,"lat":2.0},{"lon":3.0,"lat":4.0}]]"#));
325        assert!(!json.contains("draw_mode"));
326        assert!(!json.contains("stroke"));
327
328        let deserialized: DrawingLayer = serde_json::from_str(&json).unwrap();
329
330        // Check that polylines are restored correctly.
331        assert_eq!(deserialized.polylines, layer.polylines);
332
333        // Check that skipped fields have their values from the `default()` implementation,
334        // not from the original `layer` object.
335        assert_eq!(deserialized.draw_mode, DrawMode::Disabled);
336        assert_eq!(deserialized.stroke.width, 2.0);
337        assert_eq!(deserialized.stroke.color, Color32::RED);
338    }
339}