Several parts of Cartographer (the Advanced controls in the map downloads dialog, the ArcGIS export filter, and the /map/feature/v2 API endpoints) all take a filter: a short expression that says which features you want to include in your download. The filter language is CQL2, an open standard from the Open Geospatial Consortium (OGC 21-065). This page is enough to write most filters; the full standard is linked at the end.
The shape of a filter#
A filter compares an attribute of the feature with a value, and joins comparisons with AND, OR and NOT.
status = 'approved'
status = 'approved' AND riverName LIKE 'Wey%'
(status = 'draft' OR status = 'review') AND NOT surveyor IS NULL- Attribute names are written as they appear in the layer's attribute list (the Attributes column of a map layer, or
?properties=). Names are case-sensitive. A name with spaces or punctuation goes in double quotes:"site name" = 'Wey'. - Text values go in single quotes. To put an apostrophe inside, double it:
'O''Brien'. - Numbers are bare:
ph > 7.5. True and false areTRUEandFALSE. - Keywords (
AND,LIKE,IN...) can be upper or lower case. ANDbinds tighter thanOR, so use brackets when you mix them.
Comparisons#
| Write | Meaning |
a = 'x', a <> 'x' | Equal, not equal |
a < 5, a <= 5, a > 5, a >= 5 | Less than, at most, greater than, at least (numbers, text, dates) |
a BETWEEN 1 AND 10 | In the range, both ends included |
a IN ('x', 'y', 'z') | Any of the listed values (NOT IN for none of them) |
a LIKE 'Wey%' | Text pattern: % is any run of characters, _ is one character. NOT LIKE for the opposite |
CASEI(a) = CASEI('wey') | Compare ignoring upper/lower case (works with LIKE and IN too) |
a IS NULL, a IS NOT NULL | The attribute has no value / has a value |
Two attributes can be compared with each other, and simple arithmetic works: upstreamWidth > downstreamWidth * 2.
Dates and times#
Dates and timestamps are written with a keyword so they are not mistaken for text:
timestamp >= DATE('2026-03-01')
timestamp < TIMESTAMP('2026-03-01T09:30:00Z')A DATE used in a comparison means the very start of that day (midnight UTC). For "on that day" or "in that period", use the temporal operators, which treat a date as the whole day:
T_INTERSECTS(timestamp, DATE('2026-03-04'))
T_INTERSECTS(timestamp, INTERVAL('2026-03-01', '2026-03-31'))
T_AFTER(timestamp, DATE('2026-03-31'))
T_INTERSECTS(timestamp, INTERVAL('2026-01-01', '..'))T_BEFORE, T_AFTER, T_INTERSECTS, T_DURING and T_DISJOINT are the useful ones. An interval end written '..' is open.
Locations#
The layer's location can be tested against a shape. Coordinates are longitude then latitude in decimal degrees (WGS84), or British National Grid eastings and northings if you say so with filter-crs=EPSG:27700 on the API.
S_INTERSECTS(@geometry, BBOX(-2.6, 51.3, -2.4, 51.5))
S_WITHIN(@geometry, POLYGON((-2.6 51.3, -2.4 51.3, -2.4 51.5, -2.6 51.5, -2.6 51.3)))
CRT_DWITHIN(@geometry, POINT(-2.5 51.4), 500)BBOX(west, south, east, north)is a rectangle.POINT,LINESTRINGandPOLYGONare written in well-known text, as above. A polygon's first and last point are the same.S_INTERSECTS,S_WITHIN,S_CONTAINS,S_DISJOINT,S_TOUCHES,S_CROSSES,S_OVERLAPSandS_EQUALSare the standard spatial tests.CRT_DWITHIN(geometry, point, metres)is Cartographer's own: features within a distance of a point.CRT_POINT(geometry)is also Cartographer's own: it reduces a line or polygon to the single point Cartographer draws it at (a line's midpoint, a polygon's centre), the same rule as thepointgeometry transform in downloads. Use it to test a line or polygon layer by where it is drawn rather than by everything it touches:S_INTERSECTS(CRT_POINT(@geometry), BBOX(-2.6, 51.3, -2.4, 51.5))orCRT_DWITHIN(CRT_POINT(@geometry), POINT(-2.5 51.4), 500).
Standard names for the usual attributes#
Every layer names its attributes its own way, so Cartographer gives the common ones a standard name starting with @. These work in any layer that has the attribute:
| Name | What it is |
@status | The survey status: 'draft', 'complete', 'review', 'approved'... |
@timestamp | When the survey was made |
@geometry | The feature's location |
@primarykey | The feature's id |
@project, @survey, @team, @surveyor | The ids of the project, survey, team and surveyor behind the feature |
@status = 'approved' AND T_INTERSECTS(@timestamp, INTERVAL('2026-01-01', '..'))Worked examples#
- Approved surveys on the River Wey:
@status = 'approved' AND CASEI(riverName) LIKE CASEI('%wey%') - Anything that is not a draft, from March 2026:
@status <> 'draft' AND T_INTERSECTS(@timestamp, INTERVAL('2026-03-01', '2026-03-31')) - Within 250 m of a point, with a pH reading:
CRT_DWITHIN(@geometry, POINT(-2.5 51.4), 250) AND ph IS NOT NULL - One of a few known sites:
siteCode IN ('WEY01', 'WEY02', 'WEY07')
When a filter is refused#
Cartographer checks a filter against the layer before running it. The message tells you what is wrong:
- could not parse filter at offset N: expected ... - a typo; the offset counts characters from the start of the filter.
- attribute not found: name - the layer has no attribute of that name. Check the layer's attribute list; names are case-sensitive.
- expected number: ph / expected string: riverName - the value is the wrong kind for that attribute (a text value needs quotes; a number must not have them).
- unknown value for status: Approved (expected one of ...) - an attribute with a fixed set of values was given one not in the set.
- unsupported operation: geometry: equality comparison - that operator does not apply to that kind of attribute (locations are compared with the
S_tests, not=).
For developers#
The API takes the filter as ?filter= (text) or, with ?filter-lang=cql2-json, as the JSON encoding; the POST form of the search takes it in the body as either. The survey search (/survey/v3) takes the same filter over the survey's own attributes - @status, @timestamp, @geometry, @team, @surveyor, description, surveyorName, created, updated... - plus CRT_FULLTEXT('terms'), which runs the search the search bar runs. ?filter-crs= names the coordinate system of the shapes in the filter: CRS84 (the default), EPSG:4326 or EPSG:27700. The TypeScript client has typed builders for the JSON form (cql2.and, cql2.eq, cql2.crtDwithin, cql2.crtPoint...).
The standard: OGC 21-065r3, Common Query Language (CQL2). Cartographer implements the Basic, Advanced Comparison, Case-insensitive, Spatial, Temporal, Property-Property, Arithmetic and Functions conformance classes, and not arrays or accent-insensitive comparison.
