tangram_core_rs 0.4.1

A framework for real-time analysis of ADS-B and Mode S surveillance data
Documentation
use crate::stream::Positioned;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use tracing::info;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BoundingBox {
    pub north_east_lat: f64,
    pub north_east_lng: f64,
    pub south_west_lat: f64,
    pub south_west_lng: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SelectedEntity {
    pub id: String,
    #[serde(rename = "typeName")]
    pub type_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BoundingBoxMessage {
    #[serde(rename = "connectionId")]
    pub connection_id: String,
    #[serde(rename = "northEastLat")]
    pub north_east_lat: f64,
    #[serde(rename = "northEastLng")]
    pub north_east_lng: f64,
    #[serde(rename = "southWestLat")]
    pub south_west_lat: f64,
    #[serde(rename = "southWestLng")]
    pub south_west_lng: f64,
    #[serde(rename = "selectedEntities")]
    pub selected_entities: Vec<SelectedEntity>,
}

#[derive(Default)]
pub struct BoundingBoxState {
    pub bboxes: HashMap<String, BoundingBox>,
    pub selections: HashMap<String, Vec<SelectedEntity>>,
    pub clients: HashSet<String>,
}

impl BoundingBoxState {
    pub fn new() -> Self {
        Self {
            bboxes: HashMap::new(),
            selections: HashMap::new(),
            clients: HashSet::new(),
        }
    }

    pub fn set_bbox(&mut self, connection_id: &str, bbox: BoundingBox) {
        self.bboxes.insert(connection_id.to_string(), bbox.clone());
        info!(
            "Updated {} bounding box: NE({}, {}), SW({}, {})",
            connection_id,
            bbox.north_east_lat,
            bbox.north_east_lng,
            bbox.south_west_lat,
            bbox.south_west_lng
        );
    }

    pub fn set_selected(&mut self, connection_id: &str, entities: Vec<SelectedEntity>) {
        if entities.is_empty() {
            self.selections.remove(connection_id);
        } else {
            self.selections.insert(connection_id.to_string(), entities);
        }
    }

    pub fn has_bbox(&self, connection_id: &str) -> bool {
        self.bboxes.contains_key(connection_id)
    }

    pub fn get_bbox(&self, connection_id: &str) -> Option<&BoundingBox> {
        self.bboxes.get(connection_id)
    }

    pub fn get_selected(&self, connection_id: &str) -> Option<&Vec<SelectedEntity>> {
        self.selections.get(connection_id)
    }

    pub fn remove_client(&mut self, connection_id: &str) {
        self.bboxes.remove(connection_id);
        self.selections.remove(connection_id);
        self.clients.remove(connection_id);
    }
}

pub fn is_within_bbox<T: Positioned>(
    item: &T,
    state: &BoundingBoxState,
    connection_id: &str,
) -> bool {
    if !state.has_bbox(connection_id) {
        return true;
    }

    let bbox = match state.get_bbox(connection_id) {
        Some(bbox) => bbox,
        None => return true,
    };

    let lat = match item.latitude() {
        Some(lat) => lat,
        None => return false,
    };

    let lng = match item.longitude() {
        Some(lng) => lng,
        None => return false,
    };

    bbox.south_west_lat <= lat
        && lat <= bbox.north_east_lat
        && bbox.south_west_lng <= lng
        && lng <= bbox.north_east_lng
}