Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "gdal vector combine" subcommand
5 : * Author: Daniel Baston
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025-2026, ISciences LLC
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_vector_combine.h"
14 :
15 : #include "cpl_enumerate.h"
16 : #include "cpl_error.h"
17 : #include "gdal_priv.h"
18 : #include "gdalalg_vector_geom.h"
19 : #include "ogr_geometry.h"
20 :
21 : #include <algorithm>
22 : #include <cinttypes>
23 : #include <optional>
24 :
25 : #ifndef _
26 : #define _(x) (x)
27 : #endif
28 :
29 : //! @cond Doxygen_Suppress
30 :
31 99 : GDALVectorCombineAlgorithm::GDALVectorCombineAlgorithm(bool standaloneStep)
32 : : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
33 99 : standaloneStep)
34 : {
35 : AddArg("group-by", 0,
36 198 : _("Names of field(s) by which inputs should be grouped"), &m_groupBy)
37 99 : .SetDuplicateValuesAllowed(false);
38 :
39 : AddArg("keep-nested", 0,
40 : _("Avoid combining the components of multipart geometries"),
41 99 : &m_keepNested);
42 :
43 : AddArg("add-extra-fields", 0,
44 : _("Whether to add extra fields, depending on if they have identical "
45 : "values within each group"),
46 198 : &m_addExtraFields)
47 99 : .SetChoices(NO, SOMETIMES_IDENTICAL, ALWAYS_IDENTICAL)
48 99 : .SetDefault(m_addExtraFields)
49 : .AddValidationAction(
50 4 : [this]()
51 : {
52 : // We check the SQLITE driver availability, because we need to
53 : // issue a SQL request using the SQLITE dialect, but that works
54 : // on any source dataset.
55 8 : if (m_addExtraFields != NO &&
56 4 : GetGDALDriverManager()->GetDriverByName("SQLITE") ==
57 : nullptr)
58 : {
59 0 : ReportError(CE_Failure, CPLE_NotSupported,
60 : "The SQLITE driver must be available for "
61 : "add-extra-fields=%s",
62 : m_addExtraFields.c_str());
63 0 : return false;
64 : }
65 4 : return true;
66 99 : });
67 99 : }
68 :
69 : namespace
70 : {
71 : class GDALVectorCombineOutputLayer final
72 : : public GDALVectorNonStreamingAlgorithmLayer
73 : {
74 : /** Identify which fields have, at least for one group, the same
75 : * value within the rows of the group, and add them to the destination
76 : * feature definition, after the group-by fields.
77 : */
78 2 : void IdentifySrcFieldsThatCanBeCopied(GDALDataset &srcDS,
79 : const std::string &addExtraFields)
80 : {
81 2 : const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
82 2 : if (srcDefn->GetFieldCount() > static_cast<int>(m_groupBy.size()))
83 : {
84 4 : std::vector<std::pair<std::string, int>> extraFieldCandidates;
85 :
86 2 : const auto itSrcFields = srcDefn->GetFields();
87 20 : for (const auto [iSrcField, srcFieldDefn] :
88 22 : cpl::enumerate(itSrcFields))
89 : {
90 10 : const char *fieldName = srcFieldDefn->GetNameRef();
91 10 : if (std::find(m_groupBy.begin(), m_groupBy.end(), fieldName) ==
92 20 : m_groupBy.end())
93 : {
94 : extraFieldCandidates.emplace_back(
95 8 : fieldName, static_cast<int>(iSrcField));
96 : }
97 : }
98 :
99 4 : std::string sql("SELECT ");
100 2 : bool addComma = false;
101 10 : for (const auto &[fieldName, _] : extraFieldCandidates)
102 : {
103 8 : if (addComma)
104 6 : sql += ", ";
105 8 : addComma = true;
106 8 : if (addExtraFields ==
107 : GDALVectorCombineAlgorithm::ALWAYS_IDENTICAL)
108 4 : sql += "MIN(";
109 : else
110 4 : sql += "MAX(";
111 8 : sql += CPLQuotedSQLIdentifier(fieldName.c_str());
112 8 : sql += ')';
113 : }
114 2 : sql += " FROM (SELECT ";
115 2 : addComma = false;
116 10 : for (const auto &[fieldName, _] : extraFieldCandidates)
117 : {
118 8 : if (addComma)
119 6 : sql += ", ";
120 8 : addComma = true;
121 8 : sql += "(COUNT(DISTINCT COALESCE(";
122 8 : sql += CPLQuotedSQLIdentifier(fieldName.c_str());
123 8 : sql += ", '__NULL__')) == 1) AS ";
124 8 : sql += CPLQuotedSQLIdentifier(fieldName.c_str());
125 : }
126 2 : sql += " FROM ";
127 2 : sql += CPLQuotedSQLIdentifier(GetLayerDefn()->GetName());
128 2 : if (!m_groupBy.empty())
129 : {
130 2 : sql += " GROUP BY ";
131 2 : addComma = false;
132 4 : for (const auto &fieldName : m_groupBy)
133 : {
134 2 : if (addComma)
135 0 : sql += ", ";
136 2 : addComma = true;
137 2 : sql += CPLQuotedSQLIdentifier(fieldName.c_str());
138 : }
139 : }
140 2 : sql += ") dummy_table_name";
141 :
142 2 : auto poSQLyr = srcDS.ExecuteSQL(sql.c_str(), nullptr, "SQLite");
143 2 : if (poSQLyr)
144 : {
145 : auto poResultFeature =
146 4 : std::unique_ptr<OGRFeature>(poSQLyr->GetNextFeature());
147 2 : if (poResultFeature)
148 : {
149 2 : CPLAssert(poResultFeature->GetFieldCount() ==
150 : static_cast<int>(extraFieldCandidates.size()));
151 16 : for (const auto &[iSqlCol, srcFieldInfo] :
152 18 : cpl::enumerate(extraFieldCandidates))
153 : {
154 8 : const int iSrcField = srcFieldInfo.second;
155 8 : if (poResultFeature->GetFieldAsInteger(
156 8 : static_cast<int>(iSqlCol)) == 1)
157 : {
158 10 : m_defn->AddFieldDefn(
159 5 : srcDefn->GetFieldDefn(iSrcField));
160 5 : m_srcExtraFieldIndices.push_back(iSrcField);
161 : }
162 : else
163 : {
164 3 : CPLDebugOnly(
165 : "gdalalg_vector_combine",
166 : "Field %s has the same values within a group",
167 : srcFieldInfo.first.c_str());
168 : }
169 : }
170 : }
171 2 : srcDS.ReleaseResultSet(poSQLyr);
172 : }
173 : }
174 2 : }
175 :
176 : public:
177 21 : explicit GDALVectorCombineOutputLayer(
178 : GDALDataset &srcDS, OGRLayer &srcLayer, int geomFieldIndex,
179 : const std::vector<std::string> &groupBy, bool keepNested,
180 : const std::string &addExtraFields)
181 21 : : GDALVectorNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
182 : m_groupBy(groupBy), m_defn(OGRFeatureDefnRefCountedPtr::makeInstance(
183 21 : srcLayer.GetLayerDefn()->GetName())),
184 42 : m_keepNested(keepNested)
185 : {
186 21 : const OGRFeatureDefn *srcDefn = m_srcLayer.GetLayerDefn();
187 :
188 : // Copy field definitions for attribute fields used in
189 : // --group-by. All other attributes are discarded.
190 29 : for (const auto &fieldName : m_groupBy)
191 : {
192 : // RunStep already checked that the field exists
193 8 : const auto iField = srcDefn->GetFieldIndex(fieldName.c_str());
194 8 : CPLAssert(iField >= 0);
195 :
196 8 : m_srcGroupByFieldIndices.push_back(iField);
197 8 : m_defn->AddFieldDefn(srcDefn->GetFieldDefn(iField));
198 : }
199 :
200 21 : if (addExtraFields != GDALVectorCombineAlgorithm::NO)
201 2 : IdentifySrcFieldsThatCanBeCopied(srcDS, addExtraFields);
202 :
203 : // Create a new geometry field corresponding to each input geometry
204 : // field. An appropriate type is worked out below.
205 21 : m_defn->SetGeomType(wkbNone); // Remove default geometry field
206 43 : for (const OGRGeomFieldDefn *srcGeomDefn : srcDefn->GetGeomFields())
207 : {
208 22 : const auto eSrcGeomType = srcGeomDefn->GetType();
209 22 : const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eSrcGeomType));
210 22 : const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eSrcGeomType));
211 :
212 : OGRwkbGeometryType eDstGeomType =
213 22 : OGR_GT_SetModifier(wkbGeometryCollection, bHasZ, bHasM);
214 :
215 : // If the layer claims to have single-part geometries, choose a more
216 : // specific output type like "MultiPoint" rather than "GeometryCollection"
217 40 : if (wkbFlatten(eSrcGeomType) != wkbUnknown &&
218 18 : !OGR_GT_IsSubClassOf(wkbFlatten(eSrcGeomType),
219 : wkbGeometryCollection))
220 : {
221 18 : eDstGeomType = OGR_GT_GetCollection(eSrcGeomType);
222 : }
223 :
224 : auto dstGeomDefn = std::make_unique<OGRGeomFieldDefn>(
225 44 : srcGeomDefn->GetNameRef(), eDstGeomType);
226 22 : dstGeomDefn->SetSpatialRef(srcGeomDefn->GetSpatialRef());
227 22 : m_defn->AddGeomFieldDefn(std::move(dstGeomDefn));
228 : }
229 21 : }
230 :
231 19 : GIntBig GetFeatureCount(int bForce) override
232 : {
233 19 : if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
234 : {
235 13 : return static_cast<GIntBig>(m_features.size());
236 : }
237 :
238 6 : return OGRLayer::GetFeatureCount(bForce);
239 : }
240 :
241 223 : const OGRFeatureDefn *GetLayerDefn() const override
242 : {
243 223 : return m_defn.get();
244 : }
245 :
246 4 : OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
247 : bool bForce) override
248 : {
249 4 : return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
250 : }
251 :
252 0 : OGRErr IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent,
253 : bool bForce) override
254 : {
255 0 : return m_srcLayer.GetExtent3D(iGeomField, psExtent, bForce);
256 : }
257 :
258 166 : std::unique_ptr<OGRFeature> GetNextProcessedFeature() override
259 : {
260 166 : if (!m_itFeature)
261 : {
262 61 : m_itFeature = m_features.begin();
263 : }
264 :
265 166 : if (m_itFeature.value() == m_features.end())
266 : {
267 37 : return nullptr;
268 : }
269 :
270 : std::unique_ptr<OGRFeature> feature(
271 258 : m_itFeature.value()->second->Clone());
272 129 : feature->SetFID(m_nProcessedFeaturesRead++);
273 129 : ++m_itFeature.value();
274 129 : return feature;
275 : }
276 :
277 21 : bool Process(GDALProgressFunc pfnProgress, void *pProgressData) override
278 : {
279 21 : const int nGeomFields = m_srcLayer.GetLayerDefn()->GetGeomFieldCount();
280 :
281 : const GIntBig nLayerFeatures =
282 21 : m_srcLayer.TestCapability(OLCFastFeatureCount)
283 21 : ? m_srcLayer.GetFeatureCount(false)
284 21 : : -1;
285 : const double dfInvLayerFeatures =
286 21 : 1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
287 :
288 21 : GIntBig nFeaturesRead = 0;
289 :
290 : struct PairSourceFeatureUniqueValues
291 : {
292 : std::unique_ptr<OGRFeature> poSrcFeature{};
293 : std::vector<std::optional<std::string>> srcUniqueValues{};
294 : };
295 :
296 : std::map<OGRFeature *, PairSourceFeatureUniqueValues>
297 42 : mapDstFeatureToOtherFields;
298 :
299 42 : std::vector<std::string> fieldValues(m_srcGroupByFieldIndices.size());
300 : std::vector<std::string> extraFieldValues(
301 42 : m_srcExtraFieldIndices.size());
302 :
303 : std::vector<int> srcDstFieldMap(
304 42 : m_srcLayer.GetLayerDefn()->GetFieldCount(), -1);
305 16 : for (const auto [iDstField, iSrcField] :
306 29 : cpl::enumerate(m_srcGroupByFieldIndices))
307 : {
308 8 : srcDstFieldMap[iSrcField] = static_cast<int>(iDstField);
309 : }
310 :
311 108 : for (const auto &srcFeature : m_srcLayer)
312 : {
313 112 : for (const auto [iDstField, iSrcField] :
314 199 : cpl::enumerate(m_srcGroupByFieldIndices))
315 : {
316 56 : fieldValues[iDstField] =
317 56 : srcFeature->GetFieldAsString(iSrcField);
318 : }
319 :
320 40 : for (const auto [iExtraField, iSrcField] :
321 127 : cpl::enumerate(m_srcExtraFieldIndices))
322 : {
323 20 : extraFieldValues[iExtraField] =
324 20 : srcFeature->GetFieldAsString(iSrcField);
325 : }
326 :
327 : OGRFeature *dstFeature;
328 :
329 87 : if (auto it = m_features.find(fieldValues); it == m_features.end())
330 : {
331 35 : it = m_features
332 70 : .insert(std::pair(
333 : fieldValues,
334 105 : std::make_unique<OGRFeature>(m_defn.get())))
335 : .first;
336 35 : dstFeature = it->second.get();
337 :
338 35 : dstFeature->SetFrom(srcFeature.get(), srcDstFieldMap.data(),
339 : false);
340 :
341 71 : for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
342 : {
343 : OGRGeomFieldDefn *poGeomDefn =
344 36 : m_defn->GetGeomFieldDefn(iGeomField);
345 36 : const auto eGeomType = poGeomDefn->GetType();
346 :
347 : std::unique_ptr<OGRGeometry> poGeom(
348 36 : OGRGeometryFactory::createGeometry(eGeomType));
349 36 : poGeom->assignSpatialReference(poGeomDefn->GetSpatialRef());
350 :
351 36 : dstFeature->SetGeomField(iGeomField, std::move(poGeom));
352 : }
353 :
354 35 : if (!m_srcExtraFieldIndices.empty())
355 : {
356 8 : PairSourceFeatureUniqueValues pair;
357 4 : pair.poSrcFeature.reset(srcFeature->Clone());
358 14 : for (const std::string &s : extraFieldValues)
359 10 : pair.srcUniqueValues.push_back(s);
360 4 : mapDstFeatureToOtherFields[dstFeature] = std::move(pair);
361 : }
362 : }
363 : else
364 : {
365 52 : dstFeature = it->second.get();
366 :
367 : // Check that the extra field values for that source feature
368 : // are the same as for other source features of the same group.
369 : // If not the case, cancel the extra field value for that group.
370 52 : if (!m_srcExtraFieldIndices.empty())
371 : {
372 : auto iterOtherFields =
373 4 : mapDstFeatureToOtherFields.find(dstFeature);
374 4 : CPLAssert(iterOtherFields !=
375 : mapDstFeatureToOtherFields.end());
376 : auto &srcUniqueValues =
377 4 : iterOtherFields->second.srcUniqueValues;
378 4 : CPLAssert(srcUniqueValues.size() ==
379 : extraFieldValues.size());
380 20 : for (const auto &[i, sVal] :
381 24 : cpl::enumerate(extraFieldValues))
382 : {
383 20 : if (srcUniqueValues[i].has_value() &&
384 10 : *(srcUniqueValues[i]) != sVal)
385 : {
386 1 : srcUniqueValues[i].reset();
387 : }
388 : }
389 : }
390 : }
391 :
392 176 : for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
393 : {
394 : OGRGeomFieldDefn *poGeomFieldDefn =
395 89 : m_defn->GetGeomFieldDefn(iGeomField);
396 :
397 : std::unique_ptr<OGRGeometry> poSrcGeom(
398 89 : srcFeature->StealGeometry(iGeomField));
399 89 : if (poSrcGeom != nullptr && !poSrcGeom->IsEmpty())
400 : {
401 87 : const auto eSrcType = poSrcGeom->getGeometryType();
402 87 : const auto bSrcIsCollection = OGR_GT_IsSubClassOf(
403 : wkbFlatten(eSrcType), wkbGeometryCollection);
404 : const auto bDstIsUntypedCollection =
405 87 : wkbFlatten(poGeomFieldDefn->GetType()) ==
406 87 : wkbGeometryCollection;
407 :
408 : // Did this geometry unexpectedly have Z?
409 87 : if (OGR_GT_HasZ(eSrcType) !=
410 87 : OGR_GT_HasZ(poGeomFieldDefn->GetType()))
411 : {
412 4 : AddZ(iGeomField);
413 : }
414 :
415 : // Did this geometry unexpectedly have M?
416 87 : if (OGR_GT_HasM(eSrcType) !=
417 87 : OGR_GT_HasM(poGeomFieldDefn->GetType()))
418 : {
419 10 : AddM(iGeomField);
420 : }
421 :
422 : // Do we need to change the output from a typed collection
423 : // like MultiPolygon to a generic GeometryCollection?
424 87 : if (m_keepNested && bSrcIsCollection &&
425 3 : !bDstIsUntypedCollection)
426 : {
427 2 : SetTypeGeometryCollection(iGeomField);
428 : }
429 :
430 : OGRGeometryCollection *poDstGeom =
431 87 : cpl::down_cast<OGRGeometryCollection *>(
432 : dstFeature->GetGeomFieldRef(iGeomField));
433 :
434 87 : if (m_keepNested || !bSrcIsCollection)
435 : {
436 84 : if (poDstGeom->addGeometry(std::move(poSrcGeom)) !=
437 : OGRERR_NONE)
438 : {
439 0 : CPLError(
440 : CE_Failure, CPLE_AppDefined,
441 : "Failed to add geometry of type %s to output "
442 : "feature of type %s",
443 : OGRGeometryTypeToName(eSrcType),
444 : OGRGeometryTypeToName(
445 0 : poDstGeom->getGeometryType()));
446 0 : return false;
447 : }
448 : }
449 : else
450 : {
451 : std::unique_ptr<OGRGeometryCollection>
452 : poSrcGeomCollection(
453 3 : poSrcGeom.release()->toGeometryCollection());
454 6 : if (poDstGeom->addGeometryComponents(
455 6 : std::move(poSrcGeomCollection)) != OGRERR_NONE)
456 : {
457 0 : CPLError(CE_Failure, CPLE_AppDefined,
458 : "Failed to add components from geometry "
459 : "of type %s to output "
460 : "feature of type %s",
461 : OGRGeometryTypeToName(eSrcType),
462 : OGRGeometryTypeToName(
463 0 : poDstGeom->getGeometryType()));
464 0 : return false;
465 : }
466 : }
467 : }
468 : }
469 :
470 87 : if (pfnProgress && nLayerFeatures > 0 &&
471 0 : !pfnProgress(static_cast<double>(++nFeaturesRead) *
472 : dfInvLayerFeatures,
473 : "", pProgressData))
474 : {
475 0 : CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
476 0 : return false;
477 : }
478 : }
479 :
480 : // Copy extra fields from source features that have a same value
481 : // among each groups
482 8 : for (const auto &[poDstFeature, pairSourceFeatureUniqueValues] :
483 29 : mapDstFeatureToOtherFields)
484 : {
485 20 : for (const auto [iExtraField, iSrcField] :
486 24 : cpl::enumerate(m_srcExtraFieldIndices))
487 : {
488 : const int iDstField = static_cast<int>(
489 10 : m_srcGroupByFieldIndices.size() + iExtraField);
490 10 : if (pairSourceFeatureUniqueValues.srcUniqueValues[iExtraField])
491 : {
492 : const auto poRawField =
493 : pairSourceFeatureUniqueValues.poSrcFeature
494 9 : ->GetRawFieldRef(iSrcField);
495 9 : poDstFeature->SetField(iDstField, poRawField);
496 : }
497 : }
498 : }
499 :
500 21 : if (pfnProgress)
501 : {
502 0 : pfnProgress(1.0, "", pProgressData);
503 : }
504 :
505 21 : return true;
506 : }
507 :
508 39 : int TestCapability(const char *pszCap) const override
509 : {
510 39 : if (EQUAL(pszCap, OLCFastFeatureCount))
511 : {
512 0 : return true;
513 : }
514 :
515 39 : if (EQUAL(pszCap, OLCStringsAsUTF8) ||
516 26 : EQUAL(pszCap, OLCFastGetExtent) ||
517 24 : EQUAL(pszCap, OLCFastGetExtent3D) ||
518 24 : EQUAL(pszCap, OLCCurveGeometries) ||
519 20 : EQUAL(pszCap, OLCMeasuredGeometries) ||
520 16 : EQUAL(pszCap, OLCZGeometries))
521 : {
522 26 : return m_srcLayer.TestCapability(pszCap);
523 : }
524 :
525 13 : return false;
526 : }
527 :
528 99 : void ResetReading() override
529 : {
530 99 : m_itFeature.reset();
531 99 : m_nProcessedFeaturesRead = 0;
532 99 : }
533 :
534 : CPL_DISALLOW_COPY_ASSIGN(GDALVectorCombineOutputLayer)
535 :
536 : private:
537 10 : void AddM(int iGeomField)
538 : {
539 : OGRGeomFieldDefn *poGeomFieldDefn =
540 10 : m_defn->GetGeomFieldDefn(iGeomField);
541 20 : whileUnsealing(poGeomFieldDefn)
542 10 : ->SetType(OGR_GT_SetM(poGeomFieldDefn->GetType()));
543 :
544 20 : for (auto &[_, poFeature] : m_features)
545 : {
546 10 : poFeature->GetGeomFieldRef(iGeomField)->setMeasured(true);
547 : }
548 10 : }
549 :
550 4 : void AddZ(int iGeomField)
551 : {
552 : OGRGeomFieldDefn *poGeomFieldDefn =
553 4 : m_defn->GetGeomFieldDefn(iGeomField);
554 8 : whileUnsealing(poGeomFieldDefn)
555 4 : ->SetType(OGR_GT_SetZ(poGeomFieldDefn->GetType()));
556 :
557 8 : for (auto &[_, poFeature] : m_features)
558 : {
559 4 : poFeature->GetGeomFieldRef(iGeomField)->set3D(true);
560 : }
561 4 : }
562 :
563 2 : void SetTypeGeometryCollection(int iGeomField)
564 : {
565 : OGRGeomFieldDefn *poGeomFieldDefn =
566 2 : m_defn->GetGeomFieldDefn(iGeomField);
567 2 : const bool hasZ = CPL_TO_BOOL(OGR_GT_HasZ(poGeomFieldDefn->GetType()));
568 2 : const bool hasM = CPL_TO_BOOL(OGR_GT_HasM(poGeomFieldDefn->GetType()));
569 :
570 4 : whileUnsealing(poGeomFieldDefn)
571 2 : ->SetType(OGR_GT_SetModifier(wkbGeometryCollection, hasZ, hasM));
572 :
573 4 : for (auto &[_, poFeature] : m_features)
574 : {
575 : std::unique_ptr<OGRGeometry> poTmpGeom(
576 2 : poFeature->StealGeometry(iGeomField));
577 4 : poTmpGeom = OGRGeometryFactory::forceTo(std::move(poTmpGeom),
578 2 : poGeomFieldDefn->GetType());
579 2 : CPLAssert(poTmpGeom);
580 2 : poFeature->SetGeomField(iGeomField, std::move(poTmpGeom));
581 : }
582 2 : }
583 :
584 : const std::vector<std::string> m_groupBy{};
585 : std::vector<int> m_srcGroupByFieldIndices{};
586 : std::vector<int> m_srcExtraFieldIndices{};
587 : std::map<std::vector<std::string>, std::unique_ptr<OGRFeature>>
588 : m_features{};
589 : std::optional<decltype(m_features)::const_iterator> m_itFeature{};
590 : const OGRFeatureDefnRefCountedPtr m_defn;
591 : GIntBig m_nProcessedFeaturesRead = 0;
592 : const bool m_keepNested;
593 : };
594 : } // namespace
595 :
596 22 : bool GDALVectorCombineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
597 : {
598 22 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
599 : auto poDstDS =
600 44 : std::make_unique<GDALVectorNonStreamingAlgorithmDataset>(*poSrcDS);
601 :
602 44 : GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
603 :
604 43 : for (auto &&poSrcLayer : poSrcDS->GetLayers())
605 : {
606 22 : if (m_inputLayerNames.empty() ||
607 0 : std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
608 22 : poSrcLayer->GetDescription()) != m_inputLayerNames.end())
609 : {
610 22 : const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
611 22 : if (poSrcLayerDefn->GetGeomFieldCount() == 0)
612 : {
613 0 : if (m_inputLayerNames.empty())
614 0 : continue;
615 0 : ReportError(CE_Failure, CPLE_AppDefined,
616 : "Specified layer '%s' has no geometry field",
617 0 : poSrcLayer->GetDescription());
618 0 : return false;
619 : }
620 :
621 : // Check that all attributes exist
622 30 : for (const auto &fieldName : m_groupBy)
623 : {
624 : const int iSrcFieldIndex =
625 9 : poSrcLayerDefn->GetFieldIndex(fieldName.c_str());
626 9 : if (iSrcFieldIndex == -1)
627 : {
628 1 : ReportError(CE_Failure, CPLE_AppDefined,
629 : "Specified attribute field '%s' does not exist "
630 : "in layer '%s'",
631 : fieldName.c_str(),
632 1 : poSrcLayer->GetDescription());
633 1 : return false;
634 : }
635 : }
636 :
637 21 : progressHelper.AddProcessedLayer(*poSrcLayer);
638 : }
639 : }
640 :
641 42 : for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc,
642 84 : layerProgressData] : progressHelper)
643 : {
644 : auto poLayer = std::make_unique<GDALVectorCombineOutputLayer>(
645 0 : *poSrcDS, *poSrcLayer, -1, m_groupBy, m_keepNested,
646 21 : m_addExtraFields);
647 :
648 21 : if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc,
649 : layerProgressData.get()))
650 : {
651 0 : return false;
652 : }
653 : }
654 :
655 21 : m_outputDataset.Set(std::move(poDstDS));
656 :
657 21 : return true;
658 : }
659 :
660 : GDALVectorCombineAlgorithmStandalone::~GDALVectorCombineAlgorithmStandalone() =
661 : default;
662 :
663 : //! @endcond
|