1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::error::{GeoJsonError, Result};
9use crate::types::{BBox, Crs, ForeignMembers, Geometry};
10
11#[derive(Debug, Clone, PartialEq, Serialize)]
24#[serde(untagged)]
25pub enum FeatureId {
26 String(String),
28 Number(i64),
30}
31
32impl<'de> Deserialize<'de> for FeatureId {
33 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
34 where
35 D: serde::Deserializer<'de>,
36 {
37 use serde::de::Error as _;
38 let value = serde_json::Value::deserialize(deserializer)?;
39 match &value {
40 serde_json::Value::String(s) => Ok(FeatureId::String(s.clone())),
41 serde_json::Value::Number(n) => n
42 .as_i64()
43 .map(FeatureId::Number)
44 .ok_or_else(|| D::Error::custom("FeatureId number must be representable as i64")),
45 _ => Err(D::Error::custom("FeatureId must be a string or number")),
46 }
47 }
48}
49
50impl FeatureId {
51 pub fn string<S: Into<String>>(s: S) -> Self {
53 Self::String(s.into())
54 }
55
56 pub const fn number(n: i64) -> Self {
58 Self::Number(n)
59 }
60
61 #[must_use]
63 pub fn as_string(&self) -> String {
64 match self {
65 Self::String(s) => s.clone(),
66 Self::Number(n) => n.to_string(),
67 }
68 }
69}
70
71impl From<String> for FeatureId {
72 fn from(s: String) -> Self {
73 Self::String(s)
74 }
75}
76
77impl From<&str> for FeatureId {
78 fn from(s: &str) -> Self {
79 Self::String(s.to_string())
80 }
81}
82
83impl From<i64> for FeatureId {
84 fn from(n: i64) -> Self {
85 Self::Number(n)
86 }
87}
88
89impl From<i32> for FeatureId {
90 fn from(n: i32) -> Self {
91 Self::Number(i64::from(n))
92 }
93}
94
95impl From<u32> for FeatureId {
96 fn from(n: u32) -> Self {
97 Self::Number(i64::from(n))
98 }
99}
100
101pub type Properties = serde_json::Map<String, Value>;
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct Feature {
110 #[serde(rename = "type")]
112 pub feature_type: String,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub id: Option<FeatureId>,
117
118 pub geometry: Option<Geometry>,
120
121 pub properties: Option<Properties>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub bbox: Option<BBox>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub crs: Option<Crs>,
131
132 #[serde(flatten)]
134 pub foreign_members: Option<ForeignMembers>,
135}
136
137impl Feature {
138 pub fn new(geometry: Option<Geometry>, properties: Option<Properties>) -> Self {
140 Self {
141 feature_type: "Feature".to_string(),
142 id: None,
143 geometry,
144 properties,
145 bbox: None,
146 crs: None,
147 foreign_members: None,
148 }
149 }
150
151 pub fn with_id<I: Into<FeatureId>>(
153 id: I,
154 geometry: Option<Geometry>,
155 properties: Option<Properties>,
156 ) -> Self {
157 Self {
158 feature_type: "Feature".to_string(),
159 id: Some(id.into()),
160 geometry,
161 properties,
162 bbox: None,
163 crs: None,
164 foreign_members: None,
165 }
166 }
167
168 pub fn set_id<I: Into<FeatureId>>(&mut self, id: I) {
170 self.id = Some(id.into());
171 }
172
173 pub fn set_geometry(&mut self, geometry: Geometry) {
175 self.geometry = Some(geometry);
176 }
177
178 pub fn set_properties(&mut self, properties: Properties) {
180 self.properties = Some(properties);
181 }
182
183 pub fn add_property<K: Into<String>, V: Into<Value>>(&mut self, key: K, value: V) {
185 let props = self.properties.get_or_insert_with(Properties::new);
186 props.insert(key.into(), value.into());
187 }
188
189 #[must_use]
191 pub fn get_property(&self, key: &str) -> Option<&Value> {
192 self.properties.as_ref().and_then(|p| p.get(key))
193 }
194
195 pub fn set_bbox(&mut self, bbox: BBox) {
197 self.bbox = Some(bbox);
198 }
199
200 pub fn compute_bbox(&mut self) {
202 if let Some(ref geometry) = self.geometry {
203 self.bbox = geometry.compute_bbox();
204 }
205 }
206
207 pub fn set_crs(&mut self, crs: Crs) {
209 self.crs = Some(crs);
210 }
211
212 pub fn validate(&self) -> Result<()> {
214 if self.feature_type != "Feature" {
215 return Err(GeoJsonError::InvalidFeature {
216 message: format!(
217 "Invalid type: expected 'Feature', got '{}'",
218 self.feature_type
219 ),
220 feature_id: self.id.as_ref().map(|id| id.as_string()),
221 });
222 }
223
224 if let Some(ref geometry) = self.geometry {
225 geometry
226 .validate()
227 .map_err(|e| GeoJsonError::InvalidFeature {
228 message: format!("Invalid geometry: {e}"),
229 feature_id: self.id.as_ref().map(|id| id.as_string()),
230 })?;
231 }
232
233 if let Some(ref bbox) = self.bbox {
234 crate::types::geometry::validate_bbox(bbox)?;
235 }
236
237 Ok(())
238 }
239
240 #[must_use]
242 pub const fn has_geometry(&self) -> bool {
243 self.geometry.is_some()
244 }
245
246 #[must_use]
248 pub const fn has_properties(&self) -> bool {
249 self.properties.is_some()
250 }
251
252 #[must_use]
254 pub fn property_count(&self) -> usize {
255 self.properties.as_ref().map_or(0, |p| p.len())
256 }
257}
258
259impl Default for Feature {
260 fn default() -> Self {
261 Self::new(None, None)
262 }
263}
264
265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct FeatureCollection {
270 #[serde(rename = "type")]
272 pub collection_type: String,
273
274 pub features: Vec<Feature>,
276
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub bbox: Option<BBox>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub crs: Option<Crs>,
284
285 #[serde(flatten)]
287 pub foreign_members: Option<ForeignMembers>,
288}
289
290impl FeatureCollection {
291 pub fn new(features: Vec<Feature>) -> Self {
293 Self {
294 collection_type: "FeatureCollection".to_string(),
295 features,
296 bbox: None,
297 crs: None,
298 foreign_members: None,
299 }
300 }
301
302 pub fn empty() -> Self {
304 Self::new(Vec::new())
305 }
306
307 pub fn with_capacity(capacity: usize) -> Self {
309 Self {
310 collection_type: "FeatureCollection".to_string(),
311 features: Vec::with_capacity(capacity),
312 bbox: None,
313 crs: None,
314 foreign_members: None,
315 }
316 }
317
318 pub fn add_feature(&mut self, feature: Feature) {
320 self.features.push(feature);
321 }
322
323 pub fn add_features(&mut self, features: Vec<Feature>) {
325 self.features.extend(features);
326 }
327
328 pub fn set_bbox(&mut self, bbox: BBox) {
330 self.bbox = Some(bbox);
331 }
332
333 pub fn compute_bbox(&mut self) {
335 if self.features.is_empty() {
336 self.bbox = None;
337 return;
338 }
339
340 let mut min_x = f64::INFINITY;
341 let mut min_y = f64::INFINITY;
342 let mut max_x = f64::NEG_INFINITY;
343 let mut max_y = f64::NEG_INFINITY;
344
345 for feature in &self.features {
346 if let Some(ref geometry) = feature.geometry {
347 if let Some(bbox) = geometry.compute_bbox() {
348 if bbox.len() >= 4 {
349 min_x = min_x.min(bbox[0]);
350 min_y = min_y.min(bbox[1]);
351 max_x = max_x.max(bbox[2]);
352 max_y = max_y.max(bbox[3]);
353 }
354 }
355 }
356 }
357
358 if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite() {
359 self.bbox = Some(vec![min_x, min_y, max_x, max_y]);
360 }
361 }
362
363 pub fn set_crs(&mut self, crs: Crs) {
365 self.crs = Some(crs);
366 }
367
368 pub fn validate(&self) -> Result<()> {
370 if self.collection_type != "FeatureCollection" {
371 return Err(GeoJsonError::InvalidFeatureCollection {
372 message: format!(
373 "Invalid type: expected 'FeatureCollection', got '{}'",
374 self.collection_type
375 ),
376 });
377 }
378
379 for (i, feature) in self.features.iter().enumerate() {
380 feature
381 .validate()
382 .map_err(|e| GeoJsonError::validation_at(e.to_string(), format!("features/{i}")))?;
383 }
384
385 if let Some(ref bbox) = self.bbox {
386 crate::types::geometry::validate_bbox(bbox)?;
387 }
388
389 Ok(())
390 }
391
392 #[must_use]
394 pub fn len(&self) -> usize {
395 self.features.len()
396 }
397
398 #[must_use]
400 pub fn is_empty(&self) -> bool {
401 self.features.is_empty()
402 }
403
404 pub fn iter(&self) -> impl Iterator<Item = &Feature> {
406 self.features.iter()
407 }
408
409 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Feature> {
411 self.features.iter_mut()
412 }
413
414 pub fn filter<F>(&self, predicate: F) -> Self
416 where
417 F: Fn(&Feature) -> bool,
418 {
419 Self::new(
420 self.features
421 .iter()
422 .filter(|f| predicate(f))
423 .cloned()
424 .collect(),
425 )
426 }
427
428 pub fn with_property(&self, key: &str, value: &Value) -> Self {
430 self.filter(|f| f.properties.as_ref().and_then(|p| p.get(key)) == Some(value))
431 }
432
433 pub fn clear(&mut self) {
435 self.features.clear();
436 self.bbox = None;
437 }
438
439 pub fn retain<F>(&mut self, predicate: F)
441 where
442 F: FnMut(&Feature) -> bool,
443 {
444 self.features.retain(predicate);
445 }
446}
447
448impl Default for FeatureCollection {
449 fn default() -> Self {
450 Self::empty()
451 }
452}
453
454impl IntoIterator for FeatureCollection {
455 type Item = Feature;
456 type IntoIter = std::vec::IntoIter<Feature>;
457
458 fn into_iter(self) -> Self::IntoIter {
459 self.features.into_iter()
460 }
461}
462
463impl<'a> IntoIterator for &'a FeatureCollection {
464 type Item = &'a Feature;
465 type IntoIter = std::slice::Iter<'a, Feature>;
466
467 fn into_iter(self) -> Self::IntoIter {
468 self.features.iter()
469 }
470}
471
472impl<'a> IntoIterator for &'a mut FeatureCollection {
473 type Item = &'a mut Feature;
474 type IntoIter = std::slice::IterMut<'a, Feature>;
475
476 fn into_iter(self) -> Self::IntoIter {
477 self.features.iter_mut()
478 }
479}
480
481impl FromIterator<Feature> for FeatureCollection {
482 fn from_iter<T: IntoIterator<Item = Feature>>(iter: T) -> Self {
483 Self::new(iter.into_iter().collect())
484 }
485}
486
487#[cfg(test)]
488#[allow(clippy::panic)]
489mod tests {
490 use super::*;
491 use crate::types::geometry::Point;
492
493 #[test]
494 fn test_feature_id() {
495 let string_id = FeatureId::string("feature-1");
496 assert_eq!(string_id.as_string(), "feature-1");
497
498 let num_id = FeatureId::number(42);
499 assert_eq!(num_id.as_string(), "42");
500 }
501
502 #[test]
503 fn test_feature_creation() {
504 let point = Point::new_2d(-122.4, 37.8).expect("valid point");
505 let geometry = Geometry::Point(point);
506
507 let mut props = Properties::new();
508 props.insert(
509 "name".to_string(),
510 Value::String("San Francisco".to_string()),
511 );
512
513 let feature = Feature::new(Some(geometry), Some(props));
514 assert!(feature.has_geometry());
515 assert!(feature.has_properties());
516 assert_eq!(feature.property_count(), 1);
517 }
518
519 #[test]
520 fn test_feature_with_id() {
521 let point = Point::new_2d(0.0, 0.0).expect("valid point");
522 let geometry = Geometry::Point(point);
523
524 let feature = Feature::with_id("test-id", Some(geometry), None);
525 assert!(feature.id.is_some());
526 if let Some(FeatureId::String(id)) = &feature.id {
527 assert_eq!(id, "test-id");
528 } else {
529 panic!("Expected string ID");
530 }
531 }
532
533 #[test]
534 fn test_feature_properties() {
535 let mut feature = Feature::default();
536 feature.add_property("name", "Test");
537 feature.add_property("count", 42);
538
539 assert_eq!(feature.property_count(), 2);
540 assert!(feature.get_property("name").is_some());
541 }
542
543 #[test]
544 fn test_feature_validation() {
545 let point = Point::new_2d(-122.4, 37.8).expect("valid point");
546 let geometry = Geometry::Point(point);
547 let feature = Feature::new(Some(geometry), None);
548
549 assert!(feature.validate().is_ok());
550 }
551
552 #[test]
553 fn test_feature_collection_creation() {
554 let fc = FeatureCollection::empty();
555 assert!(fc.is_empty());
556 assert_eq!(fc.len(), 0);
557 }
558
559 #[test]
560 fn test_feature_collection_add() {
561 let mut fc = FeatureCollection::empty();
562
563 let point = Point::new_2d(0.0, 0.0).expect("valid point");
564 let geometry = Geometry::Point(point);
565 let feature = Feature::new(Some(geometry), None);
566
567 fc.add_feature(feature);
568 assert_eq!(fc.len(), 1);
569 assert!(!fc.is_empty());
570 }
571
572 #[test]
573 fn test_feature_collection_compute_bbox() {
574 let mut fc = FeatureCollection::empty();
575
576 let p1 = Point::new_2d(0.0, 0.0).expect("valid point");
577 let p2 = Point::new_2d(10.0, 10.0).expect("valid point");
578
579 fc.add_feature(Feature::new(Some(Geometry::Point(p1)), None));
580 fc.add_feature(Feature::new(Some(Geometry::Point(p2)), None));
581
582 fc.compute_bbox();
583 assert!(fc.bbox.is_some());
584
585 if let Some(bbox) = &fc.bbox {
586 assert_eq!(bbox[0], 0.0);
587 assert_eq!(bbox[1], 0.0);
588 assert_eq!(bbox[2], 10.0);
589 assert_eq!(bbox[3], 10.0);
590 }
591 }
592
593 #[test]
594 fn test_feature_collection_filter() {
595 let mut fc = FeatureCollection::empty();
596
597 for i in 0..5 {
598 let point = Point::new_2d(f64::from(i), f64::from(i)).expect("valid point");
599 let mut feature = Feature::new(Some(Geometry::Point(point)), None);
600 feature.add_property("id", i);
601 fc.add_feature(feature);
602 }
603
604 let filtered = fc.with_property("id", &Value::Number(serde_json::Number::from(2)));
605 assert_eq!(filtered.len(), 1);
606 }
607
608 #[test]
609 fn test_feature_collection_iterator() {
610 let mut fc = FeatureCollection::with_capacity(3);
611
612 for i in 0..3 {
613 let point = Point::new_2d(f64::from(i), f64::from(i)).expect("valid point");
614 fc.add_feature(Feature::new(Some(Geometry::Point(point)), None));
615 }
616
617 let count = fc.iter().count();
618 assert_eq!(count, 3);
619 }
620
621 #[test]
622 fn test_feature_collection_validation() {
623 let mut fc = FeatureCollection::empty();
624
625 let point = Point::new_2d(0.0, 0.0).expect("valid point");
626 let feature = Feature::new(Some(Geometry::Point(point)), None);
627 fc.add_feature(feature);
628
629 assert!(fc.validate().is_ok());
630 }
631}