use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WorldId(pub u64);
impl WorldId {
pub const UNASSIGNED: WorldId = WorldId(0);
#[must_use]
pub fn is_unassigned(&self) -> bool {
self.0 == 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct EnuPoint {
pub east_m: f64,
pub north_m: f64,
pub up_m: f64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "shape", rename_all = "snake_case")]
pub enum ZoneBoundsEnu {
Rectangle {
min_e: f64,
min_n: f64,
max_e: f64,
max_n: f64,
},
Circle {
center_e: f64,
center_n: f64,
radius_m: f64,
},
Polygon {
vertices: Vec<(f64, f64)>,
},
}
impl ZoneBoundsEnu {
#[must_use]
pub fn contains(&self, p: &EnuPoint) -> bool {
match self {
Self::Rectangle { min_e, min_n, max_e, max_n } => {
p.east_m >= *min_e && p.east_m <= *max_e && p.north_m >= *min_n && p.north_m <= *max_n
}
Self::Circle { center_e, center_n, radius_m } => {
let de = p.east_m - center_e;
let dn = p.north_m - center_n;
(de * de + dn * dn).sqrt() <= *radius_m
}
Self::Polygon { vertices } => point_in_polygon(p.east_m, p.north_m, vertices),
}
}
}
fn point_in_polygon(px: f64, py: f64, verts: &[(f64, f64)]) -> bool {
if verts.len() < 3 {
return false;
}
let mut inside = false;
let mut j = verts.len() - 1;
for i in 0..verts.len() {
let (xi, yi) = verts[i];
let (xj, yj) = verts[j];
let intersect = ((yi > py) != (yj > py))
&& (px < (xj - xi) * (py - yi) / (yj - yi) + xi);
if intersect {
inside = !inside;
}
j = i;
}
inside
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SensorModality {
WifiCsi,
MmWave,
Uwb,
Presence,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnchorKind {
Reflector,
Furniture,
UwbBeacon,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SemanticProvenance {
pub evidence: Vec<String>,
pub model_version: String,
pub calibration_version: String,
pub privacy_decision: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WorldNode {
Room {
id: WorldId,
area_id: Option<String>,
name: String,
bounds_enu: ZoneBoundsEnu,
floor: i16,
},
Zone {
id: WorldId,
parent_room: WorldId,
name: String,
bounds_enu: ZoneBoundsEnu,
},
Wall {
id: WorldId,
a: EnuPoint,
b: EnuPoint,
rf_attenuation_db: f32,
},
Doorway {
id: WorldId,
center: EnuPoint,
width_m: f32,
},
Sensor {
id: WorldId,
device_id: String,
position: EnuPoint,
modality: SensorModality,
},
RfLink {
id: WorldId,
tx: WorldId,
rx: WorldId,
link_group_id: Option<String>,
center_freq_mhz: u32,
},
PersonTrack {
id: WorldId,
track_id: u64,
last_position: EnuPoint,
reid_embedding_ref: Option<String>,
},
ObjectAnchor {
id: WorldId,
position: EnuPoint,
anchor_kind: AnchorKind,
confidence: f32,
},
Event {
id: WorldId,
event_type: String,
at_unix_ms: i64,
located_in: Option<WorldId>,
},
SemanticState {
id: WorldId,
statement: String,
confidence: f32,
provenance: SemanticProvenance,
valid_from_unix_ms: i64,
},
}
impl WorldNode {
#[must_use]
pub fn id(&self) -> WorldId {
match self {
Self::Room { id, .. }
| Self::Zone { id, .. }
| Self::Wall { id, .. }
| Self::Doorway { id, .. }
| Self::Sensor { id, .. }
| Self::RfLink { id, .. }
| Self::PersonTrack { id, .. }
| Self::ObjectAnchor { id, .. }
| Self::Event { id, .. }
| Self::SemanticState { id, .. } => *id,
}
}
pub(crate) fn set_id(&mut self, new: WorldId) {
match self {
Self::Room { id, .. }
| Self::Zone { id, .. }
| Self::Wall { id, .. }
| Self::Doorway { id, .. }
| Self::Sensor { id, .. }
| Self::RfLink { id, .. }
| Self::PersonTrack { id, .. }
| Self::ObjectAnchor { id, .. }
| Self::Event { id, .. }
| Self::SemanticState { id, .. } => *id = new,
}
}
#[must_use]
pub fn kind(&self) -> &'static str {
match self {
Self::Room { .. } => "room",
Self::Zone { .. } => "zone",
Self::Wall { .. } => "wall",
Self::Doorway { .. } => "doorway",
Self::Sensor { .. } => "sensor",
Self::RfLink { .. } => "rf_link",
Self::PersonTrack { .. } => "person_track",
Self::ObjectAnchor { .. } => "object_anchor",
Self::Event { .. } => "event",
Self::SemanticState { .. } => "semantic_state",
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "rel", rename_all = "snake_case")]
pub enum WorldEdge {
Observes {
quality: f32,
last_seen_unix_ms: i64,
},
LocatedIn {
since_unix_ms: i64,
},
AdjacentTo {
via_doorway: WorldId,
},
Supports {
strength: f32,
},
Contradicts {
magnitude: f32,
flag: String,
},
DerivedFrom {
evidence: String,
},
PrivacyLimitedBy {
mode: String,
action: String,
allowed: bool,
},
}
impl WorldEdge {
#[must_use]
pub fn rel(&self) -> &'static str {
match self {
Self::Observes { .. } => "observes",
Self::LocatedIn { .. } => "located_in",
Self::AdjacentTo { .. } => "adjacent_to",
Self::Supports { .. } => "supports",
Self::Contradicts { .. } => "contradicts",
Self::DerivedFrom { .. } => "derived_from",
Self::PrivacyLimitedBy { .. } => "privacy_limited_by",
}
}
}