Skip to main content

spatialrust_lod/
copc.rs

1use crate::{LodError, LodIndex, LodResult, NodeId};
2
3/// Converts selected index node bounds into one bounded COPC resolution query.
4///
5/// The caller remains responsible for executing and cancelling the query. This
6/// adapter does not open a file, issue a range request, or materialize points.
7pub fn copc_query_for_nodes(
8    index: &LodIndex,
9    nodes: &[NodeId],
10    max_resolution: f64,
11) -> LodResult<spatialrust_io::CopcQuery> {
12    if nodes.is_empty() {
13        return Err(LodError::Copc("at least one selected node is required".into()));
14    }
15    if !max_resolution.is_finite() || max_resolution <= 0.0 {
16        return Err(LodError::Copc("COPC max resolution must be finite and positive".into()));
17    }
18    let first = index.node(nodes[0]).ok_or(LodError::UnknownNode(nodes[0].0))?;
19    let mut bounds = first.bounds;
20    for id in &nodes[1..] {
21        let node = index.node(*id).ok_or(LodError::UnknownNode(id.0))?;
22        bounds = bounds.union(node.bounds);
23    }
24    let copc_bounds = spatialrust_io::CopcBounds::new(
25        [bounds.min.x as f64, bounds.min.y as f64, bounds.min.z as f64],
26        [bounds.max.x as f64, bounds.max.y as f64, bounds.max.z as f64],
27    );
28    let query = spatialrust_io::CopcQuery::with_resolution(copc_bounds, max_resolution);
29    query.validate().map_err(|error| LodError::Copc(error.to_string()))?;
30    Ok(query)
31}
32
33#[cfg(test)]
34mod tests {
35    use spatialrust_math::Vec3;
36
37    use crate::{LodBounds, LodIndex, LodNode, NodeId};
38
39    #[test]
40    fn selected_nodes_become_one_bounded_resolution_query() {
41        let index = LodIndex::try_new([
42            LodNode {
43                id: NodeId(0),
44                parent: None,
45                children: vec![NodeId(1)],
46                bounds: LodBounds::try_new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0))
47                    .unwrap(),
48                geometric_error: 1.0,
49                point_count: 10,
50                host_bytes: 40,
51                upload_bytes: 40,
52                gpu_bytes: 40,
53            },
54            LodNode {
55                id: NodeId(1),
56                parent: Some(NodeId(0)),
57                children: Vec::new(),
58                bounds: LodBounds::try_new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0))
59                    .unwrap(),
60                geometric_error: 0.5,
61                point_count: 5,
62                host_bytes: 20,
63                upload_bytes: 20,
64                gpu_bytes: 20,
65            },
66        ])
67        .unwrap();
68        let query = super::copc_query_for_nodes(&index, &[NodeId(1)], 0.25).unwrap();
69        assert_eq!(query.bounds.min, [0.0, 0.0, 0.0]);
70        assert_eq!(query.bounds.max, [1.0, 1.0, 1.0]);
71        assert_eq!(query.max_resolution, Some(0.25));
72        assert!(super::copc_query_for_nodes(&index, &[], 0.25).is_err());
73    }
74}