Skip to main content

copc_streaming/
fields.rs

1//! Field selection for selective LAZ decompression.
2
3use bitflags::bitflags;
4
5bitflags! {
6    /// Which point fields to decompress.
7    ///
8    /// On LAS 1.4 layered point formats (6, 7, 8 — the formats COPC mandates),
9    /// LAZ decompression is organized as independent per-field byte layers.
10    /// Omitted fields skip arithmetic decoding of their layer entirely, which
11    /// is a direct CPU saving proportional to the number of layers dropped.
12    ///
13    /// The following fields are always decoded regardless of the mask because
14    /// they share a single base layer: `x`, `y`, `return_number`,
15    /// `number_of_returns`, and `scanner_channel`. They are "free" in the
16    /// sense that you cannot skip them. The minimum mask that still gives
17    /// full 3D geometry is `Fields::Z` (just the Z layer on top of the
18    /// always-on base).
19    ///
20    /// When a field is not present in the mask, its bytes in the decompressed
21    /// chunk remain zero. Calling a column accessor for a skipped field on a
22    /// [`Chunk`](crate::Chunk) returns `None`, so downstream code cannot
23    /// silently read stale zeros.
24    ///
25    /// # Composing masks
26    ///
27    /// Use bitwise `|` to combine flags and [`Fields::empty`] /
28    /// [`Fields::ALL`] as the extremes:
29    ///
30    /// ```
31    /// use copc_streaming::Fields;
32    ///
33    /// // Geometry only.
34    /// let geometry = Fields::Z;
35    ///
36    /// // Geometry + color.
37    /// let geometry_rgb = Fields::Z | Fields::RGB;
38    ///
39    /// // Geometry + gps time.
40    /// let geometry_time = Fields::Z | Fields::GPS_TIME;
41    ///
42    /// // Full decode — required if you want `Chunk::to_points()` or
43    /// // `Chunk::points_at()` to succeed.
44    /// let everything = Fields::ALL;
45    ///
46    /// // `Fields::empty()` decodes only the always-on base layer:
47    /// // x, y, return_number, number_of_returns, scanner_channel.
48    /// let bare = Fields::empty();
49    /// ```
50    #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
51    pub struct Fields: u32 {
52        /// Z coordinate.
53        const Z               = 1 << 0;
54        /// Intensity (return strength).
55        const INTENSITY       = 1 << 1;
56        /// Classification code.
57        const CLASSIFICATION  = 1 << 2;
58        /// Flag bits (synthetic / key-point / withheld / overlap, etc.).
59        const FLAGS           = 1 << 3;
60        /// Scan angle.
61        const SCAN_ANGLE      = 1 << 4;
62        /// User data byte.
63        const USER_DATA       = 1 << 5;
64        /// Point source ID.
65        const POINT_SOURCE_ID = 1 << 6;
66        /// GPS time.
67        const GPS_TIME        = 1 << 7;
68        /// RGB color triple.
69        const RGB             = 1 << 8;
70        /// Near-infrared value.
71        const NIR             = 1 << 9;
72        /// Full-waveform packet data.
73        const WAVEPACKET      = 1 << 10;
74        /// Extra bytes region.
75        const EXTRA_BYTES     = 1 << 11;
76
77        /// Decode every field. The only mask that lets a chunk be materialized
78        /// back into `las::Point` values via [`Chunk::to_points`](crate::Chunk::to_points).
79        const ALL = Self::Z.bits()
80                  | Self::INTENSITY.bits()
81                  | Self::CLASSIFICATION.bits()
82                  | Self::FLAGS.bits()
83                  | Self::SCAN_ANGLE.bits()
84                  | Self::USER_DATA.bits()
85                  | Self::POINT_SOURCE_ID.bits()
86                  | Self::GPS_TIME.bits()
87                  | Self::RGB.bits()
88                  | Self::NIR.bits()
89                  | Self::WAVEPACKET.bits()
90                  | Self::EXTRA_BYTES.bits();
91    }
92}
93
94impl Fields {
95    /// Build a [`laz::DecompressionSelection`] matching this mask.
96    ///
97    /// Uses the laz builder API (`xy_returns_channel()` + `decompress_*`)
98    /// instead of a raw bit-cast, so the public `Fields` layout is not
99    /// coupled to laz's internal representation.
100    pub(crate) fn to_laz_selection(self) -> laz::DecompressionSelection {
101        let mut sel = laz::DecompressionSelection::xy_returns_channel();
102        if self.contains(Fields::Z) {
103            sel = sel.decompress_z();
104        }
105        if self.contains(Fields::INTENSITY) {
106            sel = sel.decompress_intensity();
107        }
108        if self.contains(Fields::CLASSIFICATION) {
109            sel = sel.decompress_classification();
110        }
111        if self.contains(Fields::FLAGS) {
112            sel = sel.decompress_flags();
113        }
114        if self.contains(Fields::SCAN_ANGLE) {
115            sel = sel.decompress_scan_angle();
116        }
117        if self.contains(Fields::USER_DATA) {
118            sel = sel.decompress_user_data();
119        }
120        if self.contains(Fields::POINT_SOURCE_ID) {
121            sel = sel.decompress_point_source_id();
122        }
123        if self.contains(Fields::GPS_TIME) {
124            sel = sel.decompress_gps_time();
125        }
126        if self.contains(Fields::RGB) {
127            sel = sel.decompress_rgb();
128        }
129        if self.contains(Fields::NIR) {
130            sel = sel.decompress_nir();
131        }
132        if self.contains(Fields::WAVEPACKET) {
133            sel = sel.decompress_wavepacket();
134        }
135        if self.contains(Fields::EXTRA_BYTES) {
136            sel = sel.decompress_extra_bytes();
137        }
138        sel
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn all_contains_every_layer() {
148        assert!(Fields::ALL.contains(Fields::Z));
149        assert!(Fields::ALL.contains(Fields::INTENSITY));
150        assert!(Fields::ALL.contains(Fields::CLASSIFICATION));
151        assert!(Fields::ALL.contains(Fields::FLAGS));
152        assert!(Fields::ALL.contains(Fields::SCAN_ANGLE));
153        assert!(Fields::ALL.contains(Fields::USER_DATA));
154        assert!(Fields::ALL.contains(Fields::POINT_SOURCE_ID));
155        assert!(Fields::ALL.contains(Fields::GPS_TIME));
156        assert!(Fields::ALL.contains(Fields::RGB));
157        assert!(Fields::ALL.contains(Fields::NIR));
158        assert!(Fields::ALL.contains(Fields::WAVEPACKET));
159        assert!(Fields::ALL.contains(Fields::EXTRA_BYTES));
160    }
161
162    #[test]
163    fn composition() {
164        let f = Fields::Z | Fields::GPS_TIME;
165        assert!(f.contains(Fields::Z));
166        assert!(f.contains(Fields::GPS_TIME));
167        assert!(!f.contains(Fields::RGB));
168        assert!(!f.contains(Fields::INTENSITY));
169    }
170
171    #[test]
172    fn empty_mask_has_no_layers_set() {
173        let sel = Fields::empty().to_laz_selection();
174        assert!(!sel.should_decompress_z());
175        assert!(!sel.should_decompress_classification());
176        assert!(!sel.should_decompress_intensity());
177        assert!(!sel.should_decompress_gps_time());
178        assert!(!sel.should_decompress_rgb());
179    }
180
181    #[test]
182    fn selective_mask_enables_only_requested_layers() {
183        let sel = (Fields::Z | Fields::GPS_TIME).to_laz_selection();
184        assert!(sel.should_decompress_z());
185        assert!(sel.should_decompress_gps_time());
186        assert!(!sel.should_decompress_rgb());
187        assert!(!sel.should_decompress_intensity());
188        assert!(!sel.should_decompress_classification());
189        assert!(!sel.should_decompress_nir());
190    }
191
192    #[test]
193    fn all_mask_enables_every_layer() {
194        let sel = Fields::ALL.to_laz_selection();
195        assert!(sel.should_decompress_z());
196        assert!(sel.should_decompress_intensity());
197        assert!(sel.should_decompress_classification());
198        assert!(sel.should_decompress_flags());
199        assert!(sel.should_decompress_scan_angle());
200        assert!(sel.should_decompress_user_data());
201        assert!(sel.should_decompress_point_source_id());
202        assert!(sel.should_decompress_gps_time());
203        assert!(sel.should_decompress_rgb());
204        assert!(sel.should_decompress_nir());
205        assert!(sel.should_decompress_wavepacket());
206        assert!(sel.should_decompress_extra_bytes());
207    }
208}