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 109 : GDALVectorCombineAlgorithm::GDALVectorCombineAlgorithm(bool standaloneStep)
32 : : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
33 109 : standaloneStep)
34 : {
35 : auto &groupByArg =
36 : AddArg("group-by", 0,
37 : _("Names of field(s) by which inputs should be grouped"),
38 218 : &m_groupBy)
39 109 : .SetDuplicateValuesAllowed(false);
40 218 : SetAutoCompleteFunctionForFieldName(
41 109 : groupByArg, GetArg(GDAL_ARG_NAME_INPUT_LAYER),
42 : /* attributeFields = */ true,
43 109 : /* geometryFields = */ false, m_inputDataset);
44 :
45 : AddArg("keep-nested", 0,
46 : _("Avoid combining the components of multipart geometries"),
47 109 : &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 218 : &m_addExtraFields)
53 109 : .SetChoices(NO, SOMETIMES_IDENTICAL, ALWAYS_IDENTICAL)
54 109 : .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 109 : });
73 109 : }
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 25 : explicit GDALVectorCombineOutputLayer(
184 : GDALDataset &srcDS, OGRLayer &srcLayer, int geomFieldIndex,
185 : const std::vector<std::string> &groupBy, bool keepNested,
186 : const std::string &addExtraFields)
187 25 : : GDALVectorNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex),
188 : m_groupBy(groupBy), m_defn(OGRFeatureDefnRefCountedPtr::makeInstance(
189 25 : srcLayer.GetLayerDefn()->GetName())),
190 50 : m_keepNested(keepNested)
191 : {
192 25 : 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 33 : 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 25 : 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 25 : m_defn->SetGeomType(wkbNone); // Remove default geometry field
212 51 : for (const OGRGeomFieldDefn *srcGeomDefn : srcDefn->GetGeomFields())
213 : {
214 26 : const auto eSrcGeomType = srcGeomDefn->GetType();
215 26 : const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eSrcGeomType));
216 26 : const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eSrcGeomType));
217 :
218 : OGRwkbGeometryType eDstGeomType =
219 26 : 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 48 : if (wkbFlatten(eSrcGeomType) != wkbUnknown &&
224 22 : !OGR_GT_IsSubClassOf(wkbFlatten(eSrcGeomType),
225 : wkbGeometryCollection))
226 : {
227 20 : eDstGeomType = OGR_GT_GetCollection(eSrcGeomType);
228 : }
229 :
230 : auto dstGeomDefn = std::make_unique<OGRGeomFieldDefn>(
231 52 : srcGeomDefn->GetNameRef(), eDstGeomType);
232 26 : dstGeomDefn->SetSpatialRef(srcGeomDefn->GetSpatialRef());
233 26 : m_defn->AddGeomFieldDefn(std::move(dstGeomDefn));
234 : }
235 25 : }
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 170 : std::unique_ptr<OGRFeature> GetNextProcessedFeature() override
265 : {
266 170 : if (!m_itFeature)
267 : {
268 65 : m_itFeature = m_features.begin();
269 : }
270 :
271 170 : if (m_itFeature.value() == m_features.end())
272 : {
273 37 : return nullptr;
274 : }
275 :
276 : std::unique_ptr<OGRFeature> feature(
277 266 : m_itFeature.value()->second->Clone());
278 133 : feature->SetFID(m_nProcessedFeaturesRead++);
279 133 : ++m_itFeature.value();
280 133 : return feature;
281 : }
282 :
283 25 : bool Process(GDALProgressFunc pfnProgress, void *pProgressData) override
284 : {
285 25 : const int nGeomFields = m_srcLayer.GetLayerDefn()->GetGeomFieldCount();
286 :
287 : const GIntBig nLayerFeatures =
288 25 : m_srcLayer.TestCapability(OLCFastFeatureCount)
289 25 : ? m_srcLayer.GetFeatureCount(false)
290 25 : : -1;
291 : const double dfInvLayerFeatures =
292 25 : 1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
293 :
294 25 : 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 50 : mapDstFeatureToOtherFields;
304 :
305 50 : std::vector<std::string> fieldValues(m_srcGroupByFieldIndices.size());
306 : std::vector<std::string> extraFieldValues(
307 50 : m_srcExtraFieldIndices.size());
308 :
309 : std::vector<int> srcDstFieldMap(
310 50 : m_srcLayer.GetLayerDefn()->GetFieldCount(), -1);
311 16 : for (const auto [iDstField, iSrcField] :
312 33 : cpl::enumerate(m_srcGroupByFieldIndices))
313 : {
314 8 : srcDstFieldMap[iSrcField] = static_cast<int>(iDstField);
315 : }
316 :
317 118 : for (const auto &srcFeature : m_srcLayer)
318 : {
319 112 : for (const auto [iDstField, iSrcField] :
320 205 : cpl::enumerate(m_srcGroupByFieldIndices))
321 : {
322 56 : fieldValues[iDstField] =
323 56 : srcFeature->GetFieldAsString(iSrcField);
324 : }
325 :
326 40 : for (const auto [iExtraField, iSrcField] :
327 133 : cpl::enumerate(m_srcExtraFieldIndices))
328 : {
329 20 : extraFieldValues[iExtraField] =
330 20 : srcFeature->GetFieldAsString(iSrcField);
331 : }
332 :
333 : OGRFeature *dstFeature;
334 :
335 93 : if (auto it = m_features.find(fieldValues); it == m_features.end())
336 : {
337 39 : it = m_features
338 78 : .insert(std::pair(
339 : fieldValues,
340 117 : std::make_unique<OGRFeature>(m_defn.get())))
341 : .first;
342 39 : dstFeature = it->second.get();
343 :
344 39 : dstFeature->SetFrom(srcFeature.get(), srcDstFieldMap.data(),
345 : false);
346 :
347 79 : for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
348 : {
349 : OGRGeomFieldDefn *poGeomDefn =
350 40 : m_defn->GetGeomFieldDefn(iGeomField);
351 40 : const auto eGeomType = poGeomDefn->GetType();
352 :
353 : std::unique_ptr<OGRGeometry> poGeom(
354 40 : OGRGeometryFactory::createGeometry(eGeomType));
355 40 : poGeom->assignSpatialReference(poGeomDefn->GetSpatialRef());
356 :
357 40 : dstFeature->SetGeomField(iGeomField, std::move(poGeom));
358 : }
359 :
360 39 : 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 54 : 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 54 : 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 188 : for (int iGeomField = 0; iGeomField < nGeomFields; iGeomField++)
399 : {
400 : OGRGeomFieldDefn *poGeomFieldDefn =
401 95 : m_defn->GetGeomFieldDefn(iGeomField);
402 :
403 : std::unique_ptr<OGRGeometry> poSrcGeom(
404 95 : srcFeature->StealGeometry(iGeomField));
405 95 : if (poSrcGeom != nullptr && !poSrcGeom->IsEmpty())
406 : {
407 93 : const auto eSrcType = poSrcGeom->getGeometryType();
408 93 : const auto bSrcIsCollection = OGR_GT_IsSubClassOf(
409 : wkbFlatten(eSrcType), wkbGeometryCollection);
410 : const auto bDstIsUntypedCollection =
411 93 : wkbFlatten(poGeomFieldDefn->GetType()) ==
412 93 : wkbGeometryCollection;
413 :
414 : // Did this geometry unexpectedly have Z?
415 93 : if (OGR_GT_HasZ(eSrcType) !=
416 93 : OGR_GT_HasZ(poGeomFieldDefn->GetType()))
417 : {
418 4 : AddZ(iGeomField);
419 : }
420 :
421 : // Did this geometry unexpectedly have M?
422 93 : if (OGR_GT_HasM(eSrcType) !=
423 93 : 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 93 : if (m_keepNested && bSrcIsCollection &&
431 3 : !bDstIsUntypedCollection)
432 : {
433 2 : SetTypeGeometryCollection(iGeomField);
434 : }
435 :
436 : OGRGeometryCollection *poDstGeom =
437 93 : cpl::down_cast<OGRGeometryCollection *>(
438 : dstFeature->GetGeomFieldRef(iGeomField));
439 :
440 93 : if (m_keepNested || !bSrcIsCollection)
441 : {
442 : // A Triangle is not an acceptable MultiPolygon
443 : // member. addGeometryDirectly() leaves us its
444 : // ownership then, and we convert it.
445 : OGRErr eErr =
446 70 : poDstGeom->addGeometryDirectly(poSrcGeom.get());
447 70 : if (eErr == OGRERR_NONE)
448 : {
449 66 : CPL_IGNORE_RET_VAL(poSrcGeom.release());
450 : }
451 : else
452 : {
453 4 : eErr = poDstGeom->addGeometry(
454 12 : OGRGeometryFactory::forceTo(
455 4 : std::move(poSrcGeom),
456 : OGR_GT_GetSingle(
457 4 : poDstGeom->getGeometryType())));
458 : }
459 70 : if (eErr != OGRERR_NONE)
460 : {
461 0 : CPLError(
462 : CE_Failure, CPLE_AppDefined,
463 : "Failed to add geometry of type %s to output "
464 : "feature of type %s",
465 : OGRGeometryTypeToName(eSrcType),
466 : OGRGeometryTypeToName(
467 0 : poDstGeom->getGeometryType()));
468 0 : return false;
469 70 : }
470 : }
471 : else
472 : {
473 : std::unique_ptr<OGRGeometryCollection>
474 : poSrcGeomCollection(
475 23 : poSrcGeom.release()->toGeometryCollection());
476 46 : if (poDstGeom->addGeometryComponents(
477 46 : std::move(poSrcGeomCollection)) != OGRERR_NONE)
478 : {
479 0 : CPLError(CE_Failure, CPLE_AppDefined,
480 : "Failed to add components from geometry "
481 : "of type %s to output "
482 : "feature of type %s",
483 : OGRGeometryTypeToName(eSrcType),
484 : OGRGeometryTypeToName(
485 0 : poDstGeom->getGeometryType()));
486 0 : return false;
487 : }
488 : }
489 : }
490 : }
491 :
492 93 : if (pfnProgress && nLayerFeatures > 0 &&
493 0 : !pfnProgress(static_cast<double>(++nFeaturesRead) *
494 : dfInvLayerFeatures,
495 : "", pProgressData))
496 : {
497 0 : CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
498 0 : return false;
499 : }
500 : }
501 :
502 : // Copy extra fields from source features that have a same value
503 : // among each groups
504 8 : for (const auto &[poDstFeature, pairSourceFeatureUniqueValues] :
505 33 : mapDstFeatureToOtherFields)
506 : {
507 20 : for (const auto [iExtraField, iSrcField] :
508 24 : cpl::enumerate(m_srcExtraFieldIndices))
509 : {
510 : const int iDstField = static_cast<int>(
511 10 : m_srcGroupByFieldIndices.size() + iExtraField);
512 10 : if (pairSourceFeatureUniqueValues.srcUniqueValues[iExtraField])
513 : {
514 : const auto poRawField =
515 : pairSourceFeatureUniqueValues.poSrcFeature
516 9 : ->GetRawFieldRef(iSrcField);
517 9 : poDstFeature->SetField(iDstField, poRawField);
518 : }
519 : }
520 : }
521 :
522 25 : if (pfnProgress)
523 : {
524 0 : pfnProgress(1.0, "", pProgressData);
525 : }
526 :
527 25 : return true;
528 : }
529 :
530 39 : bool TestCapability(const char *pszCap) const override
531 : {
532 39 : if (EQUAL(pszCap, OLCFastFeatureCount))
533 : {
534 0 : return true;
535 : }
536 :
537 39 : if (EQUAL(pszCap, OLCStringsAsUTF8) ||
538 26 : EQUAL(pszCap, OLCFastGetExtent) ||
539 24 : EQUAL(pszCap, OLCFastGetExtent3D) ||
540 24 : EQUAL(pszCap, OLCCurveGeometries) ||
541 20 : EQUAL(pszCap, OLCMeasuredGeometries) ||
542 16 : EQUAL(pszCap, OLCZGeometries))
543 : {
544 26 : return m_srcLayer.TestCapability(pszCap);
545 : }
546 :
547 13 : return false;
548 : }
549 :
550 99 : void ResetReading() override
551 : {
552 99 : m_itFeature.reset();
553 99 : m_nProcessedFeaturesRead = 0;
554 99 : }
555 :
556 : CPL_DISALLOW_COPY_ASSIGN(GDALVectorCombineOutputLayer)
557 :
558 : private:
559 10 : void AddM(int iGeomField)
560 : {
561 : OGRGeomFieldDefn *poGeomFieldDefn =
562 10 : m_defn->GetGeomFieldDefn(iGeomField);
563 20 : whileUnsealing(poGeomFieldDefn)
564 10 : ->SetType(OGR_GT_SetM(poGeomFieldDefn->GetType()));
565 :
566 20 : for (auto &[_, poFeature] : m_features)
567 : {
568 10 : poFeature->GetGeomFieldRef(iGeomField)->setMeasured(true);
569 : }
570 10 : }
571 :
572 4 : void AddZ(int iGeomField)
573 : {
574 : OGRGeomFieldDefn *poGeomFieldDefn =
575 4 : m_defn->GetGeomFieldDefn(iGeomField);
576 8 : whileUnsealing(poGeomFieldDefn)
577 4 : ->SetType(OGR_GT_SetZ(poGeomFieldDefn->GetType()));
578 :
579 8 : for (auto &[_, poFeature] : m_features)
580 : {
581 4 : poFeature->GetGeomFieldRef(iGeomField)->set3D(true);
582 : }
583 4 : }
584 :
585 2 : void SetTypeGeometryCollection(int iGeomField)
586 : {
587 : OGRGeomFieldDefn *poGeomFieldDefn =
588 2 : m_defn->GetGeomFieldDefn(iGeomField);
589 2 : const bool hasZ = CPL_TO_BOOL(OGR_GT_HasZ(poGeomFieldDefn->GetType()));
590 2 : const bool hasM = CPL_TO_BOOL(OGR_GT_HasM(poGeomFieldDefn->GetType()));
591 :
592 4 : whileUnsealing(poGeomFieldDefn)
593 2 : ->SetType(OGR_GT_SetModifier(wkbGeometryCollection, hasZ, hasM));
594 :
595 4 : for (auto &[_, poFeature] : m_features)
596 : {
597 : std::unique_ptr<OGRGeometry> poTmpGeom(
598 2 : poFeature->StealGeometry(iGeomField));
599 4 : poTmpGeom = OGRGeometryFactory::forceTo(std::move(poTmpGeom),
600 2 : poGeomFieldDefn->GetType());
601 2 : CPLAssert(poTmpGeom);
602 2 : poFeature->SetGeomField(iGeomField, std::move(poTmpGeom));
603 : }
604 2 : }
605 :
606 : const std::vector<std::string> m_groupBy{};
607 : std::vector<int> m_srcGroupByFieldIndices{};
608 : std::vector<int> m_srcExtraFieldIndices{};
609 : std::map<std::vector<std::string>, std::unique_ptr<OGRFeature>>
610 : m_features{};
611 : std::optional<decltype(m_features)::const_iterator> m_itFeature{};
612 : const OGRFeatureDefnRefCountedPtr m_defn;
613 : GIntBig m_nProcessedFeaturesRead = 0;
614 : const bool m_keepNested;
615 : };
616 : } // namespace
617 :
618 26 : bool GDALVectorCombineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
619 : {
620 26 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
621 : auto poDstDS =
622 52 : std::make_unique<GDALVectorNonStreamingAlgorithmDataset>(*poSrcDS);
623 :
624 52 : GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
625 :
626 51 : for (auto &&poSrcLayer : poSrcDS->GetLayers())
627 : {
628 26 : if (m_inputLayerNames.empty() ||
629 0 : std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
630 26 : poSrcLayer->GetDescription()) != m_inputLayerNames.end())
631 : {
632 26 : const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
633 26 : if (poSrcLayerDefn->GetGeomFieldCount() == 0)
634 : {
635 0 : if (m_inputLayerNames.empty())
636 0 : continue;
637 0 : ReportError(CE_Failure, CPLE_AppDefined,
638 : "Specified layer '%s' has no geometry field",
639 0 : poSrcLayer->GetDescription());
640 0 : return false;
641 : }
642 :
643 : // Check that all attributes exist
644 34 : for (const auto &fieldName : m_groupBy)
645 : {
646 : const int iSrcFieldIndex =
647 9 : poSrcLayerDefn->GetFieldIndex(fieldName.c_str());
648 9 : if (iSrcFieldIndex == -1)
649 : {
650 1 : ReportError(CE_Failure, CPLE_AppDefined,
651 : "Specified attribute field '%s' does not exist "
652 : "in layer '%s'",
653 : fieldName.c_str(),
654 1 : poSrcLayer->GetDescription());
655 1 : return false;
656 : }
657 : }
658 :
659 25 : progressHelper.AddProcessedLayer(*poSrcLayer);
660 : }
661 : }
662 :
663 50 : for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc,
664 100 : layerProgressData] : progressHelper)
665 : {
666 : auto poLayer = std::make_unique<GDALVectorCombineOutputLayer>(
667 0 : *poSrcDS, *poSrcLayer, -1, m_groupBy, m_keepNested,
668 25 : m_addExtraFields);
669 :
670 25 : if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc,
671 : layerProgressData.get()))
672 : {
673 0 : return false;
674 : }
675 : }
676 :
677 25 : m_outputDataset.Set(std::move(poDstDS));
678 :
679 25 : return true;
680 : }
681 :
682 : GDALVectorCombineAlgorithmStandalone::~GDALVectorCombineAlgorithmStandalone() =
683 : default;
684 :
685 : //! @endcond
|