maps_engine_rust/
geofence.rs1use crate::geo::{haversine, LonLat};
4
5#[derive(Debug, Clone)]
7pub struct PolygonFence {
8 pub name: String,
9 pub vertices: Vec<LonLat>,
11}
12
13impl PolygonFence {
14 pub fn new(name: impl Into<String>, vertices: Vec<LonLat>) -> Self {
15 PolygonFence {
16 name: name.into(),
17 vertices,
18 }
19 }
20
21 pub fn contains(&self, p: LonLat) -> bool {
23 let n = self.vertices.len();
24 if n < 3 {
25 return false;
26 }
27 let mut inside = false;
28 let mut j = n - 1;
29 for i in 0..n {
30 let vi = self.vertices[i];
31 let vj = self.vertices[j];
32 if (vi.lat > p.lat) != (vj.lat > p.lat)
33 && p.lon < (vj.lon - vi.lon) * (p.lat - vi.lat) / (vj.lat - vi.lat) + vi.lon
34 {
35 inside = !inside;
36 }
37 j = i;
38 }
39 inside
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct CircleFence {
46 pub name: String,
47 pub center: LonLat,
48 pub radius_m: f64,
49}
50
51impl CircleFence {
52 pub fn new(name: impl Into<String>, center: LonLat, radius_m: f64) -> Self {
53 CircleFence {
54 name: name.into(),
55 center,
56 radius_m,
57 }
58 }
59
60 pub fn contains(&self, p: LonLat) -> bool {
61 haversine(self.center, p) <= self.radius_m
62 }
63}
64
65#[derive(Debug, Clone)]
67pub enum Geofence {
68 Polygon(PolygonFence),
69 Circle(CircleFence),
70}
71
72impl Geofence {
73 pub fn name(&self) -> &str {
74 match self {
75 Geofence::Polygon(f) => &f.name,
76 Geofence::Circle(f) => &f.name,
77 }
78 }
79
80 pub fn contains(&self, p: LonLat) -> bool {
81 match self {
82 Geofence::Polygon(f) => f.contains(p),
83 Geofence::Circle(f) => f.contains(p),
84 }
85 }
86}
87
88pub fn fences_at(fences: &[Geofence], p: LonLat) -> Vec<&str> {
90 fences
91 .iter()
92 .filter(|f| f.contains(p))
93 .map(|f| f.name())
94 .collect()
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn square() -> PolygonFence {
102 PolygonFence::new(
103 "sq",
104 vec![
105 LonLat::new(0.0, 0.0),
106 LonLat::new(1.0, 0.0),
107 LonLat::new(1.0, 1.0),
108 LonLat::new(0.0, 1.0),
109 ],
110 )
111 }
112
113 #[test]
114 fn point_inside_square() {
115 assert!(square().contains(LonLat::new(0.5, 0.5)));
116 }
117
118 #[test]
119 fn point_outside_square() {
120 assert!(!square().contains(LonLat::new(2.0, 0.5)));
121 assert!(!square().contains(LonLat::new(0.5, -1.0)));
122 }
123
124 #[test]
125 fn degenerate_polygon_never_contains() {
126 let f = PolygonFence::new("line", vec![LonLat::new(0.0, 0.0), LonLat::new(1.0, 1.0)]);
127 assert!(!f.contains(LonLat::new(0.5, 0.5)));
128 }
129
130 #[test]
131 fn circle_fence_radius() {
132 let c = LonLat::new(105.85, 21.02);
133 let f = CircleFence::new("home", c, 500.0);
134 assert!(f.contains(c));
135 assert!(!f.contains(LonLat::new(105.90, 21.02)));
136 }
137
138 #[test]
139 fn fences_at_lists_matches() {
140 let fences = vec![
141 Geofence::Polygon(square()),
142 Geofence::Circle(CircleFence::new("far", LonLat::new(10.0, 10.0), 100.0)),
143 ];
144 let hits = fences_at(&fences, LonLat::new(0.5, 0.5));
145 assert_eq!(hits, vec!["sq"]);
146 }
147}