@@ -2,6 +2,7 @@ import { isEmpty } from 'lodash-es'
22import { dbClient as _client , createIndex } from './database-client.js'
33import logger from './logger.js'
44import { ValidationError } from './errors.js'
5+ import { bboxToPolygon } from './geo-utils.js'
56
67const COLLECTIONS_INDEX = process . env [ 'COLLECTIONS_INDEX' ] || 'collections'
78const DEFAULT_INDICES = [ '*' , '-.*' , '-collections' ]
@@ -19,6 +20,7 @@ const OP = {
1920 IN : 'in' ,
2021 BETWEEN : 'between' ,
2122 LIKE : 'like' ,
23+ S_INTERSECTS : 's_intersects' ,
2224}
2325const RANGE_TRANSLATION = {
2426 '<' : 'lt' ,
@@ -32,6 +34,8 @@ const UNPREFIXED_FIELDS = [
3234 'geometry' ,
3335 'bbox'
3436]
37+ const GEOMETRY_TYPES = [ 'Point' , 'LineString' , 'Polygon' , 'MultiPoint' ,
38+ 'MultiLineString' , 'MultiPolygon' , 'GeometryCollection' ]
3539
3640let collectionToIndexMapping = null
3741let unrestrictedIndices = null
@@ -101,6 +105,84 @@ export function buildDatetimeQuery(parameters) {
101105 return dateQuery
102106}
103107
108+ function IN ( cql2Field , cql2Value ) {
109+ if ( ! Array . isArray ( cql2Value ) || cql2Value . length === 0 ) {
110+ throw new ValidationError ( "Operand for 'in' must be a non-empty array" )
111+ }
112+ if ( ! cql2Value . every ( ( x ) => x !== Object ( x ) ) ) {
113+ throw new ValidationError (
114+ "Operand for 'in' must contain only string, number, or boolean types"
115+ )
116+ }
117+
118+ return {
119+ terms : {
120+ [ cql2Field ] : cql2Value
121+ }
122+ }
123+ }
124+
125+ function between ( cql2Field , filterArgs ) {
126+ if ( filterArgs . length < 3 ) {
127+ throw new ValidationError ( "Two operands must be provided for the 'between' operator" )
128+ }
129+
130+ const cql2Value1 = filterArgs [ 1 ]
131+ const cql2Value2 = filterArgs [ 2 ]
132+ if ( ! ( typeof cql2Value1 === 'number' && typeof cql2Value2 === 'number' ) ) {
133+ throw new ValidationError ( "Operands for 'between' must be numbers" )
134+ }
135+
136+ if ( cql2Value1 > cql2Value2 ) {
137+ throw new ValidationError (
138+ "For the 'between' operator, the first operand must be less than or equal "
139+ + 'to the second operand'
140+ )
141+ }
142+
143+ return {
144+ range : {
145+ [ cql2Field ] : {
146+ gte : cql2Value1 ,
147+ lte : cql2Value2
148+ }
149+ }
150+ }
151+ }
152+
153+ function sIntersects ( cql2Field , cql2Value ) {
154+ // cql2Value can be either:
155+ // 1) { "bbox": [swLon, swLat, neLon, neLat] }
156+ // 2) geojson geometry
157+
158+ let geom = null
159+
160+ if ( cql2Value . bbox ) {
161+ geom = bboxToPolygon ( cql2Value . bbox , true )
162+ }
163+
164+ if ( cql2Value . type && cql2Value . coordinates ) {
165+ if ( ! GEOMETRY_TYPES . includes ( cql2Value . type ) ) {
166+ throw new ValidationError (
167+ `Operand for 's_intersects' must be a GeoJSON geometry: type was '${ cql2Value . type } '`
168+ )
169+ }
170+ geom = cql2Value
171+ }
172+
173+ if ( ! geom ) {
174+ throw new ValidationError (
175+ "Operand for 's_intersects' must be a bbox literal or GeoJSON geometry"
176+ )
177+ }
178+
179+ return {
180+ geo_shape : {
181+ [ cql2Field ] : { shape : geom }
182+ }
183+ }
184+ }
185+
104186function buildQueryExtQuery ( query ) {
105187 const eq = 'eq'
106188 const inop = 'in'
@@ -280,49 +362,13 @@ function buildFilterExtQuery(filter) {
280362 }
281363 }
282364 case OP . IN :
283- if ( ! Array . isArray ( cql2Value ) || cql2Value . length === 0 ) {
284- throw new ValidationError ( "Operand for 'in' must be a non-empty array" )
285- }
286- if ( ! cql2Value . every ( ( x ) => x !== Object ( x ) ) ) {
287- throw new ValidationError (
288- "Operand for 'in' must contain only string, number, or boolean types"
289- )
290- }
291-
292- return {
293- terms : {
294- [ cql2Field ] : cql2Value
295- }
296- }
365+ return IN ( cql2Field , cql2Value )
297366 case OP . BETWEEN :
298- if ( filter . args . length < 3 ) {
299- throw new ValidationError ( "Two operands must be provided for the 'between' operator" )
300- }
301-
302- // eslint-disable-next-line no-case-declarations
303- const cql2Value2 = filter . args [ 2 ]
304- if ( ! ( typeof cql2Value === 'number' && typeof cql2Value2 === 'number' ) ) {
305- throw new ValidationError ( "Operands for 'between' must be numbers" )
306- }
307-
308- if ( cql2Value > cql2Value2 ) {
309- throw new ValidationError (
310- "For the 'between' operator, the first operand must be less than or equal "
311- + 'to the second operand'
312- )
313- }
314-
315- return {
316- range : {
317- [ cql2Field ] : {
318- gte : cql2Value ,
319- lte : cql2Value2
320- }
321- }
322- }
323-
367+ return between ( cql2Field , filter . args )
324368 case OP . LIKE :
325369 throw new ValidationError ( "The 'like' operator is not currently supported" )
370+ case OP . S_INTERSECTS :
371+ return sIntersects ( cql2Field , cql2Value )
326372
327373 // should not get here
328374 default :
0 commit comments