1use nodedb_types::geometry::Geometry;
18
19const WKB_POINT: u32 = 1;
21const WKB_LINESTRING: u32 = 2;
22const WKB_POLYGON: u32 = 3;
23const WKB_MULTIPOINT: u32 = 4;
24const WKB_MULTILINESTRING: u32 = 5;
25const WKB_MULTIPOLYGON: u32 = 6;
26const WKB_GEOMETRYCOLLECTION: u32 = 7;
27
28const BYTE_ORDER_LE: u8 = 1;
29
30pub fn geometry_to_wkb(geom: &Geometry) -> Vec<u8> {
32 let mut buf = Vec::with_capacity(64);
34 write_geometry(&mut buf, geom);
35 buf
36}
37
38pub fn geometry_from_wkb(data: &[u8]) -> Option<Geometry> {
42 let mut cursor = 0;
43 read_geometry(data, &mut cursor)
44}
45
46pub fn wkb_bbox(data: &[u8]) -> Option<nodedb_types::BoundingBox> {
51 let geom = geometry_from_wkb(data)?;
52 Some(nodedb_types::geometry_bbox(&geom))
53}
54
55fn write_geometry(buf: &mut Vec<u8>, geom: &Geometry) {
58 match geom {
59 Geometry::Point { coordinates } => {
60 write_header(buf, WKB_POINT);
61 write_f64(buf, coordinates[0]);
62 write_f64(buf, coordinates[1]);
63 }
64 Geometry::LineString { coordinates } => {
65 write_header(buf, WKB_LINESTRING);
66 write_u32(buf, coordinates.len() as u32);
67 for c in coordinates {
68 write_f64(buf, c[0]);
69 write_f64(buf, c[1]);
70 }
71 }
72 Geometry::Polygon { coordinates } => {
73 write_header(buf, WKB_POLYGON);
74 write_u32(buf, coordinates.len() as u32);
75 for ring in coordinates {
76 write_u32(buf, ring.len() as u32);
77 for c in ring {
78 write_f64(buf, c[0]);
79 write_f64(buf, c[1]);
80 }
81 }
82 }
83 Geometry::MultiPoint { coordinates } => {
84 write_header(buf, WKB_MULTIPOINT);
85 write_u32(buf, coordinates.len() as u32);
86 for c in coordinates {
87 write_header(buf, WKB_POINT);
89 write_f64(buf, c[0]);
90 write_f64(buf, c[1]);
91 }
92 }
93 Geometry::MultiLineString { coordinates } => {
94 write_header(buf, WKB_MULTILINESTRING);
95 write_u32(buf, coordinates.len() as u32);
96 for ls in coordinates {
97 write_geometry(
98 buf,
99 &Geometry::LineString {
100 coordinates: ls.clone(),
101 },
102 );
103 }
104 }
105 Geometry::MultiPolygon { coordinates } => {
106 write_header(buf, WKB_MULTIPOLYGON);
107 write_u32(buf, coordinates.len() as u32);
108 for poly in coordinates {
109 write_geometry(
110 buf,
111 &Geometry::Polygon {
112 coordinates: poly.clone(),
113 },
114 );
115 }
116 }
117 Geometry::GeometryCollection { geometries } => {
118 write_header(buf, WKB_GEOMETRYCOLLECTION);
119 write_u32(buf, geometries.len() as u32);
120 for g in geometries {
121 write_geometry(buf, g);
122 }
123 }
124
125 _ => {
127 write_header(buf, WKB_GEOMETRYCOLLECTION);
128 write_u32(buf, 0);
129 }
130 }
131}
132
133fn write_header(buf: &mut Vec<u8>, wkb_type: u32) {
134 buf.push(BYTE_ORDER_LE);
135 write_u32(buf, wkb_type);
136}
137
138fn write_u32(buf: &mut Vec<u8>, val: u32) {
139 buf.extend_from_slice(&val.to_le_bytes());
140}
141
142fn write_f64(buf: &mut Vec<u8>, val: f64) {
143 buf.extend_from_slice(&val.to_le_bytes());
144}
145
146fn read_geometry(data: &[u8], cursor: &mut usize) -> Option<Geometry> {
149 let byte_order = read_u8(data, cursor)?;
150 let is_le = byte_order == 1;
151 let wkb_type = read_u32(data, cursor, is_le)?;
152
153 match wkb_type {
154 WKB_POINT => {
155 let x = read_f64(data, cursor, is_le)?;
156 let y = read_f64(data, cursor, is_le)?;
157 Some(Geometry::Point {
158 coordinates: [x, y],
159 })
160 }
161 WKB_LINESTRING => {
162 let n = read_u32(data, cursor, is_le)? as usize;
163 let coords = read_coords(data, cursor, n, is_le)?;
164 Some(Geometry::LineString {
165 coordinates: coords,
166 })
167 }
168 WKB_POLYGON => {
169 let num_rings = read_u32(data, cursor, is_le)? as usize;
170 let mut rings = Vec::with_capacity(num_rings);
172 for _ in 0..num_rings {
173 let n = read_u32(data, cursor, is_le)? as usize;
174 let ring = read_coords(data, cursor, n, is_le)?;
175 rings.push(ring);
176 }
177 Some(Geometry::Polygon { coordinates: rings })
178 }
179 WKB_MULTIPOINT => {
180 let count = read_u32(data, cursor, is_le)? as usize;
181 let mut coords = Vec::with_capacity(count);
183 for _ in 0..count {
184 let inner = read_geometry(data, cursor)?;
185 if let Geometry::Point { coordinates } = inner {
186 coords.push(coordinates);
187 } else {
188 return None;
189 }
190 }
191 Some(Geometry::MultiPoint {
192 coordinates: coords,
193 })
194 }
195 WKB_MULTILINESTRING => {
196 let count = read_u32(data, cursor, is_le)? as usize;
197 let mut lines = Vec::with_capacity(count);
199 for _ in 0..count {
200 let inner = read_geometry(data, cursor)?;
201 if let Geometry::LineString { coordinates } = inner {
202 lines.push(coordinates);
203 } else {
204 return None;
205 }
206 }
207 Some(Geometry::MultiLineString { coordinates: lines })
208 }
209 WKB_MULTIPOLYGON => {
210 let count = read_u32(data, cursor, is_le)? as usize;
211 let mut polys = Vec::with_capacity(count);
213 for _ in 0..count {
214 let inner = read_geometry(data, cursor)?;
215 if let Geometry::Polygon { coordinates } = inner {
216 polys.push(coordinates);
217 } else {
218 return None;
219 }
220 }
221 Some(Geometry::MultiPolygon { coordinates: polys })
222 }
223 WKB_GEOMETRYCOLLECTION => {
224 let count = read_u32(data, cursor, is_le)? as usize;
225 let mut geoms = Vec::with_capacity(count);
227 for _ in 0..count {
228 geoms.push(read_geometry(data, cursor)?);
229 }
230 Some(Geometry::GeometryCollection { geometries: geoms })
231 }
232 _ => None,
233 }
234}
235
236fn read_u8(data: &[u8], cursor: &mut usize) -> Option<u8> {
237 if *cursor >= data.len() {
238 return None;
239 }
240 let val = data[*cursor];
241 *cursor += 1;
242 Some(val)
243}
244
245fn read_u32(data: &[u8], cursor: &mut usize, is_le: bool) -> Option<u32> {
246 if *cursor + 4 > data.len() {
247 return None;
248 }
249 let bytes: [u8; 4] = [
250 data[*cursor],
251 data[*cursor + 1],
252 data[*cursor + 2],
253 data[*cursor + 3],
254 ];
255 *cursor += 4;
256 Some(if is_le {
257 u32::from_le_bytes(bytes)
258 } else {
259 u32::from_be_bytes(bytes)
260 })
261}
262
263fn read_f64(data: &[u8], cursor: &mut usize, is_le: bool) -> Option<f64> {
264 if *cursor + 8 > data.len() {
265 return None;
266 }
267 let bytes: [u8; 8] = [
268 data[*cursor],
269 data[*cursor + 1],
270 data[*cursor + 2],
271 data[*cursor + 3],
272 data[*cursor + 4],
273 data[*cursor + 5],
274 data[*cursor + 6],
275 data[*cursor + 7],
276 ];
277 *cursor += 8;
278 Some(if is_le {
279 f64::from_le_bytes(bytes)
280 } else {
281 f64::from_be_bytes(bytes)
282 })
283}
284
285fn read_coords(
286 data: &[u8],
287 cursor: &mut usize,
288 count: usize,
289 is_le: bool,
290) -> Option<Vec<[f64; 2]>> {
291 let mut coords = Vec::with_capacity(count);
293 for _ in 0..count {
294 let x = read_f64(data, cursor, is_le)?;
295 let y = read_f64(data, cursor, is_le)?;
296 coords.push([x, y]);
297 }
298 Some(coords)
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn point_roundtrip() {
307 let geom = Geometry::point(-73.9857, 40.7484);
308 let wkb = geometry_to_wkb(&geom);
309 let decoded = geometry_from_wkb(&wkb).unwrap();
310 assert_eq!(geom, decoded);
311 }
312
313 #[test]
314 fn linestring_roundtrip() {
315 let geom = Geometry::line_string(vec![[0.0, 0.0], [1.0, 1.0], [2.0, 0.0]]);
316 let wkb = geometry_to_wkb(&geom);
317 let decoded = geometry_from_wkb(&wkb).unwrap();
318 assert_eq!(geom, decoded);
319 }
320
321 #[test]
322 fn polygon_roundtrip() {
323 let geom = Geometry::polygon(vec![
324 vec![
325 [0.0, 0.0],
326 [10.0, 0.0],
327 [10.0, 10.0],
328 [0.0, 10.0],
329 [0.0, 0.0],
330 ],
331 vec![[2.0, 2.0], [3.0, 2.0], [3.0, 3.0], [2.0, 3.0], [2.0, 2.0]], ]);
333 let wkb = geometry_to_wkb(&geom);
334 let decoded = geometry_from_wkb(&wkb).unwrap();
335 assert_eq!(geom, decoded);
336 }
337
338 #[test]
339 fn multipoint_roundtrip() {
340 let geom = Geometry::MultiPoint {
341 coordinates: vec![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]],
342 };
343 let wkb = geometry_to_wkb(&geom);
344 let decoded = geometry_from_wkb(&wkb).unwrap();
345 assert_eq!(geom, decoded);
346 }
347
348 #[test]
349 fn multilinestring_roundtrip() {
350 let geom = Geometry::MultiLineString {
351 coordinates: vec![
352 vec![[0.0, 0.0], [1.0, 1.0]],
353 vec![[2.0, 2.0], [3.0, 3.0], [4.0, 2.0]],
354 ],
355 };
356 let wkb = geometry_to_wkb(&geom);
357 let decoded = geometry_from_wkb(&wkb).unwrap();
358 assert_eq!(geom, decoded);
359 }
360
361 #[test]
362 fn multipolygon_roundtrip() {
363 let geom = Geometry::MultiPolygon {
364 coordinates: vec![
365 vec![vec![
366 [0.0, 0.0],
367 [1.0, 0.0],
368 [1.0, 1.0],
369 [0.0, 1.0],
370 [0.0, 0.0],
371 ]],
372 vec![vec![
373 [5.0, 5.0],
374 [6.0, 5.0],
375 [6.0, 6.0],
376 [5.0, 6.0],
377 [5.0, 5.0],
378 ]],
379 ],
380 };
381 let wkb = geometry_to_wkb(&geom);
382 let decoded = geometry_from_wkb(&wkb).unwrap();
383 assert_eq!(geom, decoded);
384 }
385
386 #[test]
387 fn geometry_collection_roundtrip() {
388 let geom = Geometry::GeometryCollection {
389 geometries: vec![
390 Geometry::point(1.0, 2.0),
391 Geometry::line_string(vec![[0.0, 0.0], [1.0, 1.0]]),
392 ],
393 };
394 let wkb = geometry_to_wkb(&geom);
395 let decoded = geometry_from_wkb(&wkb).unwrap();
396 assert_eq!(geom, decoded);
397 }
398
399 #[test]
400 fn truncated_data_returns_none() {
401 let wkb = geometry_to_wkb(&Geometry::point(1.0, 2.0));
402 assert!(geometry_from_wkb(&wkb[..3]).is_none());
403 assert!(geometry_from_wkb(&[]).is_none());
404 }
405
406 #[test]
407 fn invalid_type_returns_none() {
408 let mut wkb = geometry_to_wkb(&Geometry::point(1.0, 2.0));
409 wkb[1] = 99; assert!(geometry_from_wkb(&wkb).is_none());
411 }
412
413 #[test]
414 fn wkb_bbox_extraction() {
415 let geom = Geometry::polygon(vec![vec![
416 [-10.0, -5.0],
417 [10.0, -5.0],
418 [10.0, 5.0],
419 [-10.0, 5.0],
420 [-10.0, -5.0],
421 ]]);
422 let wkb = geometry_to_wkb(&geom);
423 let bb = wkb_bbox(&wkb).unwrap();
424 assert_eq!(bb.min_lng, -10.0);
425 assert_eq!(bb.max_lng, 10.0);
426 assert_eq!(bb.min_lat, -5.0);
427 assert_eq!(bb.max_lat, 5.0);
428 }
429
430 #[test]
431 fn point_wkb_size() {
432 let wkb = geometry_to_wkb(&Geometry::point(0.0, 0.0));
433 assert_eq!(wkb.len(), 21);
435 }
436}