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