Skip to main content

sedona_geometry/
types.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17use std::{fmt::Display, str::FromStr};
18
19use geo_traits::{Dimensions, GeometryTrait};
20use serde::{Deserialize, Serialize};
21use serde_with::{DeserializeFromStr, SerializeDisplay};
22
23use crate::error::SedonaGeometryError;
24
25/// Geometry types
26///
27/// An enumerator for the set of natively supported geometry types without
28/// considering [Dimensions]. See [GeometryTypeAndDimensions] for a struct to
29/// track both.
30///
31/// This is named GeometryTypeId such that it does not conflict with
32/// [geo_traits::GeometryType].
33#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Clone, Copy)]
34pub enum GeometryTypeId {
35    /// Unknown or mixed geometry type
36    Geometry,
37    /// Point geometry type
38    Point,
39    /// LineString geometry type
40    LineString,
41    /// Polygon geometry type
42    Polygon,
43    /// MultiPoint geometry type
44    MultiPoint,
45    /// MultiLineString geometry type
46    MultiLineString,
47    /// MultiPolygon geometry type
48    MultiPolygon,
49    /// GeometryCollection geometry type
50    GeometryCollection,
51}
52
53impl GeometryTypeId {
54    /// Construct a geometry type from a WKB type integer
55    ///
56    /// Parses the geometry type (not dimension) component of a WKB type code (e.g.,
57    /// 1 for Point...7 for GeometryCollection).
58    pub fn try_from_wkb_id(wkb_id: u32) -> Result<Self, SedonaGeometryError> {
59        match wkb_id {
60            0 => Ok(Self::Geometry),
61            1 => Ok(Self::Point),
62            2 => Ok(Self::LineString),
63            3 => Ok(Self::Polygon),
64            4 => Ok(Self::MultiPoint),
65            5 => Ok(Self::MultiLineString),
66            6 => Ok(Self::MultiPolygon),
67            7 => Ok(Self::GeometryCollection),
68            _ => Err(SedonaGeometryError::Invalid(format!(
69                "Unknown geometry type identifier {wkb_id}"
70            ))),
71        }
72    }
73
74    /// WKB integer identifier
75    ///
76    /// The GeometryType portion of the WKB identifier (e.g., 1 for Point...7 for GeometryCollection).
77    pub fn wkb_id(&self) -> u32 {
78        match self {
79            Self::Geometry => 0,
80            Self::Point => 1,
81            Self::LineString => 2,
82            Self::Polygon => 3,
83            Self::MultiPoint => 4,
84            Self::MultiLineString => 5,
85            Self::MultiPolygon => 6,
86            Self::GeometryCollection => 7,
87        }
88    }
89
90    /// GeoJSON/GeoParquet string identifier
91    ///
92    /// The identifier used by GeoJSON and GeoParquet to refer to this geometry type.
93    /// Use [FromStr] to parse such a string back into a GeometryTypeId.
94    pub fn geojson_id(&self) -> &'static str {
95        match self {
96            Self::Geometry => "Geometry",
97            Self::Point => "Point",
98            Self::LineString => "LineString",
99            Self::Polygon => "Polygon",
100            Self::MultiPoint => "MultiPoint",
101            Self::MultiLineString => "MultiLineString",
102            Self::MultiPolygon => "MultiPolygon",
103            Self::GeometryCollection => "GeometryCollection",
104        }
105    }
106}
107
108impl FromStr for GeometryTypeId {
109    type Err = SedonaGeometryError;
110
111    fn from_str(value: &str) -> Result<Self, Self::Err> {
112        let value_lower = value.to_ascii_lowercase();
113        match value_lower.as_str() {
114            "geometry" => Ok(Self::Geometry),
115            "point" => Ok(Self::Point),
116            "linestring" => Ok(Self::LineString),
117            "polygon" => Ok(Self::Polygon),
118            "multipoint" => Ok(Self::MultiPoint),
119            "multilinestring" => Ok(Self::MultiLineString),
120            "multipolygon" => Ok(Self::MultiPolygon),
121            "geometrycollection" => Ok(Self::GeometryCollection),
122            _ => Err(SedonaGeometryError::Invalid(format!(
123                "Invalid geometry type string: '{value}'"
124            ))),
125        }
126    }
127}
128
129/// Geometry type and dimension
130///
131/// Combines a [GeometryTypeId] with [Dimensions] to handle cases where these concepts
132/// are represented together (e.g., GeoParquet geometry types, WKB geometry type integers,
133/// Parquet GeoStatistics). For sanity's sake, this combined concept is also frequently
134/// just called "geometry type".
135#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
136pub struct GeometryTypeAndDimensions {
137    geometry_type: GeometryTypeId,
138    dimensions: Dimensions,
139}
140
141impl GeometryTypeAndDimensions {
142    /// Create from [GeometryTypeId] and [Dimensions]
143    pub fn new(geometry_type: GeometryTypeId, dimensions: Dimensions) -> Self {
144        Self {
145            geometry_type,
146            dimensions,
147        }
148    }
149
150    /// Create from [GeometryTrait]
151    pub fn try_from_geom(geom: &impl GeometryTrait) -> Result<Self, SedonaGeometryError> {
152        let dimensions = geom.dim();
153        let geometry_type = match geom.as_type() {
154            geo_traits::GeometryType::Point(_) => GeometryTypeId::Point,
155            geo_traits::GeometryType::LineString(_) => GeometryTypeId::LineString,
156            geo_traits::GeometryType::Polygon(_) => GeometryTypeId::Polygon,
157            geo_traits::GeometryType::MultiPoint(_) => GeometryTypeId::MultiPoint,
158            geo_traits::GeometryType::MultiLineString(_) => GeometryTypeId::MultiLineString,
159            geo_traits::GeometryType::MultiPolygon(_) => GeometryTypeId::MultiPolygon,
160            geo_traits::GeometryType::GeometryCollection(_) => GeometryTypeId::GeometryCollection,
161            _ => {
162                return Err(SedonaGeometryError::Invalid(
163                    "Unsupported geometry type".to_string(),
164                ))
165            }
166        };
167
168        Ok(Self::new(geometry_type, dimensions))
169    }
170
171    /// The [GeometryTypeId]
172    pub fn geometry_type(&self) -> GeometryTypeId {
173        self.geometry_type
174    }
175
176    /// The [Dimensions]
177    pub fn dimensions(&self) -> Dimensions {
178        self.dimensions
179    }
180
181    /// Create from an ISO WKB integer identifier (e.g., 1001 for Point Z)
182    pub fn try_from_wkb_id(wkb_id: u32) -> Result<Self, SedonaGeometryError> {
183        let dimensions = match wkb_id / 1000 {
184            0 => Dimensions::Xy,
185            1 => Dimensions::Xyz,
186            2 => Dimensions::Xym,
187            3 => Dimensions::Xyzm,
188            _ => {
189                return Err(SedonaGeometryError::Invalid(format!(
190                    "Unknown dimensions in ISO WKB geometry type: {wkb_id}"
191                )))
192            }
193        };
194
195        let geometry_type = GeometryTypeId::try_from_wkb_id(wkb_id % 1000)?;
196        Ok(Self {
197            geometry_type,
198            dimensions,
199        })
200    }
201
202    /// ISO WKB integer identifier (e.g., 1001 for Point Z)
203    pub fn wkb_id(&self) -> u32 {
204        let dimensions_id = match self.dimensions {
205            Dimensions::Xy => 0,
206            Dimensions::Xyz => 1000,
207            Dimensions::Xym => 2000,
208            Dimensions::Xyzm => 3000,
209            Dimensions::Unknown(n) => match n {
210                2 => 0,
211                3 => 1000,
212                4 => 3000,
213                _ => {
214                    // Avoid a panic unless in debug mode
215                    debug_assert!(false, "Unknown dimensions in GeometryTypeAndDimensions");
216                    0
217                }
218            },
219        };
220
221        dimensions_id + self.geometry_type.wkb_id()
222    }
223
224    /// GeoJSON/GeoParquet identifier (e.g., Point Z, LineString, Polygon ZM)
225    pub fn geojson_id(&self) -> String {
226        self.to_string()
227    }
228}
229
230impl From<(GeometryTypeId, Dimensions)> for GeometryTypeAndDimensions {
231    fn from(value: (GeometryTypeId, Dimensions)) -> Self {
232        Self {
233            geometry_type: value.0,
234            dimensions: value.1,
235        }
236    }
237}
238
239impl Display for GeometryTypeAndDimensions {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        let suffix = match self.dimensions {
242            Dimensions::Xy => "",
243            Dimensions::Xyz => " Z",
244            Dimensions::Xym => " M",
245            Dimensions::Xyzm => " ZM",
246            Dimensions::Unknown(_) => " Unknown",
247        };
248
249        f.write_str(self.geometry_type.geojson_id())?;
250        f.write_str(suffix)
251    }
252}
253
254impl FromStr for GeometryTypeAndDimensions {
255    type Err = SedonaGeometryError;
256
257    fn from_str(value: &str) -> Result<Self, Self::Err> {
258        let mut parts = value.split_ascii_whitespace();
259        let geometry_type = match parts.next() {
260            Some(maybe_geometry_type) => GeometryTypeId::from_str(maybe_geometry_type)?,
261            None => {
262                return Err(SedonaGeometryError::Invalid(format!(
263                    "Invalid geometry type string: '{value}'"
264                )))
265            }
266        };
267
268        let dimensions = match parts.next() {
269            Some(maybe_dimensions) => match maybe_dimensions {
270                "z" | "Z" => Dimensions::Xyz,
271                "m" | "M" => Dimensions::Xym,
272                "zm" | "ZM" => Dimensions::Xyzm,
273                _ => {
274                    return Err(SedonaGeometryError::Invalid(format!(
275                        "invalid geometry type string: '{value}'"
276                    )))
277                }
278            },
279            None => Dimensions::Xy,
280        };
281
282        if parts.next().is_some() {
283            return Err(SedonaGeometryError::Invalid(format!(
284                "invalid geometry type string: '{value}'"
285            )));
286        }
287
288        Ok(Self {
289            geometry_type,
290            dimensions,
291        })
292    }
293}
294
295/// A set containing [`GeometryTypeAndDimensions`] values
296///
297/// This set is conceptually similar to `HashSet<GeometryTypeAndDimensions>` but
298/// uses a compact bitset representation for efficiency.
299///
300/// This set only supports the standard dimensions: XY, XYZ, XYM, and XYZM.
301/// Unknown dimensions (other than these four standard types) are not supported
302/// and will be rejected by [`insert`](Self::insert) or silently ignored by
303/// [`insert_or_ignore`](Self::insert_or_ignore).
304#[derive(Clone, Debug, Default, PartialEq, Eq)]
305pub struct GeometryTypeAndDimensionsSet {
306    /// Bitset encoding geometry types and dimensions.
307    ///
308    /// Uses bits 0-31 where each geometry type's WKB ID (0-7) is encoded
309    /// at different offsets based on dimensions:
310    /// - XY: bits 0-7
311    /// - XYZ: bits 8-15
312    /// - XYM: bits 16-23
313    /// - XYZM: bits 24-31
314    types: u32,
315}
316
317impl GeometryTypeAndDimensionsSet {
318    #[inline]
319    pub fn new() -> Self {
320        Self { types: 0 }
321    }
322
323    /// Insert a geometry type and dimensions into the set.
324    ///
325    /// Returns an error if the dimensions are unknown (not one of XY, XYZ, XYM, or XYZM).
326    /// Only the standard four dimension types are supported; attempting to insert
327    /// a geometry with `Dimensions::Unknown(_)` will result in an error.
328    #[inline]
329    pub fn insert(
330        &mut self,
331        type_and_dim: &GeometryTypeAndDimensions,
332    ) -> Result<(), SedonaGeometryError> {
333        if let Dimensions::Unknown(n) = type_and_dim.dimensions() {
334            return Err(SedonaGeometryError::Invalid(format!(
335                "Unknown dimensions {n} in GeometryTypeAndDimensionsSet::insert"
336            )));
337        }
338        self.insert_or_ignore(type_and_dim);
339        Ok(())
340    }
341
342    /// Insert a geometry type and dimensions into the set, ignoring unknown dimensions.
343    ///
344    /// If the dimensions are unknown (not one of XY, XYZ, XYM, or XYZM), this method
345    /// silently ignores the insertion without returning an error. This is useful when
346    /// processing data that may contain unsupported dimension types that should be
347    /// skipped rather than causing an error.
348    #[inline]
349    pub fn insert_or_ignore(&mut self, type_and_dim: &GeometryTypeAndDimensions) {
350        let geom_shift = type_and_dim.geometry_type().wkb_id();
351        // WKB ID must be < 8 to fit in the bitset layout (8 bits per dimension)
352        if geom_shift >= 8 {
353            panic!(
354                "Invalid geometry type wkb_id {geom_shift} in GeometryTypeAndDimensionsSet::insert_or_ignore"
355            );
356        }
357        let dim_shift = match type_and_dim.dimensions() {
358            geo_traits::Dimensions::Unknown(_) => {
359                // Ignore unknown dimensions
360                return;
361            }
362            geo_traits::Dimensions::Xy => 0,
363            geo_traits::Dimensions::Xyz => 8,
364            geo_traits::Dimensions::Xym => 16,
365            geo_traits::Dimensions::Xyzm => 24,
366        };
367        let bit_position = geom_shift + dim_shift;
368        self.types |= 1 << bit_position;
369    }
370
371    /// Merge the given set into this set.
372    #[inline]
373    pub fn merge(&mut self, other: &Self) {
374        self.types |= other.types;
375    }
376
377    /// Returns `true` if the set contains no geometry types.
378    #[inline]
379    pub fn is_empty(&self) -> bool {
380        self.types == 0
381    }
382
383    /// Returns the number of geometry types in the set.
384    #[inline]
385    pub fn size(&self) -> usize {
386        self.types.count_ones() as usize
387    }
388
389    /// Clears the set, removing all geometry types.
390    #[inline]
391    pub fn clear(&mut self) {
392        self.types = 0;
393    }
394
395    /// Returns an iterator over the geometry types in the set.
396    pub fn iter(&self) -> GeometryTypeSetIter {
397        GeometryTypeSetIter {
398            types: self.types,
399            current_bit: 0,
400        }
401    }
402}
403
404/// Iterator over [`GeometryTypeAndDimensions`] values in a [`GeometryTypeAndDimensionsSet`]
405pub struct GeometryTypeSetIter {
406    types: u32,
407    current_bit: u32,
408}
409
410impl Iterator for GeometryTypeSetIter {
411    type Item = GeometryTypeAndDimensions;
412
413    fn next(&mut self) -> Option<Self::Item> {
414        // Find the next set bit
415        while self.current_bit < 32 {
416            let bit = self.current_bit;
417            self.current_bit += 1;
418
419            if (self.types & (1 << bit)) != 0 {
420                // Decode the bit position into geometry type and dimensions
421                let dim_shift = (bit / 8) * 8;
422                let geom_shift = bit % 8;
423                let dimensions = match dim_shift {
424                    0 => Dimensions::Xy,
425                    8 => Dimensions::Xyz,
426                    16 => Dimensions::Xym,
427                    24 => Dimensions::Xyzm,
428                    _ => panic!(
429                        "Invalid dimension bits at position {bit} in GeometryTypeAndDimensionsSet"
430                    ),
431                };
432
433                let geometry_type = GeometryTypeId::try_from_wkb_id(geom_shift)
434                    .expect("Invalid geometry type wkb_id in GeometryTypeAndDimensionsSet");
435
436                return Some(GeometryTypeAndDimensions::new(geometry_type, dimensions));
437            }
438        }
439
440        None
441    }
442}
443
444impl IntoIterator for &GeometryTypeAndDimensionsSet {
445    type Item = GeometryTypeAndDimensions;
446    type IntoIter = GeometryTypeSetIter;
447
448    fn into_iter(self) -> Self::IntoIter {
449        self.iter()
450    }
451}
452
453// Serialize as a Vec to maintain compatibility with HashSet JSON format
454impl Serialize for GeometryTypeAndDimensionsSet {
455    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
456    where
457        S: serde::Serializer,
458    {
459        use serde::ser::SerializeSeq; // codespell:ignore ser
460        let mut seq = serializer.serialize_seq(Some(self.size()))?;
461        for item in self.iter() {
462            seq.serialize_element(&item)?;
463        }
464        seq.end()
465    }
466}
467
468// Deserialize from a Vec (which is what HashSet was serialized as)
469impl<'de> Deserialize<'de> for GeometryTypeAndDimensionsSet {
470    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
471    where
472        D: serde::Deserializer<'de>,
473    {
474        use serde::de::Error;
475        let items: Vec<GeometryTypeAndDimensions> = Vec::deserialize(deserializer)?;
476        let mut set = GeometryTypeAndDimensionsSet::new();
477        for item in items {
478            set.insert(&item).map_err(D::Error::custom)?;
479        }
480        Ok(set)
481    }
482}
483
484#[cfg(test)]
485mod test {
486    use super::*;
487
488    use rstest::rstest;
489    use Dimensions::*;
490    use GeometryTypeId::*;
491
492    #[rstest]
493    fn geometry_type_wkb_id_roundtrip(
494        #[values(
495            (Geometry, 0),
496            (Point, 1),
497            (LineString, 2),
498            (Polygon, 3),
499            (MultiPoint, 4),
500            (MultiLineString, 5),
501            (MultiPolygon, 6),
502            (GeometryCollection, 7)
503        )]
504        geometry_type_and_id: (GeometryTypeId, u32),
505    ) {
506        let (geometry_type, wkb_id) = geometry_type_and_id;
507        assert_eq!(geometry_type.wkb_id(), wkb_id);
508        assert_eq!(
509            GeometryTypeId::try_from_wkb_id(wkb_id).unwrap(),
510            geometry_type
511        );
512    }
513
514    #[test]
515    fn geometry_type_wkb_id_err() {
516        let err = GeometryTypeId::try_from_wkb_id(17).unwrap_err();
517        assert_eq!(err.to_string(), "Unknown geometry type identifier 17");
518    }
519
520    #[rstest]
521    fn geometry_type_str_roundtrip(
522        #[values(
523            (Geometry, "Geometry"),
524            (Point, "Point"),
525            (LineString, "LineString"),
526            (Polygon, "Polygon"),
527            (MultiPoint, "MultiPoint"),
528            (MultiLineString, "MultiLineString"),
529            (MultiPolygon, "MultiPolygon"),
530            (GeometryCollection, "GeometryCollection")
531        )]
532        geometry_type_and_str: (GeometryTypeId, &str),
533    ) {
534        let (geometry_type, string) = geometry_type_and_str;
535        assert_eq!(geometry_type.geojson_id(), string);
536        assert_eq!(GeometryTypeId::from_str(string).unwrap(), geometry_type);
537    }
538
539    #[test]
540    fn geometry_type_str_err() {
541        let err = GeometryTypeId::from_str("gazornenplat").unwrap_err();
542        assert_eq!(
543            err.to_string(),
544            "Invalid geometry type string: 'gazornenplat'"
545        );
546    }
547
548    #[rstest]
549    fn geometry_type_dims_wkb_id_roundtrip(
550        #[values(
551            (Geometry, 0),
552            (Point, 1),
553            (LineString, 2),
554            (Polygon, 3),
555            (MultiPoint, 4),
556            (MultiLineString, 5),
557            (MultiPolygon, 6),
558            (GeometryCollection, 7)
559        )]
560        geometry_type_and_id: (GeometryTypeId, u32),
561        #[values(
562            (Xy, 0),
563            (Xyz, 1000),
564            (Xym, 2000),
565            (Xyzm, 3000),
566        )]
567        dimensions_and_id: (Dimensions, u32),
568    ) {
569        let (geometry_type, geometry_type_id) = geometry_type_and_id;
570        let (dimensions, dimensions_id) = dimensions_and_id;
571
572        let value = GeometryTypeAndDimensions::new(geometry_type, dimensions);
573        assert_eq!(value.wkb_id(), dimensions_id + geometry_type_id);
574        assert_eq!(
575            GeometryTypeAndDimensions::try_from_wkb_id(dimensions_id + geometry_type_id).unwrap(),
576            value
577        );
578    }
579
580    #[test]
581    fn geometry_type_dims_wkb_id_err() {
582        let err = GeometryTypeAndDimensions::try_from_wkb_id(17).unwrap_err();
583        assert_eq!(err.to_string(), "Unknown geometry type identifier 17");
584
585        let err = GeometryTypeAndDimensions::try_from_wkb_id(4000).unwrap_err();
586        assert_eq!(
587            err.to_string(),
588            "Unknown dimensions in ISO WKB geometry type: 4000"
589        );
590    }
591
592    #[rstest]
593    fn geometry_type_dims_str_roundtrip(
594        #[values(
595            (Geometry, "Geometry"),
596            (Point, "Point"),
597            (LineString, "LineString"),
598            (Polygon, "Polygon"),
599            (MultiPoint, "MultiPoint"),
600            (MultiLineString, "MultiLineString"),
601            (MultiPolygon, "MultiPolygon"),
602            (GeometryCollection, "GeometryCollection")
603        )]
604        geometry_type_and_str: (GeometryTypeId, &str),
605        #[values(
606            (Xy, ""),
607            (Xyz, " Z"),
608            (Xym, " M"),
609            (Xyzm, " ZM"),
610        )]
611        dimensions_and_suffix: (Dimensions, &str),
612    ) {
613        let (geometry_type, geometry_type_id) = geometry_type_and_str;
614        let (dimensions, dimensions_id) = dimensions_and_suffix;
615        let string_id = geometry_type_id.to_string() + dimensions_id;
616
617        let value = GeometryTypeAndDimensions::new(geometry_type, dimensions);
618        assert_eq!(value.geojson_id(), string_id);
619        assert_eq!(
620            GeometryTypeAndDimensions::from_str(string_id.as_str()).unwrap(),
621            value
622        );
623    }
624
625    #[test]
626    fn geometry_type_set_new_is_empty() {
627        let set = GeometryTypeAndDimensionsSet::new();
628        assert!(set.is_empty());
629        assert_eq!(set.size(), 0);
630        assert_eq!(set.iter().count(), 0);
631    }
632
633    #[test]
634    fn geometry_type_set_insert_single() {
635        let mut set = GeometryTypeAndDimensionsSet::new();
636        let point_xy = GeometryTypeAndDimensions::new(Point, Xy);
637
638        set.insert(&point_xy).unwrap();
639        assert!(!set.is_empty());
640        assert_eq!(set.size(), 1);
641
642        let items: Vec<_> = set.iter().collect();
643        assert_eq!(items.len(), 1);
644        assert_eq!(items[0], point_xy);
645    }
646
647    #[test]
648    fn geometry_type_set_insert_duplicate() {
649        let mut set = GeometryTypeAndDimensionsSet::new();
650        let point_xy = GeometryTypeAndDimensions::new(Point, Xy);
651
652        set.insert(&point_xy).unwrap();
653        set.insert(&point_xy).unwrap();
654        set.insert(&point_xy).unwrap();
655
656        assert_eq!(set.size(), 1);
657        let items: Vec<_> = set.iter().collect();
658        assert_eq!(items.len(), 1);
659        assert_eq!(items[0], point_xy);
660    }
661
662    #[test]
663    fn geometry_type_set_insert_all_types() {
664        let mut set = GeometryTypeAndDimensionsSet::new();
665
666        // Insert all geometry types with XY dimension
667        for geom_type in [
668            Geometry,
669            Point,
670            LineString,
671            Polygon,
672            MultiPoint,
673            MultiLineString,
674            MultiPolygon,
675            GeometryCollection,
676        ] {
677            set.insert(&GeometryTypeAndDimensions::new(geom_type, Xy))
678                .unwrap();
679        }
680
681        assert_eq!(set.size(), 8);
682        let items: Vec<_> = set.iter().collect();
683        assert_eq!(items.len(), 8);
684    }
685
686    #[test]
687    fn geometry_type_set_insert_unknown_dimension() {
688        let mut set = GeometryTypeAndDimensionsSet::new();
689        let point_unknown = GeometryTypeAndDimensions::new(Point, Dimensions::Unknown(2));
690
691        let result = set.insert(&point_unknown);
692
693        // Unknown dimensions should return an error
694        assert!(result.is_err());
695        assert_eq!(
696            result.unwrap_err().to_string(),
697            "Unknown dimensions 2 in GeometryTypeAndDimensionsSet::insert"
698        );
699        assert!(set.is_empty());
700    }
701
702    #[test]
703    fn geometry_type_set_clear() {
704        let mut set = GeometryTypeAndDimensionsSet::new();
705        let point_xy = GeometryTypeAndDimensions::new(Point, Xy);
706        let linestring_xyz = GeometryTypeAndDimensions::new(LineString, Xyz);
707
708        set.insert(&point_xy).unwrap();
709        set.insert(&linestring_xyz).unwrap();
710        assert!(!set.is_empty());
711        assert_eq!(set.size(), 2);
712
713        set.clear();
714        assert!(set.is_empty());
715        assert_eq!(set.size(), 0);
716        assert_eq!(set.iter().count(), 0);
717    }
718
719    #[test]
720    fn geometry_type_set_merge() {
721        let mut set1 = GeometryTypeAndDimensionsSet::new();
722        let mut set2 = GeometryTypeAndDimensionsSet::new();
723
724        let point_xy = GeometryTypeAndDimensions::new(Point, Xy);
725        let linestring_xy = GeometryTypeAndDimensions::new(LineString, Xy);
726        let polygon_xyz = GeometryTypeAndDimensions::new(Polygon, Xyz);
727
728        set1.insert(&point_xy).unwrap();
729        set1.insert(&linestring_xy).unwrap();
730
731        set2.insert(&linestring_xy).unwrap(); // Duplicate
732        set2.insert(&polygon_xyz).unwrap();
733
734        set1.merge(&set2);
735
736        assert_eq!(set1.size(), 3);
737        let items: Vec<_> = set1.iter().collect();
738        assert_eq!(items.len(), 3);
739        assert!(items.contains(&point_xy));
740        assert!(items.contains(&linestring_xy));
741        assert!(items.contains(&polygon_xyz));
742    }
743
744    #[test]
745    fn geometry_type_set_comprehensive() {
746        let mut set = GeometryTypeAndDimensionsSet::new();
747
748        // Add a mix of geometry types and dimensions
749        let test_types = vec![
750            GeometryTypeAndDimensions::new(Geometry, Xy),
751            GeometryTypeAndDimensions::new(Point, Xy),
752            GeometryTypeAndDimensions::new(LineString, Xyz),
753            GeometryTypeAndDimensions::new(Polygon, Xym),
754            GeometryTypeAndDimensions::new(MultiPoint, Xyzm),
755            GeometryTypeAndDimensions::new(MultiLineString, Xy),
756            GeometryTypeAndDimensions::new(MultiPolygon, Xyz),
757            GeometryTypeAndDimensions::new(GeometryCollection, Xym),
758            GeometryTypeAndDimensions::new(GeometryCollection, Xyzm),
759        ];
760
761        for type_and_dim in &test_types {
762            set.insert(type_and_dim).unwrap();
763        }
764
765        assert_eq!(set.size(), test_types.len());
766        let items: Vec<_> = set.iter().collect();
767        assert_eq!(items.len(), test_types.len());
768
769        for type_and_dim in &test_types {
770            assert!(items.contains(type_and_dim));
771        }
772    }
773
774    #[test]
775    fn geometry_type_set_serde_empty() {
776        let set = GeometryTypeAndDimensionsSet::new();
777
778        // Serialize
779        let json = serde_json::to_string(&set).unwrap();
780        assert_eq!(json, "[]");
781
782        // Deserialize
783        let deserialized: GeometryTypeAndDimensionsSet = serde_json::from_str(&json).unwrap();
784        assert!(deserialized.is_empty());
785        assert_eq!(deserialized.size(), 0);
786    }
787
788    #[test]
789    fn geometry_type_set_serde_single() {
790        let mut set = GeometryTypeAndDimensionsSet::new();
791        let point_xy = GeometryTypeAndDimensions::new(Point, Xy);
792        set.insert(&point_xy).unwrap();
793
794        // Serialize
795        let json = serde_json::to_string(&set).unwrap();
796        assert_eq!(json, "[\"Point\"]");
797
798        // Deserialize
799        let deserialized: GeometryTypeAndDimensionsSet = serde_json::from_str(&json).unwrap();
800        assert_eq!(deserialized.size(), 1);
801        let items: Vec<_> = deserialized.iter().collect();
802        assert_eq!(items[0], point_xy);
803    }
804
805    #[test]
806    fn geometry_type_set_serde_multiple() {
807        let mut set = GeometryTypeAndDimensionsSet::new();
808
809        let test_types = vec![
810            GeometryTypeAndDimensions::new(Point, Xy),
811            GeometryTypeAndDimensions::new(LineString, Xyz),
812            GeometryTypeAndDimensions::new(Polygon, Xyzm),
813        ];
814
815        for type_and_dim in &test_types {
816            set.insert(type_and_dim).unwrap();
817        }
818
819        // Serialize
820        let json = serde_json::to_string(&set).unwrap();
821        assert_eq!(json, "[\"Point\",\"LineString Z\",\"Polygon ZM\"]");
822
823        // Deserialize
824        let deserialized: GeometryTypeAndDimensionsSet = serde_json::from_str(&json).unwrap();
825        assert_eq!(deserialized.size(), test_types.len());
826
827        let items: Vec<_> = deserialized.iter().collect();
828        for type_and_dim in &test_types {
829            assert!(items.contains(type_and_dim));
830        }
831    }
832
833    #[test]
834    fn geometry_type_set_serde_roundtrip() {
835        let mut set = GeometryTypeAndDimensionsSet::new();
836
837        // Add all combinations of one geometry type with different dimensions
838        set.insert(&GeometryTypeAndDimensions::new(Point, Xy))
839            .unwrap();
840        set.insert(&GeometryTypeAndDimensions::new(Point, Xyz))
841            .unwrap();
842        set.insert(&GeometryTypeAndDimensions::new(Point, Xym))
843            .unwrap();
844        set.insert(&GeometryTypeAndDimensions::new(Point, Xyzm))
845            .unwrap();
846        set.insert(&GeometryTypeAndDimensions::new(LineString, Xy))
847            .unwrap();
848
849        // Serialize to JSON
850        let json = serde_json::to_string(&set).unwrap();
851        assert_eq!(
852            json,
853            "[\"Point\",\"LineString\",\"Point Z\",\"Point M\",\"Point ZM\"]"
854        );
855
856        // Deserialize back
857        let deserialized: GeometryTypeAndDimensionsSet = serde_json::from_str(&json).unwrap();
858
859        // Verify the deserialized set matches the original
860        assert_eq!(set.size(), deserialized.size());
861
862        let original_items: Vec<_> = set.iter().collect();
863        let deserialized_items: Vec<_> = deserialized.iter().collect();
864
865        assert_eq!(original_items.len(), deserialized_items.len());
866        for item in &original_items {
867            assert!(deserialized_items.contains(item));
868        }
869    }
870}