Skip to content

Commit cc63329

Browse files
committed
wkt macro to create geo_types
1 parent d13e70c commit cc63329

File tree

3 files changed

+324
-1
lines changed

3 files changed

+324
-1
lines changed

geo-types/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ pub use error::Error;
134134
#[macro_use]
135135
mod macros;
136136

137+
#[macro_use]
138+
mod wkt_macro;
139+
137140
#[cfg(feature = "arbitrary")]
138141
mod arbitrary;
139142

geo-types/src/wkt_macro.rs

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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 inavlid
5+
/// WKT syntax.
6+
///
7+
/// Note that `POINT EMPTY` is not accepted because it is not representable as a `geo_types::Point`.
8+
///
9+
/// ```
10+
/// use geo_types::wkt;
11+
/// let point = wkt! { POINT(1.0 2.0) };
12+
/// assert_eq!(point.x(), 1.0);
13+
/// assert_eq!(point.y(), 2.0);
14+
///
15+
/// let geometry_collection = wkt! {
16+
/// GEOMETRYCOLLECTION(
17+
/// POINT(1.0 2.0),
18+
/// LINESTRING EMPTY,
19+
/// POLYGON((0.0 0.0,1.0 0.0,1.0 1.0,0.0 0.0))
20+
/// )
21+
/// };
22+
/// assert_eq!(geometry_collection.len(), 3);
23+
/// ```
24+
#[macro_export(local_inner_macros)]
25+
macro_rules! wkt {
26+
// Hide distracting implementation details from the generated rustdoc.
27+
($($wkt:tt)+) => {
28+
{
29+
wkt_internal!($($wkt)+)
30+
}
31+
};
32+
}
33+
34+
#[macro_export(local_inner_macros)]
35+
#[doc(hidden)]
36+
macro_rules! wkt_internal {
37+
(POINT EMPTY) => {
38+
std::compile_error!("EMPTY points are not supported in geo-types")
39+
};
40+
(POINT($x: literal $y: literal)) => {
41+
point!(x: $x, y: $y)
42+
};
43+
(POINT $($tail: tt)*) => {
44+
std::compile_error!("Invalid POINT wkt");
45+
};
46+
(LINESTRING EMPTY) => {
47+
line_string![]
48+
};
49+
(LINESTRING ($($x: literal $y: literal),+)) => {
50+
line_string![
51+
$(coord!(x: $x, y: $y)),*
52+
]
53+
};
54+
(LINESTRING ()) => {
55+
std::compile_error!("use `EMPTY` instead of () for an empty collection")
56+
};
57+
(LINESTRING $($tail: tt)*) => {
58+
std::compile_error!("Invalid LINESTRING wkt");
59+
};
60+
(POLYGON EMPTY) => {
61+
polygon![]
62+
};
63+
(POLYGON ( $exterior_tt: tt )) => {
64+
$crate::Polygon::new(wkt!(LINESTRING $exterior_tt), std::vec![])
65+
};
66+
(POLYGON( $exterior_tt: tt, $($interiors_tt: tt),+ )) => {
67+
$crate::Polygon::new(
68+
wkt!(LINESTRING $exterior_tt),
69+
std::vec![
70+
$(wkt!(LINESTRING $interiors_tt)),*
71+
]
72+
)
73+
};
74+
(POLYGON ()) => {
75+
std::compile_error!("use `EMPTY` instead of () for an empty collection")
76+
};
77+
(POLYGON $($tail: tt)*) => {
78+
std::compile_error!("Invalid POLYGON wkt");
79+
};
80+
(MULTIPOINT EMPTY) => {
81+
$crate::MultiPoint(std::vec![])
82+
};
83+
(MULTIPOINT ()) => {
84+
std::compile_error!("use `EMPTY` instead of () for an empty collection")
85+
};
86+
(MULTIPOINT ($($x: literal $y: literal),* )) => {
87+
$crate::MultiPoint(
88+
std::vec![$(point!(x: $x, y: $y)),*]
89+
)
90+
};
91+
(MULTIPOINT $($tail: tt)*) => {
92+
std::compile_error!("Invalid MULTIPOINT wkt");
93+
};
94+
(MULTILINESTRING EMPTY) => {
95+
$crate::MultiLineString(std::vec![])
96+
};
97+
(MULTILINESTRING ()) => {
98+
std::compile_error!("use `EMPTY` instead of () for an empty collection")
99+
};
100+
(MULTILINESTRING ( $($line_string_tt: tt),* )) => {
101+
$crate::MultiLineString(std::vec![
102+
$(wkt!(LINESTRING $line_string_tt)),*
103+
])
104+
};
105+
(MULTILINESTRING $($tail: tt)*) => {
106+
std::compile_error!("Invalid MULTILINESTRING wkt");
107+
};
108+
(MULTIPOLYGON EMPTY) => {
109+
$crate::MultiPolygon(std::vec![])
110+
};
111+
(MULTIPOLYGON ()) => {
112+
std::compile_error!("use `EMPTY` instead of () for an empty collection")
113+
};
114+
(MULTIPOLYGON ( $($polygon_tt: tt),* )) => {
115+
$crate::MultiPolygon(std::vec![
116+
$(wkt!(POLYGON $polygon_tt)),*
117+
])
118+
};
119+
(MULTIPOLYGON $($tail: tt)*) => {
120+
std::compile_error!("Invalid MULTIPOLYGON wkt");
121+
};
122+
(GEOMETRYCOLLECTION EMPTY) => {
123+
$crate::GeometryCollection(std::vec![])
124+
};
125+
(GEOMETRYCOLLECTION ()) => {
126+
std::compile_error!("use `EMPTY` instead of () for an empty collection")
127+
};
128+
(GEOMETRYCOLLECTION ( $($el_type:tt $el_tt: tt),* )) => {
129+
$crate::GeometryCollection(std::vec![
130+
$($crate::Geometry::from(wkt_internal!($el_type $el_tt))),*
131+
])
132+
};
133+
(GEOMETRYCOLLECTION $($tail: tt)*) => {
134+
std::compile_error!("Invalid GEOMETRYCOLLECTION wkt");
135+
};
136+
($name: ident ($($tail: tt)*)) => {
137+
std::compile_error!("Unknown type. Must be one of POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, or GEOMETRYCOLLECTION");
138+
};
139+
}
140+
141+
#[cfg(test)]
142+
mod test {
143+
use crate::geometry::*;
144+
145+
#[test]
146+
fn point() {
147+
let point = wkt! { POINT(1.0 2.0) };
148+
assert_eq!(point.x(), 1.0);
149+
assert_eq!(point.y(), 2.0);
150+
151+
let point = wkt! { POINT(1.0 2.0) };
152+
assert_eq!(point.x(), 1.0);
153+
assert_eq!(point.y(), 2.0);
154+
155+
// This (rightfully) fails to compile because geo-types doesn't support "empty" points
156+
// wkt! { POINT EMPTY }
157+
}
158+
159+
#[test]
160+
fn empty_line_string() {
161+
let line_string: LineString<f64> = wkt! { LINESTRING EMPTY };
162+
assert_eq!(line_string.0.len(), 0);
163+
164+
// This (rightfully) fails to compile because its invalid wkt
165+
// wkt! { LINESTRING() }
166+
}
167+
168+
#[test]
169+
fn line_string() {
170+
let line_string = wkt! { LINESTRING(1.0 2.0,3.0 4.0) };
171+
assert_eq!(line_string.0.len(), 2);
172+
assert_eq!(line_string[0], coord! { x: 1.0, y: 2.0 });
173+
}
174+
175+
#[test]
176+
fn empty_polygon() {
177+
let polygon: Polygon = wkt! { POLYGON EMPTY };
178+
assert_eq!(polygon.exterior().0.len(), 0);
179+
assert_eq!(polygon.interiors().len(), 0);
180+
181+
// This (rightfully) fails to compile because its invalid wkt
182+
// wkt! { POLYGON() }
183+
}
184+
185+
#[test]
186+
fn polygon() {
187+
let polygon = wkt! { POLYGON((1.0 2.0)) };
188+
assert_eq!(polygon.exterior().0.len(), 1);
189+
assert_eq!(polygon.exterior().0[0], coord! { x: 1.0, y: 2.0 });
190+
191+
let polygon = wkt! { POLYGON((1.0 2.0,3.0 4.0)) };
192+
// Note: an extra coord is added to close the linestring
193+
assert_eq!(polygon.exterior().0.len(), 3);
194+
assert_eq!(polygon.exterior().0[0], coord! { x: 1.0, y: 2.0 });
195+
assert_eq!(polygon.exterior().0[1], coord! { x: 3.0, y: 4.0 });
196+
assert_eq!(polygon.exterior().0[2], coord! { x: 1.0, y: 2.0 });
197+
198+
let polygon = wkt! { POLYGON((1.0 2.0), (1.1 2.1)) };
199+
assert_eq!(polygon.exterior().0.len(), 1);
200+
assert_eq!(polygon.interiors().len(), 1);
201+
202+
assert_eq!(polygon.exterior().0[0], coord! { x: 1.0, y: 2.0 });
203+
assert_eq!(polygon.interiors()[0].0[0], coord! { x: 1.1, y: 2.1 });
204+
205+
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)) };
206+
assert_eq!(polygon.exterior().0.len(), 3);
207+
assert_eq!(polygon.interiors().len(), 2);
208+
assert_eq!(polygon.interiors()[1][1], coord! { x: 3.2, y: 4.2 });
209+
}
210+
211+
#[test]
212+
fn empty_multi_point() {
213+
let multipoint: MultiPoint = wkt! { MULTIPOINT EMPTY };
214+
assert!(multipoint.0.is_empty());
215+
// This (rightfully) fails to compile because its invalid wkt
216+
// wkt! { MULTIPOINT() }
217+
}
218+
219+
#[test]
220+
fn multi_point() {
221+
let multi_point = wkt! { MULTIPOINT(1.0 2.0) };
222+
assert_eq!(multi_point.0, vec![point! { x: 1.0, y: 2.0}]);
223+
224+
let multi_point = wkt! { MULTIPOINT(1.0 2.0,3.0 4.0) };
225+
assert_eq!(
226+
multi_point.0,
227+
vec![point! { x: 1.0, y: 2.0}, point! { x: 3.0, y: 4.0}]
228+
);
229+
}
230+
231+
#[test]
232+
fn empty_multi_line_string() {
233+
let multi_line_string: MultiLineString = wkt! { MULTILINESTRING EMPTY };
234+
assert_eq!(multi_line_string.0, vec![]);
235+
// This (rightfully) fails to compile because its invalid wkt
236+
// wkt! { MULTILINESTRING() }
237+
}
238+
#[test]
239+
fn multi_line_string() {
240+
let multi_line_string = wkt! { MULTILINESTRING ((1.0 2.0,3.0 4.0)) };
241+
assert_eq!(multi_line_string.0.len(), 1);
242+
assert_eq!(multi_line_string.0[0].0[1], coord! { x: 3.0, y: 4.0 });
243+
let multi_line_string = wkt! { MULTILINESTRING ((1.0 2.0,3.0 4.0),(5.0 6.0,7.0 8.0)) };
244+
assert_eq!(multi_line_string.0.len(), 2);
245+
assert_eq!(multi_line_string.0[1].0[1], coord! { x: 7.0, y: 8.0 });
246+
247+
let multi_line_string = wkt! { MULTILINESTRING ((1.0 2.0,3.0 4.0),EMPTY) };
248+
assert_eq!(multi_line_string.0.len(), 2);
249+
assert_eq!(multi_line_string.0[1].0.len(), 0);
250+
}
251+
252+
#[test]
253+
fn empty_multi_polygon() {
254+
let multi_polygon: MultiPolygon = wkt! { MULTIPOLYGON EMPTY };
255+
assert!(multi_polygon.0.is_empty());
256+
257+
// This (rightfully) fails to compile because its invalid wkt
258+
// wkt! { MULTIPOLYGON() }
259+
}
260+
261+
#[test]
262+
fn multi_line_polygon() {
263+
let multi_polygon = wkt! { MULTIPOLYGON (((1.0 2.0))) };
264+
assert_eq!(multi_polygon.0.len(), 1);
265+
assert_eq!(multi_polygon.0[0].exterior().0[0], coord! { x: 1.0, y: 2.0});
266+
267+
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))) };
268+
assert_eq!(multi_polygon.0.len(), 2);
269+
assert_eq!(
270+
multi_polygon.0[0].interiors()[1].0[0],
271+
coord! { x: 1.2, y: 2.2}
272+
);
273+
274+
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) };
275+
assert_eq!(multi_polygon.0.len(), 2);
276+
assert_eq!(
277+
multi_polygon.0[0].interiors()[1].0[0],
278+
coord! { x: 1.2, y: 2.2}
279+
);
280+
assert!(multi_polygon.0[1].exterior().0.is_empty());
281+
}
282+
283+
#[test]
284+
fn empty_geometry_collection() {
285+
let geometry_collection: GeometryCollection = wkt! { GEOMETRYCOLLECTION EMPTY };
286+
assert!(geometry_collection.is_empty());
287+
288+
// This (rightfully) fails to compile because its invalid wkt
289+
// wkt! { MULTIPOLYGON() }
290+
}
291+
292+
#[test]
293+
fn geometry_collection() {
294+
let geometry_collection = wkt! { GEOMETRYCOLLECTION (POINT (40.0 10.0), LINESTRING (10.0 10.0, 20.0 20.0, 10.0 40.0), POLYGON ((40.0 40.0, 20.0 45.0, 45.0 30.0, 40.0 40.0))) };
295+
assert_eq!(geometry_collection.len(), 3);
296+
297+
let Geometry::LineString(line_string) = &geometry_collection[1] else {
298+
panic!(
299+
"unexpected geometry: {geometry:?}",
300+
geometry = geometry_collection[1]
301+
);
302+
};
303+
assert_eq!(line_string.0[1], coord! {x: 20.0, y: 20.0 });
304+
}
305+
306+
#[test]
307+
fn other_numeric_types() {
308+
let point: Point<i32> = wkt!(POINT(1 2));
309+
assert_eq!(point.x(), 1i32);
310+
assert_eq!(point.y(), 2i32);
311+
312+
let point: Point<u64> = wkt!(POINT(1 2));
313+
assert_eq!(point.x(), 1u64);
314+
assert_eq!(point.y(), 2u64);
315+
316+
let point: Point<f32> = wkt!(POINT(1.0 2.0));
317+
assert_eq!(point.x(), 1.0f32);
318+
assert_eq!(point.y(), 2.0f32);
319+
}
320+
}

geo/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ extern crate rstar;
209209
pub use crate::algorithm::*;
210210
pub use crate::types::Closest;
211211

212-
pub use geo_types::{coord, line_string, point, polygon, CoordFloat, CoordNum};
212+
pub use geo_types::{coord, line_string, point, polygon, wkt, CoordFloat, CoordNum};
213213

214214
pub mod geometry;
215215
pub use geometry::*;

0 commit comments

Comments
 (0)