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