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