nodedb_array/tile/
layout.rs1use crate::coord::encode::encode_tile_prefix_with_order;
11use crate::coord::normalize::bits_per_dim;
12use crate::error::{ArrayError, ArrayResult};
13use crate::schema::ArraySchema;
14use crate::schema::dim_spec::DimType;
15use crate::types::TileId;
16use crate::types::coord::value::CoordValue;
17use crate::types::domain::DomainBound;
18
19pub fn tile_indices_for_cell(schema: &ArraySchema, coord: &[CoordValue]) -> ArrayResult<Vec<u64>> {
21 if coord.len() != schema.arity() {
22 return Err(ArrayError::CoordArityMismatch {
23 array: schema.name.clone(),
24 expected: schema.arity(),
25 got: coord.len(),
26 });
27 }
28 let mut out = Vec::with_capacity(schema.arity());
29 for (i, dim) in schema.dims.iter().enumerate() {
30 let extent = schema.tile_extents[i];
31 let cell = &coord[i];
32 let lo = &dim.domain.lo;
33 let off = match (dim.dtype, cell, lo) {
34 (DimType::Int64, CoordValue::Int64(v), DomainBound::Int64(lo))
35 | (DimType::TimestampMs, CoordValue::TimestampMs(v), DomainBound::TimestampMs(lo)) => {
36 if v < lo {
37 return out_of_domain(schema, dim.name.as_str(), "below domain_lo");
38 }
39 let delta = (*v as i128) - (*lo as i128);
40 (delta as u128 / u128::from(extent)) as u64
41 }
42 (DimType::Float64, CoordValue::Float64(v), DomainBound::Float64(lo)) => {
43 if !v.is_finite() || v < lo {
44 return out_of_domain(
45 schema,
46 dim.name.as_str(),
47 "below domain_lo or non-finite",
48 );
49 }
50 ((v - lo) as u64) / extent
51 }
52 (DimType::String, CoordValue::String(s), DomainBound::String(_)) => {
53 crate::coord::string_hash::hash_string_modulo(s, extent.max(1))
56 }
57 _ => {
58 return Err(ArrayError::CoordOutOfDomain {
59 array: schema.name.clone(),
60 dim: dim.name.clone(),
61 detail: "coord variant does not match dim dtype".to_string(),
62 });
63 }
64 };
65 out.push(off);
66 }
67 Ok(out)
68}
69
70pub fn tile_id_for_cell(
74 schema: &ArraySchema,
75 coord: &[CoordValue],
76 system_from_ms: i64,
77) -> ArrayResult<TileId> {
78 let tile_indices = tile_indices_for_cell(schema, coord)?;
79 let bits = bits_per_dim(schema.arity());
80 let prefix = encode_tile_prefix_with_order(schema, &tile_indices, bits)?;
81 Ok(TileId::new(prefix, system_from_ms))
82}
83
84fn out_of_domain<T>(schema: &ArraySchema, dim: &str, detail: &str) -> ArrayResult<T> {
85 Err(ArrayError::CoordOutOfDomain {
86 array: schema.name.clone(),
87 dim: dim.to_string(),
88 detail: detail.to_string(),
89 })
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::schema::ArraySchemaBuilder;
96 use crate::schema::attr_spec::{AttrSpec, AttrType};
97 use crate::schema::dim_spec::DimSpec;
98 use crate::types::domain::Domain;
99
100 fn schema() -> ArraySchema {
101 ArraySchemaBuilder::new("g")
102 .dim(DimSpec::new(
103 "x",
104 DimType::Int64,
105 Domain::new(DomainBound::Int64(0), DomainBound::Int64(99)),
106 ))
107 .dim(DimSpec::new(
108 "y",
109 DimType::Int64,
110 Domain::new(DomainBound::Int64(0), DomainBound::Int64(99)),
111 ))
112 .attr(AttrSpec::new("v", AttrType::Int64, false))
113 .tile_extents(vec![10, 10])
114 .build()
115 .unwrap()
116 }
117
118 #[test]
119 fn tile_indices_floor_by_extent() {
120 let s = schema();
121 let t = tile_indices_for_cell(&s, &[CoordValue::Int64(7), CoordValue::Int64(15)]).unwrap();
122 assert_eq!(t, vec![0, 1]);
123 }
124
125 #[test]
126 fn cells_in_same_tile_share_prefix() {
127 let s = schema();
128 let t1 = tile_id_for_cell(&s, &[CoordValue::Int64(0), CoordValue::Int64(0)], 0).unwrap();
129 let t2 = tile_id_for_cell(&s, &[CoordValue::Int64(9), CoordValue::Int64(9)], 0).unwrap();
130 assert_eq!(t1.hilbert_prefix, t2.hilbert_prefix);
131 }
132
133 #[test]
134 fn cells_in_different_tiles_have_different_prefixes() {
135 let s = schema();
136 let t1 = tile_id_for_cell(&s, &[CoordValue::Int64(0), CoordValue::Int64(0)], 0).unwrap();
137 let t2 = tile_id_for_cell(&s, &[CoordValue::Int64(50), CoordValue::Int64(50)], 0).unwrap();
138 assert_ne!(t1.hilbert_prefix, t2.hilbert_prefix);
139 }
140
141 #[test]
142 fn system_from_ms_propagates() {
143 let s = schema();
144 let t = tile_id_for_cell(&s, &[CoordValue::Int64(0), CoordValue::Int64(0)], 1234).unwrap();
145 assert_eq!(t.system_from_ms, 1234);
146 }
147}