1use rustial_math::GeoCoord;
27use std::collections::HashMap;
28use std::fmt;
29
30#[derive(Debug, Clone)]
36pub struct Point {
37 pub coord: GeoCoord,
39}
40
41#[derive(Debug, Clone)]
46pub struct LineString {
47 pub coords: Vec<GeoCoord>,
49}
50
51#[derive(Debug, Clone)]
60pub struct Polygon {
61 pub exterior: Vec<GeoCoord>,
63 pub interiors: Vec<Vec<GeoCoord>>,
65}
66
67#[derive(Debug, Clone)]
69pub struct MultiPoint {
70 pub points: Vec<Point>,
72}
73
74#[derive(Debug, Clone)]
76pub struct MultiLineString {
77 pub lines: Vec<LineString>,
79}
80
81#[derive(Debug, Clone)]
83pub struct MultiPolygon {
84 pub polygons: Vec<Polygon>,
86}
87
88#[derive(Debug, Clone)]
96pub enum Geometry {
97 Point(Point),
99 LineString(LineString),
101 Polygon(Polygon),
103 MultiPoint(MultiPoint),
105 MultiLineString(MultiLineString),
107 MultiPolygon(MultiPolygon),
109 GeometryCollection(Vec<Geometry>),
111}
112
113impl Geometry {
114 pub fn type_name(&self) -> &'static str {
118 match self {
119 Geometry::Point(_) => "Point",
120 Geometry::LineString(_) => "LineString",
121 Geometry::Polygon(_) => "Polygon",
122 Geometry::MultiPoint(_) => "MultiPoint",
123 Geometry::MultiLineString(_) => "MultiLineString",
124 Geometry::MultiPolygon(_) => "MultiPolygon",
125 Geometry::GeometryCollection(_) => "GeometryCollection",
126 }
127 }
128
129 pub fn is_empty(&self) -> bool {
134 match self {
135 Geometry::Point(_) => false,
136 Geometry::LineString(ls) => ls.coords.is_empty(),
137 Geometry::Polygon(p) => p.exterior.is_empty(),
138 Geometry::MultiPoint(mp) => mp.points.is_empty(),
139 Geometry::MultiLineString(mls) => mls.lines.is_empty(),
140 Geometry::MultiPolygon(mp) => mp.polygons.is_empty(),
141 Geometry::GeometryCollection(gc) => gc.iter().all(|g| g.is_empty()),
142 }
143 }
144
145 pub fn coord_count(&self) -> usize {
149 match self {
150 Geometry::Point(_) => 1,
151 Geometry::LineString(ls) => ls.coords.len(),
152 Geometry::Polygon(p) => {
153 p.exterior.len() + p.interiors.iter().map(|h| h.len()).sum::<usize>()
154 }
155 Geometry::MultiPoint(mp) => mp.points.len(),
156 Geometry::MultiLineString(mls) => mls.lines.iter().map(|l| l.coords.len()).sum(),
157 Geometry::MultiPolygon(mp) => mp
158 .polygons
159 .iter()
160 .map(|p| p.exterior.len() + p.interiors.iter().map(|h| h.len()).sum::<usize>())
161 .sum(),
162 Geometry::GeometryCollection(gc) => gc.iter().map(|g| g.coord_count()).sum(),
163 }
164 }
165}
166
167#[derive(Debug, Clone)]
177pub enum PropertyValue {
178 Null,
180 Bool(bool),
182 Number(f64),
184 String(String),
186}
187
188impl PropertyValue {
189 pub fn is_null(&self) -> bool {
191 matches!(self, PropertyValue::Null)
192 }
193
194 pub fn as_bool(&self) -> Option<bool> {
196 match self {
197 PropertyValue::Bool(b) => Some(*b),
198 _ => None,
199 }
200 }
201
202 pub fn as_f64(&self) -> Option<f64> {
204 match self {
205 PropertyValue::Number(n) => Some(*n),
206 _ => None,
207 }
208 }
209
210 pub fn as_str(&self) -> Option<&str> {
212 match self {
213 PropertyValue::String(s) => Some(s),
214 _ => None,
215 }
216 }
217}
218
219impl fmt::Display for PropertyValue {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self {
222 PropertyValue::Null => write!(f, "null"),
223 PropertyValue::Bool(b) => write!(f, "{b}"),
224 PropertyValue::Number(n) => write!(f, "{n}"),
225 PropertyValue::String(s) => write!(f, "{s}"),
226 }
227 }
228}
229
230impl PartialEq for PropertyValue {
231 fn eq(&self, other: &Self) -> bool {
232 match (self, other) {
233 (PropertyValue::Null, PropertyValue::Null) => true,
234 (PropertyValue::Bool(a), PropertyValue::Bool(b)) => a == b,
235 (PropertyValue::Number(a), PropertyValue::Number(b)) => a == b,
236 (PropertyValue::String(a), PropertyValue::String(b)) => a == b,
237 _ => false,
238 }
239 }
240}
241
242#[derive(Debug, Clone)]
250pub struct Feature {
251 pub geometry: Geometry,
253 pub properties: HashMap<String, PropertyValue>,
255}
256
257impl Feature {
258 pub fn property(&self, key: &str) -> Option<&PropertyValue> {
260 self.properties.get(key)
261 }
262}
263
264#[derive(Debug, Clone, Default)]
269pub struct FeatureCollection {
270 pub features: Vec<Feature>,
272}
273
274impl FeatureCollection {
275 pub fn len(&self) -> usize {
277 self.features.len()
278 }
279
280 pub fn is_empty(&self) -> bool {
282 self.features.is_empty()
283 }
284
285 pub fn total_coords(&self) -> usize {
287 self.features.iter().map(|f| f.geometry.coord_count()).sum()
288 }
289
290 pub fn iter(&self) -> std::slice::Iter<'_, Feature> {
292 self.features.iter()
293 }
294
295 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Feature> {
297 self.features.iter_mut()
298 }
299}
300
301impl<'a> IntoIterator for &'a FeatureCollection {
302 type Item = &'a Feature;
303 type IntoIter = std::slice::Iter<'a, Feature>;
304
305 fn into_iter(self) -> Self::IntoIter {
306 self.features.iter()
307 }
308}
309
310impl<'a> IntoIterator for &'a mut FeatureCollection {
311 type Item = &'a mut Feature;
312 type IntoIter = std::slice::IterMut<'a, Feature>;
313
314 fn into_iter(self) -> Self::IntoIter {
315 self.features.iter_mut()
316 }
317}
318
319#[cfg(test)]
324mod tests {
325 use super::*;
326
327 fn sample_point() -> Geometry {
328 Geometry::Point(Point {
329 coord: GeoCoord::from_lat_lon(51.1, 17.0),
330 })
331 }
332
333 fn sample_linestring() -> Geometry {
334 Geometry::LineString(LineString {
335 coords: vec![
336 GeoCoord::from_lat_lon(0.0, 0.0),
337 GeoCoord::from_lat_lon(1.0, 1.0),
338 GeoCoord::from_lat_lon(2.0, 2.0),
339 ],
340 })
341 }
342
343 fn sample_polygon() -> Geometry {
344 Geometry::Polygon(Polygon {
345 exterior: vec![
346 GeoCoord::from_lat_lon(0.0, 0.0),
347 GeoCoord::from_lat_lon(0.0, 1.0),
348 GeoCoord::from_lat_lon(1.0, 1.0),
349 GeoCoord::from_lat_lon(1.0, 0.0),
350 GeoCoord::from_lat_lon(0.0, 0.0),
351 ],
352 interiors: vec![vec![
353 GeoCoord::from_lat_lon(0.2, 0.2),
354 GeoCoord::from_lat_lon(0.2, 0.4),
355 GeoCoord::from_lat_lon(0.4, 0.4),
356 GeoCoord::from_lat_lon(0.4, 0.2),
357 GeoCoord::from_lat_lon(0.2, 0.2),
358 ]],
359 })
360 }
361
362 #[test]
365 fn type_name_point() {
366 assert_eq!(sample_point().type_name(), "Point");
367 }
368
369 #[test]
370 fn type_name_linestring() {
371 assert_eq!(sample_linestring().type_name(), "LineString");
372 }
373
374 #[test]
375 fn type_name_polygon() {
376 assert_eq!(sample_polygon().type_name(), "Polygon");
377 }
378
379 #[test]
380 fn type_name_multi_and_collection() {
381 let mp = Geometry::MultiPoint(MultiPoint {
382 points: vec![Point {
383 coord: GeoCoord::from_lat_lon(0.0, 0.0),
384 }],
385 });
386 assert_eq!(mp.type_name(), "MultiPoint");
387
388 let mls = Geometry::MultiLineString(MultiLineString { lines: vec![] });
389 assert_eq!(mls.type_name(), "MultiLineString");
390
391 let mpoly = Geometry::MultiPolygon(MultiPolygon { polygons: vec![] });
392 assert_eq!(mpoly.type_name(), "MultiPolygon");
393
394 let gc = Geometry::GeometryCollection(vec![]);
395 assert_eq!(gc.type_name(), "GeometryCollection");
396 }
397
398 #[test]
401 fn point_is_never_empty() {
402 assert!(!sample_point().is_empty());
403 }
404
405 #[test]
406 fn empty_linestring_is_empty() {
407 let ls = Geometry::LineString(LineString { coords: vec![] });
408 assert!(ls.is_empty());
409 assert!(!sample_linestring().is_empty());
410 }
411
412 #[test]
413 fn empty_polygon_is_empty() {
414 let p = Geometry::Polygon(Polygon {
415 exterior: vec![],
416 interiors: vec![],
417 });
418 assert!(p.is_empty());
419 assert!(!sample_polygon().is_empty());
420 }
421
422 #[test]
423 fn empty_geometry_collection() {
424 let gc = Geometry::GeometryCollection(vec![]);
425 assert!(gc.is_empty());
426
427 let gc_with_empty = Geometry::GeometryCollection(vec![Geometry::LineString(LineString {
428 coords: vec![],
429 })]);
430 assert!(gc_with_empty.is_empty());
431
432 let gc_with_content = Geometry::GeometryCollection(vec![sample_point()]);
433 assert!(!gc_with_content.is_empty());
434 }
435
436 #[test]
439 fn coord_count_point() {
440 assert_eq!(sample_point().coord_count(), 1);
441 }
442
443 #[test]
444 fn coord_count_linestring() {
445 assert_eq!(sample_linestring().coord_count(), 3);
446 }
447
448 #[test]
449 fn coord_count_polygon_with_hole() {
450 assert_eq!(sample_polygon().coord_count(), 10);
452 }
453
454 #[test]
455 fn coord_count_multi_polygon() {
456 let mp = Geometry::MultiPolygon(MultiPolygon {
457 polygons: vec![
458 Polygon {
459 exterior: vec![
460 GeoCoord::from_lat_lon(0.0, 0.0),
461 GeoCoord::from_lat_lon(0.0, 1.0),
462 GeoCoord::from_lat_lon(1.0, 0.0),
463 ],
464 interiors: vec![],
465 },
466 Polygon {
467 exterior: vec![
468 GeoCoord::from_lat_lon(2.0, 2.0),
469 GeoCoord::from_lat_lon(2.0, 3.0),
470 GeoCoord::from_lat_lon(3.0, 2.0),
471 GeoCoord::from_lat_lon(2.0, 2.0),
472 ],
473 interiors: vec![],
474 },
475 ],
476 });
477 assert_eq!(mp.coord_count(), 7);
478 }
479
480 #[test]
481 fn coord_count_geometry_collection() {
482 let gc = Geometry::GeometryCollection(vec![sample_point(), sample_linestring()]);
483 assert_eq!(gc.coord_count(), 4); }
485
486 #[test]
489 fn property_value_accessors() {
490 assert!(PropertyValue::Null.is_null());
491 assert!(!PropertyValue::Bool(true).is_null());
492
493 assert_eq!(PropertyValue::Bool(true).as_bool(), Some(true));
494 assert_eq!(PropertyValue::Number(42.0).as_bool(), None);
495
496 assert_eq!(PropertyValue::Number(3.125).as_f64(), Some(3.125));
497 assert_eq!(PropertyValue::String("hi".into()).as_f64(), None);
498
499 assert_eq!(
500 PropertyValue::String("hello".into()).as_str(),
501 Some("hello")
502 );
503 assert_eq!(PropertyValue::Null.as_str(), None);
504 }
505
506 #[test]
507 fn property_value_display() {
508 assert_eq!(format!("{}", PropertyValue::Null), "null");
509 assert_eq!(format!("{}", PropertyValue::Bool(false)), "false");
510 assert_eq!(format!("{}", PropertyValue::Number(1.5)), "1.5");
511 assert_eq!(
512 format!("{}", PropertyValue::String("abc".into())),
513 "abc"
514 );
515 }
516
517 #[test]
518 fn property_value_equality() {
519 assert_eq!(PropertyValue::Null, PropertyValue::Null);
520 assert_eq!(PropertyValue::Bool(true), PropertyValue::Bool(true));
521 assert_ne!(PropertyValue::Bool(true), PropertyValue::Bool(false));
522 assert_eq!(PropertyValue::Number(1.0), PropertyValue::Number(1.0));
523 assert_ne!(PropertyValue::Number(1.0), PropertyValue::Number(2.0));
524 assert_eq!(
525 PropertyValue::String("a".into()),
526 PropertyValue::String("a".into())
527 );
528 assert_ne!(PropertyValue::Null, PropertyValue::Bool(false));
529 }
530
531 #[test]
534 fn feature_property_lookup() {
535 let mut props = HashMap::new();
536 props.insert("name".into(), PropertyValue::String("test".into()));
537 let feature = Feature {
538 geometry: sample_point(),
539 properties: props,
540 };
541 assert_eq!(
542 feature.property("name").and_then(|v| v.as_str()),
543 Some("test")
544 );
545 assert!(feature.property("missing").is_none());
546 }
547
548 #[test]
551 fn feature_collection_len_and_empty() {
552 let fc = FeatureCollection::default();
553 assert!(fc.is_empty());
554 assert_eq!(fc.len(), 0);
555 }
556
557 #[test]
558 fn feature_collection_total_coords() {
559 let fc = FeatureCollection {
560 features: vec![
561 Feature {
562 geometry: sample_point(),
563 properties: HashMap::new(),
564 },
565 Feature {
566 geometry: sample_linestring(),
567 properties: HashMap::new(),
568 },
569 ],
570 };
571 assert_eq!(fc.total_coords(), 4); assert_eq!(fc.len(), 2);
573 }
574
575 #[test]
576 fn feature_collection_iteration() {
577 let fc = FeatureCollection {
578 features: vec![
579 Feature {
580 geometry: sample_point(),
581 properties: HashMap::new(),
582 },
583 Feature {
584 geometry: sample_linestring(),
585 properties: HashMap::new(),
586 },
587 ],
588 };
589 let names: Vec<_> = fc.iter().map(|f| f.geometry.type_name()).collect();
590 assert_eq!(names, vec!["Point", "LineString"]);
591
592 let count = (&fc).into_iter().count();
594 assert_eq!(count, 2);
595 }
596}