Skip to content

Commit 2caec3e

Browse files
committed
feat: allow implicit z geometries
1 parent c24e44b commit 2caec3e

File tree

6 files changed

+57
-25
lines changed

6 files changed

+57
-25
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ geojson = "0.24.1"
1010
geozero = "0.14.0"
1111
lazy_static = "1.5"
1212
pest = "2.7"
13-
pest_derive = "2.7"
13+
pest_derive = { version = "2.7", features = ["grammar-extras"] }
1414
serde = "1.0"
1515
serde_derive = "1.0"
1616
serde_json = { version = "1.0", features = ["preserve_order"] }

src/cql2.pest

+19-19
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,32 @@ TIMESTAMP_STR = { DATE_STR ~ ("T" | " ") ~ TIME_STR }
2727
TORD = { QUOTE ~ (TIMESTAMP_STR | DATE_STR) ~ QUOTE }
2828

2929
// wkt
30-
PADDED_DECIMAL = _{ WHITESPACE* ~ DECIMAL* ~ WHITESPACE* }
31-
COORD = _{ PADDED_DECIMAL{1, 4} }
32-
PCOORD = _{ WHITESPACE* ~ LPAREN ~ COORD ~ RPAREN ~ WHITESPACE* }
33-
COORDLIST = _{ WHITESPACE* ~ COORD ~ (COMMADELIM ~ COORD)* ~ WHITESPACE* }
34-
PCOORDLIST = _{ WHITESPACE* ~ LPAREN ~ COORDLIST ~ RPAREN ~ WHITESPACE* }
35-
PCOORDLISTLIST = _{ WHITESPACE* ~ LPAREN ~ PCOORDLIST ~ (COMMADELIM ~ PCOORDLIST)* ~ RPAREN ~ WHITESPACE* }
36-
PCOORDLISTLISTLIST = _{ WHITESPACE* ~ LPAREN ~ PCOORDLISTLIST ~ (COMMADELIM ~ PCOORDLISTLIST)* ~ RPAREN ~ WHITESPACE* }
30+
PADDED_DECIMAL = { WHITESPACE* ~ DECIMAL ~ WHITESPACE* }
31+
COORD = { #four_d = PADDED_DECIMAL{4} | #three_d = PADDED_DECIMAL{3} | #two_d = PADDED_DECIMAL{2} }
32+
PCOORD = { WHITESPACE* ~ LPAREN ~ COORD ~ RPAREN ~ WHITESPACE* }
33+
COORDLIST = { WHITESPACE* ~ COORD ~ (COMMADELIM ~ COORD)* ~ WHITESPACE* }
34+
PCOORDLIST = { WHITESPACE* ~ LPAREN ~ COORDLIST ~ RPAREN ~ WHITESPACE* }
35+
PCOORDLISTLIST = { WHITESPACE* ~ LPAREN ~ PCOORDLIST ~ (COMMADELIM ~ PCOORDLIST)* ~ RPAREN ~ WHITESPACE* }
36+
PCOORDLISTLISTLIST = { WHITESPACE* ~ LPAREN ~ PCOORDLISTLIST ~ (COMMADELIM ~ PCOORDLISTLIST)* ~ RPAREN ~ WHITESPACE* }
3737

38-
ZM = _{ WHITESPACE* ~ (^"ZM" | ^"Z" | ^"M")? ~ WHITESPACE* }
38+
ZM = { WHITESPACE* ~ (^"ZM" | ^"Z" | ^"M")? ~ WHITESPACE* }
3939

40-
POINT = @{ ^"POINT" ~ ZM ~ PCOORD }
41-
LINESTRING = @{ ^"LINESTRING" ~ ZM ~ PCOORDLIST }
42-
POLYGON = @{ ^"POLYGON" ~ ZM ~ PCOORDLISTLIST }
40+
POINT = ${ ^"POINT" ~ ZM ~ PCOORD }
41+
LINESTRING = ${ ^"LINESTRING" ~ ZM ~ PCOORDLIST }
42+
POLYGON = ${ ^"POLYGON" ~ ZM ~ PCOORDLISTLIST }
4343

44-
MULTIPOINT_1 = _{ ^"MULTIPOINT" ~ ZM ~ PCOORDLIST }
45-
MULTIPOINT_2 = _{ ^"MULTIPOINT" ~ ZM ~ PCOORDLISTLIST }
46-
MULTIPOINT = @{ MULTIPOINT_1 | MULTIPOINT_2 }
44+
MULTIPOINT_1 = ${ ^"MULTIPOINT" ~ ZM ~ PCOORDLIST }
45+
MULTIPOINT_2 = ${ ^"MULTIPOINT" ~ ZM ~ PCOORDLISTLIST }
46+
MULTIPOINT = ${ MULTIPOINT_1 | MULTIPOINT_2 }
4747

48-
MULTILINESTRING = @{ ^"MULTILINESTRING" ~ ZM ~ PCOORDLISTLIST }
49-
MULTIPOLYGON = @{ ^"MULTIPOLYGON" ~ ZM ~ PCOORDLISTLISTLIST }
48+
MULTILINESTRING = ${ ^"MULTILINESTRING" ~ ZM ~ PCOORDLISTLIST }
49+
MULTIPOLYGON = ${ ^"MULTIPOLYGON" ~ ZM ~ PCOORDLISTLISTLIST }
5050

51-
GEOMETRY_SINGLE = _{ WHITESPACE* ~ (POINT | LINESTRING | POLYGON | MULTIPOINT | MULTILINESTRING | MULTIPOLYGON) ~ WHITESPACE* }
51+
GEOMETRY_SINGLE = ${ WHITESPACE* ~ (POINT | LINESTRING | POLYGON | MULTIPOINT | MULTILINESTRING | MULTIPOLYGON) ~ WHITESPACE* }
5252

53-
GEOMETRY_COLLECTION = @{ ^"GEOMETRYCOLLECTION" ~ WHITESPACE* ~ LPAREN ~ GEOMETRY_SINGLE ~ (COMMADELIM ~ GEOMETRY_SINGLE)* ~ RPAREN }
53+
GEOMETRY_COLLECTION = ${ ^"GEOMETRYCOLLECTION" ~ WHITESPACE* ~ LPAREN ~ GEOMETRY_SINGLE ~ (COMMADELIM ~ GEOMETRY_SINGLE)* ~ RPAREN }
5454

55-
GEOMETRY = @{ GEOMETRY_SINGLE | GEOMETRY_COLLECTION }
55+
GEOMETRY = ${ GEOMETRY_SINGLE | GEOMETRY_COLLECTION }
5656

5757
IdentifierInner = _{
5858
ALPHABETIC ~ (ALPHABETIC | NUMBER | UNDERSCORE | PERIOD | COLON)*

src/expr.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ impl FromStr for Expr {
281281
}
282282
}
283283
}
284-
285284
#[cfg(test)]
286285
mod tests {
287286
use super::Expr;
@@ -292,6 +291,12 @@ mod tests {
292291
assert_eq!("POINT Z(-105.1019 40.1672 4981)", point.to_text().unwrap());
293292
}
294293

294+
#[test]
295+
fn implicit_z() {
296+
let point: Expr = "POINT (-105.1019 40.1672 4981)".parse().unwrap();
297+
assert_eq!("POINT Z(-105.1019 40.1672 4981)", point.to_text().unwrap());
298+
}
299+
295300
#[test]
296301
fn keep_m() {
297302
let point: Expr = "POINT M(-105.1019 40.1672 42)".parse().unwrap();

src/parser.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,30 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Result<Expr, Error> {
102102
Rule::Identifier => Ok(Expr::Property {
103103
property: strip_quotes(primary.as_str()).to_string(),
104104
}),
105-
Rule::GEOMETRY => Ok(Expr::Geometry(Geometry::Wkt(primary.as_str().to_string()))),
105+
Rule::GEOMETRY => {
106+
// These are some incredibly annoying backflips to handle
107+
// geometries without `Z` but that have 3D coordinates. It's
108+
// not part of OGC WKT, but CQL2 demands 🤦‍♀️.
109+
let start = primary.as_span().start();
110+
let s = primary.as_str().to_string();
111+
let pairs = primary.into_inner();
112+
if pairs.find_first_tagged("three_d").is_some() {
113+
let zm = pairs
114+
.flatten()
115+
.find(|pair| matches!(pair.as_rule(), Rule::ZM))
116+
.expect("all geometries should have a ZM rule");
117+
if zm.as_str().chars().all(|c| c.is_ascii_whitespace()) {
118+
let span = zm.as_span();
119+
let s = format!(
120+
"{} Z{}",
121+
&s[0..span.start() - start],
122+
&s[span.end() - start..]
123+
);
124+
return Ok(Expr::Geometry(Geometry::Wkt(s)));
125+
}
126+
}
127+
Ok(Expr::Geometry(Geometry::Wkt(s)))
128+
}
106129
Rule::Function => {
107130
let mut pairs = primary.into_inner();
108131
let op = strip_quotes(

tests/README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
# Expected test output
1+
# Tests
2+
3+
The `fixtures` directory is copied directly from <https://github.com/opengeospatial/ogcapi-features/tree/cql2-1.0.0/cql2/standard/schema/examples>.
4+
5+
## Expected test output
26

37
To generate:
48

59
```shell
6-
tests/expected/generate
10+
tests/generate-expected
711
```
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
S_WITHIN(POLYGON Z((-49.88024 0.5 -75993.341684, -1.5 -0.99999 -100000.0, 0.0 0.5 -0.333333, -49.88024 0.5 -75993.341684), (-65.887123 2.00001 -100000.0, 0.333333 -53.017711 -79471.332949, 180.0 0.0 1852.616704, -65.887123 2.00001 -100000.0)), "geometry")
1+
S_WITHIN(POLYGON ((-49.88024 0.5 -75993.341684, -1.5 -0.99999 -100000.0, 0.0 0.5 -0.333333, -49.88024 0.5 -75993.341684), (-65.887123 2.00001 -100000.0, 0.333333 -53.017711 -79471.332949, 180.0 0.0 1852.616704, -65.887123 2.00001 -100000.0)), "geometry")

0 commit comments

Comments
 (0)