Skip to contents

Introduction

CQL2 is an OGC standard that enables complex filter expressions on OAFeat3 or STAC web services. CQL2 standard states that expressions can be represented in JSON or TEXT formats. Our implementation intends to convert native R expressions into CQL2 valid expressions without needing cumbersome nested lists or dictionaries. Also, we can make CQL2 filter requisition in JSON or TEXT formats with the same filter representation.

Translating R expressions to CQL2 syntax

To explain the difference between the TEXT and JSON CQL2 representation, let’s start with a simple example. In the following code, we have a valid CQL2 expression (in TEXT format) that refers to two properties, vehicle_height and bridge_clearance.

vehicle_height > (bridge_clearance - 1))

This filter expression can be passed in the HTTP GET verb implemented by the service to retrieve only those features that satisfy the condition. The same expression can be represented in JSON format, which is more suitable for HTTP POST requests:

{
  "op": ">",
  "args": [
    {"property":"vehicle_height"},
    {
      "op": "-",
      "args": [
        {"property":"bridge_clearance"},
        1
      ]
    }
  ]
}

Note how properties vehicle_height and bridge_clearance are represented in this format. They are elements of an object containing a property member. Also, they go as arguments of operators (in this case,> and - operators).

In the R language, the JSON above could be represented by nested lists, which would be somewhat cumbersome to write. To produce valid CQL2 filter expressions, we use the R abstract syntax tree (AST) from R expressions that can be converted to TEXT or JSON formats. Let us see the same previous example written in R CQL2:

cql2_text(vehicle_height > (bridge_clearance - 1)) # TEXT format
#> vehicle_height > bridge_clearance - 1
cql2_json(vehicle_height > (bridge_clearance - 1)) # JSON format
#> {"op":">","args":[{"property":"vehicle_height"},{"op":"-","args":[{"property":"bridge_clearance"},1]}]}

In both cases, the same CQL2 object representation is built from the expression using AST of R expression evaluation. Then, the object is converted into TEXT or JSON format.

CQL2 filters in TEXT format are sometimes represented the same way as in the R expression. However, this should only sometimes be the case, as we can see in some examples provided below.

Data types and literal values

A literal value is any part of a CQL2 filter expression used the same as specified in the expression.

The scalar data types are: character string, number, boolean, timestamp, and date.

character string

cql2_text("Via dell'Avvento")
#> 'Via dell''Avvento'
cql2_json("Via dell'Avvento")
#> "Via dell'Avvento"

number

cql2_text(3.1415)
#> 3.1415
cql2_json(-100)
#> -100

boolean

cql2_text(TRUE)
#> true
cql2_json(FALSE)
#> false

timestamp

cql2_text(timestamp("1969-07-20T20:17:40Z"))
#> TIMESTAMP('1969-07-20T20:17:40Z')
cql2_json(timestamp("1969-07-20T20:17:40Z"))
#> {"timestamp":"1969-07-20T20:17:40Z"}

date

cql2_text(date("1969-07-20"))
#> DATE('1969-07-20')
cql2_json(date("1969-07-20"))
#> {"date":"1969-07-20"}

Property references

The property of an item can be evaluated in the CQL2 filter expression by its name.

cql2_text(windSpeed > 1)
#> windSpeed > 1
cql2_json(windSpeed > 1)
#> {"op":">","args":[{"property":"windSpeed"},1]}

Standard comparison predicates

A comparison predicate evaluates if two scalar expressions satisfy the specified comparison operator.

The standard comparison operators are: =, !=, <, >, <=, >=, and IS NULL.

cql2_text(city == "Crato")
#> city = 'Crato'
cql2_json(city == "Jacareí")
#> {"op":"=","args":[{"property":"city"},"Jacareí"]}
cql2_text(avg(windSpeed) < 4)
#> avg(windSpeed) < 4
cql2_json(avg(windSpeed) < 4)
#> {"op":"<","args":[{"function":{"name":"avg","args":[{"property":"windSpeed"}]}},4]}
cql2_text(balance - 150.0 > 0)
#> balance - 150 > 0
cql2_json(balance - 150.0 > 0)
#> {"op":">","args":[{"op":"-","args":[{"property":"balance"},150]},0]}
cql2_text(updated >= date('1970-01-01'))
#> updated >= DATE('1970-01-01')
cql2_json(updated >= date('1970-01-01'))
#> {"op":">=","args":[{"property":"updated"},{"date":"1970-01-01"}]}

IS NULL operator

cql2_text(!is_null(geometry))
#> geometry IS NOT NULL
cql2_json(!is_null(geometry))
#> {"op":"not","args":[{"op":"isNull","args":[{"property":"geometry"}]}]}

Advanced comparison operators

A comparison predicate evaluates if two scalar expressions satisfy the specified comparison operator.

Advanced comparison operators are: LIKE, BETWEEN, and IN.

LIKE operator

cql2_text(name %like% "Smith%")
#> name LIKE 'Smith%'
cql2_json(name %like% "Smith%")
#> {"op":"like","args":[{"property":"name"},"Smith%"]}

BETWEEN operator

cql2_text(between(depth, 100.0, 150.0))
#> depth BETWEEN 100 AND 150
cql2_json(between(depth, 100.0, 150.0))
#> {"op":"between","args":[{"property":"depth"},100,150]}

IN operator

cql2_text(cityName %in% list('Toronto', 'Frankfurt', 'Tokyo', 'New York'))
#> cityName IN ('Toronto','Frankfurt','Tokyo','New York')
cql2_json(cityName %in% list('Toronto', 'Frankfurt', 'Tokyo', 'New York'))
#> {"op":"in","args":[{"property":"cityName"},["Toronto","Frankfurt","Tokyo","New York"]]}
cql2_text(!category %in% list(1, 2, 3, 4))
#> category NOT IN (1,2,3,4)
cql2_json(!category %in% list(1, 2, 3, 4))
#> {"op":"not","args":[{"op":"in","args":[{"property":"category"},[1,2,3,4]]}]}

Spatial operators

A spatial predicate evaluates if two spatial expressions satisfy the specified spatial operator.

The supported spatial operators are: S_INTERSECTS, S_EQUALS, S_DISJOINT, S_TOUCHES, S_WITHIN, S_OVERLAPS, S_CROSSES, and S_CONTAINS.

poly <- list(
  type = "Polygon",
  coordinates = list(
    rbind(
      c(0,0),
      c(0,1),
      c(0,1)
    )
  ))
cql2_text(s_intersects(geometry, {{poly}}))
#> S_INTERSECTS(geometry,POLYGON((0 0,0 1,0 1)))
cql2_json(s_intersects(geometry, {{poly}}))
#> {"op":"s_intersects","args":[{"property":"geometry"},{"type":"Polygon","coordinates":[[[0,0],[0,1],[0,1]]]}]}

Note: We provide an escape to evaluate user variables using {{ or !!. Both symbols are largely used in the R Data Science community.

Temporal operators

A temporal predicate evaluates if two temporal expressions satisfy the specified temporal operator.

The supported temporal operators are: T_AFTER, T_BEFORE, T_CONTAINS, T_DISJOINT, T_DURING, T_EQUALS, T_FINISHEDBY, T_FINISHES, T_INTERSECTS, T_MEETS, T_METBY, T_OVERLAPPEDBY, T_OVERLAPS, T_STARTEDBY, and T_STARTS.

cql2_text(t_intersects(event_date, interval("1969-07-16T05:32:00Z", "1969-07-24T16:50:35Z")))
#> T_INTERSECTS(event_date,INTERVAL('1969-07-16T05:32:00Z','1969-07-24T16:50:35Z'))
cql2_json(t_intersects(event_date, interval("1969-07-16T05:32:00Z", "1969-07-24T16:50:35Z")))
#> {"op":"t_intersects","args":[{"property":"event_date"},{"interval":["1969-07-16T05:32:00Z","1969-07-24T16:50:35Z"]}]}

Support for functions in CQL2

Functions allow implementations to extend the language.

Example of a function that returns a geometry value.

cql2_text(s_within(road, Buffer(geometry, 10, "m")))
#> S_WITHIN(road,Buffer(geometry,10,'m'))
cql2_json(s_within(road, Buffer(geometry, 10, "m")))
#> {"op":"s_within","args":[{"property":"road"},{"function":{"name":"Buffer","args":[{"property":"geometry"},10,"m"]}}]}

Conclusion

In conclusion, this tutorial has demonstrated using the rstac package to build CQL2 expressions, making it easier for R users to write syntactically correct filter criteria for STAC services. This functionality can be an alternative for users to construct CQL2 expressions easily and efficiently. For more about CQL2 in rstac, type the command ?ext_filter.