1use bitvec::vec::BitVec;
2
3use crate::decoder::{Morton, PropKind, TileLayer};
4use crate::encoder::model::{CurveParams, StagedLayer};
5use crate::encoder::property::encode::write_properties;
6use crate::encoder::{
7 Codecs, Encoder, EncoderConfig, SortStrategy, StagedId, spatial_sort_likely_to_help,
8};
9use crate::{MltError, MltResult, PropValue};
10
11impl StagedLayer {
12 #[hotpath::measure]
19 pub fn encode_into(self, mut enc: Encoder, codecs: &mut Codecs) -> MltResult<Encoder> {
20 if self.name.is_empty() {
21 return Err(MltError::MissingLayerName);
22 }
23 let column_count = usize::from(!matches!(self.id, StagedId::None))
24 + 1 + self.properties.len();
26
27 let StagedLayer {
28 name,
29 extent,
30 id,
31 geometry,
32 properties,
33 } = self;
34
35 id.write_to(&mut enc, codecs)?;
36 geometry.write_to(&mut enc, codecs)?;
37 write_properties(&properties, &mut enc, codecs)?;
38 enc.write_header(&name, extent.get(), column_count)?;
39
40 Ok(enc)
41 }
42}
43
44fn seed_curve_caches(enc: &mut Encoder, curve_params: CurveParams) {
49 enc.hilbert_cache = Some(curve_params);
50 enc.morton_cache = Morton::new(curve_params.bits, curve_params.shift).ok();
51}
52
53const SORT_TRIAL_THRESHOLD: usize = 512;
56
57impl TileLayer {
58 #[hotpath::measure]
68 pub fn encode(self, cfg: EncoderConfig) -> MltResult<Vec<u8>> {
69 if self.name().is_empty() {
70 return Err(MltError::MissingLayerName);
71 }
72 if self.features().is_empty() {
73 return Ok(Vec::new());
74 }
75
76 let mut sort_by = vec![SortStrategy::Unsorted];
77 let try_spatial_sort =
78 cfg.attempt_spatial_morton_sort() || cfg.attempt_spatial_hilbert_sort();
79 if try_spatial_sort
80 && (self.feature_count() < SORT_TRIAL_THRESHOLD || spatial_sort_likely_to_help(&self))
81 {
82 if cfg.attempt_spatial_morton_sort() {
83 sort_by.push(SortStrategy::SpatialMorton);
84 }
85 if cfg.attempt_spatial_hilbert_sort() {
86 sort_by.push(SortStrategy::SpatialHilbert);
87 }
88 }
89 if cfg.attempt_id_sort() {
90 sort_by.push(SortStrategy::Id);
91 }
92
93 let stats = self.analyze(cfg.allow_shared_dict())?;
94 let curve_params = self.curve_params();
97
98 let mut enc = Encoder::new(cfg);
102 seed_curve_caches(&mut enc, curve_params);
103
104 let (last, init) = sort_by.split_last().expect("at least one strategy");
105 if init.is_empty() {
106 let mut codecs = Codecs::default();
107 StagedLayer::from_tile(self, *last, &stats, cfg.tessellate(), curve_params)
108 .encode_into(enc, &mut codecs)?
109 } else {
110 let mut codecs = Codecs::default();
111 enc = {
112 let first = init[0];
113 StagedLayer::from_tile(self.clone(), first, &stats, cfg.tessellate(), curve_params)
114 .encode_into(enc, &mut codecs)?
115 };
116 let mut best = enc.preserve_results();
117 for &sort in &init[1..] {
119 let layer = StagedLayer::from_tile(
120 self.clone(),
121 sort,
122 &stats,
123 cfg.tessellate(),
124 curve_params,
125 );
126 enc = layer.encode_into(enc, &mut codecs)?;
127 if enc.total_len() < best.total_len() {
128 best = enc.preserve_results();
129 } else {
130 enc.clear_results();
136 }
137 }
138 let layer = StagedLayer::from_tile(self, *last, &stats, cfg.tessellate(), curve_params);
140 enc = layer.encode_into(enc, &mut codecs)?;
141 if enc.total_len() < best.total_len() {
142 best = enc.preserve_results();
143 }
144 best
145 }
146 .into_layer_bytes()
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum Presence {
153 AllNull,
155 AllPresent,
157 Mixed,
159 SameAsProp(usize),
161}
162impl Presence {
163 #[must_use]
165 pub fn from_bits(bits: &BitVec<u8>, existing: &[(BitVec<u8>, usize)]) -> Self {
166 if bits.not_any() {
167 Self::AllNull
168 } else if bits.all() {
169 Self::AllPresent
170 } else if let Some((_, idx)) = existing.iter().find(|(v, _)| v == bits) {
171 Self::SameAsProp(*idx)
172 } else {
173 Self::Mixed
174 }
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub enum SharedDictRole {
181 None,
183 Owner(String),
185 Member(usize),
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct PropertyStats {
192 pub presence: Presence,
193 pub stats: PropertyTypedStats,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct LayerStats {
199 pub id: Option<PropertyStats>,
200 pub properties: Vec<PropertyStats>,
201}
202
203#[derive(Debug, Clone, Default, PartialEq, Eq)]
205pub enum PropertyTypedStats {
206 #[default]
208 None,
209 Bool,
210 Signed {
211 min: i64,
212 max: i64,
213 },
214 Unsigned {
215 min: u64,
216 max: u64,
217 },
218 F32,
219 F64,
220 String {
221 shared_dict: SharedDictRole,
222 },
223}
224
225impl PropertyTypedStats {
226 #[must_use]
227 pub fn values_fit_u32(&self) -> bool {
228 match self {
229 Self::None | Self::Bool | Self::F32 | Self::F64 | Self::String { .. } => false,
230 Self::Signed { min, max } => *min >= 0 && u32::try_from(*max).is_ok(),
231 Self::Unsigned { max, .. } => u32::try_from(*max).is_ok(),
232 }
233 }
234
235 #[must_use]
239 pub fn values_fit_i32(&self) -> bool {
240 match self {
241 Self::None | Self::Bool | Self::F32 | Self::F64 | Self::String { .. } => false,
242 Self::Signed { min, max } => i32::try_from(*min).is_ok() && i32::try_from(*max).is_ok(),
243 Self::Unsigned { max, .. } => i32::try_from(*max).is_ok(),
244 }
245 }
246
247 #[must_use]
248 pub fn shared_dict(&self) -> SharedDictRole {
249 match self {
250 Self::String { shared_dict, .. } => shared_dict.clone(),
251 _ => SharedDictRole::None,
252 }
253 }
254
255 pub(crate) fn set_shared_dict(&mut self, role: SharedDictRole) {
256 match self {
257 Self::String { shared_dict, .. } => *shared_dict = role,
258 _ => debug_assert_eq!(role, SharedDictRole::None),
259 }
260 }
261
262 pub(crate) fn push(
263 &mut self,
264 prop: &PropValue,
265 column_idx: usize,
266 property_name: &str,
267 ) -> MltResult<bool> {
268 match prop {
269 PropValue::Bool(Some(_)) => {
270 self.merge_same_kind(Self::Bool, column_idx, property_name)?;
271 }
272 PropValue::I8(Some(v)) => {
273 self.merge_signed(i64::from(*v), column_idx, property_name)?;
274 }
275 PropValue::U8(Some(v)) => {
276 self.merge_unsigned(u64::from(*v), column_idx, property_name)?;
277 }
278 PropValue::I32(Some(v)) => {
279 self.merge_signed(i64::from(*v), column_idx, property_name)?;
280 }
281 PropValue::U32(Some(v)) => {
282 self.merge_unsigned(u64::from(*v), column_idx, property_name)?;
283 }
284 PropValue::I64(Some(v)) => self.merge_signed(*v, column_idx, property_name)?,
285 PropValue::U64(Some(v)) => self.merge_unsigned(*v, column_idx, property_name)?,
286 PropValue::F32(Some(_)) => {
287 self.merge_same_kind(Self::F32, column_idx, property_name)?;
288 }
289 PropValue::F64(Some(_)) => {
290 self.merge_same_kind(Self::F64, column_idx, property_name)?;
291 }
292 PropValue::Str(Some(_)) => self.merge_string(column_idx, property_name)?,
293 _ => return Ok(false),
294 }
295 Ok(true)
296 }
297
298 fn merge_signed(
299 &mut self,
300 value: i64,
301 column_idx: usize,
302 property_name: &str,
303 ) -> MltResult<()> {
304 match self {
305 Self::None => {
306 *self = Self::Signed {
307 min: value,
308 max: value,
309 };
310 }
311 Self::Signed { min, max } => {
312 *min = (*min).min(value);
313 *max = (*max).max(value);
314 }
315 _ => return mixed_prop_err(column_idx, property_name),
316 }
317 Ok(())
318 }
319
320 fn merge_unsigned(
321 &mut self,
322 value: u64,
323 column_idx: usize,
324 property_name: &str,
325 ) -> MltResult<()> {
326 match self {
327 Self::None => {
328 *self = Self::Unsigned {
329 min: value,
330 max: value,
331 };
332 }
333 Self::Unsigned { min, max } => {
334 *min = (*min).min(value);
335 *max = (*max).max(value);
336 }
337 _ => return mixed_prop_err(column_idx, property_name),
338 }
339 Ok(())
340 }
341
342 fn merge_string(&mut self, column_idx: usize, property_name: &str) -> MltResult<()> {
343 match self {
344 Self::None => {
345 *self = Self::String {
346 shared_dict: SharedDictRole::None,
347 };
348 }
349 Self::String { .. } => {}
350 _ => return mixed_prop_err(column_idx, property_name),
351 }
352 Ok(())
353 }
354
355 fn merge_same_kind(
356 &mut self,
357 kind: Self,
358 column_idx: usize,
359 property_name: &str,
360 ) -> MltResult<()> {
361 match self {
362 Self::None => *self = kind,
363 Self::Bool if matches!(kind, Self::Bool) => {}
364 Self::F32 if matches!(kind, Self::F32) => {}
365 Self::F64 if matches!(kind, Self::F64) => {}
366 _ => return mixed_prop_err(column_idx, property_name),
367 }
368 Ok(())
369 }
370}
371
372impl TileLayer {
373 #[hotpath::measure]
375 pub(crate) fn analyze(&self, allow_shared_dict: bool) -> MltResult<LayerStats> {
376 let mut property_bits = Vec::with_capacity(self.property_names().len());
377 let mut properties = self.analyze_properties(&mut property_bits)?;
378 let id = self.analyze_ids(&property_bits);
379 if allow_shared_dict {
380 self.group_string_properties(&mut properties);
381 }
382 Ok(LayerStats { id, properties })
383 }
384
385 fn analyze_ids(&self, property_bits: &[(BitVec<u8>, usize)]) -> Option<PropertyStats> {
386 let mut min = u64::MAX;
387 let mut max = 0u64;
388 let mut bits = BitVec::<u8>::with_capacity(self.feature_count());
389 for feature in self.features() {
390 if let Some(id) = feature.id() {
391 min = min.min(id);
392 max = max.max(id);
393 bits.push(true);
394 } else {
395 bits.push(false);
396 }
397 }
398 let presence = Presence::from_bits(&bits, property_bits);
399 if presence == Presence::AllNull {
400 None
401 } else {
402 Some(PropertyStats {
403 presence,
404 stats: PropertyTypedStats::Unsigned { min, max },
405 })
406 }
407 }
408
409 fn analyze_properties(
410 &self,
411 property_bits: &mut Vec<(BitVec<u8>, usize)>,
412 ) -> MltResult<Vec<PropertyStats>> {
413 self.property_names()
414 .iter()
415 .enumerate()
416 .map(|(col_idx, name)| -> MltResult<PropertyStats> {
417 let mut kind = None;
418 let mut stats = PropertyTypedStats::default();
419 let mut bits = BitVec::<u8>::with_capacity(self.feature_count());
420 for feature in self.features() {
421 let prop = feature.properties().get(col_idx);
422 if let Some(prop_kind) = prop.map(PropKind::from) {
423 match kind {
424 Some(kind) if kind != prop_kind => {
425 return mixed_prop_err(col_idx, name.as_str());
426 }
427 None => kind = Some(prop_kind),
428 _ => {}
429 }
430 }
431 if let Some(prop) = prop
432 && stats.push(prop, col_idx, name)?
433 {
434 bits.push(true);
435 } else {
436 bits.push(false);
437 }
438 }
439
440 let presence = Presence::from_bits(&bits, property_bits);
441 if presence == Presence::Mixed {
442 property_bits.push((bits, col_idx));
443 }
444 Ok(PropertyStats { presence, stats })
445 })
446 .collect()
447 }
448}
449
450#[inline]
451fn mixed_prop_err<T>(column_idx: usize, property_name: &str) -> MltResult<T> {
452 Err(MltError::MixedPropertyTypes(
453 column_idx,
454 property_name.to_owned(),
455 ))
456}