• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MapServer / MapServer / 22113288002

17 Feb 2026 07:49PM UTC coverage: 41.892% (+0.09%) from 41.804%
22113288002

push

github

web-flow
Merge pull request #7433 from rouault/ogcapi_part3_queryables

OGCAPI Features Part 3 Filtering: add support for 'Queryables' and 'Queryables as query parameter'

224 of 245 new or added lines in 4 files covered. (91.43%)

328 existing lines in 4 files now uncovered.

63165 of 150782 relevant lines covered (41.89%)

25378.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.15
/src/mapogcfiltercommon.cpp
1
/**********************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  OGC Filter Encoding implementation
6
 * Author:   Y. Assefa, DM Solutions Group (assefa@dmsolutions.ca)
7
 *
8
 **********************************************************************
9
 * Copyright (c) 2003, Y. Assefa, DM Solutions Group Inc
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 ****************************************************************************/
28

29
#include "mapogcfilter.h"
30
#include "mapserver.h"
31
#include "mapows.h"
32
#include "mapowscommon.h"
33
#include "cpl_minixml.h"
34

35
#include <string>
36

37
static std::string FLTEscapePropertyName(const char *pszStr,
388✔
38
                                         char chEscapeChar) {
39
  std::string ret;
40
  for (; *pszStr; ++pszStr) {
2,664✔
41
    if (*pszStr == chEscapeChar) {
2,276✔
42
      ret += chEscapeChar;
43
      ret += chEscapeChar;
44
    } else {
45
      ret += *pszStr;
46
    }
47
  }
48
  return ret;
388✔
49
}
50

51
static std::string
52
FLTGetIsLikeComparisonCommonExpression(FilterEncodingNode *psFilterNode) {
30✔
53
  /* From
54
   * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html#tag_09_04
55
   */
56
  /* also add double quote because we are within a string */
57
  const char *pszRegexSpecialCharsAndDoubleQuote = "\\^${[().*+?|\"";
58

59
  if (!psFilterNode || !psFilterNode->pOther || !psFilterNode->psLeftNode ||
30✔
60
      !psFilterNode->psRightNode || !psFilterNode->psRightNode->pszValue)
30✔
61
    return std::string();
62

63
  const FEPropertyIsLike *propIsLike =
64
      (const FEPropertyIsLike *)psFilterNode->pOther;
65
  const char *pszWild = propIsLike->pszWildCard;
30✔
66
  const char *pszSingle = propIsLike->pszSingleChar;
30✔
67
  const char *pszEscape = propIsLike->pszEscapeChar;
30✔
68
  const bool bCaseInsensitive = propIsLike->bCaseInsensitive != 0;
30✔
69

70
  if (!pszWild || strlen(pszWild) == 0 || !pszSingle ||
30✔
71
      strlen(pszSingle) == 0 || !pszEscape || strlen(pszEscape) == 0)
30✔
72
    return std::string();
73

74
  /* -------------------------------------------------------------------- */
75
  /*      Use operand with regular expressions.                           */
76
  /* -------------------------------------------------------------------- */
77
  std::string expr("(\"[");
30✔
78

79
  /* attribute */
80
  expr += FLTEscapePropertyName(psFilterNode->psLeftNode->pszValue, '"');
30✔
81

82
  /* #3521 */
83
  if (bCaseInsensitive)
30✔
84
    expr += "]\" ~* \"";
85
  else
86
    expr += "]\" ~ \"";
87

88
  const char *pszValue = psFilterNode->psRightNode->pszValue;
30✔
89
  const size_t nLength = strlen(pszValue);
30✔
90

91
  if (nLength > 0) {
30✔
92
    expr += '^';
93
  }
94
  for (size_t i = 0; i < nLength; i++) {
193✔
95
    if (pszValue[i] == pszSingle[0]) {
163✔
96
      expr += '.';
97
      /* The Filter escape character is supposed to only escape the single,
98
       * wildcard and escape character */
99
    } else if (pszValue[i] == pszEscape[0] &&
159✔
100
               (pszValue[i + 1] == pszSingle[0] ||
6✔
101
                pszValue[i + 1] == pszWild[0] ||
4✔
102
                pszValue[i + 1] == pszEscape[0])) {
103
      if (pszValue[i + 1] == '\\') {
6✔
104
        /* Tricky case: \ must be escaped ncce in the regular expression context
105
           so that the regexp matches it as an ordinary character.
106
           But as \ is also the escape character for MapServer string, we
107
           must escape it again. */
108
        expr += "\\"
109
                "\\"
110
                "\\"
111
                "\\";
112
      } else {
113
        /* If the escaped character is itself a regular expression special
114
         * character */
115
        /* we need to regular-expression-escape-it ! */
116
        if (strchr(pszRegexSpecialCharsAndDoubleQuote, pszValue[i + 1])) {
5✔
117
          expr += '\\';
118
        }
119
        expr += pszValue[i + 1];
5✔
120
      }
121
      i++;
122
    } else if (pszValue[i] == pszWild[0]) {
153✔
123
      expr += ".*";
124
    }
125
    /* Escape regular expressions special characters and double quote */
126
    else if (strchr(pszRegexSpecialCharsAndDoubleQuote, pszValue[i])) {
130✔
127
      if (pszValue[i] == '\\') {
23✔
128
        /* See above explantation */
129
        expr += "\\"
130
                "\\"
131
                "\\"
132
                "\\";
133
      } else {
134
        expr += '\\';
135
        expr += pszValue[i];
21✔
136
      }
137
    } else {
138
      expr += pszValue[i];
139
    }
140
  }
141
  if (nLength > 0) {
30✔
142
    expr += '$';
143
  }
144
  expr += "\")";
145
  return expr;
30✔
146
}
147

148
static std::string
149
FLTGetIsBetweenComparisonCommonExpresssion(FilterEncodingNode *psFilterNode,
17✔
150
                                           layerObj *lp) {
151
  if (psFilterNode->psLeftNode == NULL || psFilterNode->psRightNode == NULL)
17✔
152
    return std::string();
153

154
  /* -------------------------------------------------------------------- */
155
  /*      Get the bounds value which are stored like boundmin;boundmax    */
156
  /* -------------------------------------------------------------------- */
157
  const auto bounds = msStringSplit(psFilterNode->psRightNode->pszValue, ';');
17✔
158
  if (bounds.size() != 2) {
17✔
159
    return std::string();
160
  }
161

162
  /* -------------------------------------------------------------------- */
163
  /*      check if the value is a numeric value or alphanumeric. If it    */
164
  /*      is alphanumeric, add quotes around attribute and values.        */
165
  /* -------------------------------------------------------------------- */
166
  bool bString = false;
167
  bool bDateTime = false;
168

169
  const char *pszType = msOWSLookupMetadata(
17✔
170
      &(lp->metadata), "OFG",
17✔
171
      (std::string(psFilterNode->psLeftNode->pszValue) + "_type").c_str());
17✔
172
  if (pszType != NULL && (strcasecmp(pszType, "Character") == 0))
17✔
173
    bString = true;
174
  else if (pszType != NULL && (strcasecmp(pszType, "Date") == 0))
3✔
175
    bDateTime = true;
176
  else if (FLTIsNumeric(bounds[0].c_str()) == MS_FALSE)
17✔
177
    bString = true;
178

179
  if (!bString && !bDateTime) {
17✔
180
    if (FLTIsNumeric(bounds[1].c_str()) == MS_FALSE)
16✔
181
      bString = true;
182
  }
183

184
  std::string expr;
185
  /* -------------------------------------------------------------------- */
186
  /*      build expression.                                              */
187
  /* -------------------------------------------------------------------- */
188
  /* attribute */
189
  if (bString)
17✔
190
    expr += "(\"[";
191
  else
192
    expr += "([";
193

194
  expr += FLTEscapePropertyName(psFilterNode->psLeftNode->pszValue,
33✔
195
                                bString ? '"' : ']');
196

197
  if (bString)
17✔
198
    expr += "]\" ";
199
  else
200
    expr += "] ";
201

202
  expr += " >= ";
203

204
  if (bString) {
17✔
205
    expr += '\"';
206
  } else if (bDateTime) {
16✔
207
    expr += '`';
208
  }
209

210
  expr += msStdStringEscape(bounds[0].c_str());
17✔
211

212
  if (bString) {
17✔
213
    expr += '\"';
214
  } else if (bDateTime) {
16✔
215
    expr += '`';
216
  }
217

218
  expr += " AND ";
219

220
  if (bString)
17✔
221
    expr += " \"[";
222
  else
223
    expr += " [";
224

225
  /* attribute */
226
  expr += psFilterNode->psLeftNode->pszValue;
17✔
227

228
  if (bString)
17✔
229
    expr += "]\" ";
230
  else
231
    expr += "] ";
232

233
  expr += " <= ";
234

235
  if (bString) {
17✔
236
    expr += '\"';
237
  } else if (bDateTime) {
16✔
238
    expr += '`';
239
  }
240

241
  expr += msStdStringEscape(bounds[1].c_str());
17✔
242

243
  if (bString) {
17✔
244
    expr += '\"';
245
  } else if (bDateTime) {
16✔
246
    expr += '`';
247
  }
248
  expr += ')';
249

250
  return expr;
17✔
251
}
17✔
252

253
std::string FLTGetBinaryComparisonCommonExpression(layerObj *lp,
274✔
254
                                                   const char *pszPropertyName,
255
                                                   bool bForceString,
256
                                                   const char *pszOp,
257
                                                   const char *pszValue) {
258
  assert(pszPropertyName);
259
  assert(pszOp);
260

261
  /* -------------------------------------------------------------------- */
262
  /*      check if the value is a numeric value or alphanumeric. If it    */
263
  /*      is alphanumeric, add quotes around attribute and values.        */
264
  /* -------------------------------------------------------------------- */
265
  bool bString = false;
266
  bool bDateTime = false;
267
  if (pszValue) {
274✔
268
    const char *pszType =
269
        msOWSLookupMetadata(&(lp->metadata), "OFG",
271✔
270
                            (std::string(pszPropertyName) + "_type").c_str());
271✔
271
    if (pszType != NULL && (strcasecmp(pszType, "Character") == 0))
271✔
272
      bString = true;
273
    else if (pszType != NULL && (strcasecmp(pszType, "Date") == 0))
16✔
274
      bDateTime = true;
275
    else if (FLTIsNumeric(pszValue) == MS_FALSE)
238✔
276
      bString = true;
277
  }
278

279
  /* special case to be able to have empty strings in the expression. */
280
  /* propertyislike is always treated as string */
281
  if (!pszValue || bForceString)
274✔
282
    bString = true;
283

284
  /* attribute */
285
  std::string expr;
286
  if (bString)
274✔
287
    expr = "(\"[";
288
  else
289
    expr = "([";
290

291
  expr += FLTEscapePropertyName(pszPropertyName, bString ? '"' : ']');
402✔
292

293
  if (bString)
274✔
294
    expr += "]\" ";
295
  else
296
    expr += "] ";
297

298
  expr += pszOp;
299
  expr += ' ';
300

301
  /* value */
302
  if (bString) {
274✔
303
    expr += "\"";
304
  } else if (bDateTime) {
128✔
305
    expr += "`";
306
  }
307

308
  if (pszValue) {
274✔
309
    expr += msStdStringEscape(pszValue);
542✔
310
  }
311

312
  if (bString) {
274✔
313
    expr += "\"";
314
  } else if (bDateTime) {
128✔
315
    expr += "`";
316
  }
317

318
  expr += ")";
319

320
  return expr;
274✔
321
}
322

323
static std::string
324
FLTGetBinaryComparisonCommonExpression(FilterEncodingNode *psFilterNode,
270✔
325
                                       layerObj *lp) {
326

327
  const char *pszPropertyName = psFilterNode->psLeftNode->pszValue;
270✔
328
  const char *pszValue = psFilterNode->psRightNode->pszValue;
270✔
329
  const char *pszXMLOp = psFilterNode->pszValue;
270✔
330
  if (pszPropertyName && pszXMLOp) {
270✔
331
    /* special case to be able to have empty strings in the expression. */
332
    /* propertyislike is always treated as string */
333
    const bool bForceString = (strcasecmp(pszXMLOp, "PropertyIsLike") == 0);
270✔
334

335
    const char *pszOp = nullptr;
336
    if (strcasecmp(pszXMLOp, "PropertyIsEqualTo") == 0) {
270✔
337
      /* case insensitive set ? */
338
      if (psFilterNode->psRightNode->pOther &&
189✔
339
          (*(int *)psFilterNode->psRightNode->pOther) == 1)
189✔
340
        pszOp = "=*";
341
      else
342
        pszOp = "=";
343
    } else if (strcasecmp(pszXMLOp, "PropertyIsNotEqualTo") == 0)
81✔
344
      pszOp = "!=";
345
    else if (strcasecmp(pszXMLOp, "PropertyIsLessThan") == 0)
69✔
346
      pszOp = "<";
347
    else if (strcasecmp(pszXMLOp, "PropertyIsGreaterThan") == 0)
39✔
348
      pszOp = ">";
349
    else if (strcasecmp(pszXMLOp, "PropertyIsLessThanOrEqualTo") == 0)
28✔
350
      pszOp = "<=";
351
    else if (strcasecmp(pszXMLOp, "PropertyIsGreaterThanOrEqualTo") == 0)
19✔
352
      pszOp = ">=";
NEW
353
    else if (strcasecmp(pszXMLOp, "PropertyIsLike") == 0)
×
354
      pszOp = "~";
355
    else
356
      return std::string();
357

358
    return FLTGetBinaryComparisonCommonExpression(
359
        lp, pszPropertyName, bForceString, pszOp, pszValue);
270✔
360
  }
361

362
  return std::string();
363
}
364

365
static std::string
366
FLTGetLogicalComparisonCommonExpression(FilterEncodingNode *psFilterNode,
88✔
367
                                        layerObj *lp) {
368
  std::string expr;
369
  /* -------------------------------------------------------------------- */
370
  /*      OR and AND                                                      */
371
  /* -------------------------------------------------------------------- */
372
  if (psFilterNode->psLeftNode && psFilterNode->psRightNode) {
88✔
373
    char *pszTmp = FLTGetCommonExpression(psFilterNode->psLeftNode, lp);
77✔
374
    if (!pszTmp)
77✔
375
      return std::string();
376

377
    expr = '(';
378
    expr += pszTmp;
379
    msFree(pszTmp);
77✔
380
    expr += ' ';
381
    expr += psFilterNode->pszValue;
77✔
382
    expr += ' ';
383

384
    pszTmp = FLTGetCommonExpression(psFilterNode->psRightNode, lp);
77✔
385
    if (!pszTmp) {
77✔
386
      return std::string();
387
    }
388

389
    expr += pszTmp;
390
    msFree(pszTmp);
77✔
391
    expr += ')';
392
  }
393
  /* -------------------------------------------------------------------- */
394
  /*      NOT                                                             */
395
  /* -------------------------------------------------------------------- */
396
  else if (psFilterNode->psLeftNode &&
11✔
397
           strcasecmp(psFilterNode->pszValue, "NOT") == 0) {
11✔
398
    char *pszTmp = FLTGetCommonExpression(psFilterNode->psLeftNode, lp);
11✔
399
    if (!pszTmp)
11✔
400
      return std::string();
401

402
    expr = "(NOT ";
403
    expr += pszTmp;
404
    msFree(pszTmp);
11✔
405
    expr += ')';
406
  }
407

408
  return expr;
88✔
409
}
410

411
static std::string
412
FLTGetSpatialComparisonCommonExpression(FilterEncodingNode *psNode,
132✔
413
                                        layerObj *lp) {
414
  std::string expr;
415
  double dfDistance = -1;
132✔
416
  shapeObj *psTmpShape = NULL;
417
  bool bBBoxQuery = false;
418
  bool bAlreadyReprojected = false;
419

420
  if (lp == NULL)
132✔
421
    return std::string();
422

423
  /* get the shape */
424
  if (FLTIsBBoxFilter(psNode)) {
132✔
425
    rectObj sQueryRect;
426
    FLTGetBBOX(psNode, &sQueryRect);
42✔
427

428
    char szPolygon[512];
429
    snprintf(szPolygon, sizeof(szPolygon),
42✔
430
             "POLYGON((%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f "
431
             "%.18f))",
432
             sQueryRect.minx, sQueryRect.miny, sQueryRect.minx, sQueryRect.maxy,
433
             sQueryRect.maxx, sQueryRect.maxy, sQueryRect.maxx, sQueryRect.miny,
434
             sQueryRect.minx, sQueryRect.miny);
435

436
    psTmpShape = msShapeFromWKT(szPolygon);
42✔
437

438
    /*
439
    ** This is a horrible hack to deal with world-extent requests and
440
    ** reprojection. msProjectRect() detects if reprojection from longlat to
441
    ** projected SRS, and in that case it transforms the bbox to
442
    *-1e-15,-1e-15,1e15,1e15
443
    ** to ensure that all features are returned.
444
    **
445
    ** Make wfs_200_cite_filter_bbox_world.xml and
446
    *wfs_200_cite_postgis_bbox_world.xml pass
447
    */
448
    if (fabs(sQueryRect.minx - -180.0) < 1e-5 &&
42✔
449
        fabs(sQueryRect.miny - -90.0) < 1e-5 &&
2✔
450
        fabs(sQueryRect.maxx - 180.0) < 1e-5 &&
2✔
451
        fabs(sQueryRect.maxy - 90.0) < 1e-5) {
2✔
452
      if (lp->projection.numargs > 0) {
2✔
453
        projectionObj sProjTmp;
454
        if (psNode->pszSRS) {
2✔
455
          msInitProjection(&sProjTmp);
2✔
456
          msProjectionInheritContextFrom(&sProjTmp, &lp->projection);
2✔
457
        }
458
        if (psNode->pszSRS) {
2✔
459
          /* Use the non EPSG variant since axis swapping is done in
460
           * FLTDoAxisSwappingIfNecessary */
461
          if (msLoadProjectionString(&sProjTmp, psNode->pszSRS) == 0) {
2✔
462
            msProjectRect(&sProjTmp, &lp->projection, &sQueryRect);
2✔
463
          }
464
        } else if (lp->map->projection.numargs > 0)
×
465
          msProjectRect(&lp->map->projection, &lp->projection, &sQueryRect);
×
466
        if (psNode->pszSRS)
2✔
467
          msFreeProjection(&sProjTmp);
2✔
468
      }
469
      if (sQueryRect.minx <= -1e14) {
2✔
470
        msFreeShape(psTmpShape);
2✔
471
        msFree(psTmpShape);
2✔
472
        psTmpShape = (shapeObj *)msSmallMalloc(sizeof(shapeObj));
2✔
473
        msInitShape(psTmpShape);
2✔
474
        msRectToPolygon(sQueryRect, psTmpShape);
2✔
475
        bAlreadyReprojected = true;
476
      }
477
    }
478

479
    bBBoxQuery = true;
480
  } else {
481
    /* other geos type operations */
482

483
    /* project shape to layer projection. If the proj is not part of the filter
484
      query, assume that the cooredinates are in the map projection */
485

486
    int nUnit = -1;
90✔
487
    shapeObj *psQueryShape = FLTGetShape(psNode, &dfDistance, &nUnit);
90✔
488

489
    if ((strcasecmp(psNode->pszValue, "DWithin") == 0 ||
90✔
490
         strcasecmp(psNode->pszValue, "Beyond") == 0) &&
73✔
491
        dfDistance > 0) {
23✔
492
      int nLayerUnit = lp->units;
23✔
493
      if (nLayerUnit == -1)
23✔
494
        nLayerUnit = GetMapserverUnitUsingProj(&lp->projection);
×
495
      if (nLayerUnit == -1)
×
496
        nLayerUnit = lp->map->units;
×
497
      if (nLayerUnit == -1)
23✔
498
        nLayerUnit = GetMapserverUnitUsingProj(&lp->map->projection);
×
499

500
      if (nUnit >= 0 && nUnit != nLayerUnit)
23✔
501
        dfDistance *=
18✔
502
            msInchesPerUnit(nUnit, 0) /
36✔
503
            msInchesPerUnit(nLayerUnit, 0); /* target is layer units */
18✔
504
    }
505

506
    psTmpShape = psQueryShape;
507
  }
508

509
  if (psTmpShape) {
132✔
510

511
    /*
512
    ** target is layer projection
513
    */
514
    if (!bAlreadyReprojected && lp->projection.numargs > 0) {
132✔
515
      projectionObj sProjTmp;
516
      if (psNode->pszSRS) {
127✔
517
        msInitProjection(&sProjTmp);
58✔
518
        msProjectionInheritContextFrom(&sProjTmp, &lp->projection);
58✔
519
      }
520
      if (psNode->pszSRS) {
127✔
521
        /* Use the non EPSG variant since axis swapping is done in
522
         * FLTDoAxisSwappingIfNecessary */
523
        if (msLoadProjectionString(&sProjTmp, psNode->pszSRS) == 0) {
58✔
524
          msProjectShape(&sProjTmp, &lp->projection, psTmpShape);
58✔
525
        }
526
      } else if (lp->map->projection.numargs > 0)
69✔
527
        msProjectShape(&lp->map->projection, &lp->projection, psTmpShape);
69✔
528
      if (psNode->pszSRS)
127✔
529
        msFreeProjection(&sProjTmp);
58✔
530
    }
531

532
    /* function name */
533
    if (bBBoxQuery) {
132✔
534
      expr = "intersects";
535
    } else {
536
      if (strncasecmp(psNode->pszValue, "intersect", 9) == 0)
90✔
537
        expr = "intersects";
538
      else {
539
        expr = msStringToLower(std::string(psNode->pszValue));
142✔
540
      }
541
    }
542
    /* geometry binding */
543
    expr += "([shape],fromText('";
544

545
    /* filter geometry */
546
    char *pszWktText = msGEOSShapeToWKT(psTmpShape);
132✔
547
    expr += pszWktText ? pszWktText : "Cannot translate shape to WKT";
132✔
548
    expr += "')";
549
    msGEOSFreeWKT(pszWktText);
132✔
550

551
    /* (optional) beyond/dwithin distance, always 0.0 since we apply the
552
     * distance as a buffer earlier */
553
    if ((strcasecmp(psNode->pszValue, "DWithin") == 0 ||
132✔
554
         strcasecmp(psNode->pszValue, "Beyond") == 0)) {
115✔
555
      char szBuffer[32];
556
      snprintf(szBuffer, sizeof(szBuffer), ",%g", dfDistance);
23✔
557
      expr += szBuffer;
558
    }
559

560
    /* terminate the function */
561
    expr += ") = TRUE";
562
  }
563

564
  /*
565
  ** Cleanup
566
  */
567
  if (bBBoxQuery) {
132✔
568
    msFreeShape(psTmpShape);
42✔
569
    msFree(psTmpShape);
42✔
570
  }
571

572
  return expr;
132✔
573
}
574

575
static std::string
576
FLTGetFeatureIdCommonExpression(FilterEncodingNode *psFilterNode,
64✔
577
                                layerObj *lp) {
578
  std::string expr;
579

580
#if defined(USE_WMS_SVR) || defined(USE_WFS_SVR) || defined(USE_WCS_SVR) ||    \
581
    defined(USE_SOS_SVR)
582
  if (psFilterNode->pszValue) {
64✔
583
    const char *pszAttribute =
584
        msOWSLookupMetadata(&(lp->metadata), "OFG", "featureid");
64✔
585
    if (pszAttribute) {
64✔
586
      const auto tokens = msStringSplit(psFilterNode->pszValue, ',');
64✔
587
      if (!tokens.empty()) {
64✔
588
        bool bString = false;
589
        for (size_t i = 0; i < tokens.size(); i++) {
131✔
590
          const char *pszId = tokens[i].c_str();
591
          const char *pszDot = strrchr(pszId, '.');
592
          if (pszDot)
67✔
593
            pszId = pszDot + 1;
29✔
594

595
          if (i == 0) {
67✔
596
            if (FLTIsNumeric(pszId) == MS_FALSE)
64✔
597
              bString = true;
598
          }
599

600
          if (!expr.empty())
67✔
601
            expr += " OR ";
602
          else
603
            expr = '(';
604

605
          if (bString) {
67✔
606
            expr += "(\"[";
607
            expr += FLTEscapePropertyName(pszAttribute, '"');
28✔
608
            expr += "]\" == \"";
609
            expr += pszId;
610
            expr += "\")";
611
          } else {
612
            expr += "([";
613
            expr += FLTEscapePropertyName(pszAttribute, ']');
106✔
614
            expr += "] == ";
615
            expr += pszId;
616
            expr += ")";
617
          }
618
        }
619
      }
620
    }
64✔
621

622
    /* opening and closing brackets are needed for mapserver expressions */
623
    if (!expr.empty())
64✔
624
      expr += ')';
625
  }
626
#endif
627

628
  return expr;
64✔
629
}
630

631
std::string FLTGetTimeExpression(FilterEncodingNode *psFilterNode,
19✔
632
                                 layerObj *lp) {
633
  if (lp == NULL)
19✔
634
    return std::string();
635

636
  std::string expr;
637
  const char *pszTimeField = nullptr;
19✔
638
  const char *pszTimeValue = FLTGetDuring(psFilterNode, &pszTimeField);
19✔
639
  if (pszTimeField && pszTimeValue) {
19✔
640
    expressionObj old_filter;
641
    msInitExpression(&old_filter);
19✔
642
    msCopyExpression(&old_filter, &lp->filter); /* save existing filter */
19✔
643
    msFreeExpression(&lp->filter);
19✔
644
    if (msLayerSetTimeFilter(lp, pszTimeValue, pszTimeField) == MS_TRUE &&
19✔
645
        lp->filter.string) {
19✔
646
      expr = lp->filter.string;
647
    }
648
    msCopyExpression(&lp->filter, &old_filter); /* restore old filter */
19✔
649
    msFreeExpression(&old_filter);
19✔
650
  }
651
  return expr;
19✔
652
}
653

654
char *FLTGetCommonExpression(FilterEncodingNode *psFilterNode, layerObj *lp) {
620✔
655
  char *pszExpression = NULL;
656

657
  if (!psFilterNode)
620✔
658
    return NULL;
659

660
  if (psFilterNode->eType == FILTER_NODE_TYPE_COMPARISON) {
620✔
661
    if (psFilterNode->psLeftNode && psFilterNode->psRightNode) {
317✔
662
      if (FLTIsBinaryComparisonFilterType(psFilterNode->pszValue))
317✔
663
        pszExpression = msStrdup(
270✔
664
            FLTGetBinaryComparisonCommonExpression(psFilterNode, lp).c_str());
540✔
665
      else if (strcasecmp(psFilterNode->pszValue, "PropertyIsLike") == 0)
47✔
666
        pszExpression = msStrdup(
30✔
667
            FLTGetIsLikeComparisonCommonExpression(psFilterNode).c_str());
60✔
668
      else if (strcasecmp(psFilterNode->pszValue, "PropertyIsBetween") == 0)
17✔
669
        pszExpression = msStrdup(
17✔
670
            FLTGetIsBetweenComparisonCommonExpresssion(psFilterNode, lp)
34✔
671
                .c_str());
672
    }
673
  } else if (psFilterNode->eType == FILTER_NODE_TYPE_LOGICAL) {
674
    pszExpression = msStrdup(
88✔
675
        FLTGetLogicalComparisonCommonExpression(psFilterNode, lp).c_str());
176✔
676
  } else if (psFilterNode->eType == FILTER_NODE_TYPE_SPATIAL) {
677
    pszExpression = msStrdup(
132✔
678
        FLTGetSpatialComparisonCommonExpression(psFilterNode, lp).c_str());
264✔
679
  } else if (psFilterNode->eType == FILTER_NODE_TYPE_FEATUREID) {
680
    pszExpression =
681
        msStrdup(FLTGetFeatureIdCommonExpression(psFilterNode, lp).c_str());
128✔
682
  } else if (psFilterNode->eType == FILTER_NODE_TYPE_TEMPORAL) {
683
    pszExpression = msStrdup(FLTGetTimeExpression(psFilterNode, lp).c_str());
38✔
684
  }
685

686
  return pszExpression;
687
}
688

689
int FLTApplyFilterToLayerCommonExpression(mapObj *map, int iLayerIndex,
×
690
                                          const char *pszExpression) {
691
  return FLTApplyFilterToLayerCommonExpressionWithRect(
×
692
      map, iLayerIndex, pszExpression, map->extent);
×
693
}
694

695
/* rect must be in map->projection */
696
int FLTApplyFilterToLayerCommonExpressionWithRect(mapObj *map, int iLayerIndex,
390✔
697
                                                  const char *pszExpression,
698
                                                  rectObj rect) {
699
  int retval;
700

701
  const int save_startindex = map->query.startindex;
390✔
702
  const int save_maxfeatures = map->query.maxfeatures;
390✔
703
  const int save_only_cache_result_count = map->query.only_cache_result_count;
390✔
704
  const int save_cache_shapes = map->query.cache_shapes;
390✔
705
  const int save_max_cached_shape_count = map->query.max_cached_shape_count;
390✔
706
  const int save_max_cached_shape_ram_amount =
390✔
707
      map->query.max_cached_shape_ram_amount;
708
  msInitQuery(&(map->query));
390✔
709
  map->query.startindex = save_startindex;
390✔
710
  map->query.maxfeatures = save_maxfeatures;
390✔
711
  map->query.only_cache_result_count = save_only_cache_result_count;
390✔
712
  map->query.cache_shapes = save_cache_shapes;
390✔
713
  map->query.max_cached_shape_count = save_max_cached_shape_count;
390✔
714
  map->query.max_cached_shape_ram_amount = save_max_cached_shape_ram_amount;
390✔
715

716
  map->query.mode = MS_QUERY_MULTIPLE;
390✔
717
  map->query.layer = iLayerIndex;
390✔
718

719
  map->query.rect = rect;
390✔
720

721
  if (pszExpression) {
390✔
722
    map->query.type = MS_QUERY_BY_FILTER;
365✔
723
    msInitExpression(&map->query.filter);
365✔
724
    map->query.filter.string = msStrdup(pszExpression);
365✔
725
    map->query.filter.type = MS_EXPRESSION; /* a logical expression */
365✔
726

727
    retval = msQueryByFilter(map);
365✔
728
  } else {
729
    map->query.type = MS_QUERY_BY_RECT;
25✔
730
    retval = msQueryByRect(map);
25✔
731
  }
732

733
  return retval;
390✔
734
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc