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