1use crate::layers::{
46 Layer, dist_sq_to_segment, projection_factor, segments_intersect, serde_color32, serde_stroke,
47};
48use crate::projection::{GeoPos, MapProjection};
49use egui::{Color32, Mesh, Painter, Pos2, Response, Shape, Stroke};
50use log::warn;
51use serde::{Deserialize, Serialize};
52use std::any::Any;
53
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56pub enum AreaMode {
57 #[default]
59 Disabled,
60 Modify,
62}
63
64#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
66pub enum AreaShape {
67 Polygon(Vec<GeoPos>),
69 Circle {
71 center: GeoPos,
73 radius: f64,
75 points: Option<i64>,
77 },
78}
79
80#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
82pub struct Area {
83 pub shape: AreaShape,
85
86 #[serde(with = "serde_stroke")]
88 pub stroke: Stroke,
89
90 #[serde(with = "serde_color32")]
92 pub fill: Color32,
93}
94
95#[derive(Clone, Debug)]
97enum DraggedObject {
98 PolygonNode {
99 area_index: usize,
100 node_index: usize,
101 },
102 CircleCenter {
103 area_index: usize,
104 },
105 CircleRadius {
106 area_index: usize,
107 },
108}
109
110#[derive(Clone, Serialize, Deserialize)]
112#[serde(default)]
113pub struct AreaLayer {
114 areas: Vec<Area>,
115
116 #[serde(skip)]
117 pub node_radius: f32,
119
120 #[serde(skip)]
121 pub node_fill: Color32,
123
124 #[serde(skip)]
125 pub mode: AreaMode,
127
128 #[serde(skip)]
129 dragged_object: Option<DraggedObject>,
130}
131
132impl Default for AreaLayer {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl AreaLayer {
139 pub fn new() -> Self {
141 Self {
142 areas: Vec::new(),
143 node_radius: 5.0,
144 node_fill: Color32::from_rgb(0, 128, 0),
145 mode: AreaMode::default(),
146 dragged_object: None,
147 }
148 }
149
150 pub fn add_area(&mut self, area: Area) {
152 self.areas.push(area);
153 }
154
155 #[cfg(feature = "geojson")]
157 pub fn to_geojson_str(&self) -> Result<String, serde_json::Error> {
158 let features: Vec<geojson::Feature> = self
159 .areas
160 .clone()
161 .into_iter()
162 .map(geojson::Feature::from)
163 .collect();
164 let feature_collection = geojson::FeatureCollection {
165 bbox: None,
166 features,
167 foreign_members: None,
168 };
169 serde_json::to_string(&feature_collection)
170 }
171
172 #[cfg(feature = "geojson")]
174 pub fn from_geojson_str(&mut self, s: &str) -> Result<(), serde_json::Error> {
175 let feature_collection: geojson::FeatureCollection = serde_json::from_str(s)?;
176 let new_areas: Vec<Area> = feature_collection
177 .features
178 .into_iter()
179 .into_iter()
180 .filter_map(|f| Area::try_from(f).ok())
181 .collect();
182 self.areas.extend(new_areas);
183 Ok(())
184 }
185
186 fn handle_modify_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
187 if response.double_clicked() {
188 if let Some(pointer_pos) = response.interact_pointer_pos() {
189 if self.find_node_at(pointer_pos, projection).is_none() {
191 if let Some((area_idx, node_idx)) =
192 self.find_line_segment_at(pointer_pos, projection)
193 {
194 if let Some(area) = self.areas.get_mut(area_idx) {
195 if let AreaShape::Polygon(points) = &mut area.shape {
196 let p1_screen = projection.project(points[node_idx]);
197 let p2_screen =
198 projection.project(points[(node_idx + 1) % points.len()]);
199
200 let t = projection_factor(pointer_pos, p1_screen, p2_screen);
201
202 let new_pos_screen = p1_screen.lerp(p2_screen, t);
204 let new_pos_geo = projection.unproject(new_pos_screen);
205
206 points.insert(node_idx + 1, new_pos_geo);
207
208 return response.hovered();
210 }
211 }
212 }
213 }
214 }
215 }
216
217 if response.drag_started() {
218 if let Some(pointer_pos) = response.interact_pointer_pos() {
219 self.dragged_object = self.find_object_at(pointer_pos, projection);
220 }
221 }
222
223 if response.dragged() {
224 if let Some(dragged_object) = self.dragged_object.clone() {
225 if let Some(pointer_pos) = response.interact_pointer_pos() {
226 match dragged_object {
227 DraggedObject::PolygonNode {
228 area_index,
229 node_index,
230 } => {
231 if self.is_move_valid(area_index, node_index, pointer_pos, projection) {
232 if let Some(area) = self.areas.get_mut(area_index) {
233 let mut revert_info = None;
234 if let AreaShape::Polygon(points) = &mut area.shape {
235 if let Some(node) = points.get_mut(node_index) {
236 let old_pos = *node;
237 *node = projection.unproject(pointer_pos);
238 revert_info = Some(old_pos);
239 }
240 }
241
242 if let Some(old_pos) = revert_info {
243 if !area.can_triangulate(projection) {
244 warn!("Triangulation failed, cancelling drag");
245 self.dragged_object = None;
246 if let AreaShape::Polygon(points) = &mut area.shape {
247 points[node_index] = old_pos;
248 }
249 }
250 }
251 }
252 }
253 }
254 DraggedObject::CircleCenter { area_index } => {
255 if let Some(area) = self.areas.get_mut(area_index) {
256 let mut revert_center = None;
257 if let AreaShape::Circle { center, .. } = &mut area.shape {
258 revert_center = Some(*center);
259 *center = projection.unproject(pointer_pos);
260 }
261
262 if let Some(old_center) = revert_center {
263 if !area.can_triangulate(projection) {
264 warn!("Triangulation failed, cancelling drag");
265 self.dragged_object = None;
266 if let AreaShape::Circle { center, .. } = &mut area.shape {
267 *center = old_center;
268 }
269 }
270 }
271 }
272 }
273 DraggedObject::CircleRadius { area_index } => {
274 if let Some(area) = self.areas.get_mut(area_index) {
275 let mut revert_radius = None;
276 if let AreaShape::Circle {
277 center,
278 radius,
279 points: _,
280 } = &mut area.shape
281 {
282 revert_radius = Some(*radius);
283 let center_screen = projection.project(*center);
285 let new_radius_pixels = pointer_pos.distance(center_screen);
286 let new_edge_screen =
287 center_screen + egui::vec2(new_radius_pixels, 0.0);
288 let new_edge_geo = projection.unproject(new_edge_screen);
289
290 let distance_lon = (new_edge_geo.lon - center.lon).abs()
292 * (111_320.0 * center.lat.to_radians().cos());
293 let distance_lat =
294 (new_edge_geo.lat - center.lat).abs() * 110_574.0;
295 *radius = (distance_lon.powi(2) + distance_lat.powi(2)).sqrt();
296 }
297
298 if let Some(old_radius) = revert_radius {
299 if !area.can_triangulate(projection) {
300 warn!("Triangulation failed, cancelling drag");
301 self.dragged_object = None;
302 if let AreaShape::Circle { radius, .. } = &mut area.shape {
303 *radius = old_radius;
304 }
305 }
306 }
307 }
308 }
309 }
310 }
311 }
312 }
313
314 if response.drag_stopped() {
315 self.dragged_object = None;
316 }
317
318 let is_dragging = self.dragged_object.is_some();
319
320 if is_dragging {
321 response.ctx.set_cursor_icon(egui::CursorIcon::Grabbing);
322 } else if let Some(pointer_pos) = response.hover_pos() {
323 if self.find_object_at(pointer_pos, projection).is_some() {
324 response.ctx.set_cursor_icon(egui::CursorIcon::Grab);
325 }
326 }
327
328 is_dragging || response.hovered()
329 }
330
331 fn find_object_at(
332 &self,
333 screen_pos: Pos2,
334 projection: &MapProjection,
335 ) -> Option<DraggedObject> {
336 let click_tolerance_sq = (self.node_radius * 3.0).powi(2);
337
338 for (area_idx, area) in self.areas.iter().enumerate().rev() {
339 match &area.shape {
340 AreaShape::Polygon(points) => {
341 for (node_idx, node) in points.iter().enumerate() {
342 let node_screen_pos = projection.project(*node);
343 if node_screen_pos.distance_sq(screen_pos) < click_tolerance_sq {
344 return Some(DraggedObject::PolygonNode {
345 area_index: area_idx,
346 node_index: node_idx,
347 });
348 }
349 }
350 }
351 AreaShape::Circle {
352 center,
353 radius,
354 points: _,
355 } => {
356 let center_screen = projection.project(*center);
357
358 let point_on_circle_geo = GeoPos {
360 lon: center.lon + (radius / (111_320.0 * center.lat.to_radians().cos())),
361 lat: center.lat,
362 };
363 let point_on_circle_screen = projection.project(point_on_circle_geo);
364 let radius_pixels = center_screen.distance(point_on_circle_screen);
365
366 let distance_to_edge =
368 (center_screen.distance(screen_pos) - radius_pixels).abs();
369 if distance_to_edge < self.node_radius * 2.0 {
370 return Some(DraggedObject::CircleRadius {
371 area_index: area_idx,
372 });
373 }
374
375 if center_screen.distance_sq(screen_pos) < click_tolerance_sq {
377 return Some(DraggedObject::CircleCenter {
378 area_index: area_idx,
379 });
380 }
381 }
382 }
383 }
384
385 None
386 }
387
388 fn find_node_at(&self, screen_pos: Pos2, projection: &MapProjection) -> Option<(usize, usize)> {
389 match self.find_object_at(screen_pos, projection) {
390 Some(DraggedObject::PolygonNode {
391 area_index,
392 node_index,
393 }) => Some((area_index, node_index)),
394 _ => None,
395 }
396 }
397
398 fn find_line_segment_at(
399 &self,
400 screen_pos: Pos2,
401 projection: &MapProjection,
402 ) -> Option<(usize, usize)> {
403 let click_tolerance = (self.node_radius * 2.0).powi(2);
404
405 for (area_idx, area) in self.areas.iter().enumerate().rev() {
406 if let AreaShape::Polygon(points) = &area.shape {
407 if points.len() < 2 {
408 continue;
409 }
410 for i in 0..points.len() {
411 let p1 = projection.project(points[i]);
412 let p2 = projection.project(points[(i + 1) % points.len()]);
413
414 if dist_sq_to_segment(screen_pos, p1, p2) < click_tolerance {
415 return Some((area_idx, i));
416 }
417 }
418 }
419 }
420 None
421 }
422
423 fn is_move_valid(
425 &self,
426 area_idx: usize,
427 node_idx: usize,
428 new_screen_pos: Pos2,
429 projection: &MapProjection,
430 ) -> bool {
431 let area = if let Some(area) = self.areas.get(area_idx) {
432 area
433 } else {
434 return false; };
436
437 let points = match &area.shape {
438 AreaShape::Polygon(points) => points,
439 _ => return true, };
441
442 if points.len() < 3 {
443 return true;
444 }
445 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
446
447 let n = screen_points.len();
448 let prev_node_idx = (node_idx + n - 1) % n;
449 let next_node_idx = (node_idx + 1) % n;
450
451 let new_edge1 = (screen_points[prev_node_idx], new_screen_pos);
453 let new_edge2 = (new_screen_pos, screen_points[next_node_idx]);
454
455 for i in 0..n {
456 let p1_idx = i;
457 let p2_idx = (i + 1) % n;
458
459 if p1_idx == node_idx || p2_idx == node_idx {
461 continue;
462 }
463
464 let edge_to_check = (screen_points[p1_idx], screen_points[p2_idx]);
465
466 if p1_idx != prev_node_idx && p2_idx != prev_node_idx {
468 if segments_intersect(new_edge1.0, new_edge1.1, edge_to_check.0, edge_to_check.1) {
469 return false;
470 }
471 }
472
473 if p1_idx != next_node_idx && p2_idx != next_node_idx {
475 if segments_intersect(new_edge2.0, new_edge2.1, edge_to_check.0, edge_to_check.1) {
476 return false;
477 }
478 }
479 }
480
481 true
482 }
483}
484
485impl Area {
486 fn can_triangulate(&self, projection: &MapProjection) -> bool {
488 let points = self.get_points(projection);
489 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
490
491 if screen_points.len() < 3 {
492 return true;
493 }
494
495 let flat_points: Vec<f64> = screen_points
496 .iter()
497 .flat_map(|p| [p.x as f64, p.y as f64])
498 .collect();
499 earcutr::earcut(&flat_points, &[], 2).is_ok()
500 }
501
502 fn get_points(&self, projection: &MapProjection) -> Vec<GeoPos> {
504 match &self.shape {
505 AreaShape::Polygon(points) => points.clone(),
506 AreaShape::Circle {
507 center,
508 radius,
509 points,
510 } => {
511 let center_geo = *center;
513 let point_on_circle_geo = GeoPos {
514 lon: center_geo.lon
515 + (radius / (111_320.0 * center_geo.lat.to_radians().cos())),
516 lat: center_geo.lat,
517 };
518 let center_screen = projection.project(center_geo);
519 let point_on_circle_screen = projection.project(point_on_circle_geo);
520 let radius_pixels = center_screen.distance(point_on_circle_screen);
521
522 let num_points = if let Some(points) = points {
523 *points
524 } else {
525 (radius_pixels as f64 * 2.0 * std::f64::consts::PI / 10.0).ceil() as i64
528 };
529 let mut circle_points = Vec::with_capacity(num_points as usize);
530
531 for i in 0..num_points {
532 let angle = (i as f64 / num_points as f64) * 2.0 * std::f64::consts::PI;
533 let point_screen = center_screen
534 + egui::vec2(
535 radius_pixels * angle.cos() as f32,
536 radius_pixels * angle.sin() as f32,
537 );
538 circle_points.push(projection.unproject(point_screen));
539 }
540 circle_points
541 }
542 }
543 }
544}
545
546impl Layer for AreaLayer {
547 fn as_any(&self) -> &dyn Any {
548 self
549 }
550
551 fn as_any_mut(&mut self) -> &mut dyn Any {
552 self
553 }
554
555 fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
556 match self.mode {
557 AreaMode::Disabled => false,
558 AreaMode::Modify => self.handle_modify_input(response, projection),
559 }
560 }
561
562 fn draw(&self, painter: &Painter, projection: &MapProjection) {
563 for area in &self.areas {
564 let points = area.get_points(projection);
565 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
566
567 if screen_points.len() >= 3 {
569 let path_shape = Shape::Path(egui::epaint::PathShape {
571 points: screen_points.clone(),
572 closed: true,
573 fill: Color32::TRANSPARENT,
574 stroke: area.stroke.into(),
575 });
576 painter.add(path_shape);
577
578 let flat_points: Vec<f64> = screen_points
580 .iter()
581 .flat_map(|p| [p.x as f64, p.y as f64])
582 .collect();
583 match earcutr::earcut(&flat_points, &[], 2) {
584 Ok(indices) => {
585 let mut mesh = Mesh::default();
586 mesh.vertices = screen_points
587 .iter()
588 .map(|p| egui::epaint::Vertex {
589 pos: *p,
590 uv: Default::default(),
591 color: area.fill,
592 })
593 .collect();
594 mesh.indices = indices.into_iter().map(|i| i as u32).collect();
595 painter.add(Shape::Mesh(mesh.into()));
596 }
597 Err(e) => {
598 warn!("Failed to triangulate area: {:?}", e);
599 }
600 }
601 } else {
602 warn!("Invalid amount of points in area. {:?}", area);
603 }
604
605 if self.mode == AreaMode::Modify {
607 match &area.shape {
608 AreaShape::Polygon(_) => {
609 for point in &screen_points {
610 painter.circle_filled(*point, self.node_radius, self.node_fill);
611 }
612 }
613 AreaShape::Circle {
614 center,
615 radius,
616 points: _,
617 } => {
618 let center_screen = projection.project(*center);
619
620 let point_on_circle_geo = GeoPos {
622 lon: center.lon
623 + (radius / (111_320.0 * center.lat.to_radians().cos())),
624 lat: center.lat,
625 };
626 let point_on_circle_screen = projection.project(point_on_circle_geo);
627 let radius_pixels = center_screen.distance(point_on_circle_screen);
628
629 painter.circle_filled(center_screen, self.node_radius, self.node_fill);
630 let radius_handle_pos = center_screen + egui::vec2(radius_pixels, 0.0);
631 painter.circle_filled(radius_handle_pos, self.node_radius, self.node_fill);
632 }
633 }
634 }
635 }
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use crate::projection::MapProjection;
643 use egui::{Rect, pos2, vec2};
644
645 fn dummy_projection() -> MapProjection {
647 MapProjection::new(
648 10, (0.0, 0.0).into(), Rect::from_min_size(Pos2::ZERO, vec2(1000.0, 1000.0)),
651 )
652 }
653
654 #[test]
655 fn area_layer_new() {
656 let layer = AreaLayer::default();
657 assert_eq!(layer.mode, AreaMode::Disabled);
658 assert!(layer.areas.is_empty());
659 assert_eq!(layer.node_radius, 5.0);
660 }
661
662 #[test]
663 fn area_layer_add_area() {
664 let mut layer = AreaLayer::default();
665 assert_eq!(layer.areas.len(), 0);
666
667 layer.add_area(Area {
668 shape: AreaShape::Polygon(vec![
669 (0.0, 0.0).into(),
670 (1.0, 0.0).into(),
671 (0.0, 1.0).into(),
672 ]),
673 stroke: Default::default(),
674 fill: Default::default(),
675 });
676
677 assert_eq!(layer.areas.len(), 1);
678 }
679
680 #[test]
681 fn circle_get_points_with_fixed_number() {
682 let projection = dummy_projection();
683 let area = Area {
684 shape: AreaShape::Circle {
685 center: (0.0, 0.0).into(),
686 radius: 1000.0,
687 points: Some(16),
688 },
689 stroke: Default::default(),
690 fill: Default::default(),
691 };
692
693 let points = area.get_points(&projection);
694 assert_eq!(points.len(), 16);
695 }
696
697 #[test]
698 fn find_object_at_empty() {
699 let layer = AreaLayer::default();
700 let projection = dummy_projection();
701 let position = pos2(100.0, 100.0);
702
703 assert!(layer.find_object_at(position, &projection).is_none());
704 }
705
706 #[test]
707 fn find_object_at_polygon_node() {
708 let projection = dummy_projection();
709 let mut layer = AreaLayer::default();
710 let geo_pos = projection.unproject(pos2(100.0, 100.0));
711
712 layer.add_area(Area {
713 shape: AreaShape::Polygon(vec![geo_pos]),
714 stroke: Default::default(),
715 fill: Default::default(),
716 });
717
718 let found = layer.find_object_at(pos2(100.0, 100.0), &projection);
720 assert!(matches!(
721 found,
722 Some(DraggedObject::PolygonNode {
723 area_index: 0,
724 node_index: 0
725 })
726 ));
727
728 let found_nearby = layer.find_object_at(pos2(101.0, 101.0), &projection);
730 assert!(matches!(
731 found_nearby,
732 Some(DraggedObject::PolygonNode {
733 area_index: 0,
734 node_index: 0
735 })
736 ));
737
738 let not_found = layer.find_object_at(pos2(200.0, 200.0), &projection);
740 assert!(not_found.is_none());
741 }
742
743 #[test]
744 fn area_layer_serde() {
745 let mut layer = AreaLayer::default();
746 layer.add_area(Area {
747 shape: AreaShape::Polygon(vec![(0.0, 0.0).into()]),
748 stroke: Stroke::new(1.0, Color32::RED),
749 fill: Color32::BLUE,
750 });
751
752 let json = serde_json::to_string(&layer).unwrap();
753 let deserialized: AreaLayer = serde_json::from_str(&json).unwrap();
754
755 assert_eq!(deserialized.areas.len(), 1);
756 assert_eq!(deserialized.mode, AreaMode::Disabled); }
758
759 #[test]
760 fn test_can_triangulate_valid() {
761 let projection = dummy_projection();
762 let area = Area {
763 shape: AreaShape::Polygon(vec![
764 (0.0, 0.0).into(),
765 (10.0, 0.0).into(),
766 (0.0, 10.0).into(),
767 ]),
768 stroke: Default::default(),
769 fill: Default::default(),
770 };
771
772 assert!(area.can_triangulate(&projection));
773 }
774
775 #[test]
776 fn test_can_triangulate_insufficient_points() {
777 let projection = dummy_projection();
778 let area = Area {
779 shape: AreaShape::Polygon(vec![
780 (0.0, 0.0).into(),
781 (10.0, 0.0).into(),
782 ]),
783 stroke: Default::default(),
784 fill: Default::default(),
785 };
786
787 assert!(area.can_triangulate(&projection));
790 }
791
792 #[cfg(feature = "geojson")]
793 mod geojson_tests {
794 use super::*;
795
796 #[test]
797 fn area_layer_geojson_polygon() {
798 let mut layer = AreaLayer::default();
799 layer.add_area(Area {
800 shape: AreaShape::Polygon(vec![
801 (10.0, 20.0).into(),
802 (30.0, 40.0).into(),
803 (50.0, 60.0).into(),
804 ]),
805 stroke: Stroke::new(2.0, Color32::from_rgb(0, 0, 255)),
806 fill: Color32::from_rgba_unmultiplied(255, 0, 0, 128),
807 });
808
809 let geojson_str = layer.to_geojson_str().unwrap();
810
811 let mut new_layer = AreaLayer::default();
812 new_layer.from_geojson_str(&geojson_str).unwrap();
813
814 assert_eq!(new_layer.areas.len(), 1);
815 assert_eq!(layer.areas[0], new_layer.areas[0]);
816 }
817
818 #[test]
819 fn area_layer_geojson_circle() {
820 let mut layer = AreaLayer::default();
821 layer.add_area(Area {
822 shape: AreaShape::Circle {
823 center: (10.0, 20.0).into(),
824 radius: 1000.0,
825 points: Some(32),
826 },
827 stroke: Default::default(),
828 fill: Default::default(),
829 });
830
831 let geojson_str = layer.to_geojson_str().unwrap();
832 let mut new_layer = AreaLayer::default();
833 new_layer.from_geojson_str(&geojson_str).unwrap();
834
835 assert_eq!(new_layer.areas.len(), 1);
836 assert_eq!(layer.areas[0].shape, new_layer.areas[0].shape);
837 }
838 }
839
840 #[test]
841 fn find_node_at_on_segment() {
842 let projection = dummy_projection();
843 let mut layer = AreaLayer::default();
844
845 let p1 = projection.unproject(pos2(100.0, 100.0));
846 let p2 = projection.unproject(pos2(200.0, 100.0));
847
848 layer.add_area(Area {
849 shape: AreaShape::Polygon(vec![p1, p2, projection.unproject(pos2(150.0, 200.0))]), stroke: Default::default(),
851 fill: Default::default(),
852 });
853
854 let click_pos = pos2(150.0, 100.0);
856
857 assert!(layer.find_node_at(click_pos, &projection).is_none());
859
860 let segment = layer.find_line_segment_at(click_pos, &projection);
862 assert!(segment.is_some());
863 assert_eq!(segment.unwrap().0, 0); assert_eq!(segment.unwrap().1, 0);
865 }
866}