1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5pub struct GeoPosition {
6 pub lat: f64,
8
9 pub lon: f64,
11
12 #[serde(skip_serializing_if = "Option::is_none")]
14 pub alt: Option<f64>,
15}
16
17impl GeoPosition {
18 pub fn new(lat: f64, lon: f64) -> Self {
19 Self {
20 lat,
21 lon,
22 alt: None,
23 }
24 }
25
26 pub fn with_altitude(mut self, alt: f64) -> Self {
27 self.alt = Some(alt);
28 self
29 }
30
31 pub fn distance_to(&self, other: &GeoPosition) -> f64 {
33 let r = 6371000.0; let lat1 = self.lat.to_radians();
35 let lat2 = other.lat.to_radians();
36 let delta_lat = (other.lat - self.lat).to_radians();
37 let delta_lon = (other.lon - self.lon).to_radians();
38
39 let a = (delta_lat / 2.0).sin().powi(2)
40 + lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
41 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
42
43 r * c
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
49pub enum HierarchyLevel {
50 Platform = 0,
52 Squad = 1,
54 Platoon = 2,
56 Company = 3,
58}
59
60impl HierarchyLevel {
61 pub fn parent(&self) -> Option<HierarchyLevel> {
63 match self {
64 HierarchyLevel::Platform => Some(HierarchyLevel::Squad),
65 HierarchyLevel::Squad => Some(HierarchyLevel::Platoon),
66 HierarchyLevel::Platoon => Some(HierarchyLevel::Company),
67 HierarchyLevel::Company => None,
68 }
69 }
70
71 pub fn child(&self) -> Option<HierarchyLevel> {
73 match self {
74 HierarchyLevel::Platform => None,
75 HierarchyLevel::Squad => Some(HierarchyLevel::Platform),
76 HierarchyLevel::Platoon => Some(HierarchyLevel::Squad),
77 HierarchyLevel::Company => Some(HierarchyLevel::Platoon),
78 }
79 }
80
81 pub fn can_be_parent_of(&self, child: &HierarchyLevel) -> bool {
84 self > child
85 }
86}
87
88impl std::fmt::Display for HierarchyLevel {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 HierarchyLevel::Platform => write!(f, "Platform"),
92 HierarchyLevel::Squad => write!(f, "Squad"),
93 HierarchyLevel::Platoon => write!(f, "Platoon"),
94 HierarchyLevel::Company => write!(f, "Company"),
95 }
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct GeographicBeacon {
102 pub node_id: String,
104
105 pub position: GeoPosition,
107
108 pub geohash: String,
110
111 pub hierarchy_level: HierarchyLevel,
113
114 #[serde(default)]
116 pub capabilities: Vec<String>,
117
118 pub operational: bool,
120
121 pub timestamp: u64,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub mobility: Option<super::config::NodeMobility>,
127
128 #[serde(default)]
130 pub can_parent: bool,
131
132 #[serde(default)]
134 pub parent_priority: u8,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub resources: Option<super::config::NodeResources>,
139
140 #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
142 pub metadata: std::collections::HashMap<String, String>,
143}
144
145impl GeographicBeacon {
146 pub fn new(node_id: String, position: GeoPosition, hierarchy_level: HierarchyLevel) -> Self {
148 use geohash::encode;
149 use geohash::Coord;
150
151 let geohash = encode(
152 Coord {
153 x: position.lon,
154 y: position.lat,
155 },
156 7,
157 )
158 .unwrap_or_else(|_| String::from("invalid"));
159
160 let timestamp = std::time::SystemTime::now()
161 .duration_since(std::time::UNIX_EPOCH)
162 .map(|d| d.as_secs())
163 .unwrap_or(0);
164
165 Self {
166 node_id,
167 position,
168 geohash,
169 hierarchy_level,
170 capabilities: Vec::new(),
171 operational: true,
172 timestamp,
173 mobility: None,
174 can_parent: false,
175 parent_priority: 0,
176 resources: None,
177 metadata: std::collections::HashMap::new(),
178 }
179 }
180
181 pub fn update_timestamp(&mut self) {
183 self.timestamp = std::time::SystemTime::now()
184 .duration_since(std::time::UNIX_EPOCH)
185 .map(|d| d.as_secs())
186 .unwrap_or(0);
187 }
188
189 pub fn add_capability(&mut self, capability: String) {
191 if !self.capabilities.contains(&capability) {
192 self.capabilities.push(capability);
193 }
194 }
195
196 pub fn has_capability(&self, capability: &str) -> bool {
198 self.capabilities.iter().any(|c| c == capability)
199 }
200
201 pub fn age_seconds(&self) -> u64 {
203 std::time::SystemTime::now()
204 .duration_since(std::time::UNIX_EPOCH)
205 .map(|d| d.as_secs())
206 .unwrap_or(0)
207 .saturating_sub(self.timestamp)
208 }
209
210 pub fn is_expired(&self, ttl_seconds: u64) -> bool {
212 self.age_seconds() > ttl_seconds
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_geo_position_distance() {
222 let pos1 = GeoPosition::new(37.7749, -122.4194); let pos2 = GeoPosition::new(34.0522, -118.2437); let distance = pos1.distance_to(&pos2);
227
228 assert!(distance > 550_000.0 && distance < 570_000.0);
230 }
231
232 #[test]
233 fn test_hierarchy_level_parent_child() {
234 assert_eq!(
235 HierarchyLevel::Platform.parent(),
236 Some(HierarchyLevel::Squad)
237 );
238 assert_eq!(
239 HierarchyLevel::Squad.parent(),
240 Some(HierarchyLevel::Platoon)
241 );
242 assert_eq!(
243 HierarchyLevel::Platoon.parent(),
244 Some(HierarchyLevel::Company)
245 );
246 assert_eq!(HierarchyLevel::Company.parent(), None);
247
248 assert_eq!(HierarchyLevel::Platform.child(), None);
249 assert_eq!(
250 HierarchyLevel::Squad.child(),
251 Some(HierarchyLevel::Platform)
252 );
253 assert_eq!(HierarchyLevel::Platoon.child(), Some(HierarchyLevel::Squad));
254 assert_eq!(
255 HierarchyLevel::Company.child(),
256 Some(HierarchyLevel::Platoon)
257 );
258 }
259
260 #[test]
261 fn test_geographic_beacon_creation() {
262 let position = GeoPosition::new(37.7749, -122.4194);
263 let beacon = GeographicBeacon::new(
264 "test-node-1".to_string(),
265 position,
266 HierarchyLevel::Platform,
267 );
268
269 assert_eq!(beacon.node_id, "test-node-1");
270 assert_eq!(beacon.position.lat, 37.7749);
271 assert_eq!(beacon.hierarchy_level, HierarchyLevel::Platform);
272 assert!(beacon.operational);
273 assert!(!beacon.geohash.is_empty());
274 }
275
276 #[test]
277 fn test_beacon_capabilities() {
278 let position = GeoPosition::new(37.7749, -122.4194);
279 let mut beacon =
280 GeographicBeacon::new("test-node".to_string(), position, HierarchyLevel::Squad);
281
282 beacon.add_capability("ai-inference".to_string());
283 beacon.add_capability("sensor-fusion".to_string());
284
285 assert!(beacon.has_capability("ai-inference"));
286 assert!(beacon.has_capability("sensor-fusion"));
287 assert!(!beacon.has_capability("non-existent"));
288 }
289
290 #[test]
291 fn test_beacon_serialization() {
292 let position = GeoPosition::new(37.7749, -122.4194);
293 let beacon =
294 GeographicBeacon::new("test-node".to_string(), position, HierarchyLevel::Platform);
295
296 let json = serde_json::to_string(&beacon).unwrap();
298 assert!(json.contains("test-node"));
299 assert!(json.contains("37.7749"));
300
301 let deserialized: GeographicBeacon = serde_json::from_str(&json).unwrap();
303 assert_eq!(deserialized.node_id, beacon.node_id);
304 assert_eq!(deserialized.position.lat, beacon.position.lat);
305 }
306}