1use std::num::NonZeroU32;
9
10use crate::{MltError, MltResult};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Extent(NonZeroU32);
18
19impl Extent {
20 pub fn new(value: u32) -> MltResult<Self> {
21 NonZeroU32::new(value)
22 .map(Self)
23 .ok_or(MltError::InvalidExtent(value))
24 }
25
26 #[must_use]
27 pub fn get(self) -> u32 {
28 self.0.get()
29 }
30}
31
32impl From<Extent> for NonZeroU32 {
33 fn from(value: Extent) -> Self {
34 value.0
35 }
36}
37
38#[derive(Debug, Clone, PartialEq)]
44pub struct TileLayer {
45 pub(crate) name: String,
46 pub(crate) extent: Extent,
47 pub(crate) property_names: Vec<String>,
49 pub(crate) property_kinds: Vec<PropKind>,
51 pub(crate) features: Vec<TileFeature>,
52}
53
54#[derive(Debug, Clone, PartialEq)]
56pub struct TileFeature {
57 pub(crate) id: Option<u64>,
58 pub(crate) geometry: geo_types::Geometry<i32>,
60 pub(crate) properties: Vec<PropValue>,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub struct PropertyKey(usize);
67
68impl PropertyKey {
69 #[must_use]
70 pub fn index(self) -> usize {
71 self.0
72 }
73}
74
75impl TileLayer {
76 pub fn new(name: impl Into<String>, extent: u32) -> MltResult<Self> {
77 Self::with_capacity(name, extent, 0)
78 }
79
80 pub fn with_capacity(name: impl Into<String>, extent: u32, features: usize) -> MltResult<Self> {
81 let name = name.into();
82 validate_layer_name(&name)?;
83 let extent = Extent::new(extent)?;
84 Ok(Self {
85 name,
86 extent,
87 property_names: Vec::new(),
88 property_kinds: Vec::new(),
89 features: Vec::with_capacity(features),
90 })
91 }
92
93 pub(crate) fn from_parts(
94 name: impl Into<String>,
95 extent: u32,
96 property_names: Vec<String>,
97 features: Vec<TileFeature>,
98 ) -> MltResult<Self> {
99 let name = name.into();
100 validate_layer_name(&name)?;
101 let extent = Extent::new(extent)?;
102 validate_property_names(&property_names)?;
103 let property_kinds = infer_property_kinds(&property_names, &features)?;
104 let layer = Self {
105 name,
106 extent,
107 property_names,
108 property_kinds,
109 features,
110 };
111 Ok(layer)
112 }
113
114 #[must_use]
115 pub fn name(&self) -> &str {
116 &self.name
117 }
118
119 #[must_use]
120 pub fn extent(&self) -> Extent {
121 self.extent
122 }
123
124 #[must_use]
125 pub fn property_names(&self) -> &[String] {
126 &self.property_names
127 }
128
129 #[must_use]
130 pub fn features(&self) -> &[TileFeature] {
131 &self.features
132 }
133
134 #[must_use]
135 pub(crate) fn features_mut(&mut self) -> &mut [TileFeature] {
136 &mut self.features
137 }
138
139 #[must_use]
140 pub fn feature_count(&self) -> usize {
141 self.features.len()
142 }
143
144 pub fn add_property(
145 &mut self,
146 name: impl Into<String>,
147 kind: PropKind,
148 ) -> MltResult<PropertyKey> {
149 let name = name.into();
150 if self.property_names.contains(&name) {
151 return Err(MltError::DuplicatePropertyName(name));
152 }
153 for feature in &mut self.features {
154 feature.properties.push(PropValue::null(kind));
155 }
156 self.property_names.push(name);
157 self.property_kinds.push(kind);
158 Ok(PropertyKey(self.property_names.len() - 1))
159 }
160
161 pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
162 self.validate_feature(&feature)?;
163 self.features.push(feature);
164 Ok(())
165 }
166
167 pub fn builder(name: impl Into<String>, extent: u32) -> MltResult<TileLayerBuilder> {
168 Ok(TileLayerBuilder {
169 layer: Self::new(name, extent)?,
170 })
171 }
172
173 fn validate_feature(&self, feature: &TileFeature) -> MltResult<()> {
174 let expected = self.property_names.len();
175 let actual = feature.properties.len();
176 if actual != expected {
177 return Err(MltError::PropertyLengthMismatch { expected, actual });
178 }
179 for (idx, prop) in feature.properties.iter().enumerate() {
180 let expected = self.property_kinds[idx];
181 let actual = PropKind::from(prop);
182 if actual != expected {
183 return Err(MltError::PropertyKindMismatch {
184 index: idx,
185 expected,
186 actual,
187 });
188 }
189 }
190 Ok(())
191 }
192}
193
194impl TileFeature {
195 #[must_use]
196 pub fn new(geometry: geo_types::Geometry<i32>) -> Self {
197 Self {
198 id: None,
199 geometry,
200 properties: Vec::new(),
201 }
202 }
203
204 #[must_use]
205 pub fn with_id(geometry: geo_types::Geometry<i32>, id: u64) -> Self {
206 Self {
207 id: Some(id),
208 geometry,
209 properties: Vec::new(),
210 }
211 }
212
213 #[must_use]
214 pub fn id(&self) -> Option<u64> {
215 self.id
216 }
217
218 #[must_use]
219 pub fn geometry(&self) -> &geo_types::Geometry<i32> {
220 &self.geometry
221 }
222
223 #[must_use]
224 pub fn properties(&self) -> &[PropValue] {
225 &self.properties
226 }
227
228 #[must_use]
229 pub(crate) fn properties_mut(&mut self) -> &mut [PropValue] {
230 &mut self.properties
231 }
232
233 pub fn set_property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<()> {
234 let Some(prop) = self.properties.get_mut(key.index()) else {
235 return Err(MltError::PropertyLengthMismatch {
236 expected: key.index() + 1,
237 actual: self.properties.len(),
238 });
239 };
240 let expected = PropKind::from(&*prop);
241 let actual = PropKind::from(&value);
242 if actual != expected {
243 return Err(MltError::PropertyKindMismatch {
244 index: key.index(),
245 expected,
246 actual,
247 });
248 }
249 *prop = value;
250 Ok(())
251 }
252}
253
254pub struct TileLayerBuilder {
255 layer: TileLayer,
256}
257
258impl TileLayerBuilder {
259 pub fn add_property(
260 &mut self,
261 name: impl Into<String>,
262 kind: PropKind,
263 ) -> MltResult<PropertyKey> {
264 self.layer.add_property(name, kind)
265 }
266
267 pub fn feature(&mut self, geometry: geo_types::Geometry<i32>) -> TileFeatureBuilder<'_> {
268 let properties = self
269 .layer
270 .property_kinds
271 .iter()
272 .copied()
273 .map(PropValue::null)
274 .collect();
275 TileFeatureBuilder {
276 layer: self,
277 feature: TileFeature {
278 id: None,
279 geometry,
280 properties,
281 },
282 }
283 }
284
285 pub fn push_feature(&mut self, feature: TileFeature) -> MltResult<()> {
286 self.layer.push_feature(feature)
287 }
288
289 #[must_use]
290 pub fn finish(self) -> TileLayer {
291 self.layer
292 }
293}
294
295pub struct TileFeatureBuilder<'a> {
296 layer: &'a mut TileLayerBuilder,
297 feature: TileFeature,
298}
299
300impl TileFeatureBuilder<'_> {
301 pub fn id(&mut self, id: Option<u64>) -> &mut Self {
302 self.feature.id = id;
303 self
304 }
305
306 pub fn property(&mut self, key: PropertyKey, value: PropValue) -> MltResult<&mut Self> {
307 self.feature.set_property(key, value)?;
308 Ok(self)
309 }
310
311 pub fn finish(self) -> MltResult<()> {
312 self.layer.push_feature(self.feature)
313 }
314}
315
316#[derive(Debug, Clone, PartialEq)]
324pub enum PropValue {
325 Bool(Option<bool>),
326 I8(Option<i8>),
327 U8(Option<u8>),
328 I32(Option<i32>),
329 U32(Option<u32>),
330 I64(Option<i64>),
331 U64(Option<u64>),
332 F32(Option<f32>),
333 F64(Option<f64>),
334 Str(Option<String>),
335}
336
337impl PropValue {
338 #[must_use]
339 pub fn kind(&self) -> PropKind {
340 self.into()
341 }
342
343 #[must_use]
344 pub fn is_null(&self) -> bool {
345 match self {
346 Self::Bool(v) => v.is_none(),
347 Self::I8(v) => v.is_none(),
348 Self::U8(v) => v.is_none(),
349 Self::I32(v) => v.is_none(),
350 Self::U32(v) => v.is_none(),
351 Self::I64(v) => v.is_none(),
352 Self::U64(v) => v.is_none(),
353 Self::F32(v) => v.is_none(),
354 Self::F64(v) => v.is_none(),
355 Self::Str(v) => v.is_none(),
356 }
357 }
358
359 #[must_use]
360 pub fn null(kind: PropKind) -> Self {
361 match kind {
362 PropKind::Bool => Self::Bool(None),
363 PropKind::I8 => Self::I8(None),
364 PropKind::U8 => Self::U8(None),
365 PropKind::I32 => Self::I32(None),
366 PropKind::U32 => Self::U32(None),
367 PropKind::I64 => Self::I64(None),
368 PropKind::U64 => Self::U64(None),
369 PropKind::F32 => Self::F32(None),
370 PropKind::F64 => Self::F64(None),
371 PropKind::Str => Self::Str(None),
372 }
373 }
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::IntoStaticStr)]
377#[strum(serialize_all = "lowercase")]
378pub enum PropKind {
379 Bool,
380 I8,
381 U8,
382 I32,
383 U32,
384 I64,
385 U64,
386 F32,
387 F64,
388 Str,
389}
390
391impl From<&PropValue> for PropKind {
392 fn from(prop: &PropValue) -> Self {
393 match prop {
394 PropValue::Bool(_) => Self::Bool,
395 PropValue::I8(_) => Self::I8,
396 PropValue::U8(_) => Self::U8,
397 PropValue::I32(_) => Self::I32,
398 PropValue::U32(_) => Self::U32,
399 PropValue::I64(_) => Self::I64,
400 PropValue::U64(_) => Self::U64,
401 PropValue::F32(_) => Self::F32,
402 PropValue::F64(_) => Self::F64,
403 PropValue::Str(_) => Self::Str,
404 }
405 }
406}
407
408fn validate_layer_name(name: &str) -> MltResult<()> {
409 if name.is_empty() {
410 Err(MltError::MissingLayerName)
411 } else {
412 Ok(())
413 }
414}
415
416fn validate_property_names(names: &[String]) -> MltResult<()> {
417 for (i, name) in names.iter().enumerate() {
420 if names[..i].iter().any(|n| n == name) {
421 return Err(MltError::DuplicatePropertyName(name.clone()));
422 }
423 }
424 Ok(())
425}
426
427fn infer_property_kinds(names: &[String], features: &[TileFeature]) -> MltResult<Vec<PropKind>> {
428 let mut kinds = vec![None; names.len()];
429 for feature in features {
430 let expected = names.len();
431 let actual = feature.properties.len();
432 if actual != expected {
433 return Err(MltError::PropertyLengthMismatch { expected, actual });
434 }
435 for (idx, prop) in feature.properties.iter().enumerate() {
436 let actual = PropKind::from(prop);
437 match kinds[idx] {
438 Some(expected) if expected != actual => {
439 return Err(MltError::PropertyKindMismatch {
440 index: idx,
441 expected,
442 actual,
443 });
444 }
445 None => kinds[idx] = Some(actual),
446 _ => {}
447 }
448 }
449 }
450 Ok(kinds
451 .into_iter()
452 .map(|kind| kind.unwrap_or(PropKind::Str))
453 .collect())
454}
455
456#[cfg(test)]
457mod tests {
458 use geo_types::{Geometry, Point};
459
460 use super::*;
461
462 fn point_feature(properties: Vec<PropValue>) -> TileFeature {
463 TileFeature {
464 id: None,
465 geometry: Geometry::Point(Point::new(0, 0)),
466 properties,
467 }
468 }
469
470 #[test]
471 fn tile_layer_constructor_rejects_empty_name() {
472 assert!(matches!(
473 TileLayer::new("", 4096),
474 Err(MltError::MissingLayerName)
475 ));
476 }
477
478 #[test]
479 fn tile_layer_constructor_rejects_zero_extent() {
480 assert!(matches!(
481 TileLayer::new("layer", 0),
482 Err(MltError::InvalidExtent(0))
483 ));
484 }
485
486 #[test]
487 fn add_property_rejects_duplicate_names() {
488 let mut layer = TileLayer::new("layer", 4096).unwrap();
489 layer.add_property("name", PropKind::Str).unwrap();
490 assert!(matches!(
491 layer.add_property("name", PropKind::Str),
492 Err(MltError::DuplicatePropertyName(name)) if name == "name"
493 ));
494 }
495
496 #[test]
497 fn from_parts_allows_empty_property_name() {
498 assert!(TileLayer::from_parts("layer", 4096, vec![String::new()], vec![]).is_ok());
500 }
501
502 #[test]
503 fn from_parts_rejects_duplicate_property_name() {
504 assert!(matches!(
505 TileLayer::from_parts("layer", 4096, vec!["dup".into(), "dup".into()], vec![]),
506 Err(MltError::DuplicatePropertyName(name)) if name == "dup"
507 ));
508 }
509
510 #[test]
511 fn push_feature_validates_property_count() {
512 let mut layer = TileLayer::new("layer", 4096).unwrap();
513 layer.add_property("name", PropKind::Str).unwrap();
514 assert!(matches!(
515 layer.push_feature(point_feature(vec![])),
516 Err(MltError::PropertyLengthMismatch {
517 expected: 1,
518 actual: 0
519 })
520 ));
521 }
522
523 #[test]
524 fn push_feature_validates_property_kind() {
525 let mut layer = TileLayer::new("layer", 4096).unwrap();
526 layer.add_property("flag", PropKind::Bool).unwrap();
527 layer
528 .push_feature(point_feature(vec![PropValue::Bool(Some(true))]))
529 .unwrap();
530 assert!(matches!(
531 layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
532 Err(MltError::PropertyKindMismatch {
533 index: 0,
534 expected: PropKind::Bool,
535 actual: PropKind::I32,
536 })
537 ));
538 }
539
540 #[test]
541 fn declared_property_kind_is_enforced_for_first_feature() {
542 let mut layer = TileLayer::new("layer", 4096).unwrap();
543 layer.add_property("flag", PropKind::Bool).unwrap();
544 assert!(matches!(
545 layer.push_feature(point_feature(vec![PropValue::I32(Some(1))])),
546 Err(MltError::PropertyKindMismatch {
547 index: 0,
548 expected: PropKind::Bool,
549 actual: PropKind::I32,
550 })
551 ));
552 }
553
554 #[test]
555 fn builder_uses_declared_property_kind_for_defaults() {
556 let mut builder = TileLayer::builder("layer", 4096).unwrap();
557 let flag = builder.add_property("flag", PropKind::Bool).unwrap();
558 let mut feature = builder.feature(Geometry::Point(Point::new(0, 0)));
559 feature.property(flag, PropValue::Bool(Some(true))).unwrap();
560 feature.finish().unwrap();
561 let layer = builder.finish();
562
563 assert_eq!(
564 layer.features()[0].properties()[0],
565 PropValue::Bool(Some(true))
566 );
567 }
568}