tg_geom/
parse.rs

1//! Geometry parsing and serialization.
2
3use core::ffi::CStr;
4
5use crate::error::{Error, Result};
6use crate::{Geom, IndexKind, Point, Rect};
7
8enum ParseFormat {
9    Wkt,
10    Wkb,
11    GeoJson,
12    Hex,
13    GeoBin,
14    Auto,
15}
16
17fn parse_error_from_c(ptr: *const libc::c_char, format: ParseFormat) -> Error {
18    let msg = if ptr.is_null() {
19        "Unknown error".to_owned()
20    } else {
21        let c_str = unsafe { CStr::from_ptr(ptr) };
22        c_str.to_string_lossy().into_owned()
23    };
24
25    if msg == "no memory" {
26        return Error::OutOfMemory;
27    }
28
29    match format {
30        ParseFormat::Wkt => Error::WktParse(msg),
31        ParseFormat::Wkb => Error::WkbParse(msg),
32        ParseFormat::GeoJson => Error::GeoJsonParse(msg),
33        ParseFormat::Hex => Error::HexParse(msg),
34        ParseFormat::GeoBin => Error::GeoBinParse(msg),
35        ParseFormat::Auto => Error::Parse(msg),
36    }
37}
38
39impl Geom {
40    pub fn from_geojson(geojson: &str) -> Result<Self> {
41        Self::from_geojson_ix(geojson, IndexKind::Default)
42    }
43
44    pub fn from_geojson_ix(geojson: &str, ix: IndexKind) -> Result<Self> {
45        let ptr = unsafe {
46            tg_geom_sys::tg_parse_geojsonn_ix(
47                geojson.as_ptr() as *const libc::c_char,
48                geojson.len(),
49                ix.into(),
50            )
51        };
52        Self::from_parsed_ptr(ptr, ParseFormat::GeoJson)
53    }
54
55    pub fn from_wkt(wkt: &str) -> Result<Self> {
56        Self::from_wkt_ix(wkt, IndexKind::Default)
57    }
58
59    pub fn from_wkt_ix(wkt: &str, ix: IndexKind) -> Result<Self> {
60        let ptr = unsafe {
61            tg_geom_sys::tg_parse_wktn_ix(wkt.as_ptr() as *const libc::c_char, wkt.len(), ix.into())
62        };
63        Self::from_parsed_ptr(ptr, ParseFormat::Wkt)
64    }
65
66    pub fn from_wkb(wkb: &[u8]) -> Result<Self> {
67        Self::from_wkb_ix(wkb, IndexKind::Default)
68    }
69
70    pub fn from_wkb_ix(wkb: &[u8], ix: IndexKind) -> Result<Self> {
71        let ptr = unsafe { tg_geom_sys::tg_parse_wkb_ix(wkb.as_ptr(), wkb.len(), ix.into()) };
72        Self::from_parsed_ptr(ptr, ParseFormat::Wkb)
73    }
74
75    pub fn from_hex(hex: &str) -> Result<Self> {
76        Self::from_hex_ix(hex, IndexKind::Default)
77    }
78
79    pub fn from_hex_ix(hex: &str, ix: IndexKind) -> Result<Self> {
80        let ptr = unsafe {
81            tg_geom_sys::tg_parse_hexn_ix(hex.as_ptr() as *const libc::c_char, hex.len(), ix.into())
82        };
83        Self::from_parsed_ptr(ptr, ParseFormat::Hex)
84    }
85
86    pub fn from_geobin(geobin: &[u8]) -> Result<Self> {
87        Self::from_geobin_ix(geobin, IndexKind::Default)
88    }
89
90    pub fn from_geobin_ix(geobin: &[u8], ix: IndexKind) -> Result<Self> {
91        let ptr =
92            unsafe { tg_geom_sys::tg_parse_geobin_ix(geobin.as_ptr(), geobin.len(), ix.into()) };
93        Self::from_parsed_ptr(ptr, ParseFormat::GeoBin)
94    }
95
96    /// Auto-detect format.
97    pub fn parse(data: &[u8]) -> Result<Self> {
98        Self::parse_ix(data, IndexKind::Default)
99    }
100
101    pub fn parse_ix(data: &[u8], ix: IndexKind) -> Result<Self> {
102        let ptr = unsafe {
103            tg_geom_sys::tg_parse_ix(data.as_ptr() as *const libc::c_void, data.len(), ix.into())
104        };
105        Self::from_parsed_ptr(ptr, ParseFormat::Auto)
106    }
107
108    fn from_parsed_ptr(ptr: *mut tg_geom_sys::tg_geom, format: ParseFormat) -> Result<Self> {
109        if ptr.is_null() {
110            return Err(Error::OutOfMemory);
111        }
112
113        let err_ptr = unsafe { tg_geom_sys::tg_geom_error(ptr) };
114        if !err_ptr.is_null() {
115            let err = parse_error_from_c(err_ptr, format);
116            unsafe { tg_geom_sys::tg_geom_free(ptr) };
117            return Err(err);
118        }
119
120        unsafe { Self::from_raw(ptr) }.ok_or(Error::OutOfMemory)
121    }
122
123    pub fn to_geojson(&self) -> String {
124        let needed =
125            unsafe { tg_geom_sys::tg_geom_geojson(self.as_ptr(), core::ptr::null_mut(), 0) };
126        if needed == 0 {
127            return String::new();
128        }
129        let mut buf = vec![0u8; needed + 1];
130        let written = unsafe {
131            tg_geom_sys::tg_geom_geojson(
132                self.as_ptr(),
133                buf.as_mut_ptr() as *mut libc::c_char,
134                buf.len(),
135            )
136        };
137        buf.truncate(written);
138        String::from_utf8_lossy(&buf).into_owned()
139    }
140
141    pub fn to_wkt(&self) -> String {
142        let needed = unsafe { tg_geom_sys::tg_geom_wkt(self.as_ptr(), core::ptr::null_mut(), 0) };
143        if needed == 0 {
144            return String::new();
145        }
146        let mut buf = vec![0u8; needed + 1];
147        let written = unsafe {
148            tg_geom_sys::tg_geom_wkt(
149                self.as_ptr(),
150                buf.as_mut_ptr() as *mut libc::c_char,
151                buf.len(),
152            )
153        };
154        buf.truncate(written);
155        String::from_utf8_lossy(&buf).into_owned()
156    }
157
158    pub fn to_wkb(&self) -> Vec<u8> {
159        let len = unsafe { tg_geom_sys::tg_geom_wkb(self.as_ptr(), core::ptr::null_mut(), 0) };
160        let mut buf = vec![0u8; len];
161        unsafe {
162            tg_geom_sys::tg_geom_wkb(self.as_ptr(), buf.as_mut_ptr(), len);
163        }
164        buf
165    }
166
167    pub fn to_hex(&self) -> String {
168        let needed = unsafe { tg_geom_sys::tg_geom_hex(self.as_ptr(), core::ptr::null_mut(), 0) };
169        if needed == 0 {
170            return String::new();
171        }
172        let mut buf = vec![0u8; needed + 1];
173        let written = unsafe {
174            tg_geom_sys::tg_geom_hex(
175                self.as_ptr(),
176                buf.as_mut_ptr() as *mut libc::c_char,
177                buf.len(),
178            )
179        };
180        buf.truncate(written);
181        String::from_utf8_lossy(&buf).into_owned()
182    }
183
184    pub fn to_geobin(&self) -> Vec<u8> {
185        let len = unsafe { tg_geom_sys::tg_geom_geobin(self.as_ptr(), core::ptr::null_mut(), 0) };
186        let mut buf = vec![0u8; len];
187        unsafe {
188            tg_geom_sys::tg_geom_geobin(self.as_ptr(), buf.as_mut_ptr(), len);
189        }
190        buf
191    }
192}
193
194/// Extract rect from `GeoBin` without full parsing.
195pub fn geobin_rect(geobin: &[u8]) -> Rect {
196    let r = unsafe { tg_geom_sys::tg_geobin_rect(geobin.as_ptr(), geobin.len()) };
197    r.into()
198}
199
200/// Extract point from `GeoBin` without full parsing.
201pub fn geobin_point(geobin: &[u8]) -> Point {
202    let p = unsafe { tg_geom_sys::tg_geobin_point(geobin.as_ptr(), geobin.len()) };
203    p.into()
204}
205
206/// Extract N-dim bounding box from `GeoBin` without full parsing.
207pub fn geobin_fullrect(geobin: &[u8]) -> (i32, [f64; 4], [f64; 4]) {
208    let mut min = [0.0f64; 4];
209    let mut max = [0.0f64; 4];
210    let dims = unsafe {
211        tg_geom_sys::tg_geobin_fullrect(
212            geobin.as_ptr(),
213            geobin.len(),
214            min.as_mut_ptr(),
215            max.as_mut_ptr(),
216        )
217    };
218    (dims, min, max)
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use crate::GeomType;
225
226    fn p(x: f64, y: f64) -> Point {
227        Point::new(x, y)
228    }
229
230    fn r(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Rect {
231        Rect::from_coords(min_x, min_y, max_x, max_y)
232    }
233
234    #[test]
235    fn test_wkt_point() {
236        let geom = Geom::from_wkt("POINT(1 2)").unwrap();
237        assert_eq!(geom.geom_type(), GeomType::Point);
238        assert_eq!(geom.point(), p(1.0, 2.0));
239    }
240
241    #[test]
242    fn test_wkt_point_z() {
243        let geom = Geom::from_wkt("POINT Z(1 2 3)").unwrap();
244        assert_eq!(geom.geom_type(), GeomType::Point);
245        assert!(geom.has_z());
246        assert_eq!(geom.z(), 3.0);
247    }
248
249    #[test]
250    fn test_wkt_point_empty() {
251        let geom = Geom::from_wkt("POINT EMPTY").unwrap();
252        assert_eq!(geom.geom_type(), GeomType::Point);
253        assert!(geom.is_empty());
254    }
255
256    #[test]
257    fn test_wkt_linestring() {
258        let geom = Geom::from_wkt("LINESTRING(0 0, 1 1, 2 2)").unwrap();
259        assert_eq!(geom.geom_type(), GeomType::LineString);
260        assert!(!geom.is_empty());
261    }
262
263    #[test]
264    fn test_wkt_linestring_empty() {
265        let geom = Geom::from_wkt("LINESTRING EMPTY").unwrap();
266        assert_eq!(geom.geom_type(), GeomType::LineString);
267        assert!(geom.is_empty());
268    }
269
270    #[test]
271    fn test_wkt_polygon() {
272        let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
273        assert_eq!(geom.geom_type(), GeomType::Polygon);
274        assert!(!geom.is_empty());
275    }
276
277    #[test]
278    fn test_wkt_polygon_with_hole() {
279        let geom =
280            Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 8 2, 8 8, 2 8, 2 2))")
281                .unwrap();
282        assert_eq!(geom.geom_type(), GeomType::Polygon);
283        let poly = geom.poly().unwrap();
284        assert_eq!(poly.num_holes(), 1);
285    }
286
287    #[test]
288    fn test_wkt_polygon_empty() {
289        let geom = Geom::from_wkt("POLYGON EMPTY").unwrap();
290        assert_eq!(geom.geom_type(), GeomType::Polygon);
291        assert!(geom.is_empty());
292    }
293
294    #[test]
295    fn test_wkt_multipoint() {
296        let geom = Geom::from_wkt("MULTIPOINT((1 2), (3 4))").unwrap();
297        assert_eq!(geom.geom_type(), GeomType::MultiPoint);
298        assert_eq!(geom.num_points(), 2);
299    }
300
301    #[test]
302    fn test_wkt_multilinestring() {
303        let geom = Geom::from_wkt("MULTILINESTRING((0 0, 1 1), (2 2, 3 3))").unwrap();
304        assert_eq!(geom.geom_type(), GeomType::MultiLineString);
305        assert_eq!(geom.num_lines(), 2);
306    }
307
308    #[test]
309    fn test_wkt_multipolygon() {
310        let geom = Geom::from_wkt(
311            "MULTIPOLYGON(((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))",
312        )
313        .unwrap();
314        assert_eq!(geom.geom_type(), GeomType::MultiPolygon);
315        assert_eq!(geom.num_polys(), 2);
316    }
317
318    #[test]
319    fn test_wkt_geometrycollection() {
320        let geom = Geom::from_wkt("GEOMETRYCOLLECTION(POINT(1 2), LINESTRING(0 0, 1 1))").unwrap();
321        assert_eq!(geom.geom_type(), GeomType::GeometryCollection);
322        assert_eq!(geom.num_geometries(), 2);
323    }
324
325    #[test]
326    fn test_wkt_parse_error() {
327        let result = Geom::from_wkt("INVALID WKT");
328        assert!(result.is_err());
329        match result {
330            Err(Error::WktParse(_)) => {}
331            _ => panic!("Expected WktParse error"),
332        }
333
334        assert!(Geom::from_wkt(" POINT ").is_err());
335        assert!(Geom::from_wkt("POINT").is_err());
336        assert!(Geom::from_wkt("POINT EMPTY").is_ok());
337        assert!(Geom::from_wkt("POINT ZM ()").is_err());
338        assert!(Geom::from_wkt("POINT Z ()").is_err());
339        assert!(Geom::from_wkt("POINT ()").is_err());
340        assert!(Geom::from_wkt("POINT ZM () asdf").is_err());
341    }
342
343    #[test]
344    fn test_wkb_roundtrip_point() {
345        let original = Geom::from_wkt("POINT(1 2)").unwrap();
346        let wkb = original.to_wkb();
347        let parsed = Geom::from_wkb(&wkb).unwrap();
348        assert_eq!(parsed.geom_type(), GeomType::Point);
349        assert_eq!(parsed.point(), p(1.0, 2.0));
350    }
351
352    #[test]
353    fn test_wkb_roundtrip_linestring() {
354        let original = Geom::from_wkt("LINESTRING(0 0, 1 1, 2 2)").unwrap();
355        let wkb = original.to_wkb();
356        let parsed = Geom::from_wkb(&wkb).unwrap();
357        assert_eq!(parsed.geom_type(), GeomType::LineString);
358    }
359
360    #[test]
361    fn test_wkb_roundtrip_polygon() {
362        let original = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
363        let wkb = original.to_wkb();
364        let parsed = Geom::from_wkb(&wkb).unwrap();
365        assert_eq!(parsed.geom_type(), GeomType::Polygon);
366    }
367
368    #[test]
369    fn test_wkb_invalid() {
370        let result = Geom::from_wkb(&[0x00, 0x01, 0x02, 0x03]);
371        assert!(result.is_err());
372    }
373
374    #[test]
375    fn test_hex_roundtrip() {
376        let original = Geom::from_wkt("POINT(1 2)").unwrap();
377        let hex = original.to_hex();
378        let parsed = Geom::from_hex(&hex).unwrap();
379        assert_eq!(parsed.geom_type(), GeomType::Point);
380        assert_eq!(parsed.point(), p(1.0, 2.0));
381    }
382
383    #[test]
384    fn test_hex_format() {
385        let geom = Geom::from_wkt("POINT(1 2)").unwrap();
386        let hex = geom.to_hex();
387        // HEX should be uppercase hex characters only
388        assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
389    }
390
391    #[test]
392    fn test_geojson_point() {
393        let geojson = r#"{"type":"Point","coordinates":[1,2]}"#;
394        let geom = Geom::from_geojson(geojson).unwrap();
395        assert_eq!(geom.geom_type(), GeomType::Point);
396        assert_eq!(geom.point(), p(1.0, 2.0));
397    }
398
399    #[test]
400    fn test_geojson_linestring() {
401        let geojson = r#"{"type":"LineString","coordinates":[[0,0],[1,1],[2,2]]}"#;
402        let geom = Geom::from_geojson(geojson).unwrap();
403        assert_eq!(geom.geom_type(), GeomType::LineString);
404    }
405
406    #[test]
407    fn test_geojson_polygon() {
408        let geojson = r#"{"type":"Polygon","coordinates":[[[0,0],[10,0],[10,10],[0,10],[0,0]]]}"#;
409        let geom = Geom::from_geojson(geojson).unwrap();
410        assert_eq!(geom.geom_type(), GeomType::Polygon);
411    }
412
413    #[test]
414    fn test_geojson_feature() {
415        let geojson = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":{"name":"test"}}"#;
416        let geom = Geom::from_geojson(geojson).unwrap();
417        assert!(geom.is_feature());
418        assert_eq!(geom.geom_type(), GeomType::Point);
419    }
420
421    #[test]
422    fn test_geojson_roundtrip() {
423        let original = Geom::from_wkt("POINT(1 2)").unwrap();
424        let geojson = original.to_geojson();
425        let parsed = Geom::from_geojson(&geojson).unwrap();
426        assert_eq!(parsed.geom_type(), GeomType::Point);
427        assert_eq!(parsed.point(), p(1.0, 2.0));
428    }
429
430    #[test]
431    fn test_geojson_invalid() {
432        let result = Geom::from_geojson("{ invalid json }");
433        assert!(result.is_err());
434    }
435
436    #[test]
437    fn test_geobin_roundtrip_point() {
438        let original = Geom::from_wkt("POINT(1 2)").unwrap();
439        let geobin = original.to_geobin();
440        let parsed = Geom::from_geobin(&geobin).unwrap();
441        assert_eq!(parsed.geom_type(), GeomType::Point);
442        assert_eq!(parsed.point(), p(1.0, 2.0));
443    }
444
445    #[test]
446    fn test_geobin_roundtrip_polygon() {
447        let original = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
448        let geobin = original.to_geobin();
449        let parsed = Geom::from_geobin(&geobin).unwrap();
450        assert_eq!(parsed.geom_type(), GeomType::Polygon);
451    }
452
453    #[test]
454    fn test_geobin_rect() {
455        let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
456        let geobin = geom.to_geobin();
457        let rect = geobin_rect(&geobin);
458        assert_eq!(rect, r(0.0, 0.0, 10.0, 10.0));
459    }
460
461    #[test]
462    fn test_geobin_point_extraction() {
463        let geom = Geom::from_wkt("POINT(5 6)").unwrap();
464        let geobin = geom.to_geobin();
465        let point = geobin_point(&geobin);
466        assert_eq!(point, p(5.0, 6.0));
467    }
468
469    #[test]
470    fn test_geobin_fullrect() {
471        let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
472        let geobin = geom.to_geobin();
473        let (dims, min, max) = geobin_fullrect(&geobin);
474        assert!(dims >= 2);
475        assert_eq!(min[0], 0.0);
476        assert_eq!(min[1], 0.0);
477        assert_eq!(max[0], 10.0);
478        assert_eq!(max[1], 10.0);
479    }
480
481    #[test]
482    fn test_parse_auto_wkt() {
483        let data = b"POINT(1 2)";
484        let geom = Geom::parse(data).unwrap();
485        assert_eq!(geom.geom_type(), GeomType::Point);
486    }
487
488    #[test]
489    fn test_parse_auto_geojson() {
490        let data = br#"{"type":"Point","coordinates":[1,2]}"#;
491        let geom = Geom::parse(data).unwrap();
492        assert_eq!(geom.geom_type(), GeomType::Point);
493    }
494
495    #[test]
496    fn test_parse_with_index_kind() {
497        let geom_default = Geom::from_wkt_ix(
498            "POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))",
499            IndexKind::Default,
500        )
501        .unwrap();
502        let geom_natural = Geom::from_wkt_ix(
503            "POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))",
504            IndexKind::Natural,
505        )
506        .unwrap();
507        let geom_none = Geom::from_wkt_ix(
508            "POLYGON((0 0, 100 0, 100 100, 0 100, 0 0))",
509            IndexKind::None,
510        )
511        .unwrap();
512
513        // All should parse the same geometry
514        assert_eq!(geom_default.geom_type(), GeomType::Polygon);
515        assert_eq!(geom_natural.geom_type(), GeomType::Polygon);
516        assert_eq!(geom_none.geom_type(), GeomType::Polygon);
517    }
518
519    #[test]
520    fn test_to_wkt_point() {
521        let geom = Geom::new_point(p(1.0, 2.0)).unwrap();
522        let wkt = geom.to_wkt();
523        assert!(wkt.contains("POINT"));
524        assert!(wkt.contains("1"));
525        assert!(wkt.contains("2"));
526    }
527
528    #[test]
529    fn test_to_wkt_polygon() {
530        let geom = Geom::from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))").unwrap();
531        let wkt = geom.to_wkt();
532        assert!(wkt.contains("POLYGON"));
533    }
534
535    #[test]
536    fn test_empty_wkt() {
537        let geom = Geom::from_wkt("POINT EMPTY").unwrap();
538        let wkt = geom.to_wkt();
539        assert!(wkt.contains("EMPTY"));
540    }
541
542    #[test]
543    fn test_multipoint_wkt() {
544        let geom = Geom::from_wkt("MULTIPOINT((0 0), (1 1), (2 2))").unwrap();
545        let wkt = geom.to_wkt();
546        assert!(wkt.contains("MULTIPOINT"));
547    }
548
549    #[test]
550    fn test_geometrycollection_wkt() {
551        let geom = Geom::from_wkt("GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0, 1 1))").unwrap();
552        let wkt = geom.to_wkt();
553        assert!(wkt.contains("GEOMETRYCOLLECTION"));
554    }
555}