1use crate::layers::{
47 Layer, dist_sq_to_segment, projection_factor, segments_intersect, serde_color32, serde_stroke,
48};
49use crate::projection::{GeoPos, MapProjection};
50use egui::{Color32, Mesh, Painter, Pos2, Response, Shape, Stroke};
51use log::warn;
52use serde::{Deserialize, Serialize};
53use std::any::Any;
54
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub enum AreaMode {
58 #[default]
60 Disabled,
61 Modify,
63}
64
65#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
67pub enum AreaShape {
68 Polygon(Vec<GeoPos>),
70 Circle {
72 center: GeoPos,
74 radius: f64,
76 points: Option<i64>,
78 },
79}
80
81#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
83pub enum FillType {
84 None,
86 #[default]
88 Solid,
89 Hatching,
91}
92
93#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
95pub struct Area {
96 pub shape: AreaShape,
98
99 #[serde(with = "serde_stroke")]
101 pub stroke: Stroke,
102
103 #[serde(with = "serde_color32")]
105 pub fill: Color32,
106
107 #[serde(default)]
109 pub fill_type: FillType,
110}
111
112#[derive(Clone, Debug)]
114enum DraggedObject {
115 PolygonNode {
116 area_index: usize,
117 node_index: usize,
118 },
119 CircleCenter {
120 area_index: usize,
121 },
122 CircleRadius {
123 area_index: usize,
124 },
125}
126
127#[derive(Clone, Serialize, Deserialize)]
129#[serde(default)]
130pub struct AreaLayer {
131 areas: Vec<Area>,
132
133 #[serde(skip)]
134 pub node_radius: f32,
136
137 #[serde(skip)]
138 pub node_fill: Color32,
140
141 #[serde(skip)]
142 pub mode: AreaMode,
144
145 #[serde(skip)]
146 dragged_object: Option<DraggedObject>,
147}
148
149impl Default for AreaLayer {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155impl AreaLayer {
156 #[must_use]
158 pub fn new() -> Self {
159 Self {
160 areas: Vec::new(),
161 node_radius: 5.0,
162 node_fill: Color32::from_rgb(0, 128, 0),
163 mode: AreaMode::default(),
164 dragged_object: None,
165 }
166 }
167
168 pub fn add_area(&mut self, area: Area) {
170 self.areas.push(area);
171 }
172
173 #[must_use]
175 pub fn areas(&self) -> &Vec<Area> {
176 &self.areas
177 }
178
179 pub fn areas_mut(&mut self) -> &mut Vec<Area> {
181 &mut self.areas
182 }
183
184 #[cfg(feature = "geojson")]
186 pub fn to_geojson_str(&self) -> Result<String, serde_json::Error> {
187 let features: Vec<geojson::Feature> = self
188 .areas
189 .clone()
190 .into_iter()
191 .map(geojson::Feature::from)
192 .collect();
193 let feature_collection = geojson::FeatureCollection {
194 bbox: None,
195 features,
196 foreign_members: None,
197 };
198 serde_json::to_string(&feature_collection)
199 }
200
201 #[cfg(feature = "geojson")]
203 pub fn from_geojson_str(&mut self, s: &str) -> Result<(), serde_json::Error> {
204 let feature_collection: geojson::FeatureCollection = serde_json::from_str(s)?;
205 let new_areas: Vec<Area> = feature_collection
206 .features
207 .into_iter()
208 .filter_map(|f| Area::try_from(f).ok())
209 .collect();
210 self.areas.extend(new_areas);
211 Ok(())
212 }
213
214 fn handle_modify_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
215 if response.double_clicked()
216 && let Some(pointer_pos) = response.interact_pointer_pos()
217 {
218 if self.find_node_at(pointer_pos, projection).is_none()
220 && let Some((area_idx, node_idx)) =
221 self.find_line_segment_at(pointer_pos, projection)
222 && let Some(area) = self.areas.get_mut(area_idx)
223 && let AreaShape::Polygon(points) = &mut area.shape
224 {
225 let p1_screen = projection.project(points[node_idx]);
226 let p2_screen = projection.project(points[(node_idx + 1) % points.len()]);
227
228 let t = projection_factor(pointer_pos, p1_screen, p2_screen);
229
230 let new_pos_screen = p1_screen.lerp(p2_screen, t);
232 let new_pos_geo = projection.unproject(new_pos_screen);
233
234 points.insert(node_idx + 1, new_pos_geo);
235
236 return response.hovered();
238 }
239 }
240
241 if response.drag_started()
242 && let Some(pointer_pos) = response.interact_pointer_pos()
243 {
244 self.dragged_object = self.find_object_at(pointer_pos, projection);
245 }
246
247 if response.dragged()
248 && let Some(dragged_object) = self.dragged_object.clone()
249 && let Some(pointer_pos) = response.interact_pointer_pos()
250 {
251 match dragged_object {
252 DraggedObject::PolygonNode {
253 area_index,
254 node_index,
255 } => {
256 if self.is_move_valid(area_index, node_index, pointer_pos, projection)
257 && let Some(area) = self.areas.get_mut(area_index)
258 {
259 let mut revert_info = None;
260 if let AreaShape::Polygon(points) = &mut area.shape
261 && let Some(node) = points.get_mut(node_index)
262 {
263 let old_pos = *node;
264 *node = projection.unproject(pointer_pos);
265 revert_info = Some(old_pos);
266 }
267
268 if let Some(old_pos) = revert_info
269 && !area.can_triangulate(projection)
270 {
271 warn!("Triangulation failed, cancelling drag");
272 self.dragged_object = None;
273 if let AreaShape::Polygon(points) = &mut area.shape {
274 points[node_index] = old_pos;
275 }
276 }
277 }
278 }
279 DraggedObject::CircleCenter { area_index } => {
280 if let Some(area) = self.areas.get_mut(area_index) {
281 let mut revert_center = None;
282 if let AreaShape::Circle { center, .. } = &mut area.shape {
283 revert_center = Some(*center);
284 *center = projection.unproject(pointer_pos);
285 }
286
287 if let Some(old_center) = revert_center
288 && !area.can_triangulate(projection)
289 {
290 warn!("Triangulation failed, cancelling drag");
291 self.dragged_object = None;
292 if let AreaShape::Circle { center, .. } = &mut area.shape {
293 *center = old_center;
294 }
295 }
296 }
297 }
298 DraggedObject::CircleRadius { area_index } => {
299 if let Some(area) = self.areas.get_mut(area_index) {
300 let mut revert_radius = None;
301 if let AreaShape::Circle {
302 center,
303 radius,
304 points: _,
305 } = &mut area.shape
306 {
307 revert_radius = Some(*radius);
308 let center_screen = projection.project(*center);
310 let new_radius_pixels = pointer_pos.distance(center_screen);
311 let new_edge_screen =
312 center_screen + egui::vec2(new_radius_pixels, 0.0);
313 let new_edge_geo = projection.unproject(new_edge_screen);
314
315 let distance_lon = (new_edge_geo.lon - center.lon).abs()
317 * (111_320.0 * center.lat.to_radians().cos());
318 let distance_lat = (new_edge_geo.lat - center.lat).abs() * 110_574.0;
319 *radius = (distance_lon.powi(2) + distance_lat.powi(2)).sqrt();
320 }
321
322 if let Some(old_radius) = revert_radius
323 && !area.can_triangulate(projection)
324 {
325 warn!("Triangulation failed, cancelling drag");
326 self.dragged_object = None;
327 if let AreaShape::Circle { radius, .. } = &mut area.shape {
328 *radius = old_radius;
329 }
330 }
331 }
332 }
333 }
334 }
335
336 if response.drag_stopped() {
337 self.dragged_object = None;
338 }
339
340 let is_dragging = self.dragged_object.is_some();
341
342 if is_dragging {
343 response.ctx.set_cursor_icon(egui::CursorIcon::Grabbing);
344 } else if let Some(pointer_pos) = response.hover_pos()
345 && self.find_object_at(pointer_pos, projection).is_some()
346 {
347 response.ctx.set_cursor_icon(egui::CursorIcon::Grab);
348 }
349
350 is_dragging
351 || (response.hovered()
352 && self
353 .find_object_at(response.hover_pos().unwrap_or_default(), projection)
354 .is_some())
355 }
356
357 fn find_object_at(
358 &self,
359 screen_pos: Pos2,
360 projection: &MapProjection,
361 ) -> Option<DraggedObject> {
362 let click_tolerance_sq = (self.node_radius * 3.0).powi(2);
363
364 for (area_idx, area) in self.areas.iter().enumerate().rev() {
365 match &area.shape {
366 AreaShape::Polygon(points) => {
367 for (node_idx, node) in points.iter().enumerate() {
368 let node_screen_pos = projection.project(*node);
369 if node_screen_pos.distance_sq(screen_pos) < click_tolerance_sq {
370 return Some(DraggedObject::PolygonNode {
371 area_index: area_idx,
372 node_index: node_idx,
373 });
374 }
375 }
376 }
377 AreaShape::Circle {
378 center,
379 radius,
380 points: _,
381 } => {
382 let center_screen = projection.project(*center);
383
384 let point_on_circle_geo = GeoPos {
386 lon: center.lon + (radius / (111_320.0 * center.lat.to_radians().cos())),
387 lat: center.lat,
388 };
389 let point_on_circle_screen = projection.project(point_on_circle_geo);
390 let radius_pixels = center_screen.distance(point_on_circle_screen);
391
392 let distance_to_edge =
394 (center_screen.distance(screen_pos) - radius_pixels).abs();
395 if distance_to_edge < self.node_radius * 2.0 {
396 return Some(DraggedObject::CircleRadius {
397 area_index: area_idx,
398 });
399 }
400
401 if center_screen.distance_sq(screen_pos) < click_tolerance_sq {
403 return Some(DraggedObject::CircleCenter {
404 area_index: area_idx,
405 });
406 }
407 }
408 }
409 }
410
411 None
412 }
413
414 fn find_node_at(&self, screen_pos: Pos2, projection: &MapProjection) -> Option<(usize, usize)> {
415 match self.find_object_at(screen_pos, projection) {
416 Some(DraggedObject::PolygonNode {
417 area_index,
418 node_index,
419 }) => Some((area_index, node_index)),
420 _ => None,
421 }
422 }
423
424 fn find_line_segment_at(
425 &self,
426 screen_pos: Pos2,
427 projection: &MapProjection,
428 ) -> Option<(usize, usize)> {
429 let click_tolerance = (self.node_radius * 2.0).powi(2);
430
431 for (area_idx, area) in self.areas.iter().enumerate().rev() {
432 if let AreaShape::Polygon(points) = &area.shape {
433 if points.len() < 2 {
434 continue;
435 }
436 for i in 0..points.len() {
437 let p1 = projection.project(points[i]);
438 let p2 = projection.project(points[(i + 1) % points.len()]);
439
440 if dist_sq_to_segment(screen_pos, p1, p2) < click_tolerance {
441 return Some((area_idx, i));
442 }
443 }
444 }
445 }
446 None
447 }
448
449 fn is_move_valid(
451 &self,
452 area_idx: usize,
453 node_idx: usize,
454 new_screen_pos: Pos2,
455 projection: &MapProjection,
456 ) -> bool {
457 let area = if let Some(area) = self.areas.get(area_idx) {
458 area
459 } else {
460 return false; };
462
463 let points = match &area.shape {
464 AreaShape::Polygon(points) => points,
465 _ => return true, };
467
468 if points.len() < 3 {
469 return true;
470 }
471 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
472
473 let n = screen_points.len();
474 let prev_node_idx = (node_idx + n - 1) % n;
475 let next_node_idx = (node_idx + 1) % n;
476
477 let new_edge1 = (screen_points[prev_node_idx], new_screen_pos);
479 let new_edge2 = (new_screen_pos, screen_points[next_node_idx]);
480
481 for i in 0..n {
482 let p1_idx = i;
483 let p2_idx = (i + 1) % n;
484
485 if p1_idx == node_idx || p2_idx == node_idx {
487 continue;
488 }
489
490 let edge_to_check = (screen_points[p1_idx], screen_points[p2_idx]);
491
492 if p1_idx != prev_node_idx
494 && p2_idx != prev_node_idx
495 && segments_intersect(new_edge1.0, new_edge1.1, edge_to_check.0, edge_to_check.1)
496 {
497 return false;
498 }
499
500 if p1_idx != next_node_idx
502 && p2_idx != next_node_idx
503 && segments_intersect(new_edge2.0, new_edge2.1, edge_to_check.0, edge_to_check.1)
504 {
505 return false;
506 }
507 }
508
509 true
510 }
511}
512
513impl Area {
514 fn can_triangulate(&self, projection: &MapProjection) -> bool {
516 let points = self.get_points(projection);
517 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
518
519 if screen_points.len() < 3 {
520 return true;
521 }
522
523 let flat_points: Vec<f64> = screen_points
524 .iter()
525 .flat_map(|p| [f64::from(p.x), f64::from(p.y)])
526 .collect();
527 earcutr::earcut(&flat_points, &[], 2).is_ok()
528 }
529
530 fn get_points(&self, projection: &MapProjection) -> Vec<GeoPos> {
532 match &self.shape {
533 AreaShape::Polygon(points) => points.clone(),
534 AreaShape::Circle {
535 center,
536 radius,
537 points,
538 } => {
539 let center_geo = *center;
541 let point_on_circle_geo = GeoPos {
542 lon: center_geo.lon
543 + (radius / (111_320.0 * center_geo.lat.to_radians().cos())),
544 lat: center_geo.lat,
545 };
546 let center_screen = projection.project(center_geo);
547 let point_on_circle_screen = projection.project(point_on_circle_geo);
548 let radius_pixels = center_screen.distance(point_on_circle_screen);
549
550 let num_points = if let Some(points) = points {
551 *points
552 } else {
553 (f64::from(radius_pixels) * 2.0 * std::f64::consts::PI / 10.0).ceil() as i64
556 };
557 let mut circle_points = Vec::with_capacity(num_points as usize);
558
559 for i in 0..num_points {
560 let angle = (i as f64 / num_points as f64) * 2.0 * std::f64::consts::PI;
561 let point_screen = center_screen
562 + egui::vec2(
563 radius_pixels * angle.cos() as f32,
564 radius_pixels * angle.sin() as f32,
565 );
566 circle_points.push(projection.unproject(point_screen));
567 }
568 circle_points
569 }
570 }
571 }
572}
573
574fn generate_hatching_lines(screen_points: &[Pos2], spacing: f32, angle: f32) -> Vec<(Pos2, Pos2)> {
582 if screen_points.len() < 3 || spacing <= 0.0 {
583 return Vec::new();
584 }
585
586 let dir = egui::vec2(angle.cos(), angle.sin());
588 let perp = egui::vec2(-angle.sin(), angle.cos());
589
590 let mut min_perp = f32::MAX;
592 let mut max_perp = f32::MIN;
593 for p in screen_points {
594 let d = p.to_vec2().dot(perp);
595 min_perp = min_perp.min(d);
596 max_perp = max_perp.max(d);
597 }
598
599 let n = screen_points.len();
600 let mut segments = Vec::new();
601
602 let mut offset = min_perp + spacing;
604 while offset < max_perp {
605 let line_origin = Pos2::ZERO + perp * offset;
607
608 let mut t_values: Vec<f32> = Vec::new();
610 for i in 0..n {
611 let a = screen_points[i];
612 let b = screen_points[(i + 1) % n];
613 let edge = b - a;
614
615 let denom = edge.x * dir.y - edge.y * dir.x;
618 if denom.abs() < 1e-9 {
619 continue; }
621
622 let diff = a - line_origin;
623 let t_edge = -(diff.x * dir.y - diff.y * dir.x) / denom;
624
625 if (0.0..=1.0).contains(&t_edge) {
626 let t_line = if dir.x.abs() > dir.y.abs() {
628 (a.x - line_origin.x + t_edge * edge.x) / dir.x
629 } else {
630 (a.y - line_origin.y + t_edge * edge.y) / dir.y
631 };
632 t_values.push(t_line);
633 }
634 }
635
636 t_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
638
639 for pair in t_values.chunks_exact(2) {
641 let p1 = line_origin + dir * pair[0];
642 let p2 = line_origin + dir * pair[1];
643 segments.push((p1, p2));
644 }
645
646 offset += spacing;
647 }
648
649 segments
650}
651
652impl Layer for AreaLayer {
653 fn as_any(&self) -> &dyn Any {
654 self
655 }
656
657 fn as_any_mut(&mut self) -> &mut dyn Any {
658 self
659 }
660
661 fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
662 match self.mode {
663 AreaMode::Disabled => false,
664 AreaMode::Modify => self.handle_modify_input(response, projection),
665 }
666 }
667
668 fn draw(&self, painter: &Painter, projection: &MapProjection) {
669 for area in &self.areas {
670 let points = area.get_points(projection);
671 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
672
673 if screen_points.len() >= 3 {
675 let path_shape = Shape::Path(egui::epaint::PathShape {
677 points: screen_points.clone(),
678 closed: true,
679 fill: Color32::TRANSPARENT,
680 stroke: area.stroke.into(),
681 });
682 painter.add(path_shape);
683
684 match area.fill_type {
685 FillType::None => { }
686 FillType::Solid => {
687 let flat_points: Vec<f64> = screen_points
689 .iter()
690 .flat_map(|p| [f64::from(p.x), f64::from(p.y)])
691 .collect();
692 match earcutr::earcut(&flat_points, &[], 2) {
693 Ok(indices) => {
694 let mesh = Mesh {
695 vertices: screen_points
696 .iter()
697 .map(|p| egui::epaint::Vertex {
698 pos: *p,
699 uv: Default::default(),
700 color: area.fill,
701 })
702 .collect(),
703 indices: indices.into_iter().map(|i| i as u32).collect(),
704 ..Default::default()
705 };
706 painter.add(Shape::Mesh(mesh.into()));
707 }
708 Err(e) => {
709 warn!("Failed to triangulate area: {e:?}");
710 }
711 }
712 }
713 FillType::Hatching => {
714 let segments = generate_hatching_lines(
715 &screen_points,
716 8.0,
717 std::f32::consts::FRAC_PI_4,
718 );
719 for (a, b) in segments {
720 painter.line_segment([a, b], area.stroke);
721 }
722 }
723 }
724 } else {
725 warn!("Invalid amount of points in area. {area:?}");
726 }
727
728 if self.mode == AreaMode::Modify {
730 match &area.shape {
731 AreaShape::Polygon(_) => {
732 for point in &screen_points {
733 painter.circle_filled(*point, self.node_radius, self.node_fill);
734 }
735 }
736 AreaShape::Circle {
737 center,
738 radius,
739 points: _,
740 } => {
741 let center_screen = projection.project(*center);
742
743 let point_on_circle_geo = GeoPos {
745 lon: center.lon
746 + (radius / (111_320.0 * center.lat.to_radians().cos())),
747 lat: center.lat,
748 };
749 let point_on_circle_screen = projection.project(point_on_circle_geo);
750 let radius_pixels = center_screen.distance(point_on_circle_screen);
751
752 painter.circle_filled(center_screen, self.node_radius, self.node_fill);
753 let radius_handle_pos = center_screen + egui::vec2(radius_pixels, 0.0);
754 painter.circle_filled(radius_handle_pos, self.node_radius, self.node_fill);
755 }
756 }
757 }
758 }
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765 use crate::projection::MapProjection;
766 use egui::{Rect, pos2, vec2};
767
768 fn dummy_projection() -> MapProjection {
770 MapProjection::new(
771 10, (0.0, 0.0).into(), Rect::from_min_size(Pos2::ZERO, vec2(1000.0, 1000.0)),
774 )
775 }
776
777 #[test]
778 fn area_layer_new() {
779 let layer = AreaLayer::default();
780 assert_eq!(layer.mode, AreaMode::Disabled);
781 assert!(layer.areas.is_empty());
782 assert_eq!(layer.node_radius, 5.0);
783 }
784
785 #[test]
786 fn area_layer_add_area() {
787 let mut layer = AreaLayer::default();
788 assert_eq!(layer.areas.len(), 0);
789
790 layer.add_area(Area {
791 shape: AreaShape::Polygon(vec![
792 (0.0, 0.0).into(),
793 (1.0, 0.0).into(),
794 (0.0, 1.0).into(),
795 ]),
796 stroke: Default::default(),
797 fill: Default::default(),
798 fill_type: Default::default(),
799 });
800
801 assert_eq!(layer.areas.len(), 1);
802 }
803
804 #[test]
805 fn circle_get_points_with_fixed_number() {
806 let projection = dummy_projection();
807 let area = Area {
808 shape: AreaShape::Circle {
809 center: (0.0, 0.0).into(),
810 radius: 1000.0,
811 points: Some(16),
812 },
813 stroke: Default::default(),
814 fill: Default::default(),
815 fill_type: Default::default(),
816 };
817
818 let points = area.get_points(&projection);
819 assert_eq!(points.len(), 16);
820 }
821
822 #[test]
823 fn find_object_at_empty() {
824 let layer = AreaLayer::default();
825 let projection = dummy_projection();
826 let position = pos2(100.0, 100.0);
827
828 assert!(layer.find_object_at(position, &projection).is_none());
829 }
830
831 #[test]
832 fn find_object_at_polygon_node() {
833 let projection = dummy_projection();
834 let mut layer = AreaLayer::default();
835 let geo_pos = projection.unproject(pos2(100.0, 100.0));
836
837 layer.add_area(Area {
838 shape: AreaShape::Polygon(vec![geo_pos]),
839 stroke: Default::default(),
840 fill: Default::default(),
841 fill_type: Default::default(),
842 });
843
844 let found = layer.find_object_at(pos2(100.0, 100.0), &projection);
846 assert!(matches!(
847 found,
848 Some(DraggedObject::PolygonNode {
849 area_index: 0,
850 node_index: 0
851 })
852 ));
853
854 let found_nearby = layer.find_object_at(pos2(101.0, 101.0), &projection);
856 assert!(matches!(
857 found_nearby,
858 Some(DraggedObject::PolygonNode {
859 area_index: 0,
860 node_index: 0
861 })
862 ));
863
864 let not_found = layer.find_object_at(pos2(200.0, 200.0), &projection);
866 assert!(not_found.is_none());
867 }
868
869 #[test]
870 fn area_layer_serde() {
871 let mut layer = AreaLayer::default();
872 layer.add_area(Area {
873 shape: AreaShape::Polygon(vec![(0.0, 0.0).into()]),
874 stroke: Stroke::new(1.0, Color32::RED),
875 fill: Color32::BLUE,
876 fill_type: Default::default(),
877 });
878
879 let json = serde_json::to_string(&layer).unwrap();
880 let deserialized: AreaLayer = serde_json::from_str(&json).unwrap();
881
882 assert_eq!(deserialized.areas.len(), 1);
883 assert_eq!(deserialized.mode, AreaMode::Disabled); }
885
886 #[test]
887 fn test_can_triangulate_valid() {
888 let projection = dummy_projection();
889 let area = Area {
890 shape: AreaShape::Polygon(vec![
891 (0.0, 0.0).into(),
892 (10.0, 0.0).into(),
893 (0.0, 10.0).into(),
894 ]),
895 stroke: Default::default(),
896 fill: Default::default(),
897 fill_type: Default::default(),
898 };
899
900 assert!(area.can_triangulate(&projection));
901 }
902
903 #[test]
904 fn test_can_triangulate_insufficient_points() {
905 let projection = dummy_projection();
906 let area = Area {
907 shape: AreaShape::Polygon(vec![(0.0, 0.0).into(), (10.0, 0.0).into()]),
908 stroke: Default::default(),
909 fill: Default::default(),
910 fill_type: Default::default(),
911 };
912
913 assert!(area.can_triangulate(&projection));
916 }
917
918 #[cfg(feature = "geojson")]
919 mod geojson_tests {
920 use super::*;
921
922 #[test]
923 fn area_layer_geojson_polygon() {
924 let mut layer = AreaLayer::default();
925 layer.add_area(Area {
926 shape: AreaShape::Polygon(vec![
927 (10.0, 20.0).into(),
928 (30.0, 40.0).into(),
929 (50.0, 60.0).into(),
930 ]),
931 stroke: Stroke::new(2.0, Color32::from_rgb(0, 0, 255)),
932 fill: Color32::from_rgba_unmultiplied(255, 0, 0, 128),
933 fill_type: Default::default(),
934 });
935
936 let geojson_str = layer.to_geojson_str().unwrap();
937
938 let mut new_layer = AreaLayer::default();
939 new_layer.from_geojson_str(&geojson_str).unwrap();
940
941 assert_eq!(new_layer.areas.len(), 1);
942 assert_eq!(layer.areas[0], new_layer.areas[0]);
943 }
944
945 #[test]
946 fn area_layer_geojson_circle() {
947 let mut layer = AreaLayer::default();
948 layer.add_area(Area {
949 shape: AreaShape::Circle {
950 center: (10.0, 20.0).into(),
951 radius: 1000.0,
952 points: Some(32),
953 },
954 stroke: Default::default(),
955 fill: Default::default(),
956 fill_type: Default::default(),
957 });
958
959 let geojson_str = layer.to_geojson_str().unwrap();
960 let mut new_layer = AreaLayer::default();
961 new_layer.from_geojson_str(&geojson_str).unwrap();
962
963 assert_eq!(new_layer.areas.len(), 1);
964 assert_eq!(layer.areas[0].shape, new_layer.areas[0].shape);
965 }
966 }
967
968 #[test]
969 fn find_node_at_on_segment() {
970 let projection = dummy_projection();
971 let mut layer = AreaLayer::default();
972
973 let p1 = projection.unproject(pos2(100.0, 100.0));
974 let p2 = projection.unproject(pos2(200.0, 100.0));
975
976 layer.add_area(Area {
977 shape: AreaShape::Polygon(vec![p1, p2, projection.unproject(pos2(150.0, 200.0))]), stroke: Default::default(),
979 fill: Default::default(),
980 fill_type: Default::default(),
981 });
982
983 let click_pos = pos2(150.0, 100.0);
985
986 assert!(layer.find_node_at(click_pos, &projection).is_none());
988
989 let segment = layer.find_line_segment_at(click_pos, &projection);
991 assert!(segment.is_some());
992 assert_eq!(segment.unwrap().0, 0); assert_eq!(segment.unwrap().1, 0);
994 }
995}