Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "filter" step of "vector pipeline"
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_vector_filter.h"
14 :
15 : #include "gdal_priv.h"
16 : #include "ogrsf_frmts.h"
17 : #include "ogr_p.h"
18 :
19 : #include <set>
20 :
21 : //! @cond Doxygen_Suppress
22 :
23 : #ifndef _
24 : #define _(x) (x)
25 : #endif
26 :
27 : /************************************************************************/
28 : /* GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm() */
29 : /************************************************************************/
30 :
31 127 : GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm(bool standaloneStep)
32 : : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
33 127 : standaloneStep)
34 : {
35 127 : auto &layerArg = AddActiveLayerArg(&m_activeLayer);
36 127 : AddBBOXArg(&m_bbox).SetMutualExclusionGroup("bbox-geometry");
37 254 : AddArg("bbox-crs", 0, _("CRS of bounding box filter"), &m_bboxCrs)
38 254 : .SetIsCRSArg()
39 127 : .AddHiddenAlias("bbox_srs");
40 254 : AddArg("geometry", 0, _("Filter geometry (WKT or GeoJSON)"), &m_geometry)
41 127 : .SetMutualExclusionGroup("bbox-geometry");
42 254 : AddArg("geometry-crs", 0, _("CRS of filter geometry"), &m_geometryCrs)
43 254 : .SetIsCRSArg()
44 127 : .AddHiddenAlias("geometry_srs");
45 : AddArg("where", 0,
46 : _("Attribute query in a restricted form of the queries used in the "
47 : "SQL WHERE statement"),
48 254 : &m_where)
49 127 : .SetReadFromFileAtSyntaxAllowed()
50 254 : .SetMetaVar("<WHERE>|@<filename>")
51 127 : .SetRemoveSQLCommentsEnabled()
52 : .SetAutoCompleteFunction(
53 11 : [this, &layerArg](const std::string ¤tValue)
54 138 : { return CompleteWhere(layerArg, currentValue); });
55 : AddArg("update-extent", 0,
56 : _("Update layer extent to take into account the filter"),
57 127 : &m_updateExtent);
58 127 : }
59 :
60 : /************************************************************************/
61 : /* GDALVectorFilterAlgorithmLayerChangeExtent */
62 : /************************************************************************/
63 :
64 : constexpr const char *const SQL_OPERATORS[] = {
65 : "=", "<>", "<", "<=", ">", ">=", "AND", "OR", "LIKE", "BETWEEN"};
66 :
67 7 : static bool IsSQLOperator(const char *pszStr)
68 : {
69 7 : return std::find_if(std::begin(SQL_OPERATORS), std::end(SQL_OPERATORS),
70 31 : [pszStr](const char *pszStr2)
71 31 : { return EQUAL(pszStr, pszStr2); }) !=
72 7 : std::end(SQL_OPERATORS);
73 : }
74 :
75 24 : static std::string GetSQLIdentifier(const std::string &name)
76 : {
77 24 : if (name.find_first_of("'\" ") != std::string::npos)
78 : {
79 0 : char *pszEscaped = CPLEscapeString(name.c_str(), -1, CPLES_SQLI);
80 0 : std::string ret = std::string("\"").append(pszEscaped).append("\"");
81 0 : CPLFree(pszEscaped);
82 0 : return ret;
83 : }
84 : else
85 : {
86 24 : return name;
87 : }
88 : }
89 :
90 30 : static std::string GetSQLStringLiteral(const char *val)
91 : {
92 30 : char *pszEscaped = CPLEscapeString(val, -1, CPLES_SQL);
93 60 : std::string ret = std::string("'").append(pszEscaped).append("'");
94 30 : CPLFree(pszEscaped);
95 30 : return ret;
96 : }
97 :
98 : std::vector<std::string>
99 11 : GDALVectorFilterAlgorithm::CompleteWhere(const GDALAlgorithmArg &layerArg,
100 : const std::string ¤tValue) const
101 : {
102 11 : std::vector<std::string> ret;
103 22 : if (currentValue.empty() || currentValue[0] != '"' ||
104 11 : m_inputDataset.empty())
105 0 : return ret;
106 :
107 : auto poDS = std::unique_ptr<GDALDataset>(
108 11 : GDALDataset::Open(m_inputDataset[0].GetName().c_str(),
109 22 : GDAL_OF_VECTOR | GDAL_OF_READONLY));
110 11 : if (!poDS)
111 0 : return ret;
112 :
113 : // Collect field names
114 22 : std::string layerName;
115 11 : if (layerArg.IsExplicitlySet())
116 2 : layerName = layerArg.Get<std::string>();
117 22 : std::map<std::string, std::vector<OGRLayer *>> fieldNames;
118 11 : if (layerName.empty())
119 : {
120 18 : for (auto *poLayer : poDS->GetLayers())
121 : {
122 37 : for (const auto *poFieldDefn : poLayer->GetLayerDefn()->GetFields())
123 : {
124 28 : fieldNames[poFieldDefn->GetNameRef()].push_back(poLayer);
125 : }
126 : }
127 : }
128 2 : else if (auto *poLayer = poDS->GetLayerByName(layerName.c_str()))
129 : {
130 5 : for (const auto *poFieldDefn : poLayer->GetLayerDefn()->GetFields())
131 : {
132 4 : fieldNames[poFieldDefn->GetNameRef()].push_back(poLayer);
133 : }
134 : }
135 11 : if (fieldNames.empty())
136 1 : return ret;
137 :
138 : const CPLStringList aosTokens(CSLTokenizeString2(
139 10 : currentValue.c_str() + 1, " ",
140 : CSLT_HONOURSTRINGS | CSLT_HONOURSINGLEQUOTES | CSLT_PRESERVEQUOTES |
141 20 : CSLT_PRESERVEESCAPES | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES));
142 :
143 20 : std::string prefix;
144 10 : const int nTokens = aosTokens.size();
145 10 : if (nTokens > 0 && cpl::contains(fieldNames, aosTokens[nTokens - 1]))
146 : {
147 1 : prefix = currentValue.substr(1);
148 1 : if (!prefix.empty() && prefix.back() != ' ')
149 0 : prefix += ' ';
150 11 : for (const char *op : SQL_OPERATORS)
151 : {
152 10 : if (nTokens > 1 && strcmp(op, "AND") == 0)
153 0 : break;
154 10 : ret.push_back(prefix + op);
155 : }
156 : }
157 : else
158 : {
159 : const bool bLastTokenIsSQLOperator =
160 9 : nTokens > 0 && IsSQLOperator(aosTokens[nTokens - 1]);
161 9 : const int nCompleteTokens =
162 9 : nTokens + (bLastTokenIsSQLOperator ? 0 : -1);
163 23 : for (int i = 0; i < nCompleteTokens; ++i)
164 : {
165 14 : if (!prefix.empty())
166 8 : prefix += ' ';
167 14 : prefix += aosTokens[i];
168 : }
169 9 : if (!prefix.empty())
170 6 : prefix += ' ';
171 :
172 9 : const char *pszLastFieldName = nullptr;
173 9 : OGRLayer *poLastFieldLayer = nullptr;
174 9 : OGRFieldType eLastFieldType = OFTString;
175 9 : if (nCompleteTokens >= 2)
176 : {
177 6 : const char *pszFieldName = aosTokens[nCompleteTokens - 2];
178 6 : const auto oIter = fieldNames.find(pszFieldName);
179 6 : if (oIter != fieldNames.end() && oIter->second.size() == 1)
180 : {
181 : const int nIdx =
182 5 : oIter->second[0]->GetLayerDefn()->GetFieldIndex(
183 5 : pszFieldName);
184 5 : if (nIdx >= 0)
185 : {
186 5 : pszLastFieldName = pszFieldName;
187 5 : poLastFieldLayer = oIter->second[0];
188 5 : eLastFieldType = poLastFieldLayer->GetLayerDefn()
189 5 : ->GetFieldDefn(nIdx)
190 5 : ->GetType();
191 : }
192 : }
193 : }
194 :
195 38 : for (const auto &[name, layers] : fieldNames)
196 : {
197 29 : bool canAdd = false;
198 29 : if (!pszLastFieldName)
199 : {
200 12 : canAdd = true;
201 : }
202 17 : else if (name != pszLastFieldName)
203 : {
204 12 : if (layers.size() > 1)
205 : {
206 0 : canAdd = true;
207 : }
208 12 : else if (layers[0]
209 12 : ->GetLayerDefn()
210 : ->GetFieldDefn(
211 12 : layers[0]->GetLayerDefn()->GetFieldIndex(
212 12 : name.c_str()))
213 12 : ->GetType() == eLastFieldType)
214 : {
215 2 : canAdd = true;
216 : }
217 : }
218 29 : if (canAdd)
219 : {
220 14 : ret.push_back(prefix + GetSQLIdentifier(name));
221 : }
222 : }
223 :
224 9 : if (pszLastFieldName && poLastFieldLayer)
225 : {
226 5 : auto poLayer = poLastFieldLayer;
227 5 : constexpr int NOT_TOO_LARGE = 1000;
228 5 : const auto nFeatureCount = poLayer->GetFeatureCount();
229 5 : if (nFeatureCount > 0 && nFeatureCount < NOT_TOO_LARGE)
230 : {
231 5 : constexpr int VALUES_COUNT = 10;
232 : const std::string osSQLField =
233 15 : GetSQLIdentifier(pszLastFieldName);
234 : const std::string osSQLLayer =
235 15 : GetSQLIdentifier(poLayer->GetName());
236 5 : if (eLastFieldType == OFTString)
237 : {
238 6 : CPLString osSQL;
239 3 : const char *pszDialect = nullptr;
240 3 : if (GetGDALDriverManager()->GetDriverByName("SQLite"))
241 : {
242 3 : pszDialect = "SQLite";
243 : // Find 10 most frequent strings
244 : osSQL.Printf("SELECT %s, COUNT(%s) cnt FROM %s GROUP "
245 : "BY %s ORDER BY cnt DESC, %s ASC LIMIT %d",
246 : osSQLField.c_str(), osSQLField.c_str(),
247 : osSQLLayer.c_str(), osSQLField.c_str(),
248 3 : osSQLField.c_str(), VALUES_COUNT + 1);
249 : }
250 : else
251 : {
252 : osSQL.Printf("SELECT DISTINCT %s FROM %s LIMIT %d",
253 : osSQLField.c_str(), osSQLLayer.c_str(),
254 0 : VALUES_COUNT + 1);
255 : }
256 : auto poSQLLayer =
257 3 : poDS->ExecuteSQL(osSQL.c_str(), nullptr, pszDialect);
258 3 : if (poSQLLayer)
259 : {
260 3 : int nCount = 0;
261 34 : for (auto &&poFeature : poSQLLayer)
262 : {
263 31 : if (nCount == VALUES_COUNT)
264 : {
265 1 : ret.push_back(prefix + "'...other values...");
266 1 : break;
267 : }
268 30 : ret.push_back(prefix +
269 60 : GetSQLStringLiteral(
270 : poFeature->GetFieldAsString(0)));
271 30 : nCount++;
272 : }
273 3 : poDS->ReleaseResultSet(poSQLLayer);
274 : }
275 : }
276 2 : else if (eLastFieldType == OFTInteger ||
277 0 : eLastFieldType == OFTInteger64 ||
278 : eLastFieldType == OFTReal)
279 : {
280 4 : CPLString osSQL;
281 2 : const char *pszDialect = nullptr;
282 3 : if (nFeatureCount > VALUES_COUNT + 2 &&
283 1 : GetGDALDriverManager()->GetDriverByName("SQLite"))
284 : {
285 1 : pszDialect = "SQLite";
286 : // Collect lowest and highest values
287 : osSQL.Printf("SELECT DISTINCT %s FROM ("
288 : "SELECT * FROM (SELECT DISTINCT %s FROM "
289 : "%s ORDER BY %s ASC LIMIT %d) UNION ALL "
290 : "SELECT * FROM (SELECT DISTINCT %s FROM "
291 : "%s ORDER BY %s DESC LIMIT %d)"
292 : ") x ORDER BY %s",
293 : osSQLField.c_str(), osSQLField.c_str(),
294 : osSQLLayer.c_str(), osSQLField.c_str(),
295 : VALUES_COUNT / 2, osSQLField.c_str(),
296 : osSQLLayer.c_str(), osSQLField.c_str(),
297 1 : VALUES_COUNT / 2 + 1, osSQLField.c_str());
298 : }
299 : else
300 : {
301 : osSQL.Printf("SELECT DISTINCT %s FROM %s LIMIT %d",
302 : osSQLField.c_str(), osSQLLayer.c_str(),
303 1 : VALUES_COUNT + 1);
304 : }
305 : auto poSQLLayer =
306 2 : poDS->ExecuteSQL(osSQL.c_str(), nullptr, pszDialect);
307 2 : if (poSQLLayer)
308 : {
309 2 : int nCount = 0;
310 23 : for (auto &&poFeature : poSQLLayer)
311 : {
312 21 : if (nCount == VALUES_COUNT)
313 : {
314 1 : if (pszDialect)
315 : {
316 1 : ret.erase(ret.begin() + VALUES_COUNT / 2 +
317 1 : 1);
318 1 : ret.push_back(
319 2 : prefix +
320 : poFeature->GetFieldAsString(0));
321 : }
322 1 : ret.push_back(prefix + "...other values...");
323 1 : break;
324 : }
325 20 : ret.push_back(prefix +
326 : poFeature->GetFieldAsString(0));
327 20 : nCount++;
328 : }
329 2 : poDS->ReleaseResultSet(poSQLLayer);
330 : }
331 : }
332 : }
333 : }
334 : }
335 10 : return ret;
336 : }
337 :
338 : /************************************************************************/
339 : /* GDALVectorFilterAlgorithmLayerChangeExtent */
340 : /************************************************************************/
341 :
342 : namespace
343 : {
344 : class GDALVectorFilterAlgorithmLayerChangeExtent final
345 : : public GDALVectorPipelinePassthroughLayer
346 : {
347 : public:
348 1 : GDALVectorFilterAlgorithmLayerChangeExtent(
349 : OGRLayer &oSrcLayer, const OGREnvelope3D &sLayerEnvelope)
350 1 : : GDALVectorPipelinePassthroughLayer(oSrcLayer),
351 1 : m_sLayerEnvelope(sLayerEnvelope)
352 : {
353 1 : }
354 :
355 1 : OGRErr IGetExtent(int /*iGeomField*/, OGREnvelope *psExtent,
356 : bool /* bForce */) override
357 : {
358 1 : if (m_sLayerEnvelope.IsInit())
359 : {
360 1 : *psExtent = m_sLayerEnvelope;
361 1 : return OGRERR_NONE;
362 : }
363 : else
364 : {
365 0 : return OGRERR_FAILURE;
366 : }
367 : }
368 :
369 1 : OGRErr IGetExtent3D(int /*iGeomField*/, OGREnvelope3D *psExtent,
370 : bool /* bForce */) override
371 : {
372 1 : if (m_sLayerEnvelope.IsInit())
373 : {
374 1 : *psExtent = m_sLayerEnvelope;
375 1 : return OGRERR_NONE;
376 : }
377 : else
378 : {
379 0 : return OGRERR_FAILURE;
380 : }
381 : }
382 :
383 0 : int TestCapability(const char *pszCap) const override
384 : {
385 0 : if (EQUAL(pszCap, OLCFastGetExtent))
386 0 : return true;
387 0 : return m_srcLayer.TestCapability(pszCap);
388 : }
389 :
390 : private:
391 : const OGREnvelope3D m_sLayerEnvelope;
392 : };
393 :
394 : } // namespace
395 :
396 : /************************************************************************/
397 : /* GDALVectorFilterAlgorithm::RunStep() */
398 : /************************************************************************/
399 :
400 30 : bool GDALVectorFilterAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
401 : {
402 30 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
403 30 : CPLAssert(poSrcDS);
404 :
405 30 : CPLAssert(m_outputDataset.GetName().empty());
406 30 : CPLAssert(!m_outputDataset.GetDatasetRef());
407 :
408 30 : const int nLayerCount = poSrcDS->GetLayerCount();
409 :
410 60 : OGRSpatialReference oBBOX_SRS;
411 30 : if (!m_bboxCrs.empty())
412 : {
413 2 : oBBOX_SRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
414 : // Already validated
415 2 : CPL_IGNORE_RET_VAL(oBBOX_SRS.SetFromUserInput(m_bboxCrs.c_str()));
416 : }
417 :
418 30 : bool ret = true;
419 30 : if (m_bbox.size() == 4)
420 : {
421 6 : const double xmin = m_bbox[0];
422 6 : const double ymin = m_bbox[1];
423 6 : const double xmax = m_bbox[2];
424 6 : const double ymax = m_bbox[3];
425 11 : for (int i = 0; i < nLayerCount; ++i)
426 : {
427 6 : auto poSrcLayer = poSrcDS->GetLayer(i);
428 6 : ret = ret && (poSrcLayer != nullptr);
429 6 : if (poSrcLayer && (m_activeLayer.empty() ||
430 0 : m_activeLayer == poSrcLayer->GetDescription()))
431 : {
432 6 : const auto poLayerSRS = poSrcLayer->GetSpatialRef();
433 6 : if (poLayerSRS && !oBBOX_SRS.IsEmpty())
434 : {
435 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
436 : OGRCreateCoordinateTransformation(&oBBOX_SRS,
437 2 : poLayerSRS));
438 2 : if (!poCT)
439 0 : return false;
440 : double xMinLayerSRS;
441 : double yMinLayerSRS;
442 : double xMaxLayerSRS;
443 : double yMaxLayerSRS;
444 4 : if (!poCT->TransformBounds(
445 : xmin, ymin, xmax, ymax, &xMinLayerSRS,
446 2 : &yMinLayerSRS, &xMaxLayerSRS, &yMaxLayerSRS, 21))
447 : {
448 1 : ReportError(CE_Failure, CPLE_AppDefined,
449 : "Bounding box reprojection failed");
450 1 : return false;
451 : }
452 1 : poSrcLayer->SetSpatialFilterRect(
453 : xMinLayerSRS, yMinLayerSRS, xMaxLayerSRS, yMaxLayerSRS);
454 : }
455 : else
456 : {
457 4 : poSrcLayer->SetSpatialFilterRect(xmin, ymin, xmax, ymax);
458 : }
459 : }
460 : }
461 : }
462 24 : else if (!m_geometry.empty())
463 : {
464 0 : std::unique_ptr<OGRGeometry> poGeom;
465 : {
466 8 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
467 4 : auto [poGeomTmp, eErr] =
468 8 : OGRGeometryFactory::createFromWkt(m_geometry.c_str());
469 4 : if (eErr == OGRERR_NONE)
470 : {
471 2 : poGeom = std::move(poGeomTmp);
472 : }
473 : else
474 : {
475 2 : poGeom.reset(
476 : OGRGeometryFactory::createFromGeoJson(m_geometry.c_str()));
477 2 : if (poGeom && poGeom->getSpatialReference() == nullptr)
478 : {
479 : auto poSRS =
480 0 : OGRSpatialReferenceRefCountedPtr::makeInstance();
481 0 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
482 0 : CPL_IGNORE_RET_VAL(poSRS->SetFromUserInput("WGS84"));
483 0 : poGeom->assignSpatialReference(poSRS.get());
484 : }
485 : }
486 : }
487 4 : if (!poGeom)
488 : {
489 1 : CPLError(
490 : CE_Failure, CPLE_IllegalArg,
491 : "Filter geometry is neither a valid WKT or GeoJSON geometry");
492 1 : return false;
493 : }
494 :
495 3 : if (!m_geometryCrs.empty())
496 : {
497 2 : auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
498 1 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
499 : // Validity of CRS already checked by GDALAlgorithm
500 1 : CPL_IGNORE_RET_VAL(poSRS->SetFromUserInput(m_geometryCrs.c_str()));
501 1 : poGeom->assignSpatialReference(poSRS.get());
502 : }
503 :
504 6 : for (int i = 0; i < nLayerCount; ++i)
505 : {
506 3 : auto poSrcLayer = poSrcDS->GetLayer(i);
507 3 : ret = ret && (poSrcLayer != nullptr);
508 3 : if (poSrcLayer && (m_activeLayer.empty() ||
509 0 : m_activeLayer == poSrcLayer->GetDescription()))
510 : {
511 3 : const auto poLayerSRS = poSrcLayer->GetSpatialRef();
512 5 : if (poLayerSRS && poGeom->getSpatialReference() &&
513 2 : !poLayerSRS->IsSame(poGeom->getSpatialReference()))
514 : {
515 2 : std::unique_ptr<OGRGeometry> poGeomClone(poGeom->clone());
516 2 : if (poGeomClone->transformTo(poLayerSRS) == OGRERR_FAILURE)
517 : {
518 0 : ReportError(CE_Failure, CPLE_AppDefined,
519 : "Geometry reprojection failed");
520 0 : return false;
521 : }
522 2 : poSrcLayer->SetSpatialFilter(poGeomClone.get());
523 : }
524 : else
525 : {
526 1 : poSrcLayer->SetSpatialFilter(poGeom.get());
527 : }
528 : }
529 : }
530 : }
531 :
532 28 : if (ret && !m_where.empty())
533 : {
534 40 : for (int i = 0; i < nLayerCount; ++i)
535 : {
536 22 : auto poSrcLayer = poSrcDS->GetLayer(i);
537 22 : ret = ret && (poSrcLayer != nullptr);
538 28 : if (ret && (m_activeLayer.empty() ||
539 6 : m_activeLayer == poSrcLayer->GetDescription()))
540 : {
541 19 : ret = poSrcLayer->SetAttributeFilter(m_where.c_str()) ==
542 : OGRERR_NONE;
543 : }
544 : }
545 : }
546 :
547 28 : if (ret)
548 : {
549 : auto outDS =
550 27 : std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
551 :
552 27 : int64_t nTotalFeatures = 0;
553 27 : if (m_updateExtent && ctxt.m_pfnProgress)
554 : {
555 0 : for (int i = 0; ret && i < nLayerCount; ++i)
556 : {
557 0 : auto poSrcLayer = poSrcDS->GetLayer(i);
558 0 : ret = (poSrcLayer != nullptr);
559 0 : if (ret)
560 : {
561 0 : if (m_activeLayer.empty() ||
562 0 : m_activeLayer == poSrcLayer->GetDescription())
563 : {
564 0 : if (poSrcLayer->TestCapability(OLCFastFeatureCount))
565 : {
566 0 : const auto nFC = poSrcLayer->GetFeatureCount(false);
567 0 : if (nFC < 0)
568 : {
569 0 : nTotalFeatures = 0;
570 0 : break;
571 : }
572 0 : nTotalFeatures += nFC;
573 : }
574 : }
575 : }
576 : }
577 : }
578 :
579 27 : int64_t nFeatureCounter = 0;
580 58 : for (int i = 0; ret && i < nLayerCount; ++i)
581 : {
582 31 : auto poSrcLayer = poSrcDS->GetLayer(i);
583 31 : ret = (poSrcLayer != nullptr);
584 31 : if (ret)
585 : {
586 35 : if (m_updateExtent &&
587 4 : (m_activeLayer.empty() ||
588 2 : m_activeLayer == poSrcLayer->GetDescription()))
589 : {
590 1 : OGREnvelope3D sLayerEnvelope, sFeatureEnvelope;
591 2 : for (auto &&poFeature : poSrcLayer)
592 : {
593 1 : const auto poGeom = poFeature->GetGeometryRef();
594 1 : if (poGeom && !poGeom->IsEmpty())
595 : {
596 1 : poGeom->getEnvelope(&sFeatureEnvelope);
597 1 : sLayerEnvelope.Merge(sFeatureEnvelope);
598 : }
599 :
600 1 : ++nFeatureCounter;
601 1 : if (nTotalFeatures > 0 && ctxt.m_pfnProgress &&
602 0 : !ctxt.m_pfnProgress(
603 0 : static_cast<double>(nFeatureCounter) /
604 0 : static_cast<double>(nTotalFeatures),
605 : "", ctxt.m_pProgressData))
606 : {
607 0 : ReportError(CE_Failure, CPLE_UserInterrupt,
608 : "Interrupted by user");
609 0 : return false;
610 : }
611 : }
612 2 : outDS->AddLayer(
613 : *poSrcLayer,
614 : std::make_unique<
615 2 : GDALVectorFilterAlgorithmLayerChangeExtent>(
616 : *poSrcLayer, sLayerEnvelope));
617 : }
618 : else
619 : {
620 60 : outDS->AddLayer(
621 : *poSrcLayer,
622 60 : std::make_unique<GDALVectorPipelinePassthroughLayer>(
623 : *poSrcLayer));
624 : }
625 : }
626 : }
627 :
628 27 : if (ret)
629 27 : m_outputDataset.Set(std::move(outDS));
630 : }
631 :
632 28 : return ret;
633 : }
634 :
635 : GDALVectorFilterAlgorithmStandalone::~GDALVectorFilterAlgorithmStandalone() =
636 : default;
637 :
638 : //! @endcond
|