1use std::collections::HashSet;
2use std::fmt::Display;
3
4use arrow_schema::{ArrowError, Field, Fields};
5
6use crate::error::{GeoArrowError, GeoArrowResult};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Dimension {
13 XY,
15
16 XYZ,
18
19 XYM,
21
22 XYZM,
24}
25
26impl Dimension {
27 pub(crate) fn from_interleaved_field(field: &Field) -> GeoArrowResult<Self> {
28 let dim = match field.name().as_str() {
29 "xy" => Dimension::XY,
30 "xyz" => Dimension::XYZ,
31 "xym" => Dimension::XYM,
32 "xyzm" => Dimension::XYZM,
33 _ => {
34 return Err(ArrowError::SchemaError(format!(
35 "Invalid interleaved field name: {}",
36 field.name()
37 ))
38 .into());
39 }
40 };
41 Ok(dim)
42 }
43
44 pub(crate) fn from_separated_field(fields: &Fields) -> GeoArrowResult<Self> {
45 let dim = if fields.len() == 2 {
46 Self::XY
47 } else if fields.len() == 3 {
48 let field_names: HashSet<&str> =
49 HashSet::from_iter(fields.iter().map(|f| f.name().as_str()));
50 let xym_field_names = HashSet::<&str>::from_iter(["x", "y", "m"]);
51 let xyz_field_names = HashSet::<&str>::from_iter(["x", "y", "z"]);
52
53 if field_names.eq(&xym_field_names) {
54 Self::XYM
55 } else if field_names.eq(&xyz_field_names) {
56 Self::XYZ
57 } else {
58 return Err(ArrowError::SchemaError(format!(
59 "Invalid field names for separated coordinates with 3 dimensions: {field_names:?}",
60
61 ))
62 .into());
63 }
64 } else if fields.len() == 4 {
65 Self::XYZM
66 } else {
67 return Err(ArrowError::SchemaError(format!(
68 "Invalid fields for separated coordinates: {fields:?}",
69 ))
70 .into());
71 };
72 Ok(dim)
73 }
74
75 pub fn size(&self) -> usize {
77 match self {
78 Dimension::XY => 2,
79 Dimension::XYZ => 3,
80 Dimension::XYM => 3,
81 Dimension::XYZM => 4,
82 }
83 }
84}
85
86impl From<Dimension> for geo_traits::Dimensions {
87 fn from(value: Dimension) -> Self {
88 match value {
89 Dimension::XY => geo_traits::Dimensions::Xy,
90 Dimension::XYZ => geo_traits::Dimensions::Xyz,
91 Dimension::XYM => geo_traits::Dimensions::Xym,
92 Dimension::XYZM => geo_traits::Dimensions::Xyzm,
93 }
94 }
95}
96
97impl TryFrom<geo_traits::Dimensions> for Dimension {
98 type Error = GeoArrowError;
99
100 fn try_from(value: geo_traits::Dimensions) -> std::result::Result<Self, Self::Error> {
101 match value {
102 geo_traits::Dimensions::Xy | geo_traits::Dimensions::Unknown(2) => Ok(Dimension::XY),
103 geo_traits::Dimensions::Xyz | geo_traits::Dimensions::Unknown(3) => Ok(Dimension::XYZ),
104 geo_traits::Dimensions::Xym => Ok(Dimension::XYM),
105 geo_traits::Dimensions::Xyzm | geo_traits::Dimensions::Unknown(4) => {
106 Ok(Dimension::XYZM)
107 }
108 _ => Err(GeoArrowError::InvalidGeoArrow(format!(
109 "Unsupported dimension {value:?}"
110 ))),
111 }
112 }
113}
114
115impl Display for Dimension {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 Dimension::XY => write!(f, "XY"),
119 Dimension::XYZ => write!(f, "XYZ"),
120 Dimension::XYM => write!(f, "XYM"),
121 Dimension::XYZM => write!(f, "XYZM"),
122 }
123 }
124}
125
126#[cfg(test)]
127mod test {
128 use std::iter::zip;
129
130 use arrow_schema::DataType;
131
132 use super::*;
133
134 #[test]
135 fn from_interleaved() {
136 assert!(matches!(
137 Dimension::from_interleaved_field(&Field::new("xy", DataType::Null, false)).unwrap(),
138 Dimension::XY
139 ));
140
141 assert!(matches!(
142 Dimension::from_interleaved_field(&Field::new("xyz", DataType::Null, false)).unwrap(),
143 Dimension::XYZ
144 ));
145
146 assert!(matches!(
147 Dimension::from_interleaved_field(&Field::new("xym", DataType::Null, false)).unwrap(),
148 Dimension::XYM
149 ));
150
151 assert!(matches!(
152 Dimension::from_interleaved_field(&Field::new("xyzm", DataType::Null, false)).unwrap(),
153 Dimension::XYZM
154 ));
155 }
156
157 #[test]
158 fn from_bad_interleaved() {
159 assert!(
160 Dimension::from_interleaved_field(&Field::new("banana", DataType::Null, false))
161 .is_err()
162 );
163 assert!(
164 Dimension::from_interleaved_field(&Field::new("x", DataType::Null, false)).is_err()
165 );
166 assert!(
167 Dimension::from_interleaved_field(&Field::new("xyzmt", DataType::Null, false)).is_err()
168 );
169 }
170
171 fn test_fields(dims: &[&str]) -> Fields {
172 dims.iter()
173 .map(|dim| Field::new(*dim, DataType::Null, false))
174 .collect()
175 }
176
177 #[test]
178 fn from_separated() {
179 assert!(matches!(
180 Dimension::from_separated_field(&test_fields(&["x", "y"])).unwrap(),
181 Dimension::XY
182 ));
183
184 assert!(matches!(
185 Dimension::from_separated_field(&test_fields(&["x", "y", "z"])).unwrap(),
186 Dimension::XYZ
187 ));
188
189 assert!(matches!(
190 Dimension::from_separated_field(&test_fields(&["x", "y", "m"])).unwrap(),
191 Dimension::XYM
192 ));
193
194 assert!(matches!(
195 Dimension::from_separated_field(&test_fields(&["x", "y", "z", "m"])).unwrap(),
196 Dimension::XYZM
197 ));
198 }
199
200 #[test]
201 fn from_bad_separated() {
202 assert!(Dimension::from_separated_field(&test_fields(&["x"])).is_err());
203 assert!(Dimension::from_separated_field(&test_fields(&["x", "y", "a"])).is_err());
204 assert!(Dimension::from_separated_field(&test_fields(&["x", "y", "z", "m", "t"])).is_err());
205 }
206
207 #[test]
208 fn geotraits_dimensions() {
209 let geoarrow_dims = [
210 Dimension::XY,
211 Dimension::XYZ,
212 Dimension::XYM,
213 Dimension::XYZM,
214 ];
215 let geotraits_dims = [
216 geo_traits::Dimensions::Xy,
217 geo_traits::Dimensions::Xyz,
218 geo_traits::Dimensions::Xym,
219 geo_traits::Dimensions::Xyzm,
220 ];
221
222 for (geoarrow_dim, geotraits_dim) in zip(geoarrow_dims, geotraits_dims) {
223 let into_geotraits_dim: geo_traits::Dimensions = geoarrow_dim.into();
224 assert_eq!(into_geotraits_dim, geotraits_dim);
225
226 let into_geoarrow_dim: Dimension = geotraits_dim.try_into().unwrap();
227 assert_eq!(into_geoarrow_dim, geoarrow_dim);
228
229 assert_eq!(geoarrow_dim.size(), geotraits_dim.size());
230 }
231
232 let dims2: Dimension = geo_traits::Dimensions::Unknown(2).try_into().unwrap();
233 assert_eq!(dims2, Dimension::XY);
234
235 let dims3: Dimension = geo_traits::Dimensions::Unknown(3).try_into().unwrap();
236 assert_eq!(dims3, Dimension::XYZ);
237
238 let dims4: Dimension = geo_traits::Dimensions::Unknown(4).try_into().unwrap();
239 assert_eq!(dims4, Dimension::XYZM);
240
241 let dims_err: Result<Dimension, GeoArrowError> =
242 geo_traits::Dimensions::Unknown(0).try_into();
243 assert_eq!(
244 dims_err.unwrap_err().to_string(),
245 "Data not conforming to GeoArrow specification: Unsupported dimension Unknown(0)"
246 );
247 }
248}