diff --git a/Cargo.toml b/Cargo.toml index c7f905d..1eeba66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ geojson = "0.24.1" geozero = "0.14.0" lazy_static = "1.5" pest = "2.7" -pest_derive = "2.7" +pest_derive = { version = "2.7", features = ["grammar-extras"] } serde = "1.0" serde_derive = "1.0" serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/src/cql2.pest b/src/cql2.pest index 13a1d98..8f22257 100644 --- a/src/cql2.pest +++ b/src/cql2.pest @@ -27,32 +27,32 @@ TIMESTAMP_STR = { DATE_STR ~ ("T" | " ") ~ TIME_STR } TORD = { QUOTE ~ (TIMESTAMP_STR | DATE_STR) ~ QUOTE } // wkt -PADDED_DECIMAL = _{ WHITESPACE* ~ DECIMAL* ~ WHITESPACE* } -COORD = _{ PADDED_DECIMAL{1, 4} } -PCOORD = _{ WHITESPACE* ~ LPAREN ~ COORD ~ RPAREN ~ WHITESPACE* } -COORDLIST = _{ WHITESPACE* ~ COORD ~ (COMMADELIM ~ COORD)* ~ WHITESPACE* } -PCOORDLIST = _{ WHITESPACE* ~ LPAREN ~ COORDLIST ~ RPAREN ~ WHITESPACE* } -PCOORDLISTLIST = _{ WHITESPACE* ~ LPAREN ~ PCOORDLIST ~ (COMMADELIM ~ PCOORDLIST)* ~ RPAREN ~ WHITESPACE* } -PCOORDLISTLISTLIST = _{ WHITESPACE* ~ LPAREN ~ PCOORDLISTLIST ~ (COMMADELIM ~ PCOORDLISTLIST)* ~ RPAREN ~ WHITESPACE* } +PADDED_DECIMAL = { WHITESPACE* ~ DECIMAL ~ WHITESPACE* } +COORD = { #four_d = PADDED_DECIMAL{4} | #three_d = PADDED_DECIMAL{3} | #two_d = PADDED_DECIMAL{2} } +PCOORD = { WHITESPACE* ~ LPAREN ~ COORD ~ RPAREN ~ WHITESPACE* } +COORDLIST = { WHITESPACE* ~ COORD ~ (COMMADELIM ~ COORD)* ~ WHITESPACE* } +PCOORDLIST = { WHITESPACE* ~ LPAREN ~ COORDLIST ~ RPAREN ~ WHITESPACE* } +PCOORDLISTLIST = { WHITESPACE* ~ LPAREN ~ PCOORDLIST ~ (COMMADELIM ~ PCOORDLIST)* ~ RPAREN ~ WHITESPACE* } +PCOORDLISTLISTLIST = { WHITESPACE* ~ LPAREN ~ PCOORDLISTLIST ~ (COMMADELIM ~ PCOORDLISTLIST)* ~ RPAREN ~ WHITESPACE* } -ZM = _{ WHITESPACE* ~ (^"ZM" | ^"Z" | ^"M")? ~ WHITESPACE* } +ZM = { WHITESPACE* ~ (^"ZM" | ^"Z" | ^"M")? ~ WHITESPACE* } -POINT = @{ ^"POINT" ~ ZM ~ PCOORD } -LINESTRING = @{ ^"LINESTRING" ~ ZM ~ PCOORDLIST } -POLYGON = @{ ^"POLYGON" ~ ZM ~ PCOORDLISTLIST } +POINT = ${ ^"POINT" ~ ZM ~ PCOORD } +LINESTRING = ${ ^"LINESTRING" ~ ZM ~ PCOORDLIST } +POLYGON = ${ ^"POLYGON" ~ ZM ~ PCOORDLISTLIST } -MULTIPOINT_1 = _{ ^"MULTIPOINT" ~ ZM ~ PCOORDLIST } -MULTIPOINT_2 = _{ ^"MULTIPOINT" ~ ZM ~ PCOORDLISTLIST } -MULTIPOINT = @{ MULTIPOINT_1 | MULTIPOINT_2 } +MULTIPOINT_1 = ${ ^"MULTIPOINT" ~ ZM ~ PCOORDLIST } +MULTIPOINT_2 = ${ ^"MULTIPOINT" ~ ZM ~ PCOORDLISTLIST } +MULTIPOINT = ${ MULTIPOINT_1 | MULTIPOINT_2 } -MULTILINESTRING = @{ ^"MULTILINESTRING" ~ ZM ~ PCOORDLISTLIST } -MULTIPOLYGON = @{ ^"MULTIPOLYGON" ~ ZM ~ PCOORDLISTLISTLIST } +MULTILINESTRING = ${ ^"MULTILINESTRING" ~ ZM ~ PCOORDLISTLIST } +MULTIPOLYGON = ${ ^"MULTIPOLYGON" ~ ZM ~ PCOORDLISTLISTLIST } -GEOMETRY_SINGLE = _{ WHITESPACE* ~ (POINT | LINESTRING | POLYGON | MULTIPOINT | MULTILINESTRING | MULTIPOLYGON) ~ WHITESPACE* } +GEOMETRY_SINGLE = ${ WHITESPACE* ~ (POINT | LINESTRING | POLYGON | MULTIPOINT | MULTILINESTRING | MULTIPOLYGON) ~ WHITESPACE* } -GEOMETRY_COLLECTION = @{ ^"GEOMETRYCOLLECTION" ~ WHITESPACE* ~ LPAREN ~ GEOMETRY_SINGLE ~ (COMMADELIM ~ GEOMETRY_SINGLE)* ~ RPAREN } +GEOMETRY_COLLECTION = ${ ^"GEOMETRYCOLLECTION" ~ WHITESPACE* ~ LPAREN ~ GEOMETRY_SINGLE ~ (COMMADELIM ~ GEOMETRY_SINGLE)* ~ RPAREN } -GEOMETRY = @{ GEOMETRY_SINGLE | GEOMETRY_COLLECTION } +GEOMETRY = ${ GEOMETRY_SINGLE | GEOMETRY_COLLECTION } IdentifierInner = _{ ALPHABETIC ~ (ALPHABETIC | NUMBER | UNDERSCORE | PERIOD | COLON)* diff --git a/src/expr.rs b/src/expr.rs index b5bb74b..ef6bc86 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -281,7 +281,6 @@ impl FromStr for Expr { } } } - #[cfg(test)] mod tests { use super::Expr; @@ -292,6 +291,12 @@ mod tests { assert_eq!("POINT Z(-105.1019 40.1672 4981)", point.to_text().unwrap()); } + #[test] + fn implicit_z() { + let point: Expr = "POINT (-105.1019 40.1672 4981)".parse().unwrap(); + assert_eq!("POINT Z(-105.1019 40.1672 4981)", point.to_text().unwrap()); + } + #[test] fn keep_m() { let point: Expr = "POINT M(-105.1019 40.1672 42)".parse().unwrap(); diff --git a/src/parser.rs b/src/parser.rs index 5022730..e46639e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -102,7 +102,30 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Result { Rule::Identifier => Ok(Expr::Property { property: strip_quotes(primary.as_str()).to_string(), }), - Rule::GEOMETRY => Ok(Expr::Geometry(Geometry::Wkt(primary.as_str().to_string()))), + Rule::GEOMETRY => { + // These are some incredibly annoying backflips to handle + // geometries without `Z` but that have 3D coordinates. It's + // not part of OGC WKT, but CQL2 demands 🤦‍♀️. + let start = primary.as_span().start(); + let s = primary.as_str().to_string(); + let pairs = primary.into_inner(); + if pairs.find_first_tagged("three_d").is_some() { + let zm = pairs + .flatten() + .find(|pair| matches!(pair.as_rule(), Rule::ZM)) + .expect("all geometries should have a ZM rule"); + if zm.as_str().chars().all(|c| c.is_ascii_whitespace()) { + let span = zm.as_span(); + let s = format!( + "{} Z{}", + &s[0..span.start() - start], + &s[span.end() - start..] + ); + return Ok(Expr::Geometry(Geometry::Wkt(s))); + } + } + Ok(Expr::Geometry(Geometry::Wkt(s))) + } Rule::Function => { let mut pairs = primary.into_inner(); let op = strip_quotes( diff --git a/tests/README.md b/tests/README.md index bd17477..d996e2b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,11 @@ -# Expected test output +# Tests + +The `fixtures` directory is copied directly from . + +## Expected test output To generate: ```shell -tests/expected/generate +tests/generate-expected ``` diff --git a/tests/fixtures/text/example49-alt01.txt b/tests/fixtures/text/example49-alt01.txt index 434a77b..91913ee 100644 --- a/tests/fixtures/text/example49-alt01.txt +++ b/tests/fixtures/text/example49-alt01.txt @@ -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") +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")