Skip to main content

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 ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
26//!         egui::CentralPanel::default().show_inside(ui, |ui| {
27//!             ui.add(&mut self.map);
28//!         });
29//!     }
30//! }
31//! ```
32use crate::layers::{Layer, default_opacity, dist_sq_to_segment, projection_factor, serde_stroke};
33use crate::projection::{GeoPos, MapProjection};
34use egui::{Color32, Painter, Pos2, Response, Stroke};
35use serde::{Deserialize, Serialize};
36use std::any::Any;
37
38/// A polyline on the map.
39#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
40pub struct Polyline(pub Vec<GeoPos>);
41
42/// The mode of the `DrawingLayer`.
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub enum DrawMode {
45    /// The layer is not interactive.
46    #[default]
47    Disabled,
48    /// The user can draw on the map.
49    Draw,
50    /// The user can erase drawings.
51    Erase,
52}
53
54/// Layer implementation that allows the user to draw polylines on the map.
55#[derive(Clone, Serialize, Deserialize)]
56#[serde(default)]
57pub struct DrawingLayer {
58    polylines: Vec<Polyline>,
59
60    /// The stroke style for drawing aka line width and color.
61    #[serde(with = "serde_stroke")]
62    pub stroke: Stroke,
63
64    /// The current drawing mode.
65    #[serde(skip)]
66    pub draw_mode: DrawMode,
67
68    /// The opacity of the layer.
69    #[serde(default = "default_opacity")]
70    pub opacity: f32,
71}
72
73impl DrawingLayer {
74    /// Serializes the layer to a `GeoJSON` `FeatureCollection`.
75    #[cfg(feature = "geojson")]
76    /// Serializes the layer to a `GeoJSON` `FeatureCollection`.
77    #[cfg(feature = "geojson")]
78    pub fn to_geojson_str(&self, layer_id: &str) -> Result<String, serde_json::Error> {
79        let features: Vec<geojson::Feature> = self
80            .polylines
81            .clone()
82            .into_iter()
83            .map(|p| {
84                let mut feature = geojson::Feature::from(p);
85                if let Some(properties) = &mut feature.properties {
86                    properties.insert(
87                        "stroke_width".to_string(),
88                        serde_json::Value::from(self.stroke.width),
89                    );
90                    properties.insert(
91                        "stroke_color".to_string(),
92                        serde_json::Value::String(self.stroke.color.to_hex()),
93                    );
94                    properties.insert(
95                        "layer_id".to_string(),
96                        serde_json::Value::String(layer_id.to_string()),
97                    );
98                }
99                feature
100            })
101            .collect();
102
103        let mut foreign_members = serde_json::Map::new();
104        foreign_members.insert(
105            "stroke_width".to_string(),
106            serde_json::Value::from(self.stroke.width),
107        );
108        foreign_members.insert(
109            "stroke_color".to_string(),
110            serde_json::Value::String(self.stroke.color.to_hex()),
111        );
112        foreign_members.insert(
113            "layer_id".to_string(),
114            serde_json::Value::String(layer_id.to_string()),
115        );
116        foreign_members.insert(
117            "opacity".to_string(),
118            serde_json::Value::from(f64::from(self.opacity)),
119        );
120
121        let feature_collection = geojson::FeatureCollection {
122            bbox: None,
123            features,
124            foreign_members: Some(foreign_members),
125        };
126        serde_json::to_string(&feature_collection)
127    }
128
129    /// Deserializes a `GeoJSON` `FeatureCollection` and adds the features to the layer.
130    ///
131    /// If `layer_id` is provided, only features with a matching `layer_id` property will be added.
132    /// If `layer_id` is `None`, all valid features will be added.
133    #[cfg(feature = "geojson")]
134    pub fn from_geojson_str(
135        &mut self,
136        s: &str,
137        layer_id: Option<&str>,
138    ) -> Result<(), serde_json::Error> {
139        let feature_collection: geojson::FeatureCollection = serde_json::from_str(s)?;
140        let new_polylines: Vec<Polyline> = feature_collection
141            .features
142            .iter()
143            .filter_map(|f| {
144                // Filter by layer_id if provided
145                if let Some(target_id) = layer_id {
146                    if let Some(properties) = &f.properties {
147                        if let Some(val) = properties.get("layer_id") {
148                            if let Some(id) = val.as_str() {
149                                if id != target_id {
150                                    return None;
151                                }
152                            } else {
153                                // layer_id property exists but is not a string, treat as mismatch
154                                return None;
155                            }
156                        } else {
157                            // layer_id property missing, treat as mismatch
158                            return None;
159                        }
160                    } else {
161                        // No properties, treat as mismatch
162                        return None;
163                    }
164                }
165
166                let polyline = Polyline::try_from(f.clone()).ok();
167                if polyline.is_some()
168                    && let Some(properties) = &f.properties
169                {
170                    if let Some(value) = properties.get("stroke_width")
171                        && let Some(width) = value.as_f64()
172                    {
173                        self.stroke.width = width as f32;
174                    }
175                    if let Some(value) = properties.get("stroke_color")
176                        && let Some(s) = value.as_str()
177                        && let Ok(color) = Color32::from_hex(s)
178                    {
179                        self.stroke.color = color;
180                    }
181                }
182                polyline
183            })
184            .collect();
185        self.polylines.extend(new_polylines);
186
187        if let Some(foreign_members) = feature_collection.foreign_members {
188            if let Some(value) = foreign_members.get("stroke_width")
189                && let Some(width) = value.as_f64()
190            {
191                self.stroke.width = width as f32;
192            }
193            if let Some(value) = foreign_members.get("stroke_color")
194                && let Some(s) = value.as_str()
195                && let Ok(color) = Color32::from_hex(s)
196            {
197                self.stroke.color = color;
198            }
199            if let Some(value) = foreign_members.get("opacity")
200                && let Some(opacity) = value.as_f64()
201            {
202                self.opacity = opacity as f32;
203            }
204        }
205
206        Ok(())
207    }
208
209    /// Creates a new `DrawingLayer`.
210    #[must_use]
211    pub fn new(stroke: Stroke) -> Self {
212        Self {
213            polylines: Vec::new(),
214            stroke,
215            draw_mode: DrawMode::default(),
216            opacity: 1.0,
217        }
218    }
219}
220
221impl Default for DrawingLayer {
222    fn default() -> Self {
223        Self {
224            polylines: Vec::new(),
225            stroke: Stroke::new(2.0, Color32::RED),
226            draw_mode: DrawMode::default(),
227            opacity: 1.0,
228        }
229    }
230}
231
232impl DrawingLayer {
233    fn handle_draw_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
234        if response.hovered() {
235            response.ctx.set_cursor_icon(egui::CursorIcon::Crosshair);
236        }
237
238        if response.clicked()
239            && let Some(pointer_pos) = response.interact_pointer_pos()
240        {
241            let geo_pos = projection.unproject(pointer_pos);
242            if let Some(last_line) = self.polylines.last_mut()
243                && response.ctx.input(|i| i.modifiers.shift)
244            {
245                last_line.0.push(geo_pos);
246            } else {
247                // No polylines exist yet, so create a new one.
248                let geo_pos2 = projection.unproject(pointer_pos + egui::vec2(1.0, 0.0));
249                self.polylines.push(Polyline(vec![geo_pos, geo_pos2]));
250            }
251        }
252
253        if response.drag_started() {
254            self.polylines.push(Polyline(Vec::new()));
255        }
256
257        if response.dragged()
258            && let Some(pointer_pos) = response.interact_pointer_pos()
259            && let Some(last_line) = self.polylines.last_mut()
260        {
261            let geo_pos = projection.unproject(pointer_pos);
262            last_line.0.push(geo_pos);
263        }
264
265        // When drawing, we consume all interactions over the map,
266        // so that the map does not pan or zoom.
267        response.hovered()
268    }
269
270    fn handle_erase_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
271        if response.hovered() {
272            response.ctx.set_cursor_icon(egui::CursorIcon::NotAllowed);
273        }
274
275        if (response.dragged() || response.clicked())
276            && let Some(pointer_pos) = response.interact_pointer_pos()
277        {
278            self.erase_at(pointer_pos, projection);
279        }
280        response.hovered()
281    }
282
283    fn erase_at(&mut self, pointer_pos: Pos2, projection: &MapProjection) {
284        let erase_radius_screen = self.stroke.width;
285        let erase_radius_sq = erase_radius_screen * erase_radius_screen;
286
287        let old_polylines = std::mem::take(&mut self.polylines);
288        self.polylines = old_polylines
289            .into_iter()
290            .flat_map(|polyline| {
291                split_polyline_by_erase_circle(
292                    &polyline.0,
293                    pointer_pos,
294                    erase_radius_sq,
295                    projection,
296                )
297                .into_iter()
298                .map(Polyline)
299            })
300            .collect();
301    }
302}
303
304impl Layer for DrawingLayer {
305    fn as_any(&self) -> &dyn Any {
306        self
307    }
308
309    fn as_any_mut(&mut self) -> &mut dyn Any {
310        self
311    }
312
313    fn opacity(&self) -> f32 {
314        self.opacity
315    }
316
317    fn set_opacity(&mut self, opacity: f32) {
318        self.opacity = opacity;
319    }
320
321    fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
322        match self.draw_mode {
323            DrawMode::Disabled => false,
324            DrawMode::Draw => self.handle_draw_input(response, projection),
325            DrawMode::Erase => self.handle_erase_input(response, projection),
326        }
327    }
328
329    fn draw(&self, painter: &Painter, projection: &MapProjection) {
330        for polyline in &self.polylines {
331            if polyline.0.len() > 1 {
332                let screen_points: Vec<egui::Pos2> =
333                    polyline.0.iter().map(|p| projection.project(*p)).collect();
334                painter.add(egui::Shape::line(
335                    screen_points,
336                    Stroke {
337                        color: self.stroke.color.gamma_multiply(self.opacity),
338                        ..self.stroke
339                    },
340                ));
341            }
342        }
343    }
344}
345
346/// Splits a polyline into multiple polylines based on whether segments are within the erase radius.
347fn split_polyline_by_erase_circle(
348    polyline: &[GeoPos],
349    pointer_pos: Pos2,
350    erase_radius_sq: f32,
351    projection: &MapProjection,
352) -> Vec<Vec<GeoPos>> {
353    if polyline.len() < 2 {
354        return vec![];
355    }
356
357    let screen_points: Vec<Pos2> = polyline.iter().map(|p| projection.project(*p)).collect();
358
359    let mut new_polylines = Vec::new();
360    let mut current_line = Vec::new();
361    let mut in_visible_part = true;
362
363    // Check if the first segment is erased to correctly set initial state.
364    if dist_sq_to_segment(pointer_pos, screen_points[0], screen_points[1]) < erase_radius_sq {
365        in_visible_part = false;
366    } else {
367        current_line.push(polyline[0]);
368    }
369
370    for i in 0..(polyline.len() - 1) {
371        let p2_geo = polyline[i + 1];
372        let p1_screen = screen_points[i];
373        let p2_screen = screen_points[i + 1];
374
375        let segment_is_erased =
376            dist_sq_to_segment(pointer_pos, p1_screen, p2_screen) < erase_radius_sq;
377
378        if in_visible_part {
379            if segment_is_erased {
380                // Transition from visible to erased.
381                let t = projection_factor(pointer_pos, p1_screen, p2_screen);
382                let split_point_screen = p1_screen.lerp(p2_screen, t);
383                let split_point_geo = projection.unproject(split_point_screen);
384                current_line.push(split_point_geo);
385
386                if current_line.len() > 1 {
387                    new_polylines.push(std::mem::take(&mut current_line));
388                }
389                in_visible_part = false;
390            } else {
391                // Continue visible part.
392                current_line.push(p2_geo);
393            }
394        } else {
395            // In erased part
396            if !segment_is_erased {
397                // Transition from erased to visible.
398                let t = projection_factor(pointer_pos, p1_screen, p2_screen);
399                let split_point_screen = p1_screen.lerp(p2_screen, t);
400                let split_point_geo = projection.unproject(split_point_screen);
401
402                // Start new line.
403                current_line.push(split_point_geo);
404                current_line.push(p2_geo);
405                in_visible_part = true;
406            }
407            // Continue in erased part, do nothing.
408        }
409    }
410
411    if current_line.len() > 1 {
412        new_polylines.push(current_line);
413    }
414
415    new_polylines
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn drawing_layer_new() {
424        let layer = DrawingLayer::default();
425        assert_eq!(layer.draw_mode, DrawMode::Disabled);
426        assert!(layer.polylines.is_empty());
427    }
428
429    #[test]
430    fn drawing_layer_as_any() {
431        let layer = DrawingLayer::default();
432        assert!(layer.as_any().is::<DrawingLayer>());
433    }
434
435    #[test]
436    fn drawing_layer_as_any_mut() {
437        let mut layer = DrawingLayer::default();
438        assert!(layer.as_any_mut().is::<DrawingLayer>());
439    }
440
441    #[test]
442    fn drawing_layer_serde() {
443        let mut layer = DrawingLayer {
444            draw_mode: DrawMode::Draw,
445            stroke: Stroke::new(5.0, Color32::BLUE),
446            ..DrawingLayer::default()
447        };
448        layer.polylines.push(Polyline(vec![
449            GeoPos { lon: 1.0, lat: 2.0 },
450            GeoPos { lon: 3.0, lat: 4.0 },
451        ]));
452
453        let json = serde_json::to_string(&layer).unwrap();
454
455        // The serialized string should only contain polylines.
456        assert!(json.contains(r##""polylines":[[{"lon":1.0,"lat":2.0},{"lon":3.0,"lat":4.0}]],"stroke":{"width":5.0,"color":"#0000ffff"}"##));
457        assert!(!json.contains("draw_mode"));
458
459        let deserialized: DrawingLayer = serde_json::from_str(&json).unwrap();
460
461        // Check that polylines are restored correctly.
462        assert_eq!(deserialized.polylines, layer.polylines);
463
464        // Check that the stroke information is correct
465        assert_eq!(deserialized.stroke.width, 5.0);
466        assert_eq!(deserialized.stroke.color, Color32::BLUE);
467
468        // Default is drawmode disabled and its not serializable
469        assert_eq!(deserialized.draw_mode, DrawMode::Disabled);
470    }
471
472    #[cfg(feature = "geojson")]
473    mod geojson_tests {
474        use super::*;
475
476        #[test]
477        fn drawing_layer_geojson() {
478            let mut layer = DrawingLayer::default();
479            layer.polylines.push(Polyline(vec![
480                (10.0, 20.0).into(),
481                (30.0, 40.0).into(),
482                (50.0, 60.0).into(),
483            ]));
484            layer.stroke = Stroke::new(5.0, Color32::BLUE);
485
486            let geojson_str = layer.to_geojson_str("my_layer").unwrap();
487
488            // Test deserialization with matching ID
489            let mut new_layer = DrawingLayer::default();
490            new_layer
491                .from_geojson_str(&geojson_str, Some("my_layer"))
492                .unwrap();
493
494            assert_eq!(new_layer.polylines.len(), 1);
495            assert_eq!(layer.polylines[0], new_layer.polylines[0]);
496            assert_eq!(layer.stroke, new_layer.stroke);
497
498            // Test deserialization with non-matching ID
499            let mut other_layer = DrawingLayer::default();
500            other_layer
501                .from_geojson_str(&geojson_str, Some("other_layer"))
502                .unwrap();
503            assert_eq!(other_layer.polylines.len(), 0);
504
505            // Test deserialization with None ID (should include all)
506            let mut all_layer = DrawingLayer::default();
507            all_layer.from_geojson_str(&geojson_str, None).unwrap();
508            assert_eq!(all_layer.polylines.len(), 1);
509        }
510    }
511}