Filters¶
OData queries use an OData $filter expression. The
FilterBuilder assembles one safely (escaping string
literals, formatting dates, choosing attribute types) and joins every condition
with a logical and. A raw escape hatch is always available.
from cdse import FilterBuilder
flt = (
FilterBuilder()
.collection("SENTINEL-2")
.name_contains("MSIL2A")
.acquired_between("2024-05-01", "2024-05-08")
.attribute("cloudCover", "le", 20.0)
.online()
)
products = client.odata.products.search(flt)
Available conditions¶
| Method | Produces |
|---|---|
.collection(name) |
Collection/Name eq '...' |
.name(value) |
Name eq '...' |
.name_contains(value) |
contains(Name,'...') |
.online(value=True) |
Online eq true |
.acquired_between(start, end) |
ContentDate/Start ge ... and le ... |
.published_between(start, end) |
PublicationDate ge ... and le ... |
.intersects(wkt) |
OData.CSC.Intersects(area=geography'SRID=4326;...') |
.attribute(name, op, value) |
typed Attributes/.../any(...) |
.deleted_between(start, end) |
DeletionDate ... (DeletedProducts) |
.deletion_cause(cause) |
DeletionCause eq '...' (DeletedProducts) |
.raw(expression) |
the expression verbatim |
Dates accept datetime, date, or anything those construct from; naive
datetimes are treated as UTC.
Attributes¶
.attribute(name, operator, value) infers the OData attribute type from the
Python value: int → Integer, float → Double, str → String, datetime/date
→ DateTimeOffset. Operators are eq, ne, lt, le, gt, ge.
FilterBuilder().attribute("cloudCover", "le", 40.0) # Double
FilterBuilder().attribute("orbitNumber", "eq", 12) # Integer
FilterBuilder().attribute("productType", "eq", "S2MSI2A") # String
Regions of interest (ROIs)¶
Pass a WKT geometry in EPSG:4326 to .intersects(). Polygon rings must be
closed (first point equals last).
FilterBuilder().intersects("POLYGON((4 50, 5 50, 5 51, 4 51, 4 50))")
FilterBuilder().intersects("POINT(4 50)")
Multiple ROIs¶
Because the builder joins with and, calling .intersects() twice means
"intersects A and B" (an empty result for disjoint areas). For "A or B",
either use one MULTIPOLYGON:
multi = "MULTIPOLYGON(((4 50,5 50,5 51,4 51,4 50)),((10 45,11 45,11 46,10 46,10 45)))"
FilterBuilder().collection("SENTINEL-2").intersects(multi)
or build a parenthesised OR group with .raw():
a = "OData.CSC.Intersects(area=geography'SRID=4326;POLYGON((4 50,5 50,5 51,4 51,4 50))')"
b = "OData.CSC.Intersects(area=geography'SRID=4326;POLYGON((10 45,11 45,11 46,10 46,10 45))')"
FilterBuilder().collection("SENTINEL-2").raw(f"({a} or {b})")
The same $filter grammar is accepted by the
Subscriptions FilterParam, so ROIs work there too.
Ordering¶
from cdse import build_orderby
client.odata.products.search(flt, order_by=build_orderby("ContentDate/Start", "desc"))
STAC filters¶
STAC search uses its own parameters (collections, bbox, datetime,
intersects) plus the CQL2 filter extension:
client.stac.search(
collections=["sentinel-2-l2a"],
filter={"op": "<=", "args": [{"property": "eo:cloud_cover"}, 20]},
filter_lang="cql2-json",
)