mirror of
https://github.com/ruvnet/RuView
synced 2026-08-11 20:41:44 +00:00
004a63e82d
- Upgrade openssl to 0.10.78 (CVE-2026-41676), jsonwebtoken to 9.4 - Suppress unmaintained-only/no-CVE advisories in .cargo/audit.toml with per-entry rationale - Fix all `cargo clippy --all-targets -- -D warnings` errors across 35 crates: derivable_impls, needless_range_loop, map_or→is_some_and/ is_none_or, await_holding_lock (drop MutexGuard before .await), ptr_arg (&mut Vec→&mut [T]), useless_conversion, approximate_constant (2.718→E, 3.14→PI), field_reassign_with_default, manual_inspect, useless_vec, lines_filter_map_ok, print_literal, dead_code - Apply `cargo fmt --all` - Pre-existing test failure in wifi-densepose-signal (test_estimate_occupancy_noise_only) is not introduced by this PR
379 lines
11 KiB
Rust
379 lines
11 KiB
Rust
//! 3D coordinate system and location types for survivor localization.
|
|
|
|
/// 3D coordinates representing survivor position
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct Coordinates3D {
|
|
/// East-West offset from reference point (meters)
|
|
pub x: f64,
|
|
/// North-South offset from reference point (meters)
|
|
pub y: f64,
|
|
/// Vertical offset - negative is below surface (meters)
|
|
pub z: f64,
|
|
/// Uncertainty bounds for this position
|
|
pub uncertainty: LocationUncertainty,
|
|
}
|
|
|
|
impl Coordinates3D {
|
|
/// Create new coordinates with uncertainty
|
|
pub fn new(x: f64, y: f64, z: f64, uncertainty: LocationUncertainty) -> Self {
|
|
Self {
|
|
x,
|
|
y,
|
|
z,
|
|
uncertainty,
|
|
}
|
|
}
|
|
|
|
/// Create coordinates with default uncertainty
|
|
pub fn with_default_uncertainty(x: f64, y: f64, z: f64) -> Self {
|
|
Self {
|
|
x,
|
|
y,
|
|
z,
|
|
uncertainty: LocationUncertainty::default(),
|
|
}
|
|
}
|
|
|
|
/// Calculate 3D distance to another point
|
|
pub fn distance_to(&self, other: &Coordinates3D) -> f64 {
|
|
let dx = self.x - other.x;
|
|
let dy = self.y - other.y;
|
|
let dz = self.z - other.z;
|
|
(dx * dx + dy * dy + dz * dz).sqrt()
|
|
}
|
|
|
|
/// Calculate horizontal (2D) distance only
|
|
pub fn horizontal_distance_to(&self, other: &Coordinates3D) -> f64 {
|
|
let dx = self.x - other.x;
|
|
let dy = self.y - other.y;
|
|
(dx * dx + dy * dy).sqrt()
|
|
}
|
|
|
|
/// Get depth below surface (positive value)
|
|
pub fn depth(&self) -> f64 {
|
|
-self.z.min(0.0)
|
|
}
|
|
|
|
/// Check if position is below surface
|
|
pub fn is_buried(&self) -> bool {
|
|
self.z < 0.0
|
|
}
|
|
|
|
/// Get the 95% confidence radius (horizontal)
|
|
pub fn confidence_radius(&self) -> f64 {
|
|
self.uncertainty.horizontal_error
|
|
}
|
|
}
|
|
|
|
/// Uncertainty bounds for a position estimate
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct LocationUncertainty {
|
|
/// Horizontal error radius at 95% confidence (meters)
|
|
pub horizontal_error: f64,
|
|
/// Vertical error at 95% confidence (meters)
|
|
pub vertical_error: f64,
|
|
/// Confidence level (0.0-1.0)
|
|
pub confidence: f64,
|
|
}
|
|
|
|
impl Default for LocationUncertainty {
|
|
fn default() -> Self {
|
|
Self {
|
|
horizontal_error: 2.0, // 2 meter default uncertainty
|
|
vertical_error: 1.0, // 1 meter vertical uncertainty
|
|
confidence: 0.95, // 95% confidence
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LocationUncertainty {
|
|
/// Create uncertainty with specific error bounds
|
|
pub fn new(horizontal_error: f64, vertical_error: f64) -> Self {
|
|
Self {
|
|
horizontal_error,
|
|
vertical_error,
|
|
confidence: 0.95,
|
|
}
|
|
}
|
|
|
|
/// Create high-confidence uncertainty
|
|
pub fn high_confidence(horizontal_error: f64, vertical_error: f64) -> Self {
|
|
Self {
|
|
horizontal_error,
|
|
vertical_error,
|
|
confidence: 0.99,
|
|
}
|
|
}
|
|
|
|
/// Check if uncertainty is acceptable for rescue operations
|
|
pub fn is_actionable(&self) -> bool {
|
|
// Within 3 meters horizontal is generally actionable
|
|
self.horizontal_error <= 3.0 && self.confidence >= 0.8
|
|
}
|
|
|
|
/// Combine two uncertainties (for sensor fusion)
|
|
pub fn combine(&self, other: &LocationUncertainty) -> LocationUncertainty {
|
|
// Weighted combination based on confidence
|
|
let total_conf = self.confidence + other.confidence;
|
|
let w1 = self.confidence / total_conf;
|
|
let w2 = other.confidence / total_conf;
|
|
|
|
// Combined uncertainty is reduced when multiple estimates agree
|
|
let h_var1 = self.horizontal_error * self.horizontal_error;
|
|
let h_var2 = other.horizontal_error * other.horizontal_error;
|
|
let combined_h_var = 1.0 / (1.0 / h_var1 + 1.0 / h_var2);
|
|
|
|
let v_var1 = self.vertical_error * self.vertical_error;
|
|
let v_var2 = other.vertical_error * other.vertical_error;
|
|
let combined_v_var = 1.0 / (1.0 / v_var1 + 1.0 / v_var2);
|
|
|
|
LocationUncertainty {
|
|
horizontal_error: combined_h_var.sqrt(),
|
|
vertical_error: combined_v_var.sqrt(),
|
|
confidence: (w1 * self.confidence + w2 * other.confidence).min(0.99),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Depth estimate with debris profile
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct DepthEstimate {
|
|
/// Estimated depth in meters
|
|
pub depth: f64,
|
|
/// Uncertainty range (plus/minus)
|
|
pub uncertainty: f64,
|
|
/// Estimated debris composition
|
|
pub debris_profile: DebrisProfile,
|
|
/// Confidence in the estimate
|
|
pub confidence: f64,
|
|
}
|
|
|
|
impl DepthEstimate {
|
|
/// Create a new depth estimate
|
|
pub fn new(
|
|
depth: f64,
|
|
uncertainty: f64,
|
|
debris_profile: DebrisProfile,
|
|
confidence: f64,
|
|
) -> Self {
|
|
Self {
|
|
depth,
|
|
uncertainty,
|
|
debris_profile,
|
|
confidence,
|
|
}
|
|
}
|
|
|
|
/// Get minimum possible depth
|
|
pub fn min_depth(&self) -> f64 {
|
|
(self.depth - self.uncertainty).max(0.0)
|
|
}
|
|
|
|
/// Get maximum possible depth
|
|
pub fn max_depth(&self) -> f64 {
|
|
self.depth + self.uncertainty
|
|
}
|
|
|
|
/// Check if depth is shallow (easier rescue)
|
|
pub fn is_shallow(&self) -> bool {
|
|
self.depth < 1.5
|
|
}
|
|
|
|
/// Check if depth is moderate
|
|
pub fn is_moderate(&self) -> bool {
|
|
self.depth >= 1.5 && self.depth < 3.0
|
|
}
|
|
|
|
/// Check if depth is deep (difficult rescue)
|
|
pub fn is_deep(&self) -> bool {
|
|
self.depth >= 3.0
|
|
}
|
|
}
|
|
|
|
/// Profile of debris material between sensor and survivor
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub struct DebrisProfile {
|
|
/// Primary material type
|
|
pub primary_material: DebrisMaterial,
|
|
/// Estimated void fraction (0.0-1.0, higher = more air gaps)
|
|
pub void_fraction: f64,
|
|
/// Estimated moisture content (affects signal propagation)
|
|
pub moisture_content: MoistureLevel,
|
|
/// Whether metal content is detected (blocks signals)
|
|
pub metal_content: MetalContent,
|
|
}
|
|
|
|
impl Default for DebrisProfile {
|
|
fn default() -> Self {
|
|
Self {
|
|
primary_material: DebrisMaterial::Mixed,
|
|
void_fraction: 0.3,
|
|
moisture_content: MoistureLevel::Dry,
|
|
metal_content: MetalContent::None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DebrisProfile {
|
|
/// Calculate signal attenuation factor
|
|
pub fn attenuation_factor(&self) -> f64 {
|
|
let base = self.primary_material.attenuation_coefficient();
|
|
let moisture_factor = self.moisture_content.attenuation_multiplier();
|
|
let void_factor = 1.0 - (self.void_fraction * 0.3); // Voids reduce attenuation
|
|
|
|
base * moisture_factor * void_factor
|
|
}
|
|
|
|
/// Check if debris allows good signal penetration
|
|
pub fn is_penetrable(&self) -> bool {
|
|
!matches!(
|
|
self.metal_content,
|
|
MetalContent::High | MetalContent::Blocking
|
|
) && self.primary_material.attenuation_coefficient() < 5.0
|
|
}
|
|
}
|
|
|
|
/// Types of debris materials
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub enum DebrisMaterial {
|
|
/// Lightweight concrete, drywall
|
|
LightConcrete,
|
|
/// Heavy concrete, brick
|
|
HeavyConcrete,
|
|
/// Wooden structures
|
|
Wood,
|
|
/// Soil, earth
|
|
Soil,
|
|
/// Mixed rubble (typical collapse)
|
|
Mixed,
|
|
/// Snow/ice (avalanche)
|
|
Snow,
|
|
/// Metal (poor penetration)
|
|
Metal,
|
|
}
|
|
|
|
impl DebrisMaterial {
|
|
/// Get RF attenuation coefficient (dB/meter)
|
|
pub fn attenuation_coefficient(&self) -> f64 {
|
|
match self {
|
|
DebrisMaterial::Snow => 0.5,
|
|
DebrisMaterial::Wood => 1.5,
|
|
DebrisMaterial::LightConcrete => 3.0,
|
|
DebrisMaterial::Soil => 4.0,
|
|
DebrisMaterial::Mixed => 4.5,
|
|
DebrisMaterial::HeavyConcrete => 6.0,
|
|
DebrisMaterial::Metal => 20.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Moisture level in debris
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub enum MoistureLevel {
|
|
/// Dry conditions
|
|
Dry,
|
|
/// Slightly damp
|
|
Damp,
|
|
/// Wet (rain, flooding)
|
|
Wet,
|
|
/// Saturated (submerged)
|
|
Saturated,
|
|
}
|
|
|
|
impl MoistureLevel {
|
|
/// Get attenuation multiplier
|
|
pub fn attenuation_multiplier(&self) -> f64 {
|
|
match self {
|
|
MoistureLevel::Dry => 1.0,
|
|
MoistureLevel::Damp => 1.3,
|
|
MoistureLevel::Wet => 1.8,
|
|
MoistureLevel::Saturated => 2.5,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Metal content in debris
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
pub enum MetalContent {
|
|
/// No significant metal
|
|
None,
|
|
/// Low metal content (rebar, pipes)
|
|
Low,
|
|
/// Moderate metal (structural steel)
|
|
Moderate,
|
|
/// High metal content
|
|
High,
|
|
/// Metal is blocking signal
|
|
Blocking,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_distance_calculation() {
|
|
let p1 = Coordinates3D::with_default_uncertainty(0.0, 0.0, 0.0);
|
|
let p2 = Coordinates3D::with_default_uncertainty(3.0, 4.0, 0.0);
|
|
|
|
assert!((p1.distance_to(&p2) - 5.0).abs() < 0.001);
|
|
assert!((p1.horizontal_distance_to(&p2) - 5.0).abs() < 0.001);
|
|
}
|
|
|
|
#[test]
|
|
fn test_depth_calculation() {
|
|
let surface = Coordinates3D::with_default_uncertainty(0.0, 0.0, 0.0);
|
|
assert!(!surface.is_buried());
|
|
assert!(surface.depth().abs() < 0.001);
|
|
|
|
let buried = Coordinates3D::with_default_uncertainty(0.0, 0.0, -2.5);
|
|
assert!(buried.is_buried());
|
|
assert!((buried.depth() - 2.5).abs() < 0.001);
|
|
}
|
|
|
|
#[test]
|
|
fn test_uncertainty_combination() {
|
|
let u1 = LocationUncertainty::new(2.0, 1.0);
|
|
let u2 = LocationUncertainty::new(2.0, 1.0);
|
|
|
|
let combined = u1.combine(&u2);
|
|
|
|
// Combined uncertainty should be lower than individual
|
|
assert!(combined.horizontal_error < u1.horizontal_error);
|
|
}
|
|
|
|
#[test]
|
|
fn test_depth_estimate_categories() {
|
|
let shallow = DepthEstimate::new(1.0, 0.2, DebrisProfile::default(), 0.8);
|
|
assert!(shallow.is_shallow());
|
|
|
|
let moderate = DepthEstimate::new(2.0, 0.3, DebrisProfile::default(), 0.7);
|
|
assert!(moderate.is_moderate());
|
|
|
|
let deep = DepthEstimate::new(4.0, 0.5, DebrisProfile::default(), 0.6);
|
|
assert!(deep.is_deep());
|
|
}
|
|
|
|
#[test]
|
|
fn test_debris_attenuation() {
|
|
let snow = DebrisProfile {
|
|
primary_material: DebrisMaterial::Snow,
|
|
..Default::default()
|
|
};
|
|
let concrete = DebrisProfile {
|
|
primary_material: DebrisMaterial::HeavyConcrete,
|
|
..Default::default()
|
|
};
|
|
|
|
assert!(snow.attenuation_factor() < concrete.attenuation_factor());
|
|
assert!(snow.is_penetrable());
|
|
}
|
|
}
|