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