Skip to main content

rustial_engine/visualization/
instanced_column_layer.rs

1//! Instanced column overlay layer.
2
3use std::any::Any;
4
5use crate::layer::{Layer, LayerId, LayerKind};
6use super::{ColorRamp, ColumnInstanceSet};
7
8/// An instanced column overlay layer.
9///
10/// Renders a collection of box or cylinder columns anchored to
11/// geographic positions. Each column has independent height, width,
12/// colour, and pick ID. Renderers use GPU instancing for efficient
13/// draw of large datasets.
14#[derive(Debug, Clone)]
15pub struct InstancedColumnLayer {
16    id: LayerId,
17    name: String,
18    visible: bool,
19    opacity: f32,
20    z_index: i32,
21    /// Column instances.
22    pub columns: ColumnInstanceSet,
23    /// Fallback colour ramp for columns without per-instance colour.
24    pub ramp: ColorRamp,
25}
26
27impl InstancedColumnLayer {
28    /// Create a new instanced column layer.
29    pub fn new(
30        name: impl Into<String>,
31        columns: ColumnInstanceSet,
32        ramp: ColorRamp,
33    ) -> Self {
34        Self {
35            id: LayerId::next(),
36            name: name.into(),
37            visible: true,
38            opacity: 1.0,
39            z_index: 0,
40            columns,
41            ramp,
42        }
43    }
44
45    /// Replace the column set.
46    pub fn set_columns(&mut self, columns: ColumnInstanceSet) {
47        self.columns = columns;
48    }
49}
50
51impl Layer for InstancedColumnLayer {
52    fn id(&self) -> LayerId {
53        self.id
54    }
55
56    fn name(&self) -> &str {
57        &self.name
58    }
59
60    fn kind(&self) -> LayerKind {
61        LayerKind::Visualization
62    }
63
64    fn visible(&self) -> bool {
65        self.visible
66    }
67
68    fn set_visible(&mut self, visible: bool) {
69        self.visible = visible;
70    }
71
72    fn opacity(&self) -> f32 {
73        self.opacity
74    }
75
76    fn set_opacity(&mut self, opacity: f32) {
77        self.opacity = opacity.clamp(0.0, 1.0);
78    }
79
80    fn z_index(&self) -> i32 {
81        self.z_index
82    }
83
84    fn as_any(&self) -> &dyn Any {
85        self
86    }
87
88    fn as_any_mut(&mut self) -> &mut dyn Any {
89        self
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::visualization::{ColorStop, ColumnInstance};
97    use rustial_math::GeoCoord;
98
99    fn test_ramp() -> ColorRamp {
100        ColorRamp::new(vec![
101            ColorStop { value: 0.0, color: [0.0, 0.0, 1.0, 1.0] },
102            ColorStop { value: 1.0, color: [1.0, 0.0, 0.0, 1.0] },
103        ])
104    }
105
106    #[test]
107    fn instanced_column_layer_basics() {
108        let columns = ColumnInstanceSet::new(vec![
109            ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 100.0, 10.0)
110                .with_pick_id(1),
111        ]);
112        let layer = InstancedColumnLayer::new("histogram", columns, test_ramp());
113        assert_eq!(layer.name(), "histogram");
114        assert_eq!(layer.columns.len(), 1);
115        assert_eq!(layer.columns.columns[0].pick_id, 1);
116    }
117
118    #[test]
119    fn instanced_column_layer_downcast() {
120        let columns = ColumnInstanceSet::default();
121        let layer: Box<dyn Layer> = Box::new(
122            InstancedColumnLayer::new("test", columns, test_ramp()),
123        );
124        assert!(layer.as_any().downcast_ref::<InstancedColumnLayer>().is_some());
125    }
126
127    #[test]
128    fn set_columns_replaces() {
129        let mut layer = InstancedColumnLayer::new(
130            "test",
131            ColumnInstanceSet::default(),
132            test_ramp(),
133        );
134        assert!(layer.columns.is_empty());
135        layer.set_columns(ColumnInstanceSet::new(vec![
136            ColumnInstance::new(GeoCoord::from_lat_lon(0.0, 0.0), 50.0, 5.0),
137        ]));
138        assert_eq!(layer.columns.len(), 1);
139    }
140}