1use crate::layers::{
47 Layer, default_opacity, dist_sq_to_segment, projection_factor, segments_intersect,
48 serde_color32, serde_stroke,
49};
50use crate::projection::{GeoPos, MapProjection};
51use egui::{Color32, Mesh, Painter, Pos2, Response, Shape, Stroke};
52use log::warn;
53use serde::{Deserialize, Serialize};
54use std::any::Any;
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
58pub enum AreaMode {
59 #[default]
61 Disabled,
62 Modify,
64 ModifySelected,
66}
67
68#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
70pub enum AreaShape {
71 Polygon(Vec<GeoPos>),
73 Circle {
75 center: GeoPos,
77 radius: f64,
79 points: Option<i64>,
81 },
82 Ellipse {
84 center: GeoPos,
86 radius_major: f64,
88 radius_minor: f64,
90 rotation: f64,
92 points: Option<i64>,
94 },
95}
96
97#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
99pub enum FillType {
100 None,
102 #[default]
104 Solid,
105 Hatching,
107}
108
109#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
111pub struct Area {
112 pub shape: AreaShape,
114
115 #[serde(with = "serde_stroke")]
117 pub stroke: Stroke,
118
119 #[serde(with = "serde_color32")]
121 pub fill: Color32,
122
123 #[serde(default)]
125 pub fill_type: FillType,
126}
127
128#[derive(Clone, Debug)]
130enum DraggedObject {
131 PolygonNode {
132 area_index: usize,
133 node_index: usize,
134 },
135 CircleCenter {
136 area_index: usize,
137 },
138 CircleRadius {
139 area_index: usize,
140 },
141 EllipseCenter {
142 area_index: usize,
143 },
144 EllipseMajorRadius {
145 area_index: usize,
146 },
147 EllipseMinorRadius {
148 area_index: usize,
149 },
150 EllipseRotation {
151 area_index: usize,
152 },
153}
154
155#[derive(Clone, Serialize, Deserialize)]
157#[serde(default)]
158pub struct AreaLayer {
159 areas: Vec<Area>,
160
161 #[serde(skip)]
162 pub node_radius: f32,
164
165 #[serde(skip)]
166 pub node_fill: Color32,
168
169 #[serde(skip)]
170 pub mode: AreaMode,
172
173 #[serde(skip)]
174 dragged_object: Option<DraggedObject>,
175
176 #[serde(skip)]
177 hovered_object: Option<DraggedObject>,
178
179 #[serde(default = "default_opacity")]
181 pub opacity: f32,
182
183 #[serde(skip)]
184 pub selected_area: Option<usize>,
186}
187
188impl Default for AreaLayer {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194impl AreaLayer {
195 #[must_use]
197 pub fn new() -> Self {
198 Self {
199 areas: Vec::new(),
200 node_radius: 5.0,
201 node_fill: Color32::from_rgb(0, 128, 0),
202 mode: AreaMode::default(),
203 dragged_object: None,
204 hovered_object: None,
205 opacity: 1.0,
206 selected_area: None,
207 }
208 }
209
210 pub fn add_area(&mut self, area: Area) {
212 self.areas.push(area);
213 }
214
215 #[must_use]
217 pub fn areas(&self) -> &Vec<Area> {
218 &self.areas
219 }
220
221 pub fn areas_mut(&mut self) -> &mut Vec<Area> {
223 &mut self.areas
224 }
225
226 #[cfg(feature = "geojson")]
228 pub fn to_geojson_str(&self) -> Result<String, serde_json::Error> {
229 let features: Vec<geojson::Feature> = self
230 .areas
231 .clone()
232 .into_iter()
233 .map(geojson::Feature::from)
234 .collect();
235 let mut foreign_members = serde_json::Map::new();
236 foreign_members.insert(
237 "opacity".to_string(),
238 serde_json::Value::from(f64::from(self.opacity)),
239 );
240
241 let feature_collection = geojson::FeatureCollection {
242 bbox: None,
243 features,
244 foreign_members: Some(foreign_members),
245 };
246 serde_json::to_string(&feature_collection)
247 }
248
249 #[cfg(feature = "geojson")]
251 pub fn from_geojson_str(&mut self, s: &str) -> Result<(), serde_json::Error> {
252 let feature_collection: geojson::FeatureCollection = serde_json::from_str(s)?;
253 let new_areas: Vec<Area> = feature_collection
254 .features
255 .into_iter()
256 .filter_map(|f| Area::try_from(f).ok())
257 .collect();
258 self.areas.extend(new_areas);
259
260 if let Some(foreign_members) = feature_collection.foreign_members
261 && let Some(value) = foreign_members.get("opacity")
262 && let Some(opacity) = value.as_f64()
263 {
264 self.opacity = opacity as f32;
265 }
266 Ok(())
267 }
268
269 fn handle_modify_input(
270 &mut self,
271 response: &Response,
272 projection: &MapProjection,
273 limit_to_area: Option<usize>,
274 ) -> bool {
275 self.hovered_object = response
276 .hover_pos()
277 .and_then(|pos| self.find_object_at(pos, projection, limit_to_area));
278
279 if response.double_clicked()
280 && let Some(pointer_pos) = response.interact_pointer_pos()
281 {
282 if self
284 .find_node_at(pointer_pos, projection, limit_to_area)
285 .is_none()
286 && let Some((area_idx, node_idx)) =
287 self.find_line_segment_at(pointer_pos, projection, limit_to_area)
288 && let Some(area) = self.areas.get_mut(area_idx)
289 && let AreaShape::Polygon(points) = &mut area.shape
290 {
291 let p1_screen = projection.project(points[node_idx]);
292 let p2_screen = projection.project(points[(node_idx + 1) % points.len()]);
293
294 let t = projection_factor(pointer_pos, p1_screen, p2_screen);
295
296 let new_pos_screen = p1_screen.lerp(p2_screen, t);
298 let new_pos_geo = projection.unproject(new_pos_screen);
299
300 points.insert(node_idx + 1, new_pos_geo);
301
302 return response.hovered();
304 }
305 }
306
307 if response.drag_started()
308 && let Some(pointer_pos) = response.interact_pointer_pos()
309 {
310 self.dragged_object = self.find_object_at(pointer_pos, projection, limit_to_area);
311 }
312
313 if response.dragged()
314 && let Some(dragged_object) = self.dragged_object.clone()
315 && let Some(pointer_pos) = response.interact_pointer_pos()
316 {
317 match dragged_object {
318 DraggedObject::PolygonNode {
319 area_index,
320 node_index,
321 } => {
322 if self.is_move_valid(area_index, node_index, pointer_pos, projection)
323 && let Some(area) = self.areas.get_mut(area_index)
324 {
325 let mut revert_info = None;
326 if let AreaShape::Polygon(points) = &mut area.shape
327 && let Some(node) = points.get_mut(node_index)
328 {
329 let old_pos = *node;
330 *node = projection.unproject(pointer_pos);
331 revert_info = Some(old_pos);
332 }
333
334 if let Some(old_pos) = revert_info
335 && !area.can_triangulate(projection)
336 {
337 warn!("Triangulation failed, cancelling drag");
338 self.dragged_object = None;
339 if let AreaShape::Polygon(points) = &mut area.shape {
340 points[node_index] = old_pos;
341 }
342 }
343 }
344 }
345 DraggedObject::CircleCenter { area_index } => {
346 if let Some(area) = self.areas.get_mut(area_index) {
347 let mut revert_center = None;
348 if let AreaShape::Circle { center, .. } = &mut area.shape {
349 revert_center = Some(*center);
350 *center = projection.unproject(pointer_pos);
351 }
352
353 if let Some(old_center) = revert_center
354 && !area.can_triangulate(projection)
355 {
356 warn!("Triangulation failed, cancelling drag");
357 self.dragged_object = None;
358 if let AreaShape::Circle { center, .. } = &mut area.shape {
359 *center = old_center;
360 }
361 }
362 }
363 }
364 DraggedObject::CircleRadius { area_index } => {
365 if let Some(area) = self.areas.get_mut(area_index) {
366 let mut revert_radius = None;
367 if let AreaShape::Circle {
368 center,
369 radius,
370 points: _,
371 } = &mut area.shape
372 {
373 revert_radius = Some(*radius);
374 let center_screen = projection.project(*center);
376 let new_radius_pixels = pointer_pos.distance(center_screen);
377 let new_edge_screen =
378 center_screen + egui::vec2(new_radius_pixels, 0.0);
379 let new_edge_geo = projection.unproject(new_edge_screen);
380
381 let distance_lon = (new_edge_geo.lon - center.lon).abs()
383 * (111_320.0 * center.lat.to_radians().cos().max(1e-6));
384 let distance_lat = (new_edge_geo.lat - center.lat).abs() * 110_574.0;
385 let new_val = (distance_lon.powi(2) + distance_lat.powi(2)).sqrt();
386 if new_val.is_finite() {
387 *radius = new_val;
388 }
389 }
390
391 if let Some(old_radius) = revert_radius
392 && !area.can_triangulate(projection)
393 {
394 warn!("Triangulation failed, cancelling drag");
395 self.dragged_object = None;
396 if let AreaShape::Circle { radius, .. } = &mut area.shape {
397 *radius = old_radius;
398 }
399 }
400 }
401 }
402 DraggedObject::EllipseCenter { area_index } => {
403 if let Some(area) = self.areas.get_mut(area_index) {
404 let mut revert_center = None;
405 if let AreaShape::Ellipse { center, .. } = &mut area.shape {
406 revert_center = Some(*center);
407 *center = projection.unproject(pointer_pos);
408 }
409
410 if let Some(old_center) = revert_center
411 && !area.can_triangulate(projection)
412 {
413 warn!("Triangulation failed, cancelling drag");
414 self.dragged_object = None;
415 if let AreaShape::Ellipse { center, .. } = &mut area.shape {
416 *center = old_center;
417 }
418 }
419 }
420 }
421 DraggedObject::EllipseMajorRadius { area_index } => {
422 if let Some(area) = self.areas.get_mut(area_index) {
423 let mut revert_radius_major = None;
424 if let AreaShape::Ellipse {
425 center,
426 radius_major,
427 rotation,
428 ..
429 } = &mut area.shape
430 {
431 revert_radius_major = Some(*radius_major);
432 let center_screen = projection.project(*center);
433 let cos_rot = rotation.cos() as f32;
434 let sin_rot = rotation.sin() as f32;
435 let v_major = egui::vec2(cos_rot, sin_rot);
436 let new_radius_pixels =
437 (pointer_pos - center_screen).dot(v_major).max(1.0);
438 let new_edge_screen =
439 center_screen + egui::vec2(new_radius_pixels, 0.0);
440 let new_edge_geo = projection.unproject(new_edge_screen);
441 let distance_lon = (new_edge_geo.lon - center.lon).abs()
442 * (111_320.0 * center.lat.to_radians().cos().max(1e-6));
443 let distance_lat = (new_edge_geo.lat - center.lat).abs() * 110_574.0;
444 let new_val = (distance_lon.powi(2) + distance_lat.powi(2)).sqrt();
445 if new_val.is_finite() {
446 *radius_major = new_val;
447 }
448 }
449
450 if let Some(old_radius) = revert_radius_major
451 && !area.can_triangulate(projection)
452 {
453 warn!("Triangulation failed, cancelling drag");
454 self.dragged_object = None;
455 if let AreaShape::Ellipse { radius_major, .. } = &mut area.shape {
456 *radius_major = old_radius;
457 }
458 }
459 }
460 }
461 DraggedObject::EllipseMinorRadius { area_index } => {
462 if let Some(area) = self.areas.get_mut(area_index) {
463 let mut revert_radius_minor = None;
464 if let AreaShape::Ellipse {
465 center,
466 radius_minor,
467 rotation,
468 ..
469 } = &mut area.shape
470 {
471 revert_radius_minor = Some(*radius_minor);
472 let center_screen = projection.project(*center);
473 let cos_rot = rotation.cos() as f32;
474 let sin_rot = rotation.sin() as f32;
475 let v_minor = egui::vec2(-sin_rot, cos_rot);
476 let new_radius_pixels =
477 (pointer_pos - center_screen).dot(v_minor).max(1.0);
478 let new_edge_screen =
479 center_screen + egui::vec2(new_radius_pixels, 0.0);
480 let new_edge_geo = projection.unproject(new_edge_screen);
481 let distance_lon = (new_edge_geo.lon - center.lon).abs()
482 * (111_320.0 * center.lat.to_radians().cos().max(1e-6));
483 let distance_lat = (new_edge_geo.lat - center.lat).abs() * 110_574.0;
484 let new_val = (distance_lon.powi(2) + distance_lat.powi(2)).sqrt();
485 if new_val.is_finite() {
486 *radius_minor = new_val;
487 }
488 }
489
490 if let Some(old_radius) = revert_radius_minor
491 && !area.can_triangulate(projection)
492 {
493 warn!("Triangulation failed, cancelling drag");
494 self.dragged_object = None;
495 if let AreaShape::Ellipse { radius_minor, .. } = &mut area.shape {
496 *radius_minor = old_radius;
497 }
498 }
499 }
500 }
501 DraggedObject::EllipseRotation { area_index } => {
502 if let Some(area) = self.areas.get_mut(area_index) {
503 let mut revert_rotation = None;
504 if let AreaShape::Ellipse {
505 center, rotation, ..
506 } = &mut area.shape
507 {
508 revert_rotation = Some(*rotation);
509 let center_screen = projection.project(*center);
510 let new_val = f64::from(
511 (pointer_pos - center_screen)
512 .y
513 .atan2((pointer_pos - center_screen).x),
514 );
515 if new_val.is_finite() {
516 *rotation = new_val;
517 }
518 }
519
520 if let Some(old_rotation) = revert_rotation
521 && !area.can_triangulate(projection)
522 {
523 warn!("Triangulation failed, cancelling drag");
524 self.dragged_object = None;
525 if let AreaShape::Ellipse { rotation, .. } = &mut area.shape {
526 *rotation = old_rotation;
527 }
528 }
529 }
530 }
531 }
532 }
533
534 if response.drag_stopped() {
535 self.dragged_object = None;
536 }
537
538 let is_dragging = self.dragged_object.is_some();
539
540 if is_dragging {
541 response.ctx.set_cursor_icon(egui::CursorIcon::Grabbing);
542 } else if self.hovered_object.is_some() {
543 response.ctx.set_cursor_icon(egui::CursorIcon::Grab);
544 }
545
546 is_dragging || (response.hovered() && self.hovered_object.is_some())
547 }
548
549 fn find_object_at(
550 &self,
551 screen_pos: Pos2,
552 projection: &MapProjection,
553 limit_to_area: Option<usize>,
554 ) -> Option<DraggedObject> {
555 let click_tolerance_sq = (self.node_radius * 3.0).powi(2);
556
557 for (area_idx, area) in self.areas.iter().enumerate().rev() {
558 if let Some(limit_idx) = limit_to_area
559 && area_idx != limit_idx
560 {
561 continue;
562 }
563 match &area.shape {
564 AreaShape::Polygon(points) => {
565 for (node_idx, node) in points.iter().enumerate() {
566 let node_screen_pos = projection.project(*node);
567 if node_screen_pos.distance_sq(screen_pos) < click_tolerance_sq {
568 return Some(DraggedObject::PolygonNode {
569 area_index: area_idx,
570 node_index: node_idx,
571 });
572 }
573 }
574 }
575 AreaShape::Circle {
576 center,
577 radius,
578 points: _,
579 } => {
580 let center_screen = projection.project(*center);
581
582 let point_on_circle_geo = GeoPos {
584 lon: center.lon
585 + (radius / (111_320.0 * center.lat.to_radians().cos().max(1e-6))),
586 lat: center.lat,
587 };
588 let point_on_circle_screen = projection.project(point_on_circle_geo);
589 let radius_pixels = center_screen.distance(point_on_circle_screen);
590
591 let distance_to_edge =
593 (center_screen.distance(screen_pos) - radius_pixels).abs();
594 if distance_to_edge < self.node_radius * 2.0 {
595 return Some(DraggedObject::CircleRadius {
596 area_index: area_idx,
597 });
598 }
599
600 if center_screen.distance_sq(screen_pos) < click_tolerance_sq {
602 return Some(DraggedObject::CircleCenter {
603 area_index: area_idx,
604 });
605 }
606 }
607 AreaShape::Ellipse {
608 center,
609 radius_major,
610 radius_minor,
611 rotation,
612 points: _,
613 } => {
614 let center_geo = *center;
615 let point_on_major_geo = GeoPos {
616 lon: center_geo.lon
617 + (radius_major
618 / (111_320.0 * center_geo.lat.to_radians().cos().max(1e-6))),
619 lat: center_geo.lat,
620 };
621 let point_on_minor_geo = GeoPos {
622 lon: center_geo.lon,
623 lat: center_geo.lat + (radius_minor / 110_574.0),
624 };
625 let center_screen = projection.project(center_geo);
626 let point_on_major_screen = projection.project(point_on_major_geo);
627 let point_on_minor_screen = projection.project(point_on_minor_geo);
628 let radius_major_pixels = center_screen.distance(point_on_major_screen);
629 let radius_minor_pixels = center_screen.distance(point_on_minor_screen);
630
631 let cos_rot = rotation.cos() as f32;
632 let sin_rot = rotation.sin() as f32;
633
634 let rotation_handle_pos = center_screen
636 + egui::vec2(
637 (radius_major_pixels + 20.0) * cos_rot,
638 (radius_major_pixels + 20.0) * sin_rot,
639 );
640 if rotation_handle_pos.distance_sq(screen_pos) < click_tolerance_sq {
641 return Some(DraggedObject::EllipseRotation {
642 area_index: area_idx,
643 });
644 }
645
646 let major_handle_pos = center_screen
648 + egui::vec2(radius_major_pixels * cos_rot, radius_major_pixels * sin_rot);
649 if major_handle_pos.distance_sq(screen_pos) < click_tolerance_sq {
650 return Some(DraggedObject::EllipseMajorRadius {
651 area_index: area_idx,
652 });
653 }
654
655 let minor_handle_pos = center_screen
657 + egui::vec2(
658 -radius_minor_pixels * sin_rot,
659 radius_minor_pixels * cos_rot,
660 );
661 if minor_handle_pos.distance_sq(screen_pos) < click_tolerance_sq {
662 return Some(DraggedObject::EllipseMinorRadius {
663 area_index: area_idx,
664 });
665 }
666
667 if center_screen.distance_sq(screen_pos) < click_tolerance_sq {
669 return Some(DraggedObject::EllipseCenter {
670 area_index: area_idx,
671 });
672 }
673 }
674 }
675 }
676
677 None
678 }
679
680 fn find_node_at(
681 &self,
682 screen_pos: Pos2,
683 projection: &MapProjection,
684 limit_to_area: Option<usize>,
685 ) -> Option<(usize, usize)> {
686 match self.find_object_at(screen_pos, projection, limit_to_area) {
687 Some(DraggedObject::PolygonNode {
688 area_index,
689 node_index,
690 }) => Some((area_index, node_index)),
691 _ => None,
692 }
693 }
694
695 fn find_line_segment_at(
696 &self,
697 screen_pos: Pos2,
698 projection: &MapProjection,
699 limit_to_area: Option<usize>,
700 ) -> Option<(usize, usize)> {
701 let click_tolerance = (self.node_radius * 2.0).powi(2);
702
703 for (area_idx, area) in self.areas.iter().enumerate().rev() {
704 if let Some(limit_idx) = limit_to_area
705 && area_idx != limit_idx
706 {
707 continue;
708 }
709 if let AreaShape::Polygon(points) = &area.shape {
710 if points.len() < 2 {
711 continue;
712 }
713 for i in 0..points.len() {
714 let p1 = projection.project(points[i]);
715 let p2 = projection.project(points[(i + 1) % points.len()]);
716
717 if dist_sq_to_segment(screen_pos, p1, p2) < click_tolerance {
718 return Some((area_idx, i));
719 }
720 }
721 }
722 }
723 None
724 }
725
726 fn is_move_valid(
728 &self,
729 area_idx: usize,
730 node_idx: usize,
731 new_screen_pos: Pos2,
732 projection: &MapProjection,
733 ) -> bool {
734 let area = if let Some(area) = self.areas.get(area_idx) {
735 area
736 } else {
737 return false; };
739
740 let points = match &area.shape {
741 AreaShape::Polygon(points) => points,
742 _ => return true, };
744
745 if points.len() < 3 {
746 return true;
747 }
748 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
749
750 let n = screen_points.len();
751 let prev_node_idx = (node_idx + n - 1) % n;
752 let next_node_idx = (node_idx + 1) % n;
753
754 let new_edge1 = (screen_points[prev_node_idx], new_screen_pos);
756 let new_edge2 = (new_screen_pos, screen_points[next_node_idx]);
757
758 for i in 0..n {
759 let p1_idx = i;
760 let p2_idx = (i + 1) % n;
761
762 if p1_idx == node_idx || p2_idx == node_idx {
764 continue;
765 }
766
767 let edge_to_check = (screen_points[p1_idx], screen_points[p2_idx]);
768
769 if p1_idx != prev_node_idx
771 && p2_idx != prev_node_idx
772 && segments_intersect(new_edge1.0, new_edge1.1, edge_to_check.0, edge_to_check.1)
773 {
774 return false;
775 }
776
777 if p1_idx != next_node_idx
779 && p2_idx != next_node_idx
780 && segments_intersect(new_edge2.0, new_edge2.1, edge_to_check.0, edge_to_check.1)
781 {
782 return false;
783 }
784 }
785
786 true
787 }
788}
789
790impl Area {
791 fn can_triangulate(&self, projection: &MapProjection) -> bool {
793 let points = self.get_points(projection);
794 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
795
796 if screen_points.len() < 3 {
797 return true;
798 }
799
800 let flat_points: Vec<f64> = screen_points
801 .iter()
802 .flat_map(|p| [f64::from(p.x), f64::from(p.y)])
803 .collect();
804 earcutr::earcut(&flat_points, &[], 2).is_ok()
805 }
806
807 fn get_points(&self, projection: &MapProjection) -> Vec<GeoPos> {
809 match &self.shape {
810 AreaShape::Polygon(points) => points.clone(),
811 AreaShape::Circle {
812 center,
813 radius,
814 points,
815 } => {
816 let center_geo = *center;
818 let point_on_circle_geo = GeoPos {
819 lon: center_geo.lon
820 + (radius / (111_320.0 * center_geo.lat.to_radians().cos().max(1e-6))),
821 lat: center_geo.lat,
822 };
823 let center_screen = projection.project(center_geo);
824 let point_on_circle_screen = projection.project(point_on_circle_geo);
825 let radius_pixels = center_screen.distance(point_on_circle_screen);
826
827 let num_points = if let Some(points) = points {
828 *points
829 } else {
830 (f64::from(radius_pixels) * 2.0 * std::f64::consts::PI / 10.0).ceil() as i64
833 };
834 let num_points = num_points.max(3);
835 let mut circle_points = Vec::with_capacity(num_points as usize);
836
837 for i in 0..num_points {
838 let angle = (i as f64 / num_points as f64) * 2.0 * std::f64::consts::PI;
839 let point_screen = center_screen
840 + egui::vec2(
841 radius_pixels * angle.cos() as f32,
842 radius_pixels * angle.sin() as f32,
843 );
844 circle_points.push(projection.unproject(point_screen));
845 }
846 circle_points
847 }
848 AreaShape::Ellipse {
849 center,
850 radius_major,
851 radius_minor,
852 rotation,
853 points,
854 } => {
855 let center_geo = *center;
856 let point_on_major_geo = GeoPos {
857 lon: center_geo.lon
858 + (radius_major
859 / (111_320.0 * center_geo.lat.to_radians().cos().max(1e-6))),
860 lat: center_geo.lat,
861 };
862 let point_on_minor_geo = GeoPos {
863 lon: center_geo.lon,
864 lat: center_geo.lat + (radius_minor / 110_574.0),
865 };
866 let center_screen = projection.project(center_geo);
867 let point_on_major_screen = projection.project(point_on_major_geo);
868 let point_on_minor_screen = projection.project(point_on_minor_geo);
869 let radius_major_pixels = center_screen.distance(point_on_major_screen);
870 let radius_minor_pixels = center_screen.distance(point_on_minor_screen);
871
872 let num_points = if let Some(points) = points {
873 *points
874 } else {
875 let max_radius = radius_major_pixels.max(radius_minor_pixels);
876 (f64::from(max_radius) * 2.0 * std::f64::consts::PI / 10.0).ceil() as i64
877 };
878 let num_points = num_points.max(3);
879 let mut ellipse_points = Vec::with_capacity(num_points as usize);
880 let cos_rot = rotation.cos();
881 let sin_rot = rotation.sin();
882
883 for i in 0..num_points {
884 let angle = (i as f64 / num_points as f64) * 2.0 * std::f64::consts::PI;
885 let dx = f64::from(radius_major_pixels) * angle.cos();
886 let dy = f64::from(radius_minor_pixels) * angle.sin();
887 let rx = dx * cos_rot - dy * sin_rot;
888 let ry = dx * sin_rot + dy * cos_rot;
889 let point_screen = center_screen + egui::vec2(rx as f32, ry as f32);
890 ellipse_points.push(projection.unproject(point_screen));
891 }
892 ellipse_points
893 }
894 }
895 }
896
897 pub fn contains(&self, pos: Pos2, projection: &MapProjection) -> bool {
899 match &self.shape {
900 AreaShape::Circle { center, radius, .. } => {
901 let center_screen = projection.project(*center);
902 let point_on_circle_geo = GeoPos {
903 lon: center.lon
904 + (radius / (111_320.0 * center.lat.to_radians().cos().max(1e-6))),
905 lat: center.lat,
906 };
907 let point_on_circle_screen = projection.project(point_on_circle_geo);
908 let radius_pixels = center_screen.distance(point_on_circle_screen);
909 if radius_pixels <= 0.0 {
910 return false;
911 }
912 center_screen.distance_sq(pos) <= radius_pixels.powi(2)
913 }
914 AreaShape::Ellipse {
915 center,
916 radius_major,
917 radius_minor,
918 rotation,
919 ..
920 } => {
921 let center_geo = *center;
922 let point_on_major_geo = GeoPos {
923 lon: center_geo.lon
924 + (radius_major
925 / (111_320.0 * center_geo.lat.to_radians().cos().max(1e-6))),
926 lat: center_geo.lat,
927 };
928 let point_on_minor_geo = GeoPos {
929 lon: center_geo.lon,
930 lat: center_geo.lat + (radius_minor / 110_574.0),
931 };
932 let center_screen = projection.project(center_geo);
933 let point_on_major_screen = projection.project(point_on_major_geo);
934 let point_on_minor_screen = projection.project(point_on_minor_geo);
935 let radius_major_pixels = center_screen.distance(point_on_major_screen);
936 let radius_minor_pixels = center_screen.distance(point_on_minor_screen);
937
938 if radius_major_pixels <= 0.0 || radius_minor_pixels <= 0.0 {
939 return false;
940 }
941
942 let v = pos - center_screen;
943 let cos_rot = rotation.cos() as f32;
944 let sin_rot = rotation.sin() as f32;
945 let local_x = v.x * cos_rot + v.y * sin_rot;
946 let local_y = -v.x * sin_rot + v.y * cos_rot;
947
948 let rx_f64 = f64::from(radius_major_pixels);
949 let ry_f64 = f64::from(radius_minor_pixels);
950 (f64::from(local_x) / rx_f64).powi(2) + (f64::from(local_y) / ry_f64).powi(2) <= 1.0
951 }
952 AreaShape::Polygon(_) => {
953 let points = self.get_points(projection);
954 let screen_points: Vec<Pos2> =
955 points.iter().map(|p| projection.project(*p)).collect();
956 if screen_points.len() < 3 {
957 return false;
958 }
959 let flat_points: Vec<f64> = screen_points
960 .iter()
961 .flat_map(|p| [f64::from(p.x), f64::from(p.y)])
962 .collect();
963 if let Ok(indices) = earcutr::earcut(&flat_points, &[], 2) {
964 for chunk in indices.chunks_exact(3) {
965 let p1 = screen_points[chunk[0]];
966 let p2 = screen_points[chunk[1]];
967 let p3 = screen_points[chunk[2]];
968 if point_in_triangle(pos, p1, p2, p3) {
969 return true;
970 }
971 }
972 }
973 false
974 }
975 }
976 }
977}
978
979fn point_in_triangle(p: Pos2, a: Pos2, b: Pos2, c: Pos2) -> bool {
980 let d1 = sign(p, a, b);
981 let d2 = sign(p, b, c);
982 let d3 = sign(p, c, a);
983
984 let has_neg = (d1 < 0.0) || (d2 < 0.0) || (d3 < 0.0);
985 let has_pos = (d1 > 0.0) || (d2 > 0.0) || (d3 > 0.0);
986
987 !(has_neg && has_pos)
988}
989
990fn sign(p1: Pos2, p2: Pos2, p3: Pos2) -> f32 {
991 (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
992}
993
994fn generate_hatching_lines(screen_points: &[Pos2], spacing: f32, angle: f32) -> Vec<(Pos2, Pos2)> {
1002 if screen_points.len() < 3 || spacing <= 0.0 {
1003 return Vec::new();
1004 }
1005
1006 let dir = egui::vec2(angle.cos(), angle.sin());
1008 let perp = egui::vec2(-angle.sin(), angle.cos());
1009
1010 let mut min_perp = f32::MAX;
1012 let mut max_perp = f32::MIN;
1013 for p in screen_points {
1014 let d = p.to_vec2().dot(perp);
1015 min_perp = min_perp.min(d);
1016 max_perp = max_perp.max(d);
1017 }
1018
1019 let n = screen_points.len();
1020 let mut segments = Vec::new();
1021
1022 let mut offset = min_perp + spacing;
1024 while offset < max_perp {
1025 let line_origin = Pos2::ZERO + perp * offset;
1027
1028 let mut t_values: Vec<f32> = Vec::new();
1030 for i in 0..n {
1031 let a = screen_points[i];
1032 let b = screen_points[(i + 1) % n];
1033 let edge = b - a;
1034
1035 let denom = edge.x * dir.y - edge.y * dir.x;
1038 if denom.abs() < 1e-9 {
1039 continue; }
1041
1042 let diff = a - line_origin;
1043 let t_edge = -(diff.x * dir.y - diff.y * dir.x) / denom;
1044
1045 if (0.0..=1.0).contains(&t_edge) {
1046 let t_line = if dir.x.abs() > dir.y.abs() {
1048 (a.x - line_origin.x + t_edge * edge.x) / dir.x
1049 } else {
1050 (a.y - line_origin.y + t_edge * edge.y) / dir.y
1051 };
1052 t_values.push(t_line);
1053 }
1054 }
1055
1056 t_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1058
1059 for pair in t_values.chunks_exact(2) {
1061 let p1 = line_origin + dir * pair[0];
1062 let p2 = line_origin + dir * pair[1];
1063 segments.push((p1, p2));
1064 }
1065
1066 offset += spacing;
1067 }
1068
1069 segments
1070}
1071
1072impl Layer for AreaLayer {
1073 fn as_any(&self) -> &dyn Any {
1074 self
1075 }
1076
1077 fn as_any_mut(&mut self) -> &mut dyn Any {
1078 self
1079 }
1080
1081 fn opacity(&self) -> f32 {
1082 self.opacity
1083 }
1084
1085 fn set_opacity(&mut self, opacity: f32) {
1086 self.opacity = opacity;
1087 }
1088
1089 fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
1090 match self.mode {
1091 AreaMode::Disabled => {
1092 self.hovered_object = None;
1093 false
1094 }
1095 AreaMode::Modify => self.handle_modify_input(response, projection, None),
1096 AreaMode::ModifySelected => {
1097 if response.clicked()
1098 && let Some(pointer_pos) = response.interact_pointer_pos()
1099 {
1100 let clicked_area_idx =
1102 self.areas.iter().enumerate().rev().find_map(|(idx, area)| {
1103 if area.contains(pointer_pos, projection) {
1104 Some(idx)
1105 } else {
1106 None
1107 }
1108 });
1109
1110 if clicked_area_idx != self.selected_area {
1111 self.selected_area = clicked_area_idx;
1112 return true;
1113 }
1114 }
1115
1116 if let Some(selected_idx) = self.selected_area {
1117 self.handle_modify_input(response, projection, Some(selected_idx))
1118 } else {
1119 false
1120 }
1121 }
1122 }
1123 }
1124
1125 fn draw(&self, painter: &Painter, projection: &MapProjection) {
1126 for (area_idx, area) in self.areas.iter().enumerate() {
1127 let points = area.get_points(projection);
1128 let screen_points: Vec<Pos2> = points.iter().map(|p| projection.project(*p)).collect();
1129
1130 if screen_points.len() >= 3 {
1132 let is_selected =
1133 self.mode == AreaMode::ModifySelected && self.selected_area == Some(area_idx);
1134 let stroke = if is_selected {
1135 Stroke {
1136 width: area.stroke.width * 2.0,
1137 color: area.stroke.color.gamma_multiply(self.opacity),
1138 }
1139 } else {
1140 Stroke {
1141 color: area.stroke.color.gamma_multiply(self.opacity),
1142 ..area.stroke
1143 }
1144 };
1145
1146 let path_shape = Shape::Path(egui::epaint::PathShape {
1148 points: screen_points.clone(),
1149 closed: true,
1150 fill: Color32::TRANSPARENT,
1151 stroke: stroke.into(),
1152 });
1153 painter.add(path_shape);
1154
1155 match area.fill_type {
1156 FillType::None => { }
1157 FillType::Solid => {
1158 let flat_points: Vec<f64> = screen_points
1160 .iter()
1161 .flat_map(|p| [f64::from(p.x), f64::from(p.y)])
1162 .collect();
1163 match earcutr::earcut(&flat_points, &[], 2) {
1164 Ok(indices) => {
1165 let mesh = Mesh {
1166 vertices: screen_points
1167 .iter()
1168 .map(|p| egui::epaint::Vertex {
1169 pos: *p,
1170 uv: Default::default(),
1171 color: area.fill.gamma_multiply(self.opacity),
1172 })
1173 .collect(),
1174 indices: indices.into_iter().map(|i| i as u32).collect(),
1175 ..Default::default()
1176 };
1177 painter.add(Shape::Mesh(mesh.into()));
1178 }
1179 Err(e) => {
1180 warn!("Failed to triangulate area: {e:?}");
1181 }
1182 }
1183 }
1184 FillType::Hatching => {
1185 let segments = generate_hatching_lines(
1186 &screen_points,
1187 8.0,
1188 std::f32::consts::FRAC_PI_4,
1189 );
1190 for (a, b) in segments {
1191 painter.line_segment(
1192 [a, b],
1193 Stroke {
1194 color: area.stroke.color.gamma_multiply(self.opacity),
1195 ..area.stroke
1196 },
1197 );
1198 }
1199 }
1200 }
1201 } else {
1202 warn!("Invalid amount of points in area. {area:?}");
1203 }
1204
1205 let show_nodes = self.mode == AreaMode::Modify
1207 || (self.mode == AreaMode::ModifySelected && self.selected_area == Some(area_idx));
1208 if show_nodes {
1209 match &area.shape {
1210 AreaShape::Polygon(_) => {
1211 for (node_idx, point) in screen_points.iter().enumerate() {
1212 painter.circle_filled(
1213 *point,
1214 self.node_radius,
1215 self.node_fill.gamma_multiply(self.opacity),
1216 );
1217
1218 if let Some(DraggedObject::PolygonNode {
1219 area_index,
1220 node_index,
1221 }) = self.hovered_object
1222 && area_index == area_idx
1223 && node_index == node_idx
1224 {
1225 painter.circle_stroke(
1226 *point,
1227 self.node_radius * 3.0,
1228 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1229 );
1230 }
1231 }
1232 }
1233 AreaShape::Circle {
1234 center,
1235 radius,
1236 points: _,
1237 } => {
1238 let center_screen = projection.project(*center);
1239
1240 let point_on_circle_geo = GeoPos {
1242 lon: center.lon
1243 + (radius / (111_320.0 * center.lat.to_radians().cos().max(1e-6))),
1244 lat: center.lat,
1245 };
1246 let point_on_circle_screen = projection.project(point_on_circle_geo);
1247 let radius_pixels = center_screen.distance(point_on_circle_screen);
1248
1249 painter.circle_filled(
1250 center_screen,
1251 self.node_radius,
1252 self.node_fill.gamma_multiply(self.opacity),
1253 );
1254
1255 if let Some(DraggedObject::CircleCenter { area_index }) =
1256 self.hovered_object
1257 && area_index == area_idx
1258 {
1259 painter.circle_stroke(
1260 center_screen,
1261 self.node_radius * 3.0,
1262 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1263 );
1264 }
1265
1266 let radius_handle_pos = center_screen + egui::vec2(radius_pixels, 0.0);
1267 painter.circle_filled(
1268 radius_handle_pos,
1269 self.node_radius,
1270 self.node_fill.gamma_multiply(self.opacity),
1271 );
1272
1273 if let Some(DraggedObject::CircleRadius { area_index }) =
1274 self.hovered_object
1275 && area_index == area_idx
1276 {
1277 painter.circle_stroke(
1278 radius_handle_pos,
1279 self.node_radius * 2.0,
1280 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1281 );
1282 }
1283 }
1284 AreaShape::Ellipse {
1285 center,
1286 radius_major,
1287 radius_minor,
1288 rotation,
1289 points: _,
1290 } => {
1291 let center_screen = projection.project(*center);
1292
1293 let point_on_major_geo = GeoPos {
1294 lon: center.lon
1295 + (radius_major
1296 / (111_320.0 * center.lat.to_radians().cos().max(1e-6))),
1297 lat: center.lat,
1298 };
1299 let point_on_minor_geo = GeoPos {
1300 lon: center.lon,
1301 lat: center.lat + (radius_minor / 110_574.0),
1302 };
1303 let point_on_major_screen = projection.project(point_on_major_geo);
1304 let point_on_minor_screen = projection.project(point_on_minor_geo);
1305 let radius_major_pixels = center_screen.distance(point_on_major_screen);
1306 let radius_minor_pixels = center_screen.distance(point_on_minor_screen);
1307
1308 let cos_rot = rotation.cos() as f32;
1309 let sin_rot = rotation.sin() as f32;
1310
1311 let major_handle_pos = center_screen
1312 + egui::vec2(
1313 radius_major_pixels * cos_rot,
1314 radius_major_pixels * sin_rot,
1315 );
1316 let minor_handle_pos = center_screen
1317 + egui::vec2(
1318 -radius_minor_pixels * sin_rot,
1319 radius_minor_pixels * cos_rot,
1320 );
1321 let rotation_handle_pos = center_screen
1322 + egui::vec2(
1323 (radius_major_pixels + 20.0) * cos_rot,
1324 (radius_major_pixels + 20.0) * sin_rot,
1325 );
1326
1327 painter.line_segment(
1329 [major_handle_pos, rotation_handle_pos],
1330 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1331 );
1332
1333 painter.circle_filled(
1335 center_screen,
1336 self.node_radius,
1337 self.node_fill.gamma_multiply(self.opacity),
1338 );
1339 if let Some(DraggedObject::EllipseCenter { area_index }) =
1340 self.hovered_object
1341 && area_index == area_idx
1342 {
1343 painter.circle_stroke(
1344 center_screen,
1345 self.node_radius * 3.0,
1346 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1347 );
1348 }
1349
1350 painter.circle_filled(
1352 major_handle_pos,
1353 self.node_radius,
1354 self.node_fill.gamma_multiply(self.opacity),
1355 );
1356 if let Some(DraggedObject::EllipseMajorRadius { area_index }) =
1357 self.hovered_object
1358 && area_index == area_idx
1359 {
1360 painter.circle_stroke(
1361 major_handle_pos,
1362 self.node_radius * 2.0,
1363 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1364 );
1365 }
1366
1367 painter.circle_filled(
1369 minor_handle_pos,
1370 self.node_radius,
1371 self.node_fill.gamma_multiply(self.opacity),
1372 );
1373 if let Some(DraggedObject::EllipseMinorRadius { area_index }) =
1374 self.hovered_object
1375 && area_index == area_idx
1376 {
1377 painter.circle_stroke(
1378 minor_handle_pos,
1379 self.node_radius * 2.0,
1380 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1381 );
1382 }
1383
1384 painter.circle_filled(
1386 rotation_handle_pos,
1387 self.node_radius,
1388 self.node_fill.gamma_multiply(self.opacity),
1389 );
1390 if let Some(DraggedObject::EllipseRotation { area_index }) =
1391 self.hovered_object
1392 && area_index == area_idx
1393 {
1394 painter.circle_stroke(
1395 rotation_handle_pos,
1396 self.node_radius * 2.0,
1397 Stroke::new(1.0, self.node_fill.gamma_multiply(self.opacity)),
1398 );
1399 }
1400 }
1401 }
1402 }
1403 }
1404 }
1405}
1406
1407#[cfg(test)]
1408mod tests {
1409 use super::*;
1410 use crate::projection::MapProjection;
1411 use egui::{Rect, pos2, vec2};
1412
1413 fn dummy_projection() -> MapProjection {
1415 MapProjection::new(
1416 10, (0.0, 0.0).into(), Rect::from_min_size(Pos2::ZERO, vec2(1000.0, 1000.0)),
1419 )
1420 }
1421
1422 #[test]
1423 fn area_layer_new() {
1424 let layer = AreaLayer::default();
1425 assert_eq!(layer.mode, AreaMode::Disabled);
1426 assert!(layer.areas.is_empty());
1427 assert_eq!(layer.node_radius, 5.0);
1428 }
1429
1430 #[test]
1431 fn area_layer_add_area() {
1432 let mut layer = AreaLayer::default();
1433 assert_eq!(layer.areas.len(), 0);
1434
1435 layer.add_area(Area {
1436 shape: AreaShape::Polygon(vec![
1437 (0.0, 0.0).into(),
1438 (1.0, 0.0).into(),
1439 (0.0, 1.0).into(),
1440 ]),
1441 stroke: Default::default(),
1442 fill: Default::default(),
1443 fill_type: Default::default(),
1444 });
1445
1446 assert_eq!(layer.areas.len(), 1);
1447 }
1448
1449 #[test]
1450 fn circle_get_points_with_fixed_number() {
1451 let projection = dummy_projection();
1452 let area = Area {
1453 shape: AreaShape::Circle {
1454 center: (0.0, 0.0).into(),
1455 radius: 1000.0,
1456 points: Some(16),
1457 },
1458 stroke: Default::default(),
1459 fill: Default::default(),
1460 fill_type: Default::default(),
1461 };
1462
1463 let points = area.get_points(&projection);
1464 assert_eq!(points.len(), 16);
1465 }
1466
1467 #[test]
1468 fn find_object_at_empty() {
1469 let layer = AreaLayer::default();
1470 let projection = dummy_projection();
1471 let position = pos2(100.0, 100.0);
1472
1473 assert!(layer.find_object_at(position, &projection, None).is_none());
1474 }
1475
1476 #[test]
1477 fn find_object_at_polygon_node() {
1478 let projection = dummy_projection();
1479 let mut layer = AreaLayer::default();
1480 let geo_pos = projection.unproject(pos2(100.0, 100.0));
1481
1482 layer.add_area(Area {
1483 shape: AreaShape::Polygon(vec![geo_pos]),
1484 stroke: Default::default(),
1485 fill: Default::default(),
1486 fill_type: Default::default(),
1487 });
1488
1489 let found = layer.find_object_at(pos2(100.0, 100.0), &projection, None);
1491 assert!(matches!(
1492 found,
1493 Some(DraggedObject::PolygonNode {
1494 area_index: 0,
1495 node_index: 0
1496 })
1497 ));
1498
1499 let found_nearby = layer.find_object_at(pos2(101.0, 101.0), &projection, None);
1501 assert!(matches!(
1502 found_nearby,
1503 Some(DraggedObject::PolygonNode {
1504 area_index: 0,
1505 node_index: 0
1506 })
1507 ));
1508
1509 let not_found = layer.find_object_at(pos2(200.0, 200.0), &projection, None);
1511 assert!(not_found.is_none());
1512 }
1513
1514 #[test]
1515 fn area_layer_serde() {
1516 let mut layer = AreaLayer::default();
1517 layer.add_area(Area {
1518 shape: AreaShape::Polygon(vec![(0.0, 0.0).into()]),
1519 stroke: Stroke::new(1.0, Color32::RED),
1520 fill: Color32::BLUE,
1521 fill_type: Default::default(),
1522 });
1523
1524 let json = serde_json::to_string(&layer).unwrap();
1525 let deserialized: AreaLayer = serde_json::from_str(&json).unwrap();
1526
1527 assert_eq!(deserialized.areas.len(), 1);
1528 assert_eq!(deserialized.mode, AreaMode::Disabled); }
1530
1531 #[test]
1532 fn test_can_triangulate_valid() {
1533 let projection = dummy_projection();
1534 let area = Area {
1535 shape: AreaShape::Polygon(vec![
1536 (0.0, 0.0).into(),
1537 (10.0, 0.0).into(),
1538 (0.0, 10.0).into(),
1539 ]),
1540 stroke: Default::default(),
1541 fill: Default::default(),
1542 fill_type: Default::default(),
1543 };
1544
1545 assert!(area.can_triangulate(&projection));
1546 }
1547
1548 #[test]
1549 fn test_can_triangulate_insufficient_points() {
1550 let projection = dummy_projection();
1551 let area = Area {
1552 shape: AreaShape::Polygon(vec![(0.0, 0.0).into(), (10.0, 0.0).into()]),
1553 stroke: Default::default(),
1554 fill: Default::default(),
1555 fill_type: Default::default(),
1556 };
1557
1558 assert!(area.can_triangulate(&projection));
1561 }
1562
1563 #[cfg(feature = "geojson")]
1564 mod geojson_tests {
1565 use super::*;
1566
1567 #[test]
1568 fn area_layer_geojson_polygon() {
1569 let mut layer = AreaLayer::default();
1570 layer.add_area(Area {
1571 shape: AreaShape::Polygon(vec![
1572 (10.0, 20.0).into(),
1573 (30.0, 40.0).into(),
1574 (50.0, 60.0).into(),
1575 ]),
1576 stroke: Stroke::new(2.0, Color32::from_rgb(0, 0, 255)),
1577 fill: Color32::from_rgba_unmultiplied(255, 0, 0, 128),
1578 fill_type: Default::default(),
1579 });
1580
1581 let geojson_str = layer.to_geojson_str().unwrap();
1582
1583 let mut new_layer = AreaLayer::default();
1584 new_layer.from_geojson_str(&geojson_str).unwrap();
1585
1586 assert_eq!(new_layer.areas.len(), 1);
1587 assert_eq!(layer.areas[0], new_layer.areas[0]);
1588 }
1589
1590 #[test]
1591 fn area_layer_geojson_circle() {
1592 let mut layer = AreaLayer::default();
1593 layer.add_area(Area {
1594 shape: AreaShape::Circle {
1595 center: (10.0, 20.0).into(),
1596 radius: 1000.0,
1597 points: Some(32),
1598 },
1599 stroke: Default::default(),
1600 fill: Default::default(),
1601 fill_type: Default::default(),
1602 });
1603
1604 let geojson_str = layer.to_geojson_str().unwrap();
1605 let mut new_layer = AreaLayer::default();
1606 new_layer.from_geojson_str(&geojson_str).unwrap();
1607
1608 assert_eq!(new_layer.areas.len(), 1);
1609 assert_eq!(layer.areas[0].shape, new_layer.areas[0].shape);
1610 }
1611
1612 #[test]
1613 fn area_layer_geojson_ellipse() {
1614 let mut layer = AreaLayer::default();
1615 layer.add_area(Area {
1616 shape: AreaShape::Ellipse {
1617 center: (10.0, 20.0).into(),
1618 radius_major: 2000.0,
1619 radius_minor: 1000.0,
1620 rotation: 0.78,
1621 points: Some(32),
1622 },
1623 stroke: Default::default(),
1624 fill: Default::default(),
1625 fill_type: Default::default(),
1626 });
1627
1628 let geojson_str = layer.to_geojson_str().unwrap();
1629 let mut new_layer = AreaLayer::default();
1630 new_layer.from_geojson_str(&geojson_str).unwrap();
1631
1632 assert_eq!(new_layer.areas.len(), 1);
1633 assert_eq!(layer.areas[0].shape, new_layer.areas[0].shape);
1634 }
1635 }
1636
1637 #[test]
1638 fn ellipse_get_points_with_fixed_number() {
1639 let projection = dummy_projection();
1640 let area = Area {
1641 shape: AreaShape::Ellipse {
1642 center: (0.0, 0.0).into(),
1643 radius_major: 2000.0,
1644 radius_minor: 1000.0,
1645 rotation: 0.5,
1646 points: Some(24),
1647 },
1648 stroke: Default::default(),
1649 fill: Default::default(),
1650 fill_type: Default::default(),
1651 };
1652
1653 let points = area.get_points(&projection);
1654 assert_eq!(points.len(), 24);
1655 }
1656
1657 #[test]
1658 fn ellipse_containment() {
1659 let projection = dummy_projection();
1660 let area = Area {
1661 shape: AreaShape::Ellipse {
1662 center: (0.0, 0.0).into(),
1663 radius_major: 2000.0,
1664 radius_minor: 1000.0,
1665 rotation: 0.0,
1666 points: None,
1667 },
1668 stroke: Default::default(),
1669 fill: Default::default(),
1670 fill_type: Default::default(),
1671 };
1672
1673 assert!(area.contains(projection.project((0.0, 0.0).into()), &projection));
1675
1676 let point_inside = GeoPos {
1678 lon: 0.005,
1679 lat: 0.0,
1680 };
1681 assert!(area.contains(projection.project(point_inside), &projection));
1682
1683 let point_outside = GeoPos { lon: 0.5, lat: 0.0 };
1685 assert!(!area.contains(projection.project(point_outside), &projection));
1686 }
1687
1688 #[test]
1689 fn find_node_at_on_segment() {
1690 let projection = dummy_projection();
1691 let mut layer = AreaLayer::default();
1692
1693 let p1 = projection.unproject(pos2(100.0, 100.0));
1694 let p2 = projection.unproject(pos2(200.0, 100.0));
1695
1696 layer.add_area(Area {
1697 shape: AreaShape::Polygon(vec![p1, p2, projection.unproject(pos2(150.0, 200.0))]), stroke: Default::default(),
1699 fill: Default::default(),
1700 fill_type: Default::default(),
1701 });
1702
1703 let click_pos = pos2(150.0, 100.0);
1705
1706 assert!(layer.find_node_at(click_pos, &projection, None).is_none());
1708
1709 let segment = layer.find_line_segment_at(click_pos, &projection, None);
1711 assert!(segment.is_some());
1712 assert_eq!(segment.unwrap().0, 0); assert_eq!(segment.unwrap().1, 0);
1714 }
1715}