1use crate::vector::geometry::Geometry;
6use serde::{Deserialize, Serialize};
7use serde_json::Value as JsonValue;
8
9#[cfg(feature = "std")]
10use std::collections::HashMap;
11#[cfg(feature = "std")]
12use std::string::String;
13
14#[cfg(all(not(feature = "std"), feature = "alloc"))]
15use alloc::{
16 collections::BTreeMap as HashMap,
17 string::{String, ToString},
18};
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct Feature {
23 pub id: Option<FeatureId>,
25 pub geometry: Option<Geometry>,
27 pub properties: HashMap<String, PropertyValue>,
29}
30
31impl Feature {
32 #[must_use]
34 pub fn new(geometry: Geometry) -> Self {
35 Self {
36 id: None,
37 geometry: Some(geometry),
38 properties: HashMap::new(),
39 }
40 }
41
42 #[must_use]
44 pub fn with_id(id: FeatureId, geometry: Geometry) -> Self {
45 Self {
46 id: Some(id),
47 geometry: Some(geometry),
48 properties: HashMap::new(),
49 }
50 }
51
52 #[must_use]
54 pub fn new_attribute_only() -> Self {
55 Self {
56 id: None,
57 geometry: None,
58 properties: HashMap::new(),
59 }
60 }
61
62 pub fn set_property<S: Into<String>>(&mut self, key: S, value: PropertyValue) {
64 self.properties.insert(key.into(), value);
65 }
66
67 #[must_use]
69 pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
70 self.properties.get(key)
71 }
72
73 pub fn remove_property(&mut self, key: &str) -> Option<PropertyValue> {
75 self.properties.remove(key)
76 }
77
78 #[must_use]
80 pub const fn has_geometry(&self) -> bool {
81 self.geometry.is_some()
82 }
83
84 #[must_use]
86 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
87 self.geometry
88 .as_ref()
89 .and_then(super::geometry::Geometry::bounds)
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
95#[serde(untagged)]
96pub enum FeatureId {
97 Integer(i64),
99 String(String),
101}
102
103impl From<i64> for FeatureId {
104 fn from(id: i64) -> Self {
105 Self::Integer(id)
106 }
107}
108
109impl From<String> for FeatureId {
110 fn from(id: String) -> Self {
111 Self::String(id)
112 }
113}
114
115impl From<&str> for FeatureId {
116 fn from(id: &str) -> Self {
117 Self::String(id.to_string())
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123#[serde(untagged)]
124pub enum PropertyValue {
125 Null,
127 Bool(bool),
129 Integer(i64),
131 UInteger(u64),
133 Float(f64),
135 String(String),
137 Array(Vec<PropertyValue>),
139 Object(HashMap<String, PropertyValue>),
141}
142
143impl PropertyValue {
144 #[must_use]
146 pub const fn is_null(&self) -> bool {
147 matches!(self, Self::Null)
148 }
149
150 #[cfg(feature = "std")]
152 #[must_use]
153 pub fn to_json(&self) -> JsonValue {
154 match self {
155 Self::Null => JsonValue::Null,
156 Self::Bool(b) => JsonValue::Bool(*b),
157 Self::Integer(i) => JsonValue::Number((*i).into()),
158 Self::UInteger(u) => JsonValue::Number((*u).into()),
159 Self::Float(f) => {
160 JsonValue::Number(serde_json::Number::from_f64(*f).unwrap_or_else(|| 0.into()))
161 }
162 Self::String(s) => JsonValue::String(s.clone()),
163 Self::Array(arr) => JsonValue::Array(arr.iter().map(PropertyValue::to_json).collect()),
164 Self::Object(obj) => {
165 JsonValue::Object(obj.iter().map(|(k, v)| (k.clone(), v.to_json())).collect())
166 }
167 }
168 }
169
170 #[cfg(feature = "std")]
172 #[must_use]
173 pub fn from_json(value: &JsonValue) -> Self {
174 match value {
175 JsonValue::Null => Self::Null,
176 JsonValue::Bool(b) => Self::Bool(*b),
177 JsonValue::Number(n) => {
178 if let Some(i) = n.as_i64() {
179 Self::Integer(i)
180 } else if let Some(u) = n.as_u64() {
181 Self::UInteger(u)
182 } else if let Some(f) = n.as_f64() {
183 Self::Float(f)
184 } else {
185 Self::Null
186 }
187 }
188 JsonValue::String(s) => Self::String(s.clone()),
189 JsonValue::Array(arr) => Self::Array(arr.iter().map(Self::from_json).collect()),
190 JsonValue::Object(obj) => Self::Object(
191 obj.iter()
192 .map(|(k, v)| (k.clone(), Self::from_json(v)))
193 .collect(),
194 ),
195 }
196 }
197
198 #[must_use]
200 pub fn as_string(&self) -> Option<&str> {
201 match self {
202 Self::String(s) => Some(s),
203 _ => None,
204 }
205 }
206
207 #[must_use]
209 pub const fn as_i64(&self) -> Option<i64> {
210 match self {
211 Self::Integer(i) => Some(*i),
212 _ => None,
213 }
214 }
215
216 #[must_use]
218 pub const fn as_u64(&self) -> Option<u64> {
219 match self {
220 Self::UInteger(u) => Some(*u),
221 _ => None,
222 }
223 }
224
225 #[must_use]
227 pub fn as_f64(&self) -> Option<f64> {
228 match self {
229 Self::Float(f) => Some(*f),
230 Self::Integer(i) => Some(*i as f64),
231 Self::UInteger(u) => Some(*u as f64),
232 _ => None,
233 }
234 }
235
236 #[must_use]
238 pub const fn as_bool(&self) -> Option<bool> {
239 match self {
240 Self::Bool(b) => Some(*b),
241 _ => None,
242 }
243 }
244}
245
246impl From<bool> for PropertyValue {
247 fn from(b: bool) -> Self {
248 Self::Bool(b)
249 }
250}
251
252impl From<i64> for PropertyValue {
253 fn from(i: i64) -> Self {
254 Self::Integer(i)
255 }
256}
257
258impl From<i32> for PropertyValue {
259 fn from(i: i32) -> Self {
260 Self::Integer(i64::from(i))
261 }
262}
263
264impl From<u64> for PropertyValue {
265 fn from(u: u64) -> Self {
266 Self::UInteger(u)
267 }
268}
269
270impl From<u32> for PropertyValue {
271 fn from(u: u32) -> Self {
272 Self::UInteger(u64::from(u))
273 }
274}
275
276impl From<f64> for PropertyValue {
277 fn from(f: f64) -> Self {
278 Self::Float(f)
279 }
280}
281
282impl From<f32> for PropertyValue {
283 fn from(f: f32) -> Self {
284 Self::Float(f64::from(f))
285 }
286}
287
288impl From<String> for PropertyValue {
289 fn from(s: String) -> Self {
290 Self::String(s)
291 }
292}
293
294impl From<&str> for PropertyValue {
295 fn from(s: &str) -> Self {
296 Self::String(s.to_string())
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub struct FeatureCollection {
303 pub features: Vec<Feature>,
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub metadata: Option<HashMap<String, PropertyValue>>,
308}
309
310impl FeatureCollection {
311 #[must_use]
313 pub const fn new(features: Vec<Feature>) -> Self {
314 Self {
315 features,
316 metadata: None,
317 }
318 }
319
320 #[must_use]
322 pub const fn empty() -> Self {
323 Self {
324 features: Vec::new(),
325 metadata: None,
326 }
327 }
328
329 pub fn push(&mut self, feature: Feature) {
331 self.features.push(feature);
332 }
333
334 #[must_use]
336 pub fn len(&self) -> usize {
337 self.features.len()
338 }
339
340 #[must_use]
342 pub fn is_empty(&self) -> bool {
343 self.features.is_empty()
344 }
345
346 #[must_use]
348 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
349 if self.features.is_empty() {
350 return None;
351 }
352
353 let mut min_x = f64::INFINITY;
354 let mut min_y = f64::INFINITY;
355 let mut max_x = f64::NEG_INFINITY;
356 let mut max_y = f64::NEG_INFINITY;
357
358 for feature in &self.features {
359 if let Some((x_min, y_min, x_max, y_max)) = feature.bounds() {
360 min_x = min_x.min(x_min);
361 min_y = min_y.min(y_min);
362 max_x = max_x.max(x_max);
363 max_y = max_y.max(y_max);
364 }
365 }
366
367 if min_x.is_infinite() {
368 None
369 } else {
370 Some((min_x, min_y, max_x, max_y))
371 }
372 }
373}
374
375impl Default for FeatureCollection {
376 fn default() -> Self {
377 Self::empty()
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384 use crate::vector::geometry::Point;
385
386 #[test]
387 fn test_feature_creation() {
388 let point = Point::new(1.0, 2.0);
389 let mut feature = Feature::new(Geometry::Point(point));
390
391 feature.set_property("name", PropertyValue::String("Test Point".to_string()));
392 feature.set_property("value", PropertyValue::Integer(42));
393
394 assert!(feature.has_geometry());
395 assert_eq!(feature.properties.len(), 2);
396
397 let name = feature.get_property("name");
398 assert!(name.is_some());
399 assert_eq!(name.and_then(|v| v.as_string()), Some("Test Point"));
400
401 let value = feature.get_property("value");
402 assert!(value.is_some());
403 assert_eq!(value.and_then(|v| v.as_i64()), Some(42));
404 }
405
406 #[test]
407 fn test_feature_id() {
408 let point = Point::new(1.0, 2.0);
409 let feature = Feature::with_id(FeatureId::Integer(123), Geometry::Point(point));
410
411 assert_eq!(feature.id, Some(FeatureId::Integer(123)));
412 }
413
414 #[test]
415 fn test_property_value_conversions() {
416 let pv_int = PropertyValue::from(42_i64);
417 assert_eq!(pv_int.as_i64(), Some(42));
418
419 let pv_float = PropertyValue::from(2.78_f64);
420 assert_eq!(pv_float.as_f64(), Some(2.78));
421
422 let pv_bool = PropertyValue::from(true);
423 assert_eq!(pv_bool.as_bool(), Some(true));
424
425 let pv_str = PropertyValue::from("hello");
426 assert_eq!(pv_str.as_string(), Some("hello"));
427 }
428
429 #[test]
430 fn test_feature_collection() {
431 let mut collection = FeatureCollection::empty();
432 assert!(collection.is_empty());
433
434 let point1 = Point::new(1.0, 2.0);
435 let feature1 = Feature::new(Geometry::Point(point1));
436 collection.push(feature1);
437
438 let point2 = Point::new(3.0, 4.0);
439 let feature2 = Feature::new(Geometry::Point(point2));
440 collection.push(feature2);
441
442 assert_eq!(collection.len(), 2);
443 assert!(!collection.is_empty());
444
445 let bounds = collection.bounds();
446 assert!(bounds.is_some());
447 let (min_x, min_y, max_x, max_y) = bounds.expect("bounds calculation failed");
448 assert_eq!(min_x, 1.0);
449 assert_eq!(min_y, 2.0);
450 assert_eq!(max_x, 3.0);
451 assert_eq!(max_y, 4.0);
452 }
453}