geonative_shapefile/
shape.rs1use geonative_core::{Coord, Geometry, GeometryType, LineString, Polygon};
24
25use crate::bytes::Cursor;
26use crate::error::{Result, ShpError};
27use crate::header::ShapeType;
28
29pub fn decode_record_content(content: &[u8]) -> Result<Geometry> {
30 let mut c = Cursor::new(content);
31 let stype = ShapeType::from_i32(c.read_i32_le()?)?;
32 match stype {
33 ShapeType::Null => Ok(Geometry::Empty(GeometryType::Point)),
34 ShapeType::Point => decode_point(&mut c),
35 ShapeType::Multipoint => decode_multipoint(&mut c),
36 ShapeType::Polyline => decode_polyline_or_polygon(&mut c, false),
37 ShapeType::Polygon => decode_polyline_or_polygon(&mut c, true),
38 other => Err(ShpError::unsupported(format!(
39 "shape type {other:?} (v0.1 supports 2D Null/Point/Polyline/Polygon/MultiPoint)"
40 ))),
41 }
42}
43
44fn decode_point(c: &mut Cursor<'_>) -> Result<Geometry> {
45 let x = c.read_f64_le()?;
46 let y = c.read_f64_le()?;
47 Ok(Geometry::Point(Coord {
48 x,
49 y,
50 z: None,
51 m: None,
52 }))
53}
54
55fn decode_multipoint(c: &mut Cursor<'_>) -> Result<Geometry> {
56 c.read_bytes(32)?;
58 let n = c.read_i32_le()? as usize;
59 let mut coords = Vec::with_capacity(n);
60 for _ in 0..n {
61 let x = c.read_f64_le()?;
62 let y = c.read_f64_le()?;
63 coords.push(Coord {
64 x,
65 y,
66 z: None,
67 m: None,
68 });
69 }
70 if coords.is_empty() {
71 Ok(Geometry::Empty(GeometryType::MultiPoint))
72 } else {
73 Ok(Geometry::MultiPoint(coords))
74 }
75}
76
77fn decode_polyline_or_polygon(c: &mut Cursor<'_>, is_polygon: bool) -> Result<Geometry> {
78 c.read_bytes(32)?;
80 let n_parts = c.read_i32_le()? as usize;
81 let n_points = c.read_i32_le()? as usize;
82 if n_parts == 0 || n_points == 0 {
83 return Ok(if is_polygon {
84 Geometry::Empty(GeometryType::Polygon)
85 } else {
86 Geometry::Empty(GeometryType::LineString)
87 });
88 }
89 let mut parts: Vec<usize> = Vec::with_capacity(n_parts + 1);
90 for _ in 0..n_parts {
91 parts.push(c.read_i32_le()? as usize);
92 }
93 parts.push(n_points);
94
95 let mut all_coords = Vec::with_capacity(n_points);
96 for _ in 0..n_points {
97 let x = c.read_f64_le()?;
98 let y = c.read_f64_le()?;
99 all_coords.push(Coord {
100 x,
101 y,
102 z: None,
103 m: None,
104 });
105 }
106
107 let mut part_rings: Vec<LineString> = Vec::with_capacity(n_parts);
108 for w in parts.windows(2) {
109 let (start, end) = (w[0], w[1]);
110 if start > end || end > n_points {
111 return Err(ShpError::malformed(format!(
112 "part indices out of range: {start}..{end} (total {n_points})"
113 )));
114 }
115 part_rings.push(LineString::new(all_coords[start..end].to_vec()));
116 }
117
118 if is_polygon {
119 let polygons = group_rings_esri_to_ogc(part_rings);
120 if polygons.len() == 1 {
121 Ok(Geometry::Polygon(polygons.into_iter().next().unwrap()))
122 } else {
123 Ok(Geometry::MultiPolygon(polygons))
124 }
125 } else if part_rings.len() == 1 {
126 Ok(Geometry::LineString(part_rings.into_iter().next().unwrap()))
127 } else {
128 Ok(Geometry::MultiLineString(part_rings))
129 }
130}
131
132fn group_rings_esri_to_ogc(rings: Vec<LineString>) -> Vec<Polygon> {
136 let mut polygons: Vec<Polygon> = Vec::new();
137 for ring in rings {
138 let is_outer_esri = signed_area(&ring.coords) < 0.0;
139 let mut reversed = ring;
140 reversed.coords.reverse();
141 if is_outer_esri {
142 polygons.push(Polygon::new(reversed, Vec::new()));
143 } else if let Some(last) = polygons.last_mut() {
144 last.holes.push(reversed);
145 } else {
146 polygons.push(Polygon::new(reversed, Vec::new()));
147 }
148 }
149 polygons
150}
151
152fn signed_area(coords: &[Coord]) -> f64 {
153 if coords.len() < 3 {
154 return 0.0;
155 }
156 let mut sum = 0.0;
157 let n = coords.len();
158 for i in 0..n {
159 let j = (i + 1) % n;
160 sum += coords[i].x * coords[j].y - coords[j].x * coords[i].y;
161 }
162 sum * 0.5
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 fn point_bytes(x: f64, y: f64) -> Vec<u8> {
170 let mut b = Vec::new();
171 b.extend_from_slice(&1i32.to_le_bytes()); b.extend_from_slice(&x.to_le_bytes());
173 b.extend_from_slice(&y.to_le_bytes());
174 b
175 }
176
177 fn polyline_bytes(parts: &[Vec<(f64, f64)>]) -> Vec<u8> {
178 let mut b = Vec::new();
179 b.extend_from_slice(&3i32.to_le_bytes()); for _ in 0..4 {
181 b.extend_from_slice(&0.0f64.to_le_bytes()); }
183 b.extend_from_slice(&(parts.len() as i32).to_le_bytes());
184 let n_points: i32 = parts.iter().map(|p| p.len() as i32).sum();
185 b.extend_from_slice(&n_points.to_le_bytes());
186 let mut idx = 0i32;
187 for part in parts {
188 b.extend_from_slice(&idx.to_le_bytes());
189 idx += part.len() as i32;
190 }
191 for part in parts {
192 for &(x, y) in part {
193 b.extend_from_slice(&x.to_le_bytes());
194 b.extend_from_slice(&y.to_le_bytes());
195 }
196 }
197 b
198 }
199
200 #[test]
201 fn decode_simple_point() {
202 let g = decode_record_content(&point_bytes(1.5, -3.2)).unwrap();
203 match g {
204 Geometry::Point(c) => {
205 assert_eq!(c.x, 1.5);
206 assert_eq!(c.y, -3.2);
207 }
208 _ => panic!("expected Point"),
209 }
210 }
211
212 #[test]
213 fn decode_single_part_polyline_is_linestring() {
214 let pts = vec![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
215 let g = decode_record_content(&polyline_bytes(&[pts])).unwrap();
216 match g {
217 Geometry::LineString(ls) => assert_eq!(ls.coords.len(), 3),
218 _ => panic!("expected LineString"),
219 }
220 }
221
222 #[test]
223 fn decode_two_part_polyline_is_multilinestring() {
224 let g = decode_record_content(&polyline_bytes(&[
225 vec![(0.0, 0.0), (1.0, 1.0)],
226 vec![(10.0, 10.0), (11.0, 11.0), (12.0, 12.0)],
227 ]))
228 .unwrap();
229 match g {
230 Geometry::MultiLineString(parts) => {
231 assert_eq!(parts.len(), 2);
232 assert_eq!(parts[0].coords.len(), 2);
233 assert_eq!(parts[1].coords.len(), 3);
234 }
235 _ => panic!("expected MultiLineString"),
236 }
237 }
238
239 #[test]
240 fn null_shape_returns_empty() {
241 let b = 0i32.to_le_bytes();
242 let g = decode_record_content(&b).unwrap();
243 assert!(matches!(g, Geometry::Empty(GeometryType::Point)));
244 }
245
246 #[test]
247 fn polygon_ring_orientation_flipped_to_ogc() {
248 let cw_outer: Vec<(f64, f64)> = vec![
250 (0.0, 0.0),
251 (0.0, 10.0),
252 (10.0, 10.0),
253 (10.0, 0.0),
254 (0.0, 0.0),
255 ];
256 let mut b = Vec::new();
258 b.extend_from_slice(&5i32.to_le_bytes()); for _ in 0..4 {
260 b.extend_from_slice(&0.0f64.to_le_bytes());
261 }
262 b.extend_from_slice(&1i32.to_le_bytes()); b.extend_from_slice(&(cw_outer.len() as i32).to_le_bytes());
264 b.extend_from_slice(&0i32.to_le_bytes()); for &(x, y) in &cw_outer {
266 b.extend_from_slice(&x.to_le_bytes());
267 b.extend_from_slice(&y.to_le_bytes());
268 }
269
270 let g = decode_record_content(&b).unwrap();
271 let poly = match g {
272 Geometry::Polygon(p) => p,
273 other => panic!("expected Polygon, got {:?}", other),
274 };
275 assert!(signed_area(&poly.exterior.coords) > 0.0);
277 }
278}