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