1use crate::vector::geometry::Geometry;
6use core::fmt;
7use serde::{Deserialize, Serialize};
8#[cfg(feature = "std")]
9use serde_json::Value as JsonValue;
10
11#[cfg(feature = "std")]
12use std::collections::HashMap;
13#[cfg(feature = "std")]
14use std::string::String;
15
16#[cfg(all(not(feature = "std"), feature = "alloc"))]
17use alloc::{
18 collections::BTreeMap as HashMap,
19 string::{String, ToString},
20 vec::Vec,
21};
22
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct Feature {
26 pub id: Option<FeatureId>,
28 pub geometry: Option<Geometry>,
30 pub properties: HashMap<String, FieldValue>,
32}
33
34impl Feature {
35 #[must_use]
37 pub fn new(geometry: Geometry) -> Self {
38 Self {
39 id: None,
40 geometry: Some(geometry),
41 properties: HashMap::new(),
42 }
43 }
44
45 #[must_use]
47 pub fn with_id(id: FeatureId, geometry: Geometry) -> Self {
48 Self {
49 id: Some(id),
50 geometry: Some(geometry),
51 properties: HashMap::new(),
52 }
53 }
54
55 #[must_use]
57 pub fn new_attribute_only() -> Self {
58 Self {
59 id: None,
60 geometry: None,
61 properties: HashMap::new(),
62 }
63 }
64
65 pub fn set_property<S: Into<String>>(&mut self, key: S, value: FieldValue) {
67 self.properties.insert(key.into(), value);
68 }
69
70 #[must_use]
72 pub fn get_property(&self, key: &str) -> Option<&FieldValue> {
73 self.properties.get(key)
74 }
75
76 pub fn remove_property(&mut self, key: &str) -> Option<FieldValue> {
78 self.properties.remove(key)
79 }
80
81 #[must_use]
83 pub const fn has_geometry(&self) -> bool {
84 self.geometry.is_some()
85 }
86
87 #[must_use]
89 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
90 self.geometry
91 .as_ref()
92 .and_then(super::geometry::Geometry::bounds)
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
98#[serde(untagged)]
99pub enum FeatureId {
100 Integer(i64),
102 String(String),
104}
105
106impl From<i64> for FeatureId {
107 fn from(id: i64) -> Self {
108 Self::Integer(id)
109 }
110}
111
112impl From<String> for FeatureId {
113 fn from(id: String) -> Self {
114 Self::String(id)
115 }
116}
117
118impl From<&str> for FeatureId {
119 fn from(id: &str) -> Self {
120 Self::String(id.to_string())
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137#[serde(untagged)]
138pub enum FieldValue {
139 Null,
141 Bool(bool),
143 Integer(i64),
145 UInteger(u64),
147 Float(f64),
149 String(String),
151 Array(Vec<FieldValue>),
153 Object(HashMap<String, FieldValue>),
155 #[cfg(feature = "std")]
159 Date(time::Date),
160 Blob(Vec<u8>),
162}
163
164impl FieldValue {
165 #[must_use]
167 pub const fn is_null(&self) -> bool {
168 matches!(self, Self::Null)
169 }
170
171 #[cfg(feature = "std")]
176 #[must_use]
177 pub fn to_json_value(&self) -> JsonValue {
178 match self {
179 Self::Null => JsonValue::Null,
180 Self::Bool(b) => JsonValue::Bool(*b),
181 Self::Integer(i) => JsonValue::Number((*i).into()),
182 Self::UInteger(u) => JsonValue::Number((*u).into()),
183 Self::Float(f) => {
184 JsonValue::Number(serde_json::Number::from_f64(*f).unwrap_or_else(|| 0.into()))
185 }
186 Self::String(s) => JsonValue::String(s.clone()),
187 Self::Array(arr) => JsonValue::Array(arr.iter().map(Self::to_json_value).collect()),
188 Self::Object(obj) => JsonValue::Object(
189 obj.iter()
190 .map(|(k, v)| (k.clone(), v.to_json_value()))
191 .collect(),
192 ),
193 #[cfg(feature = "std")]
194 Self::Date(d) => JsonValue::String(d.to_string()),
195 Self::Blob(bytes) => JsonValue::Array(
196 bytes
197 .iter()
198 .map(|b| JsonValue::Number(u64::from(*b).into()))
199 .collect(),
200 ),
201 }
202 }
203
204 #[cfg(feature = "std")]
208 #[must_use]
209 pub fn to_json(&self) -> JsonValue {
210 self.to_json_value()
211 }
212
213 #[cfg(feature = "std")]
219 #[must_use]
220 pub fn from_json(value: &JsonValue) -> Self {
221 match value {
222 JsonValue::Null => Self::Null,
223 JsonValue::Bool(b) => Self::Bool(*b),
224 JsonValue::Number(n) => {
225 if let Some(i) = n.as_i64() {
226 Self::Integer(i)
227 } else if let Some(u) = n.as_u64() {
228 Self::UInteger(u)
229 } else if let Some(f) = n.as_f64() {
230 Self::Float(f)
231 } else {
232 Self::Null
233 }
234 }
235 JsonValue::String(s) => Self::String(s.clone()),
236 JsonValue::Array(arr) => Self::Array(arr.iter().map(Self::from_json).collect()),
237 JsonValue::Object(obj) => Self::Object(
238 obj.iter()
239 .map(|(k, v)| (k.clone(), Self::from_json(v)))
240 .collect(),
241 ),
242 }
243 }
244
245 #[must_use]
247 pub fn as_string(&self) -> Option<&str> {
248 match self {
249 Self::String(s) => Some(s),
250 _ => None,
251 }
252 }
253
254 #[must_use]
256 pub const fn as_i64(&self) -> Option<i64> {
257 match self {
258 Self::Integer(i) => Some(*i),
259 _ => None,
260 }
261 }
262
263 #[must_use]
265 pub const fn as_u64(&self) -> Option<u64> {
266 match self {
267 Self::UInteger(u) => Some(*u),
268 _ => None,
269 }
270 }
271
272 #[must_use]
276 pub fn as_f64(&self) -> Option<f64> {
277 match self {
278 Self::Float(f) => Some(*f),
279 Self::Integer(i) => Some(*i as f64),
280 Self::UInteger(u) => Some(*u as f64),
281 _ => None,
282 }
283 }
284
285 #[must_use]
287 pub const fn as_bool(&self) -> Option<bool> {
288 match self {
289 Self::Bool(b) => Some(*b),
290 _ => None,
291 }
292 }
293
294 #[must_use]
296 pub fn as_blob(&self) -> Option<&[u8]> {
297 match self {
298 Self::Blob(b) => Some(b),
299 _ => None,
300 }
301 }
302
303 #[cfg(feature = "std")]
305 #[must_use]
306 pub const fn as_date(&self) -> Option<time::Date> {
307 match self {
308 Self::Date(d) => Some(*d),
309 _ => None,
310 }
311 }
312}
313
314impl fmt::Display for FieldValue {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 match self {
317 Self::Null => f.write_str("null"),
318 Self::Bool(b) => write!(f, "{b}"),
319 Self::Integer(i) => write!(f, "{i}"),
320 Self::UInteger(u) => write!(f, "{u}"),
321 Self::Float(v) => write!(f, "{v}"),
322 Self::String(s) => write!(f, "\"{s}\""),
323 Self::Array(arr) => {
324 write!(f, "[")?;
325 for (i, v) in arr.iter().enumerate() {
326 if i > 0 {
327 write!(f, ", ")?;
328 }
329 write!(f, "{v}")?;
330 }
331 write!(f, "]")
332 }
333 Self::Object(obj) => {
334 write!(f, "{{")?;
335 let mut first = true;
336 for (k, v) in obj.iter() {
337 if !first {
338 write!(f, ", ")?;
339 }
340 first = false;
341 write!(f, "\"{k}\": {v}")?;
342 }
343 write!(f, "}}")
344 }
345 #[cfg(feature = "std")]
346 Self::Date(d) => write!(f, "{d}"),
347 Self::Blob(b) => write!(f, "Blob({} bytes)", b.len()),
348 }
349 }
350}
351
352#[cfg(feature = "std")]
353impl From<serde_json::Value> for FieldValue {
354 fn from(v: serde_json::Value) -> Self {
355 Self::from_json(&v)
356 }
357}
358
359impl From<bool> for FieldValue {
360 fn from(b: bool) -> Self {
361 Self::Bool(b)
362 }
363}
364
365impl From<i64> for FieldValue {
366 fn from(i: i64) -> Self {
367 Self::Integer(i)
368 }
369}
370
371impl From<i32> for FieldValue {
372 fn from(i: i32) -> Self {
373 Self::Integer(i64::from(i))
374 }
375}
376
377impl From<u64> for FieldValue {
378 fn from(u: u64) -> Self {
379 Self::UInteger(u)
380 }
381}
382
383impl From<u32> for FieldValue {
384 fn from(u: u32) -> Self {
385 Self::UInteger(u64::from(u))
386 }
387}
388
389impl From<f64> for FieldValue {
390 fn from(f: f64) -> Self {
391 Self::Float(f)
392 }
393}
394
395impl From<f32> for FieldValue {
396 fn from(f: f32) -> Self {
397 Self::Float(f64::from(f))
398 }
399}
400
401impl From<String> for FieldValue {
402 fn from(s: String) -> Self {
403 Self::String(s)
404 }
405}
406
407impl From<&str> for FieldValue {
408 fn from(s: &str) -> Self {
409 Self::String(s.to_string())
410 }
411}
412
413#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub struct FeatureCollection {
416 pub features: Vec<Feature>,
418 #[serde(skip_serializing_if = "Option::is_none")]
420 pub metadata: Option<HashMap<String, FieldValue>>,
421}
422
423impl FeatureCollection {
424 #[must_use]
426 pub const fn new(features: Vec<Feature>) -> Self {
427 Self {
428 features,
429 metadata: None,
430 }
431 }
432
433 #[must_use]
435 pub const fn empty() -> Self {
436 Self {
437 features: Vec::new(),
438 metadata: None,
439 }
440 }
441
442 pub fn push(&mut self, feature: Feature) {
444 self.features.push(feature);
445 }
446
447 #[must_use]
449 pub fn len(&self) -> usize {
450 self.features.len()
451 }
452
453 #[must_use]
455 pub fn is_empty(&self) -> bool {
456 self.features.is_empty()
457 }
458
459 #[must_use]
461 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
462 if self.features.is_empty() {
463 return None;
464 }
465
466 let mut min_x = f64::INFINITY;
467 let mut min_y = f64::INFINITY;
468 let mut max_x = f64::NEG_INFINITY;
469 let mut max_y = f64::NEG_INFINITY;
470
471 for feature in &self.features {
472 if let Some((x_min, y_min, x_max, y_max)) = feature.bounds() {
473 min_x = min_x.min(x_min);
474 min_y = min_y.min(y_min);
475 max_x = max_x.max(x_max);
476 max_y = max_y.max(y_max);
477 }
478 }
479
480 if min_x.is_infinite() {
481 None
482 } else {
483 Some((min_x, min_y, max_x, max_y))
484 }
485 }
486}
487
488impl Default for FeatureCollection {
489 fn default() -> Self {
490 Self::empty()
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 #![allow(clippy::expect_used)]
497
498 use super::*;
499 use crate::vector::geometry::Point;
500
501 #[test]
502 fn test_feature_creation() {
503 let point = Point::new(1.0, 2.0);
504 let mut feature = Feature::new(Geometry::Point(point));
505
506 feature.set_property("name", FieldValue::String("Test Point".to_string()));
507 feature.set_property("value", FieldValue::Integer(42));
508
509 assert!(feature.has_geometry());
510 assert_eq!(feature.properties.len(), 2);
511
512 let name = feature.get_property("name");
513 assert!(name.is_some());
514 assert_eq!(name.and_then(|v| v.as_string()), Some("Test Point"));
515
516 let value = feature.get_property("value");
517 assert!(value.is_some());
518 assert_eq!(value.and_then(|v| v.as_i64()), Some(42));
519 }
520
521 #[test]
522 fn test_feature_id() {
523 let point = Point::new(1.0, 2.0);
524 let feature = Feature::with_id(FeatureId::Integer(123), Geometry::Point(point));
525
526 assert_eq!(feature.id, Some(FeatureId::Integer(123)));
527 }
528
529 #[test]
530 fn test_field_value_conversions() {
531 let pv_int = FieldValue::from(42_i64);
532 assert_eq!(pv_int.as_i64(), Some(42));
533
534 let pv_float = FieldValue::from(2.78_f64);
535 assert_eq!(pv_float.as_f64(), Some(2.78));
536
537 let pv_bool = FieldValue::from(true);
538 assert_eq!(pv_bool.as_bool(), Some(true));
539
540 let pv_str = FieldValue::from("hello");
541 assert_eq!(pv_str.as_string(), Some("hello"));
542 }
543
544 #[test]
545 fn test_feature_collection() {
546 let mut collection = FeatureCollection::empty();
547 assert!(collection.is_empty());
548
549 let point1 = Point::new(1.0, 2.0);
550 let feature1 = Feature::new(Geometry::Point(point1));
551 collection.push(feature1);
552
553 let point2 = Point::new(3.0, 4.0);
554 let feature2 = Feature::new(Geometry::Point(point2));
555 collection.push(feature2);
556
557 assert_eq!(collection.len(), 2);
558 assert!(!collection.is_empty());
559
560 let bounds = collection.bounds();
561 assert!(bounds.is_some());
562 let (min_x, min_y, max_x, max_y) = bounds.expect("bounds calculation failed");
563 assert_eq!(min_x, 1.0);
564 assert_eq!(min_y, 2.0);
565 assert_eq!(max_x, 3.0);
566 assert_eq!(max_y, 4.0);
567 }
568
569 #[test]
570 fn test_fieldvalue_variants_exhaustive() {
571 let _ = FieldValue::Null;
572 let _ = FieldValue::Bool(true);
573 let _ = FieldValue::Integer(-1);
574 let _ = FieldValue::UInteger(1u64);
575 let _ = FieldValue::Float(1.5);
576 #[cfg(feature = "std")]
577 let _ = FieldValue::Date(
578 time::Date::from_calendar_date(2024, time::Month::January, 1).expect("valid date"),
579 );
580 let _ = FieldValue::Blob(vec![0u8]);
581 let _ = FieldValue::String("x".into());
582 let _ = FieldValue::Array(vec![]);
583 let _ = FieldValue::Object(Default::default());
584 }
585
586 #[test]
587 #[cfg(feature = "std")]
588 fn test_fieldvalue_to_json_value_all_variants() {
589 assert_eq!(FieldValue::Null.to_json_value(), serde_json::Value::Null);
590 assert_eq!(
591 FieldValue::Bool(true).to_json_value(),
592 serde_json::Value::Bool(true)
593 );
594 let blob_json = FieldValue::Blob(vec![1, 2]).to_json_value();
595 assert!(matches!(blob_json, serde_json::Value::Array(_)));
596 if let serde_json::Value::Array(a) = blob_json {
597 assert_eq!(a.len(), 2);
598 }
599 let date =
601 time::Date::from_calendar_date(2024, time::Month::March, 15).expect("valid date");
602 let json = FieldValue::Date(date).to_json_value();
603 assert!(matches!(json, serde_json::Value::String(_)));
604 if let serde_json::Value::String(s) = json {
605 assert!(s.contains("2024"), "date string should contain year");
606 }
607 }
608
609 #[test]
610 #[cfg(feature = "std")]
611 fn test_from_json_value() {
612 let json = serde_json::json!({
613 "name": "test",
614 "count": 42,
615 "flag": true,
616 "score": 3.5,
617 "items": [1, 2, 3]
618 });
619 let fv = FieldValue::from(json);
620 assert!(matches!(fv, FieldValue::Object(_)));
621 if let FieldValue::Object(map) = fv {
622 assert!(map.contains_key("name"));
623 assert!(map.contains_key("count"));
624 }
625 }
626
627 #[test]
628 #[cfg(feature = "std")]
629 fn test_blob_accessor() {
630 let fv = FieldValue::Blob(vec![0xDE, 0xAD, 0xBE, 0xEF]);
631 assert_eq!(fv.as_blob(), Some([0xDE, 0xAD, 0xBE, 0xEFu8].as_ref()));
632 }
633
634 #[test]
635 fn test_display_fieldvalue_each_variant() {
636 assert_eq!(FieldValue::Null.to_string(), "null");
637 assert_eq!(FieldValue::Bool(true).to_string(), "true");
638 assert_eq!(FieldValue::Bool(false).to_string(), "false");
639 assert_eq!(FieldValue::Integer(-42).to_string(), "-42");
640 assert_eq!(FieldValue::UInteger(99).to_string(), "99");
641 assert_eq!(FieldValue::String("hello".into()).to_string(), "\"hello\"");
642 assert_eq!(FieldValue::Blob(vec![1, 2, 3]).to_string(), "Blob(3 bytes)");
643 assert_eq!(
644 FieldValue::Array(vec![FieldValue::Integer(1)]).to_string(),
645 "[1]"
646 );
647 }
648}