Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "vector layer-algebra" 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_layer_algebra.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 "ogr_api.h"
20 : #include "ogrsf_frmts.h"
21 :
22 : #include <algorithm>
23 :
24 : //! @cond Doxygen_Suppress
25 :
26 : #ifndef _
27 : #define _(x) (x)
28 : #endif
29 :
30 : /************************************************************************/
31 : /* GDALVectorLayerAlgebraAlgorithm() */
32 : /************************************************************************/
33 :
34 121 : GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm(
35 121 : bool standaloneStep)
36 : : GDALVectorPipelineStepAlgorithm(
37 : NAME, DESCRIPTION, HELP_URL,
38 0 : ConstructorOptions()
39 121 : .SetStandaloneStep(standaloneStep)
40 121 : .SetInputDatasetMaxCount(1)
41 121 : .SetAddInputLayerNameArgument(false)
42 121 : .SetAddDefaultArguments(false)
43 121 : .SetAddUpsertArgument(false)
44 121 : .SetAddSkipErrorsArgument(false)
45 121 : .SetOutputLayerNameAvailableInPipelineStep(true)
46 242 : .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
47 : {
48 121 : if (standaloneStep)
49 : {
50 78 : AddProgressArg();
51 : }
52 :
53 : auto &opArg =
54 242 : AddArg("operation", 0, _("Operation to perform"), &m_operation)
55 : .SetChoices("union", "intersection", "sym-difference", "identity",
56 121 : "update", "clip", "erase")
57 121 : .SetRequired();
58 121 : if (standaloneStep)
59 78 : opArg.SetPositional();
60 :
61 121 : if (standaloneStep)
62 : {
63 78 : AddVectorInputArgs(false);
64 : }
65 : else
66 : {
67 43 : AddVectorHiddenInputDatasetArg();
68 : }
69 :
70 : {
71 : auto &arg = AddArg("method", 0, _("Method vector dataset"),
72 242 : &m_methodDataset, GDAL_OF_VECTOR)
73 121 : .SetRequired();
74 121 : if (standaloneStep)
75 78 : arg.SetPositional();
76 :
77 121 : SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
78 : }
79 :
80 121 : if (standaloneStep)
81 : {
82 78 : AddVectorOutputArgs(false, false);
83 : }
84 : else
85 : {
86 43 : AddOutputLayerNameArg(/* hiddenForCLI = */ false,
87 : /* shortNameOutputLayerAllowed = */ false);
88 : }
89 :
90 : AddArg(GDAL_ARG_NAME_INPUT_LAYER, 0, _("Input layer name"),
91 121 : &m_inputLayerName);
92 :
93 121 : AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
94 :
95 121 : AddGeometryTypeArg(&m_geometryType);
96 :
97 : AddArg("input-prefix", 0,
98 242 : _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
99 121 : .SetCategory(GAAC_ADVANCED);
100 : AddArg("input-field", 0, _("Input field(s) to add to output layer"),
101 242 : &m_inputFields)
102 242 : .SetCategory(GAAC_ADVANCED)
103 121 : .SetMutualExclusionGroup("input-field");
104 : AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
105 242 : &m_noInputFields)
106 242 : .SetCategory(GAAC_ADVANCED)
107 121 : .SetMutualExclusionGroup("input-field");
108 : AddArg("all-input-field", 0, _("Add all input fields to output layer"),
109 242 : &m_allInputFields)
110 242 : .SetCategory(GAAC_ADVANCED)
111 121 : .SetMutualExclusionGroup("input-field");
112 :
113 : AddArg("method-prefix", 0,
114 : _("Prefix for fields corresponding to method layer"),
115 242 : &m_methodPrefix)
116 121 : .SetCategory(GAAC_ADVANCED);
117 : AddArg("method-field", 0, _("Method field(s) to add to output layer"),
118 242 : &m_methodFields)
119 242 : .SetCategory(GAAC_ADVANCED)
120 121 : .SetMutualExclusionGroup("method-field");
121 : AddArg("no-method-field", 0,
122 242 : _("Do not add any method field to output layer"), &m_noMethodFields)
123 242 : .SetCategory(GAAC_ADVANCED)
124 121 : .SetMutualExclusionGroup("method-field");
125 : AddArg("all-method-field", 0, _("Add all method fields to output layer"),
126 242 : &m_allMethodFields)
127 242 : .SetCategory(GAAC_ADVANCED)
128 121 : .SetMutualExclusionGroup("method-field");
129 121 : }
130 :
131 : /************************************************************************/
132 : /* GDALVectorLayerAlgebraAlgorithm::CanHandleNextStep() */
133 : /************************************************************************/
134 :
135 2 : bool GDALVectorLayerAlgebraAlgorithm::CanHandleNextStep(
136 : GDALPipelineStepAlgorithm *poNextStep) const
137 : {
138 3 : return poNextStep->GetName() == GDALVectorWriteAlgorithm::NAME &&
139 3 : poNextStep->GetOutputFormat() != "stream";
140 : }
141 :
142 : /************************************************************************/
143 : /* GDALRasterPolygonizeAlgorithm::RunImpl() */
144 : /************************************************************************/
145 :
146 34 : bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
147 : void *pProgressData)
148 : {
149 34 : GDALPipelineStepRunContext stepCtxt;
150 34 : stepCtxt.m_pfnProgress = pfnProgress;
151 34 : stepCtxt.m_pProgressData = pProgressData;
152 34 : return RunPreStepPipelineValidations() && RunStep(stepCtxt);
153 : }
154 :
155 : /************************************************************************/
156 : /* GDALVectorLayerAlgebraAlgorithm::RunImpl() */
157 : /************************************************************************/
158 :
159 37 : bool GDALVectorLayerAlgebraAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
160 : {
161 : #ifdef HAVE_GEOS
162 37 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
163 37 : CPLAssert(poSrcDS);
164 37 : auto poMethodDS = m_methodDataset.GetDatasetRef();
165 37 : CPLAssert(poMethodDS);
166 :
167 37 : if (poSrcDS == poMethodDS)
168 : {
169 1 : ReportError(CE_Failure, CPLE_NotSupported,
170 : "Input and method datasets must be different");
171 1 : return false;
172 : }
173 :
174 36 : auto poWriteStep = ctxt.m_poNextUsableStep ? ctxt.m_poNextUsableStep : this;
175 :
176 36 : GDALDataset *poDstDS = nullptr;
177 36 : bool bTemporaryFile = false;
178 36 : std::unique_ptr<GDALDataset> poNewRetDS;
179 72 : std::string outputLayerName;
180 36 : OGRLayer *poDstLayer = nullptr;
181 36 : if (!CreateDatasetSingleOutputLayerIfNeeded(ctxt, "output", poDstDS,
182 : bTemporaryFile, poNewRetDS,
183 : outputLayerName, poDstLayer))
184 : {
185 11 : return false;
186 : }
187 :
188 : OGRLayer *poInputLayer;
189 25 : if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
190 24 : poInputLayer = poSrcDS->GetLayer(0);
191 : else
192 1 : poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
193 25 : if (!poInputLayer)
194 : {
195 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot get input layer '%s'",
196 : m_inputLayerName.c_str());
197 1 : return false;
198 : }
199 :
200 : OGRLayer *poMethodLayer;
201 24 : if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
202 23 : poMethodLayer = poMethodDS->GetLayer(0);
203 : else
204 1 : poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
205 24 : if (!poMethodLayer)
206 : {
207 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot get method layer '%s'",
208 : m_methodLayerName.c_str());
209 1 : return false;
210 : }
211 :
212 23 : if (!poDstLayer)
213 : {
214 : const CPLStringList aosLayerCreationOptions(
215 20 : poWriteStep->GetLayerCreationOptions());
216 :
217 : const OGRwkbGeometryType eType =
218 20 : !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
219 19 : : poInputLayer->GetGeomType();
220 40 : poDstLayer = poDstDS->CreateLayer(outputLayerName.c_str(),
221 20 : poInputLayer->GetSpatialRef(), eType,
222 : aosLayerCreationOptions.List());
223 20 : if (!poDstLayer)
224 1 : return false;
225 : }
226 :
227 44 : CPLStringList aosOptions;
228 :
229 22 : if (m_inputFields.empty() && !m_noInputFields)
230 15 : m_allInputFields = true;
231 :
232 22 : if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
233 : {
234 28 : if (m_operation == "update" || m_operation == "clip" ||
235 13 : m_operation == "erase")
236 3 : m_noMethodFields = true;
237 : else
238 12 : m_allMethodFields = true;
239 : }
240 :
241 22 : if (m_noInputFields && m_noMethodFields)
242 : {
243 6 : aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
244 6 : aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
245 : }
246 : else
247 : {
248 : // Copy fields from input or method layer to output layer
249 : const auto CopyFields =
250 28 : [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
251 65 : const std::vector<std::string> &srcFields)
252 : {
253 : const auto contains =
254 4 : [](const std::vector<std::string> &v, const std::string &s)
255 4 : { return std::find(v.begin(), v.end(), s) != v.end(); };
256 :
257 28 : const auto poOutFDefn = poDstLayer->GetLayerDefn();
258 28 : const auto poFDefn = poSrcLayer->GetLayerDefn();
259 28 : const int nCount = poFDefn->GetFieldCount();
260 71 : for (int i = 0; i < nCount; ++i)
261 : {
262 45 : const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
263 45 : const char *pszName = poSrcFieldDefn->GetNameRef();
264 45 : if (srcFields.empty() || contains(srcFields, pszName))
265 : {
266 43 : OGRFieldDefn oField(*poSrcFieldDefn);
267 43 : const std::string outName = prefix + pszName;
268 43 : whileUnsealing(&oField)->SetName(outName.c_str());
269 80 : if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
270 37 : poDstLayer->CreateField(&oField) != OGRERR_NONE)
271 : {
272 2 : return false;
273 : }
274 : }
275 : }
276 26 : return true;
277 16 : };
278 :
279 16 : if (!m_noInputFields)
280 : {
281 63 : if (!GetArg("input-prefix")->IsExplicitlySet() &&
282 63 : m_inputPrefix.empty() && !m_noMethodFields)
283 : {
284 12 : m_inputPrefix = "input_";
285 : }
286 16 : if (!m_inputPrefix.empty())
287 : {
288 12 : aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
289 : }
290 16 : if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
291 2 : return false;
292 : }
293 :
294 15 : if (!m_noMethodFields)
295 : {
296 47 : if (!GetArg("method-prefix")->IsExplicitlySet() &&
297 47 : m_methodPrefix.empty() && !m_noInputFields)
298 : {
299 11 : m_methodPrefix = "method_";
300 : }
301 12 : if (!m_methodPrefix.empty())
302 : {
303 : aosOptions.SetNameValue("METHOD_PREFIX",
304 11 : m_methodPrefix.c_str());
305 : }
306 12 : if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
307 1 : return false;
308 : }
309 : }
310 :
311 20 : if (OGR_GT_IsSubClassOf(poDstLayer->GetGeomType(), wkbGeometryCollection))
312 : {
313 11 : aosOptions.SetNameValue("PROMOTE_TO_MULTI", "YES");
314 : }
315 :
316 : const std::map<std::string, decltype(&OGRLayer::Union)>
317 : mapOperationToMethod = {
318 : {"union", &OGRLayer::Union},
319 : {"intersection", &OGRLayer::Intersection},
320 : {"sym-difference", &OGRLayer::SymDifference},
321 : {"identity", &OGRLayer::Identity},
322 : {"update", &OGRLayer::Update},
323 : {"clip", &OGRLayer::Clip},
324 : {"erase", &OGRLayer::Erase},
325 180 : };
326 :
327 20 : const auto oIter = mapOperationToMethod.find(m_operation);
328 20 : CPLAssert(oIter != mapOperationToMethod.end());
329 20 : const auto pFunc = oIter->second;
330 20 : bool bOK = (poInputLayer->*pFunc)(poMethodLayer, poDstLayer,
331 20 : aosOptions.List(), ctxt.m_pfnProgress,
332 20 : ctxt.m_pProgressData) == OGRERR_NONE;
333 20 : if (bOK && poNewRetDS)
334 : {
335 14 : if (bTemporaryFile)
336 : {
337 2 : bOK = poNewRetDS->FlushCache() == CE_None;
338 : #if !defined(__APPLE__)
339 : // For some unknown reason, unlinking the file on MacOSX
340 : // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
341 2 : VSIUnlink(poNewRetDS->GetDescription());
342 : #endif
343 : }
344 :
345 14 : m_outputDataset.Set(std::move(poNewRetDS));
346 : }
347 :
348 20 : return bOK;
349 : #else
350 : (void)ctxt;
351 : ReportError(CE_Failure, CPLE_NotSupported,
352 : "This algorithm is only supported for builds against GEOS");
353 : return false;
354 : #endif
355 : }
356 :
357 : GDALVectorLayerAlgebraAlgorithmStandalone::
358 : ~GDALVectorLayerAlgebraAlgorithmStandalone() = default;
359 :
360 : //! @endcond
|