Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "vector concat" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdalalg_vector_concat.h"
14 : #include "gdalalg_vector_write.h"
15 :
16 : #include "cpl_conv.h"
17 : #include "cpl_enumerate.h"
18 : #include "gdal_priv.h"
19 : #include "gdal_utils.h"
20 : #include "ogrsf_frmts.h"
21 :
22 : #include "ogrlayerdecorator.h"
23 : #include "ogrunionlayer.h"
24 : #include "ogrwarpedlayer.h"
25 :
26 : #include <algorithm>
27 : #include <set>
28 :
29 : //! @cond Doxygen_Suppress
30 :
31 : #ifndef _
32 : #define _(x) (x)
33 : #endif
34 :
35 : /************************************************************************/
36 : /* GDALVectorConcatAlgorithm::GDALVectorConcatAlgorithm() */
37 : /************************************************************************/
38 :
39 70 : GDALVectorConcatAlgorithm::GDALVectorConcatAlgorithm(bool bStandalone)
40 : : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
41 0 : ConstructorOptions()
42 70 : .SetStandaloneStep(bStandalone)
43 70 : .SetAddDefaultArguments(bStandalone)
44 70 : .SetInputDatasetMaxCount(INT_MAX)
45 70 : .SetAddOutputLayerNameArgument(false)
46 140 : .SetAutoOpenInputDatasets(false))
47 : {
48 70 : if (!bStandalone)
49 : {
50 36 : AddVectorInputArgs(/* hiddenForCLI = */ false);
51 : }
52 :
53 : AddArg(
54 : "mode", 0,
55 : _("Determine the strategy to create output layers from source layers "),
56 140 : &m_mode)
57 70 : .SetChoices("merge-per-layer-name", "stack", "single")
58 70 : .SetDefault(m_mode);
59 : AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, 0,
60 : _("Name of the output vector layer (single mode), or template to "
61 : "name the output vector layers (stack mode)"),
62 70 : &m_layerNameTemplate);
63 : AddArg("source-layer-field-name", 0,
64 : _("Name of the new field to add to contain identificoncation of the "
65 : "source layer, with value determined from "
66 : "'source-layer-field-content'"),
67 70 : &m_sourceLayerFieldName);
68 : AddArg("source-layer-field-content", 0,
69 : _("A string, possibly using {AUTO_NAME}, {DS_NAME}, {DS_BASENAME}, "
70 : "{DS_INDEX}, {LAYER_NAME}, {LAYER_INDEX}"),
71 70 : &m_sourceLayerFieldContent);
72 : AddArg("field-strategy", 0,
73 : _("How to determine target fields from source fields"),
74 140 : &m_fieldStrategy)
75 70 : .SetChoices("union", "intersection")
76 70 : .SetDefault(m_fieldStrategy);
77 140 : AddArg("src-crs", 's', _("Source CRS"), &m_srsCrs)
78 140 : .SetIsCRSArg()
79 70 : .AddHiddenAlias("s_srs");
80 140 : AddArg("dst-crs", 'd', _("Destination CRS"), &m_dstCrs)
81 140 : .SetIsCRSArg()
82 70 : .AddHiddenAlias("t_srs");
83 70 : }
84 :
85 : GDALVectorConcatAlgorithm::~GDALVectorConcatAlgorithm() = default;
86 :
87 : /************************************************************************/
88 : /* GDALVectorConcatOutputDataset */
89 : /************************************************************************/
90 :
91 : class GDALVectorConcatOutputDataset final : public GDALDataset
92 : {
93 : std::vector<std::unique_ptr<OGRLayer>> m_layers{};
94 :
95 : public:
96 26 : GDALVectorConcatOutputDataset() = default;
97 :
98 33 : void AddLayer(std::unique_ptr<OGRLayer> layer)
99 : {
100 33 : m_layers.push_back(std::move(layer));
101 33 : }
102 :
103 : int GetLayerCount() const override;
104 :
105 56 : OGRLayer *GetLayer(int idx) const override
106 : {
107 56 : return idx >= 0 && idx < GetLayerCount() ? m_layers[idx].get()
108 56 : : nullptr;
109 : }
110 :
111 31 : int TestCapability(const char *pszCap) const override
112 : {
113 31 : if (EQUAL(pszCap, ODsCCurveGeometries) ||
114 29 : EQUAL(pszCap, ODsCMeasuredGeometries) ||
115 28 : EQUAL(pszCap, ODsCZGeometries))
116 : {
117 4 : return true;
118 : }
119 27 : return false;
120 : }
121 : };
122 :
123 135 : int GDALVectorConcatOutputDataset::GetLayerCount() const
124 : {
125 135 : return static_cast<int>(m_layers.size());
126 : }
127 :
128 : /************************************************************************/
129 : /* GDALVectorConcatRenamedLayer */
130 : /************************************************************************/
131 :
132 : class GDALVectorConcatRenamedLayer final : public OGRLayerDecorator
133 : {
134 : public:
135 7 : GDALVectorConcatRenamedLayer(OGRLayer *poSrcLayer,
136 : const std::string &newName)
137 7 : : OGRLayerDecorator(poSrcLayer, false), m_newName(newName)
138 : {
139 7 : }
140 :
141 : const char *GetName() const override;
142 :
143 : private:
144 : const std::string m_newName;
145 : };
146 :
147 7 : const char *GDALVectorConcatRenamedLayer::GetName() const
148 : {
149 7 : return m_newName.c_str();
150 : }
151 :
152 : /************************************************************************/
153 : /* BuildLayerName() */
154 : /************************************************************************/
155 :
156 16 : static std::string BuildLayerName(const std::string &layerNameTemplate,
157 : int dsIdx, const std::string &dsName,
158 : int lyrIdx, const std::string &lyrName)
159 : {
160 32 : CPLString ret = layerNameTemplate;
161 32 : std::string baseName;
162 : VSIStatBufL sStat;
163 16 : if (VSIStatL(dsName.c_str(), &sStat) == 0)
164 1 : baseName = CPLGetBasenameSafe(dsName.c_str());
165 :
166 16 : if (baseName == lyrName)
167 : {
168 1 : ret = ret.replaceAll("{AUTO_NAME}", baseName);
169 : }
170 : else
171 : {
172 : ret = ret.replaceAll("{AUTO_NAME}",
173 30 : std::string(baseName.empty() ? dsName : baseName)
174 15 : .append("_")
175 15 : .append(lyrName));
176 : }
177 :
178 : ret =
179 16 : ret.replaceAll("{DS_BASENAME}", !baseName.empty() ? baseName : dsName);
180 16 : ret = ret.replaceAll("{DS_NAME}", dsName);
181 16 : ret = ret.replaceAll("{DS_INDEX}", std::to_string(dsIdx).c_str());
182 16 : ret = ret.replaceAll("{LAYER_NAME}", lyrName);
183 16 : ret = ret.replaceAll("{LAYER_INDEX}", std::to_string(lyrIdx).c_str());
184 :
185 32 : return std::string(std::move(ret));
186 : }
187 :
188 : namespace
189 : {
190 :
191 : /************************************************************************/
192 : /* OpenProxiedLayer() */
193 : /************************************************************************/
194 :
195 : struct PooledInitData
196 : {
197 : std::unique_ptr<GDALDataset> poDS{};
198 : std::string osDatasetName{};
199 : std::vector<std::string> *pInputFormats = nullptr;
200 : std::vector<std::string> *pOpenOptions = nullptr;
201 : int iLayer = 0;
202 : };
203 :
204 8 : static OGRLayer *OpenProxiedLayer(void *pUserData)
205 : {
206 8 : PooledInitData *pData = static_cast<PooledInitData *>(pUserData);
207 8 : pData->poDS.reset(GDALDataset::Open(
208 : pData->osDatasetName.c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
209 16 : pData->pInputFormats ? CPLStringList(*(pData->pInputFormats)).List()
210 : : nullptr,
211 16 : pData->pOpenOptions ? CPLStringList(*(pData->pOpenOptions)).List()
212 : : nullptr,
213 : nullptr));
214 8 : if (!pData->poDS)
215 0 : return nullptr;
216 8 : return pData->poDS->GetLayer(pData->iLayer);
217 : }
218 :
219 : /************************************************************************/
220 : /* ReleaseProxiedLayer() */
221 : /************************************************************************/
222 :
223 8 : static void ReleaseProxiedLayer(OGRLayer *, void *pUserData)
224 : {
225 8 : PooledInitData *pData = static_cast<PooledInitData *>(pUserData);
226 8 : pData->poDS.reset();
227 8 : }
228 :
229 : /************************************************************************/
230 : /* FreeProxiedLayerUserData() */
231 : /************************************************************************/
232 :
233 2 : static void FreeProxiedLayerUserData(void *pUserData)
234 : {
235 2 : delete static_cast<PooledInitData *>(pUserData);
236 2 : }
237 :
238 : } // namespace
239 :
240 : /************************************************************************/
241 : /* GDALVectorConcatAlgorithm::RunStep() */
242 : /************************************************************************/
243 :
244 28 : bool GDALVectorConcatAlgorithm::RunStep(GDALPipelineStepRunContext &)
245 : {
246 28 : std::unique_ptr<OGRSpatialReference> poSrcCRS;
247 28 : if (!m_srsCrs.empty())
248 : {
249 1 : poSrcCRS = std::make_unique<OGRSpatialReference>();
250 1 : poSrcCRS->SetFromUserInput(m_srsCrs.c_str());
251 1 : poSrcCRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
252 : }
253 :
254 56 : OGRSpatialReference oDstCRS;
255 28 : if (!m_dstCrs.empty())
256 : {
257 4 : oDstCRS.SetFromUserInput(m_dstCrs.c_str());
258 4 : oDstCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
259 : }
260 :
261 : struct LayerDesc
262 : {
263 : int iDS = 0;
264 : int iLayer = 0;
265 : std::string osDatasetName{};
266 : };
267 :
268 28 : if (m_layerNameTemplate.empty())
269 : {
270 25 : if (m_mode == "single")
271 1 : m_layerNameTemplate = "merged";
272 24 : else if (m_mode == "stack")
273 2 : m_layerNameTemplate = "{AUTO_NAME}";
274 : }
275 3 : else if (m_mode == "merge-per-layer-name")
276 : {
277 1 : ReportError(CE_Failure, CPLE_IllegalArg,
278 : "'layer-name' name argument cannot be specified in "
279 : "mode=merge-per-layer-name");
280 1 : return false;
281 : }
282 :
283 27 : if (m_sourceLayerFieldContent.empty())
284 21 : m_sourceLayerFieldContent = "{AUTO_NAME}";
285 6 : else if (m_sourceLayerFieldName.empty())
286 1 : m_sourceLayerFieldName = "source_ds_lyr";
287 :
288 : const int nMaxSimultaneouslyOpened =
289 81 : std::max(atoi(CPLGetConfigOption(
290 : "GDAL_VECTOR_CONCAT_MAX_OPENED_DATASETS", "100")),
291 27 : 1);
292 :
293 : // First pass on input layers
294 54 : std::map<std::string, std::vector<LayerDesc>> allLayerNames;
295 27 : int iDS = 0;
296 27 : int nonOpenedDSCount = 0;
297 68 : for (auto &srcDS : m_inputDataset)
298 : {
299 42 : GDALDataset *poSrcDS = srcDS.GetDatasetRef();
300 0 : std::unique_ptr<GDALDataset> poTmpDS;
301 42 : if (!poSrcDS)
302 : {
303 11 : poTmpDS.reset(GDALDataset::Open(
304 11 : srcDS.GetName().c_str(), GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
305 22 : CPLStringList(m_inputFormats).List(),
306 22 : CPLStringList(m_openOptions).List(), nullptr));
307 11 : poSrcDS = poTmpDS.get();
308 11 : if (!poSrcDS)
309 0 : return false;
310 11 : if (static_cast<int>(m_inputDataset.size()) <=
311 11 : nMaxSimultaneouslyOpened)
312 : {
313 9 : srcDS.Set(std::move(poTmpDS));
314 9 : poSrcDS = srcDS.GetDatasetRef();
315 : }
316 : else
317 : {
318 2 : ++nonOpenedDSCount;
319 : }
320 : }
321 :
322 42 : int iLayer = 0;
323 91 : for (const auto &poLayer : poSrcDS->GetLayers())
324 : {
325 52 : if (m_inputLayerNames.empty() ||
326 0 : std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
327 52 : poLayer->GetName()) != m_inputLayerNames.end())
328 : {
329 54 : if (!m_dstCrs.empty() && m_srsCrs.empty() &&
330 5 : poLayer->GetSpatialRef() == nullptr)
331 : {
332 1 : ReportError(
333 : CE_Failure, CPLE_AppDefined,
334 : "Layer '%s' of '%s' has no spatial reference system",
335 1 : poLayer->GetName(), poSrcDS->GetDescription());
336 1 : return false;
337 : }
338 96 : LayerDesc layerDesc;
339 48 : layerDesc.iDS = iDS;
340 48 : layerDesc.iLayer = iLayer;
341 48 : layerDesc.osDatasetName = poSrcDS->GetDescription();
342 : const std::string outLayerName =
343 48 : m_mode == "single" ? m_layerNameTemplate
344 42 : : m_mode == "merge-per-layer-name"
345 33 : ? std::string(poLayer->GetName())
346 9 : : BuildLayerName(m_layerNameTemplate, iDS,
347 9 : poSrcDS->GetDescription(), iLayer,
348 174 : poLayer->GetName());
349 48 : CPLDebugOnly("gdal_vector_concat", "%s,%s->%s",
350 : poSrcDS->GetDescription(), poLayer->GetName(),
351 : outLayerName.c_str());
352 48 : allLayerNames[outLayerName].push_back(std::move(layerDesc));
353 : }
354 49 : ++iLayer;
355 : }
356 41 : ++iDS;
357 : }
358 :
359 52 : auto poUnionDS = std::make_unique<GDALVectorConcatOutputDataset>();
360 :
361 26 : if (nonOpenedDSCount > nMaxSimultaneouslyOpened)
362 : m_poLayerPool =
363 1 : std::make_unique<OGRLayerPool>(nMaxSimultaneouslyOpened);
364 :
365 26 : bool ret = true;
366 59 : for (const auto &[outLayerName, listOfLayers] : allLayerNames)
367 : {
368 33 : const int nLayerCount = static_cast<int>(listOfLayers.size());
369 : std::unique_ptr<OGRLayer *, VSIFreeReleaser> papoSrcLayers(
370 : static_cast<OGRLayer **>(
371 33 : CPLCalloc(nLayerCount, sizeof(OGRLayer *))));
372 81 : for (const auto [i, layer] : cpl::enumerate(listOfLayers))
373 : {
374 48 : auto &srcDS = m_inputDataset[layer.iDS];
375 48 : GDALDataset *poSrcDS = srcDS.GetDatasetRef();
376 0 : std::unique_ptr<GDALDataset> poTmpDS;
377 48 : if (!poSrcDS)
378 : {
379 2 : poTmpDS.reset(GDALDataset::Open(
380 : layer.osDatasetName.c_str(),
381 : GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
382 4 : CPLStringList(m_inputFormats).List(),
383 4 : CPLStringList(m_openOptions).List(), nullptr));
384 2 : poSrcDS = poTmpDS.get();
385 2 : if (!poSrcDS)
386 0 : return false;
387 : }
388 48 : OGRLayer *poSrcLayer = poSrcDS->GetLayer(layer.iLayer);
389 :
390 48 : if (m_poLayerPool)
391 : {
392 4 : auto pData = std::make_unique<PooledInitData>();
393 2 : pData->osDatasetName = layer.osDatasetName;
394 2 : pData->pInputFormats = &m_inputFormats;
395 2 : pData->pOpenOptions = &m_openOptions;
396 2 : pData->iLayer = layer.iLayer;
397 : auto proxiedLayer = std::make_unique<OGRProxiedLayer>(
398 0 : m_poLayerPool.get(), OpenProxiedLayer, ReleaseProxiedLayer,
399 2 : FreeProxiedLayerUserData, pData.release());
400 2 : proxiedLayer->SetDescription(poSrcLayer->GetDescription());
401 2 : m_tempLayersKeeper.push_back(std::move(proxiedLayer));
402 2 : poSrcLayer = m_tempLayersKeeper.back().get();
403 : }
404 46 : else if (poTmpDS)
405 : {
406 0 : srcDS.Set(std::move(poTmpDS));
407 : }
408 :
409 48 : if (m_sourceLayerFieldName.empty())
410 : {
411 41 : papoSrcLayers.get()[i] = poSrcLayer;
412 : }
413 : else
414 : {
415 : const std::string newSrcLayerName = BuildLayerName(
416 7 : m_sourceLayerFieldContent, listOfLayers[i].iDS,
417 7 : listOfLayers[i].osDatasetName.c_str(),
418 35 : listOfLayers[i].iLayer, poSrcLayer->GetName());
419 7 : ret = !newSrcLayerName.empty() && ret;
420 : auto poTmpLayer =
421 : std::make_unique<GDALVectorConcatRenamedLayer>(
422 7 : poSrcLayer, newSrcLayerName);
423 7 : m_tempLayersKeeper.push_back(std::move(poTmpLayer));
424 7 : papoSrcLayers.get()[i] = m_tempLayersKeeper.back().get();
425 : }
426 : }
427 :
428 : // Auto-wrap source layers if needed
429 33 : if (!m_dstCrs.empty())
430 : {
431 8 : for (int i = 0; ret && i < nLayerCount; ++i)
432 : {
433 : const OGRSpatialReference *poSrcLayerCRS;
434 5 : if (poSrcCRS)
435 1 : poSrcLayerCRS = poSrcCRS.get();
436 : else
437 4 : poSrcLayerCRS = papoSrcLayers.get()[i]->GetSpatialRef();
438 5 : if (poSrcLayerCRS && !poSrcLayerCRS->IsSame(&oDstCRS))
439 : {
440 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
441 : OGRCreateCoordinateTransformation(poSrcLayerCRS,
442 6 : &oDstCRS));
443 : auto poReversedCT =
444 : std::unique_ptr<OGRCoordinateTransformation>(
445 : OGRCreateCoordinateTransformation(&oDstCRS,
446 6 : poSrcLayerCRS));
447 3 : ret = (poCT != nullptr) && (poReversedCT != nullptr);
448 3 : if (ret)
449 : {
450 3 : m_tempLayersKeeper.push_back(
451 3 : std::make_unique<OGRWarpedLayer>(
452 3 : papoSrcLayers.get()[i], /* iGeomField = */ 0,
453 3 : /*bTakeOwnership = */ false, std::move(poCT),
454 3 : std::move(poReversedCT)));
455 3 : papoSrcLayers.get()[i] =
456 3 : m_tempLayersKeeper.back().get();
457 : }
458 : }
459 : }
460 : }
461 :
462 : auto poUnionLayer = std::make_unique<OGRUnionLayer>(
463 33 : outLayerName.c_str(), nLayerCount, papoSrcLayers.release(),
464 66 : /* bTakeLayerOwnership = */ false);
465 :
466 33 : if (!m_sourceLayerFieldName.empty())
467 : {
468 7 : poUnionLayer->SetSourceLayerFieldName(
469 : m_sourceLayerFieldName.c_str());
470 : }
471 :
472 : const FieldUnionStrategy eStrategy =
473 33 : m_fieldStrategy == "union" ? FIELD_UNION_ALL_LAYERS
474 33 : : FIELD_INTERSECTION_ALL_LAYERS;
475 33 : poUnionLayer->SetFields(eStrategy, 0, nullptr, 0, nullptr);
476 :
477 33 : poUnionDS->AddLayer(std::move(poUnionLayer));
478 : }
479 :
480 26 : if (ret)
481 : {
482 26 : m_outputDataset.Set(std::move(poUnionDS));
483 : }
484 26 : return ret;
485 : }
486 :
487 : /************************************************************************/
488 : /* GDALVectorConcatAlgorithm::RunImpl() */
489 : /************************************************************************/
490 :
491 54 : bool GDALVectorConcatAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
492 : void *pProgressData)
493 : {
494 54 : if (m_standaloneStep)
495 : {
496 27 : GDALVectorWriteAlgorithm writeAlg;
497 513 : for (auto &arg : writeAlg.GetArgs())
498 : {
499 918 : if (!arg->IsHidden() &&
500 432 : arg->GetName() != GDAL_ARG_NAME_OUTPUT_LAYER)
501 : {
502 405 : auto stepArg = GetArg(arg->GetName());
503 405 : if (stepArg && stepArg->IsExplicitlySet())
504 : {
505 54 : arg->SetSkipIfAlreadySet(true);
506 54 : arg->SetFrom(*stepArg);
507 : }
508 : }
509 : }
510 :
511 : // Already checked by GDALAlgorithm::Run()
512 27 : CPLAssert(!m_executionForStreamOutput ||
513 : EQUAL(m_format.c_str(), "stream"));
514 :
515 27 : m_standaloneStep = false;
516 27 : bool ret = Run(pfnProgress, pProgressData);
517 27 : m_standaloneStep = true;
518 27 : if (ret)
519 : {
520 25 : if (m_format == "stream")
521 : {
522 2 : ret = true;
523 : }
524 : else
525 : {
526 23 : writeAlg.m_inputDataset.clear();
527 23 : writeAlg.m_inputDataset.resize(1);
528 23 : writeAlg.m_inputDataset[0].Set(m_outputDataset.GetDatasetRef());
529 23 : if (writeAlg.Run(pfnProgress, pProgressData))
530 : {
531 23 : m_outputDataset.Set(
532 : writeAlg.m_outputDataset.GetDatasetRef());
533 23 : ret = true;
534 : }
535 : }
536 : }
537 :
538 27 : return ret;
539 : }
540 : else
541 : {
542 27 : GDALPipelineStepRunContext stepCtxt;
543 27 : stepCtxt.m_pfnProgress = pfnProgress;
544 27 : stepCtxt.m_pProgressData = pProgressData;
545 27 : return RunStep(stepCtxt);
546 : }
547 : }
548 :
549 : GDALVectorConcatAlgorithmStandalone::~GDALVectorConcatAlgorithmStandalone() =
550 : default;
551 :
552 : //! @endcond
|