Cartographer
Help topics
🔍

Filtering Map Searches with CQL2

⚠️
This page describes an advanced feature of Cartographer. Most people won’t need to use it. Feel free to ignore this page if you’re not already aware of the feature.

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.

💡
A filter is always narrowing. It can only remove features from what you can already see; it can never show you records your role does not reach.

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 are TRUE and FALSE.
  • Keywords (AND, LIKE, IN...) can be upper or lower case.
  • AND binds tighter than OR, so use brackets when you mix them.

Comparisons#

WriteMeaning
a = 'x', a <> 'x'Equal, not equal
a < 5, a <= 5, a > 5, a >= 5Less than, at most, greater than, at least (numbers, text, dates)
a BETWEEN 1 AND 10In 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 NULLThe 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, LINESTRING and POLYGON are 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_OVERLAPS and S_EQUALS are 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 the point geometry 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)) or CRT_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:

NameWhat it is
@statusThe survey status: 'draft', 'complete', 'review', 'approved'...
@timestampWhen the survey was made
@geometryThe feature's location
@primarykeyThe feature's id
@project, @survey, @team, @surveyorThe 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.