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