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