rustial-engine 0.0.1

Framework-agnostic 2.5D map engine for rustial
Documentation
//! Instanced column overlay layer.

use std::any::Any;

use super::{ColorRamp, ColumnInstanceSet};
use crate::layer::{Layer, LayerId, LayerKind};

/// An instanced column overlay layer.
///
/// Renders a collection of box or cylinder columns anchored to
/// geographic positions. Each column has independent height, width,
/// colour, and pick ID. Renderers use GPU instancing for efficient
/// draw of large datasets.
#[derive(Debug, Clone)]
pub struct InstancedColumnLayer {
    id: LayerId,
    name: String,
    visible: bool,
    opacity: f32,
    z_index: i32,
    /// Column instances.
    pub columns: ColumnInstanceSet,
    /// Fallback colour ramp for columns without per-instance colour.
    pub ramp: ColorRamp,
}

impl InstancedColumnLayer {
    /// Create a new instanced column layer.
    pub fn new(name: impl Into<String>, columns: ColumnInstanceSet, ramp: ColorRamp) -> Self {
        Self {
            id: LayerId::next(),
            name: name.into(),
            visible: true,
            opacity: 1.0,
            z_index: 0,
            columns,
            ramp,
        }
    }

    /// Replace the column set.
    pub fn set_columns(&mut self, columns: ColumnInstanceSet) {
        self.columns = columns;
    }
}

impl Layer for InstancedColumnLayer {
    fn id(&self) -> LayerId {
        self.id
    }

    fn name(&self) -> &str {
        &self.name
    }

    fn kind(&self) -> LayerKind {
        LayerKind::Visualization
    }

    fn visible(&self) -> bool {
        self.visible
    }

    fn set_visible(&mut self, visible: bool) {
        self.visible = visible;
    }

    fn opacity(&self) -> f32 {
        self.opacity
    }

    fn set_opacity(&mut self, opacity: f32) {
        self.opacity = opacity.clamp(0.0, 1.0);
    }

    fn z_index(&self) -> i32 {
        self.z_index
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::visualization::{ColorStop, ColumnInstance};
    use rustial_math::GeoCoord;

    fn test_ramp() -> ColorRamp {
        ColorRamp::new(vec![
            ColorStop {
                value: 0.0,
                color: [0.0, 0.0, 1.0, 1.0],
            },
            ColorStop {
                value: 1.0,
                color: [1.0, 0.0, 0.0, 1.0],
            },
        ])
    }

    #[test]
    fn instanced_column_layer_basics() {
        let columns = ColumnInstanceSet::new(vec![ColumnInstance::new(
            GeoCoord::from_lat_lon(0.0, 0.0),
            100.0,
            10.0,
        )
        .with_pick_id(1)]);
        let layer = InstancedColumnLayer::new("histogram", columns, test_ramp());
        assert_eq!(layer.name(), "histogram");
        assert_eq!(layer.columns.len(), 1);
        assert_eq!(layer.columns.columns[0].pick_id, 1);
    }

    #[test]
    fn instanced_column_layer_downcast() {
        let columns = ColumnInstanceSet::default();
        let layer: Box<dyn Layer> =
            Box::new(InstancedColumnLayer::new("test", columns, test_ramp()));
        assert!(layer
            .as_any()
            .downcast_ref::<InstancedColumnLayer>()
            .is_some());
    }

    #[test]
    fn set_columns_replaces() {
        let mut layer =
            InstancedColumnLayer::new("test", ColumnInstanceSet::default(), test_ramp());
        assert!(layer.columns.is_empty());
        layer.set_columns(ColumnInstanceSet::new(vec![ColumnInstance::new(
            GeoCoord::from_lat_lon(0.0, 0.0),
            50.0,
            5.0,
        )]));
        assert_eq!(layer.columns.len(), 1);
    }
}