Skip to main content

rustial_engine/visualization/
instanced_column_layer.rs

1//! Instanced column overlay layer.
2
3use std::any::Any;
4
5use super::{ColorRamp, ColumnInstanceSet};
6use crate::layer::{Layer, LayerId, LayerKind};
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(name: impl Into<String>, columns: ColumnInstanceSet, ramp: ColorRamp) -> Self {
30        Self {
31            id: LayerId::next(),
32            name: name.into(),
33            visible: true,
34            opacity: 1.0,
35            z_index: 0,
36            columns,
37            ramp,
38        }
39    }
40
41    /// Replace the column set.
42    pub fn set_columns(&mut self, columns: ColumnInstanceSet) {
43        self.columns = columns;
44    }
45}
46
47impl Layer for InstancedColumnLayer {
48    fn id(&self) -> LayerId {
49        self.id
50    }
51
52    fn name(&self) -> &str {
53        &self.name
54    }
55
56    fn kind(&self) -> LayerKind {
57        LayerKind::Visualization
58    }
59
60    fn visible(&self) -> bool {
61        self.visible
62    }
63
64    fn set_visible(&mut self, visible: bool) {
65        self.visible = visible;
66    }
67
68    fn opacity(&self) -> f32 {
69        self.opacity
70    }
71
72    fn set_opacity(&mut self, opacity: f32) {
73        self.opacity = opacity.clamp(0.0, 1.0);
74    }
75
76    fn z_index(&self) -> i32 {
77        self.z_index
78    }
79
80    fn as_any(&self) -> &dyn Any {
81        self
82    }
83
84    fn as_any_mut(&mut self) -> &mut dyn Any {
85        self
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::visualization::{ColorStop, ColumnInstance};
93    use rustial_math::GeoCoord;
94
95    fn test_ramp() -> ColorRamp {
96        ColorRamp::new(vec![
97            ColorStop {
98                value: 0.0,
99                color: [0.0, 0.0, 1.0, 1.0],
100            },
101            ColorStop {
102                value: 1.0,
103                color: [1.0, 0.0, 0.0, 1.0],
104            },
105        ])
106    }
107
108    #[test]
109    fn instanced_column_layer_basics() {
110        let columns = ColumnInstanceSet::new(vec![ColumnInstance::new(
111            GeoCoord::from_lat_lon(0.0, 0.0),
112            100.0,
113            10.0,
114        )
115        .with_pick_id(1)]);
116        let layer = InstancedColumnLayer::new("histogram", columns, test_ramp());
117        assert_eq!(layer.name(), "histogram");
118        assert_eq!(layer.columns.len(), 1);
119        assert_eq!(layer.columns.columns[0].pick_id, 1);
120    }
121
122    #[test]
123    fn instanced_column_layer_downcast() {
124        let columns = ColumnInstanceSet::default();
125        let layer: Box<dyn Layer> =
126            Box::new(InstancedColumnLayer::new("test", columns, test_ramp()));
127        assert!(layer
128            .as_any()
129            .downcast_ref::<InstancedColumnLayer>()
130            .is_some());
131    }
132
133    #[test]
134    fn set_columns_replaces() {
135        let mut layer =
136            InstancedColumnLayer::new("test", ColumnInstanceSet::default(), test_ramp());
137        assert!(layer.columns.is_empty());
138        layer.set_columns(ColumnInstanceSet::new(vec![ColumnInstance::new(
139            GeoCoord::from_lat_lon(0.0, 0.0),
140            50.0,
141            5.0,
142        )]));
143        assert_eq!(layer.columns.len(), 1);
144    }
145}