geo_types/
wkt_macro.rs

1/// Creates a [`crate::geometry`] from a
2/// [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) literal.
3///
4/// This is evaluated at compile time, so you don't need to worry about runtime errors from invalid
5/// WKT syntax.
6///
7/// Note that `POINT EMPTY` is not accepted because it is not representable as a `geo_types::Point`.
8///
9/// Note that all geometry variants are constructable with this macro.
10/// This includes [`LINE`](crate::Line), [`RECT`](crate::Rect), and [`TRIANGLE`](crate::Triangle), even though they are
11/// not standard WKT types, and will not be parsable by a standard WKT parser.
12///
13/// ```
14/// use geo_types::wkt;
15/// let point = wkt! { POINT(1.0 2.0) };
16/// assert_eq!(point.x(), 1.0);
17/// assert_eq!(point.y(), 2.0);
18///
19/// let geometry_collection = wkt! {
20///     GEOMETRYCOLLECTION(
21///         POINT(1.0 2.0),
22///         LINESTRING EMPTY,
23///         POLYGON((0.0 0.0,1.0 0.0,1.0 1.0,0.0 0.0))
24///     )
25/// };
26/// assert_eq!(geometry_collection.len(), 3);
27/// ```
28#[macro_export]
29macro_rules! wkt {
30    // Hide distracting implementation details from the generated rustdoc.
31    ($($wkt:tt)+) => {
32        {
33            $crate::wkt_internal!($($wkt)+)
34        }
35    };
36}
37
38#[macro_export]
39#[doc(hidden)]
40macro_rules! wkt_internal {
41    (POINT EMPTY) => {
42        compile_error!("EMPTY points are not supported in geo-types")
43    };
44    (POINT($x: literal $y: literal)) => {
45        $crate::point!(x: $x, y: $y)
46    };
47    (POINT $($tail: tt)*) => {
48        compile_error!("Invalid POINT wkt");
49    };
50    (LINE($a_x: literal $a_y: literal,$b_x: literal $b_y: literal)) => {
51        $crate::Line::new(
52            $crate::coord!(x: $a_x, y: $a_y),
53            $crate::coord!(x: $b_x, y: $b_y)
54        )
55    };
56    (LINESTRING EMPTY) => {
57        $crate::LineString::empty()
58    };
59    (LINESTRING ($($x: literal $y: literal),+)) => {
60        $crate::line_string![
61            $($crate::coord!(x: $x, y: $y)),*
62        ]
63    };
64    (LINESTRING ()) => {
65        compile_error!("use `EMPTY` instead of () for an empty collection")
66    };
67    (LINESTRING $($tail: tt)*) => {
68        compile_error!("Invalid LINESTRING wkt");
69    };
70    (POLYGON EMPTY) => {
71        $crate::Polygon::empty()
72    };
73    (POLYGON ( $exterior_tt: tt )) => {
74        $crate::Polygon::new($crate::wkt!(LINESTRING $exterior_tt), $crate::_alloc::vec![])
75    };
76    (POLYGON( $exterior_tt: tt, $($interiors_tt: tt),+ )) => {
77        $crate::Polygon::new(
78            $crate::wkt!(LINESTRING $exterior_tt),
79            $crate::_alloc::vec![
80               $($crate::wkt!(LINESTRING $interiors_tt)),*
81            ]
82        )
83    };
84    (POLYGON ()) => {
85        compile_error!("use `EMPTY` instead of () for an empty collection")
86    };
87    (POLYGON $($tail: tt)*) => {
88        compile_error!("Invalid POLYGON wkt");
89    };
90    (MULTIPOINT EMPTY) => {
91        $crate::MultiPoint::empty()
92    };
93    (MULTIPOINT ()) => {
94        compile_error!("use `EMPTY` instead of () for an empty collection")
95    };
96    (MULTIPOINT ($($x: literal $y: literal),* )) => {
97        $crate::MultiPoint(
98            $crate::_alloc::vec![$($crate::point!(x: $x, y: $y)),*]
99        )
100    };
101    (MULTIPOINT $($tail: tt)*) => {
102        compile_error!("Invalid MULTIPOINT wkt");
103    };
104    (MULTILINESTRING EMPTY) => {
105        $crate::MultiLineString::empty()
106    };
107    (MULTILINESTRING ()) => {
108        compile_error!("use `EMPTY` instead of () for an empty collection")
109    };
110    (MULTILINESTRING ( $($line_string_tt: tt),* )) => {
111        $crate::MultiLineString($crate::_alloc::vec![
112           $($crate::wkt!(LINESTRING $line_string_tt)),*
113        ])
114    };
115    (MULTILINESTRING $($tail: tt)*) => {
116        compile_error!("Invalid MULTILINESTRING wkt");
117    };
118    (MULTIPOLYGON EMPTY) => {
119        $crate::MultiPolygon::empty()
120    };
121    (MULTIPOLYGON ()) => {
122        compile_error!("use `EMPTY` instead of () for an empty collection")
123    };
124    (MULTIPOLYGON ( $($polygon_tt: tt),* )) => {
125        $crate::MultiPolygon($crate::_alloc::vec![
126           $($crate::wkt!(POLYGON $polygon_tt)),*
127        ])
128    };
129    (MULTIPOLYGON $($tail: tt)*) => {
130        compile_error!("Invalid MULTIPOLYGON wkt");
131    };
132    (GEOMETRYCOLLECTION EMPTY) => {
133        $crate::GeometryCollection::empty()
134    };
135    (GEOMETRYCOLLECTION ()) => {
136        compile_error!("use `EMPTY` instead of () for an empty collection")
137    };
138    (GEOMETRYCOLLECTION ( $($el_type:tt $el_tt: tt),* )) => {
139        $crate::GeometryCollection($crate::_alloc::vec![
140           $($crate::Geometry::from($crate::wkt!($el_type $el_tt))),*
141        ])
142    };
143    (GEOMETRYCOLLECTION $($tail: tt)*) => {
144        compile_error!("Invalid GEOMETRYCOLLECTION wkt");
145    };
146    (RECT($a_x: literal $a_y: literal,$b_x: literal $b_y: literal)) => {
147        $crate::Rect::new(
148            $crate::coord!(x: $a_x, y: $a_y),
149            $crate::coord!(x: $b_x, y: $b_y)
150        )
151    };
152    (TRIANGLE($a_x: literal $a_y: literal,$b_x: literal $b_y: literal,$c_x: literal $c_y: literal)) => {
153        $crate::Triangle::new(
154            $crate::coord!(x: $a_x, y: $a_y),
155            $crate::coord!(x: $b_x, y: $b_y),
156            $crate::coord!(x: $c_x, y: $c_y)
157        )
158    };
159    ($name: ident ($($tail: tt)*)) => {
160        compile_error!("Unknown type. Must be one of POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION, LINE, RECT, or TRIANGLE")
161    };
162}
163
164#[cfg(test)]
165mod test {
166    use crate::geometry::*;
167    use alloc::vec;
168
169    #[test]
170    fn point() {
171        let point = wkt! { POINT(1.0 2.0) };
172        assert_eq!(point.x(), 1.0);
173        assert_eq!(point.y(), 2.0);
174
175        let point = wkt! { POINT(1.0   2.0) };
176        assert_eq!(point.x(), 1.0);
177        assert_eq!(point.y(), 2.0);
178
179        // This (rightfully) fails to compile because geo-types doesn't support "empty" points
180        // wkt! { POINT EMPTY }
181    }
182
183    #[test]
184    fn line() {
185        let line = wkt! { LINE(1.0 2.0,3.0 4.0) };
186        assert_eq!(line.start, coord!(x: 1.0, y: 2.0));
187        assert_eq!(line.end, coord!(x: 3.0, y: 4.0));
188    }
189
190    #[test]
191    fn empty_line_string() {
192        let line_string: LineString<f64> = wkt! { LINESTRING EMPTY };
193        assert_eq!(line_string.0.len(), 0);
194
195        // This (rightfully) fails to compile because its invalid wkt
196        // wkt! { LINESTRING() }
197    }
198
199    #[test]
200    fn line_string() {
201        let line_string = wkt! { LINESTRING(1.0 2.0,3.0 4.0) };
202        assert_eq!(line_string.0.len(), 2);
203        assert_eq!(line_string[0], coord! { x: 1.0, y: 2.0 });
204    }
205
206    #[test]
207    fn empty_polygon() {
208        let polygon: Polygon = wkt! { POLYGON EMPTY };
209        assert_eq!(polygon.exterior().0.len(), 0);
210        assert_eq!(polygon.interiors().len(), 0);
211
212        // This (rightfully) fails to compile because its invalid wkt
213        // wkt! { POLYGON() }
214    }
215
216    #[test]
217    fn polygon() {
218        let polygon = wkt! { POLYGON((1.0 2.0)) };
219        assert_eq!(polygon.exterior().0.len(), 1);
220        assert_eq!(polygon.exterior().0[0], coord! { x: 1.0, y: 2.0 });
221
222        let polygon = wkt! { POLYGON((1.0 2.0,3.0 4.0)) };
223        // Note: an extra coord is added to close the linestring
224        assert_eq!(polygon.exterior().0.len(), 3);
225        assert_eq!(polygon.exterior().0[0], coord! { x: 1.0, y: 2.0 });
226        assert_eq!(polygon.exterior().0[1], coord! { x: 3.0, y: 4.0 });
227        assert_eq!(polygon.exterior().0[2], coord! { x: 1.0, y: 2.0 });
228
229        let polygon = wkt! { POLYGON((1.0 2.0), (1.1 2.1)) };
230        assert_eq!(polygon.exterior().0.len(), 1);
231        assert_eq!(polygon.interiors().len(), 1);
232
233        assert_eq!(polygon.exterior().0[0], coord! { x: 1.0, y: 2.0 });
234        assert_eq!(polygon.interiors()[0].0[0], coord! { x: 1.1, y: 2.1 });
235
236        let polygon = wkt! { POLYGON((1.0 2.0,3.0 4.0), (1.1 2.1,3.1 4.1), (1.2 2.2,3.2 4.2)) };
237        assert_eq!(polygon.exterior().0.len(), 3);
238        assert_eq!(polygon.interiors().len(), 2);
239        assert_eq!(polygon.interiors()[1][1], coord! { x: 3.2, y: 4.2 });
240    }
241
242    #[test]
243    fn empty_multi_point() {
244        let multipoint: MultiPoint = wkt! { MULTIPOINT EMPTY };
245        assert!(multipoint.0.is_empty());
246        // This (rightfully) fails to compile because its invalid wkt
247        // wkt! { MULTIPOINT() }
248    }
249
250    #[test]
251    fn multi_point() {
252        let multi_point = wkt! { MULTIPOINT(1.0 2.0) };
253        assert_eq!(multi_point.0, vec![point! { x: 1.0, y: 2.0}]);
254
255        let multi_point = wkt! { MULTIPOINT(1.0 2.0,3.0 4.0) };
256        assert_eq!(
257            multi_point.0,
258            vec![point! { x: 1.0, y: 2.0}, point! { x: 3.0, y: 4.0}]
259        );
260    }
261
262    #[test]
263    fn empty_multi_line_string() {
264        let multi_line_string: MultiLineString = wkt! { MULTILINESTRING EMPTY };
265        assert_eq!(multi_line_string.0, vec![]);
266        // This (rightfully) fails to compile because its invalid wkt
267        // wkt! { MULTILINESTRING() }
268    }
269    #[test]
270    fn multi_line_string() {
271        let multi_line_string = wkt! { MULTILINESTRING ((1.0 2.0,3.0 4.0)) };
272        assert_eq!(multi_line_string.0.len(), 1);
273        assert_eq!(multi_line_string.0[0].0[1], coord! { x: 3.0, y: 4.0 });
274        let multi_line_string = wkt! { MULTILINESTRING ((1.0 2.0,3.0 4.0),(5.0 6.0,7.0 8.0)) };
275        assert_eq!(multi_line_string.0.len(), 2);
276        assert_eq!(multi_line_string.0[1].0[1], coord! { x: 7.0, y: 8.0 });
277
278        let multi_line_string = wkt! { MULTILINESTRING ((1.0 2.0,3.0 4.0),EMPTY) };
279        assert_eq!(multi_line_string.0.len(), 2);
280        assert_eq!(multi_line_string.0[1].0.len(), 0);
281    }
282
283    #[test]
284    fn empty_multi_polygon() {
285        let multi_polygon: MultiPolygon = wkt! { MULTIPOLYGON EMPTY };
286        assert!(multi_polygon.0.is_empty());
287
288        // This (rightfully) fails to compile because its invalid wkt
289        // wkt! { MULTIPOLYGON() }
290    }
291
292    #[test]
293    fn multi_line_polygon() {
294        let multi_polygon = wkt! { MULTIPOLYGON (((1.0 2.0))) };
295        assert_eq!(multi_polygon.0.len(), 1);
296        assert_eq!(multi_polygon.0[0].exterior().0[0], coord! { x: 1.0, y: 2.0});
297
298        let multi_polygon = wkt! { MULTIPOLYGON (((1.0 2.0,3.0 4.0), (1.1 2.1,3.1 4.1), (1.2 2.2,3.2 4.2)),((1.0 2.0))) };
299        assert_eq!(multi_polygon.0.len(), 2);
300        assert_eq!(
301            multi_polygon.0[0].interiors()[1].0[0],
302            coord! { x: 1.2, y: 2.2}
303        );
304
305        let multi_polygon = wkt! { MULTIPOLYGON (((1.0 2.0,3.0 4.0), (1.1 2.1,3.1 4.1), (1.2 2.2,3.2 4.2)), EMPTY) };
306        assert_eq!(multi_polygon.0.len(), 2);
307        assert_eq!(
308            multi_polygon.0[0].interiors()[1].0[0],
309            coord! { x: 1.2, y: 2.2}
310        );
311        assert!(multi_polygon.0[1].exterior().0.is_empty());
312    }
313
314    #[test]
315    fn empty_geometry_collection() {
316        let geometry_collection: GeometryCollection = wkt! { GEOMETRYCOLLECTION EMPTY };
317        assert!(geometry_collection.is_empty());
318
319        // This (rightfully) fails to compile because its invalid wkt
320        // wkt! { MULTIPOLYGON() }
321    }
322
323    #[test]
324    fn geometry_collection() {
325        let geometry_collection = wkt! {
326            GEOMETRYCOLLECTION (
327                POINT (40.0 10.0),
328                LINESTRING (10.0 10.0, 20.0 20.0, 10.0 40.0),
329                POLYGON ((40.0 40.0, 20.0 45.0, 45.0 30.0, 40.0 40.0))
330            )
331        };
332        assert_eq!(geometry_collection.len(), 3);
333
334        let line_string = match &geometry_collection[1] {
335            Geometry::LineString(line_string) => line_string,
336            _ => panic!(
337                "unexpected geometry: {geometry:?}",
338                geometry = geometry_collection[1]
339            ),
340        };
341        assert_eq!(line_string.0[1], coord! {x: 20.0, y: 20.0 });
342    }
343
344    #[test]
345    fn rect() {
346        let rect = wkt! { RECT(1.0 2.0,3.0 4.0) };
347        assert_eq!(rect.min(), coord!(x: 1.0, y: 2.0));
348        assert_eq!(rect.max(), coord!(x: 3.0, y: 4.0));
349    }
350
351    #[test]
352    fn triangle() {
353        let triangle = wkt! { TRIANGLE(0.0 1.0,4.0 2.0,3.0 5.0) };
354        assert_eq!(triangle.0, coord!(x: 0.0, y: 1.0));
355        assert_eq!(triangle.1, coord!(x: 4.0, y: 2.0));
356        assert_eq!(triangle.2, coord!(x: 3.0, y: 5.0));
357    }
358
359    #[test]
360    fn other_numeric_types() {
361        let point: Point<i32> = wkt!(POINT(1 2));
362        assert_eq!(point.x(), 1i32);
363        assert_eq!(point.y(), 2i32);
364
365        let point: Point<u64> = wkt!(POINT(1 2));
366        assert_eq!(point.x(), 1u64);
367        assert_eq!(point.y(), 2u64);
368
369        let point: Point<f32> = wkt!(POINT(1.0 2.0));
370        assert_eq!(point.x(), 1.0f32);
371        assert_eq!(point.y(), 2.0f32);
372    }
373}