1use serde_json::{Map, Value};
9
10use crate::error::{Result, ShapefileError};
11
12#[derive(Debug, Clone, Copy)]
15pub struct Position {
16 pub x: f64,
17 pub y: f64,
18 pub third: Option<f64>,
20 pub fourth: Option<f64>,
22}
23
24impl Position {
25 fn parse(value: &Value, index: usize) -> Result<Self> {
26 let array = value.as_array().ok_or_else(|| ShapefileError::Feature {
27 index,
28 message: "a coordinate must be an array of numbers".into(),
29 })?;
30
31 if array.len() < 2 {
32 return Err(ShapefileError::Feature {
33 index,
34 message: format!("a coordinate needs at least x and y, got {}", array.len()),
35 });
36 }
37
38 let ordinate = |slot: usize| -> Result<f64> {
39 array[slot].as_f64().ok_or_else(|| ShapefileError::Feature {
40 index,
41 message: format!("coordinate ordinate {slot} is not a number"),
42 })
43 };
44
45 Ok(Self {
46 x: ordinate(0)?,
47 y: ordinate(1)?,
48 third: if array.len() > 2 {
49 Some(ordinate(2)?)
50 } else {
51 None
52 },
53 fourth: if array.len() > 3 {
54 Some(ordinate(3)?)
55 } else {
56 None
57 },
58 })
59 }
60
61 pub fn arity(&self) -> usize {
63 2 + usize::from(self.third.is_some()) + usize::from(self.fourth.is_some())
64 }
65}
66
67#[derive(Debug, Clone)]
69pub enum Geometry {
70 Point(Position),
71 MultiPoint(Vec<Position>),
72 LineString(Vec<Position>),
73 MultiLineString(Vec<Vec<Position>>),
74 Polygon(Vec<Vec<Position>>),
75 MultiPolygon(Vec<Vec<Vec<Position>>>),
76}
77
78impl Geometry {
79 pub fn family(&self) -> Family {
81 match self {
82 Geometry::Point(_) => Family::Point,
83 Geometry::MultiPoint(_) => Family::Multipoint,
84 Geometry::LineString(_) | Geometry::MultiLineString(_) => Family::Polyline,
85 Geometry::Polygon(_) | Geometry::MultiPolygon(_) => Family::Polygon,
86 }
87 }
88
89 pub fn for_each_position(&self, mut visit: impl FnMut(&Position)) {
91 match self {
92 Geometry::Point(p) => visit(p),
93 Geometry::MultiPoint(ps) | Geometry::LineString(ps) => ps.iter().for_each(visit),
94 Geometry::MultiLineString(parts) | Geometry::Polygon(parts) => {
95 parts.iter().flatten().for_each(visit)
96 }
97 Geometry::MultiPolygon(polys) => polys.iter().flatten().flatten().for_each(visit),
98 }
99 }
100
101 fn parse(value: &Value, index: usize) -> Result<Self> {
102 let object = value.as_object().ok_or_else(|| ShapefileError::Feature {
103 index,
104 message: "geometry must be an object".into(),
105 })?;
106
107 let kind =
108 object
109 .get("type")
110 .and_then(Value::as_str)
111 .ok_or_else(|| ShapefileError::Feature {
112 index,
113 message: "geometry is missing a `type`".into(),
114 })?;
115
116 if kind == "GeometryCollection" {
117 return Err(ShapefileError::GeometryCollection { index });
118 }
119
120 let coordinates = object
121 .get("coordinates")
122 .ok_or_else(|| ShapefileError::Feature {
123 index,
124 message: format!("{kind} geometry is missing `coordinates`"),
125 })?;
126
127 match kind {
128 "Point" => Ok(Geometry::Point(Position::parse(coordinates, index)?)),
129 "MultiPoint" => Ok(Geometry::MultiPoint(parse_line(coordinates, index)?)),
130 "LineString" => Ok(Geometry::LineString(parse_line(coordinates, index)?)),
131 "MultiLineString" => Ok(Geometry::MultiLineString(parse_lines(coordinates, index)?)),
132 "Polygon" => Ok(Geometry::Polygon(parse_lines(coordinates, index)?)),
133 "MultiPolygon" => {
134 let outer = coordinates
135 .as_array()
136 .ok_or_else(|| ShapefileError::Feature {
137 index,
138 message: "MultiPolygon coordinates must be an array".into(),
139 })?;
140 outer
141 .iter()
142 .map(|poly| parse_lines(poly, index))
143 .collect::<Result<Vec<_>>>()
144 .map(Geometry::MultiPolygon)
145 }
146 other => Err(ShapefileError::UnsupportedGeometry(other.to_string())),
147 }
148 }
149}
150
151fn parse_line(value: &Value, index: usize) -> Result<Vec<Position>> {
152 let array = value.as_array().ok_or_else(|| ShapefileError::Feature {
153 index,
154 message: "expected an array of coordinates".into(),
155 })?;
156 array
157 .iter()
158 .map(|pos| Position::parse(pos, index))
159 .collect()
160}
161
162fn parse_lines(value: &Value, index: usize) -> Result<Vec<Vec<Position>>> {
163 let array = value.as_array().ok_or_else(|| ShapefileError::Feature {
164 index,
165 message: "expected an array of coordinate arrays".into(),
166 })?;
167 array.iter().map(|line| parse_line(line, index)).collect()
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum Family {
173 Point,
174 Multipoint,
175 Polyline,
176 Polygon,
177}
178
179impl Family {
180 pub fn label(self) -> &'static str {
181 match self {
182 Family::Point => "Point",
183 Family::Multipoint => "Multipoint",
184 Family::Polyline => "Polyline",
185 Family::Polygon => "Polygon",
186 }
187 }
188
189 pub fn parse(name: &str) -> Option<Self> {
190 match name.to_ascii_lowercase().as_str() {
191 "point" => Some(Family::Point),
192 "multipoint" => Some(Family::Multipoint),
193 "polyline" | "linestring" | "line" => Some(Family::Polyline),
194 "polygon" => Some(Family::Polygon),
195 _ => None,
196 }
197 }
198
199 pub fn reconcile(self, other: Family, index: usize) -> Result<Family> {
204 if self == other {
205 return Ok(self);
206 }
207 match (self, other) {
208 (Family::Point, Family::Multipoint) | (Family::Multipoint, Family::Point) => {
209 Ok(Family::Multipoint)
210 }
211 _ => Err(ShapefileError::MixedGeometry {
212 first: self.label().to_string(),
213 other: other.label().to_string(),
214 index,
215 }),
216 }
217 }
218}
219
220pub struct Feature {
222 pub geometry: Option<Geometry>,
223 pub properties: Map<String, Value>,
224}
225
226pub fn normalize(root: &Value) -> Result<Vec<Feature>> {
229 match root {
230 Value::Array(items) => {
231 let mut features = Vec::with_capacity(items.len());
232 for (index, item) in items.iter().enumerate() {
233 features.push(parse_feature(item, index)?);
234 }
235 Ok(features)
236 }
237 Value::Object(object) => match object.get("type").and_then(Value::as_str) {
238 Some("FeatureCollection") => {
239 let items = object
240 .get("features")
241 .and_then(Value::as_array)
242 .ok_or_else(|| {
243 ShapefileError::Input("FeatureCollection has no `features` array".into())
244 })?;
245 let mut features = Vec::with_capacity(items.len());
246 for (index, item) in items.iter().enumerate() {
247 features.push(parse_feature(item, index)?);
248 }
249 Ok(features)
250 }
251 Some(_) => Ok(vec![parse_feature(root, 0)?]),
252 None => Err(ShapefileError::Input(
253 "expected a GeoJSON object with a `type` member".into(),
254 )),
255 },
256 _ => Err(ShapefileError::Input(
257 "expected a GeoJSON object or an array of features".into(),
258 )),
259 }
260}
261
262fn parse_feature(value: &Value, index: usize) -> Result<Feature> {
263 let object = value.as_object().ok_or_else(|| ShapefileError::Feature {
264 index,
265 message: "expected an object".into(),
266 })?;
267
268 if object.get("type").and_then(Value::as_str) != Some("Feature") {
270 return Ok(Feature {
271 geometry: Some(Geometry::parse(value, index)?),
272 properties: Map::new(),
273 });
274 }
275
276 let geometry = match object.get("geometry") {
277 None | Some(Value::Null) => None,
278 Some(geometry) => Some(Geometry::parse(geometry, index)?),
279 };
280
281 let properties = match object.get("properties") {
282 Some(Value::Object(map)) => map.clone(),
283 _ => Map::new(),
284 };
285
286 Ok(Feature {
287 geometry,
288 properties,
289 })
290}