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 :
15 : #include "cpl_conv.h"
16 : #include "gdal_priv.h"
17 : #include "gdal_utils.h"
18 : #include "ogr_api.h"
19 : #include "ogrsf_frmts.h"
20 :
21 : #include <algorithm>
22 :
23 : //! @cond Doxygen_Suppress
24 :
25 : #ifndef _
26 : #define _(x) (x)
27 : #endif
28 :
29 : /************************************************************************/
30 : /* GDALVectorLayerAlgebraAlgorithm() */
31 : /************************************************************************/
32 :
33 37 : GDALVectorLayerAlgebraAlgorithm::GDALVectorLayerAlgebraAlgorithm()
34 37 : : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
35 : {
36 37 : AddProgressArg();
37 :
38 74 : AddArg("operation", 0, _("Operation to perform"), &m_operation)
39 : .SetChoices("union", "intersection", "sym-difference", "identity",
40 37 : "update", "clip", "erase")
41 37 : .SetRequired()
42 37 : .SetPositional();
43 :
44 37 : AddOutputFormatArg(&m_format).AddMetadataItem(
45 111 : GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
46 37 : AddOpenOptionsArg(&m_openOptions);
47 37 : AddInputFormatsArg(&m_inputFormats)
48 74 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_VECTOR});
49 37 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_VECTOR);
50 :
51 : {
52 : auto &arg = AddArg("method", 0, _("Method vector dataset"),
53 74 : &m_methodDataset, GDAL_OF_VECTOR)
54 37 : .SetPositional()
55 37 : .SetRequired();
56 :
57 37 : SetAutoCompleteFunctionForFilename(arg, GDAL_OF_VECTOR);
58 : }
59 37 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
60 37 : .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
61 37 : AddCreationOptionsArg(&m_creationOptions);
62 37 : AddLayerCreationOptionsArg(&m_layerCreationOptions);
63 37 : AddOverwriteArg(&m_overwrite);
64 37 : AddUpdateArg(&m_update);
65 37 : AddOverwriteLayerArg(&m_overwriteLayer);
66 37 : AddAppendLayerArg(&m_appendLayer);
67 :
68 : AddArg(GDAL_ARG_NAME_INPUT_LAYER, 0, _("Input layer name"),
69 37 : &m_inputLayerName);
70 37 : AddArg("method-layer", 0, _("Method layer name"), &m_methodLayerName);
71 37 : AddOutputLayerNameArg(&m_outputLayerName)
72 37 : .AddHiddenAlias("nln"); // For ogr2ogr nostalgic people
73 :
74 37 : AddGeometryTypeArg(&m_geometryType);
75 :
76 : AddArg("input-prefix", 0,
77 74 : _("Prefix for fields corresponding to input layer"), &m_inputPrefix)
78 37 : .SetCategory(GAAC_ADVANCED);
79 : AddArg("input-field", 0, _("Input field(s) to add to output layer"),
80 74 : &m_inputFields)
81 74 : .SetCategory(GAAC_ADVANCED)
82 37 : .SetMutualExclusionGroup("input-field");
83 : AddArg("no-input-field", 0, _("Do not add any input field to output layer"),
84 74 : &m_noInputFields)
85 74 : .SetCategory(GAAC_ADVANCED)
86 37 : .SetMutualExclusionGroup("input-field");
87 : AddArg("all-input-field", 0, _("Add all input fields to output layer"),
88 74 : &m_allInputFields)
89 74 : .SetCategory(GAAC_ADVANCED)
90 37 : .SetMutualExclusionGroup("input-field");
91 :
92 : AddArg("method-prefix", 0,
93 : _("Prefix for fields corresponding to method layer"),
94 74 : &m_methodPrefix)
95 37 : .SetCategory(GAAC_ADVANCED);
96 : AddArg("method-field", 0, _("Method field(s) to add to output layer"),
97 74 : &m_methodFields)
98 74 : .SetCategory(GAAC_ADVANCED)
99 37 : .SetMutualExclusionGroup("method-field");
100 : AddArg("no-method-field", 0,
101 74 : _("Do not add any method field to output layer"), &m_noMethodFields)
102 74 : .SetCategory(GAAC_ADVANCED)
103 37 : .SetMutualExclusionGroup("method-field");
104 : AddArg("all-method-field", 0, _("Add all method fields to output layer"),
105 74 : &m_allMethodFields)
106 74 : .SetCategory(GAAC_ADVANCED)
107 37 : .SetMutualExclusionGroup("method-field");
108 37 : }
109 :
110 : /************************************************************************/
111 : /* GDALVectorLayerAlgebraAlgorithm::RunImpl() */
112 : /************************************************************************/
113 :
114 35 : bool GDALVectorLayerAlgebraAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
115 : void *pProgressData)
116 : {
117 : #ifdef HAVE_GEOS
118 35 : auto poSrcDS = m_inputDataset.GetDatasetRef();
119 35 : CPLAssert(poSrcDS);
120 35 : auto poMethodDS = m_methodDataset.GetDatasetRef();
121 35 : CPLAssert(poMethodDS);
122 :
123 35 : if (poSrcDS == poMethodDS)
124 : {
125 1 : ReportError(CE_Failure, CPLE_NotSupported,
126 : "Input and method datasets must be different");
127 1 : return false;
128 : }
129 :
130 34 : auto poDstDS = m_outputDataset.GetDatasetRef();
131 34 : std::unique_ptr<GDALDataset> poDstDSUniquePtr;
132 34 : const bool bNewDataset = poDstDS == nullptr;
133 34 : if (poDstDS == nullptr)
134 : {
135 19 : if (m_format.empty())
136 : {
137 : const CPLStringList aosFormats(GDALGetOutputDriversForDatasetName(
138 11 : m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR,
139 : /* bSingleMatch = */ true,
140 11 : /* bEmitWarning = */ true));
141 11 : if (aosFormats.size() != 1)
142 : {
143 1 : ReportError(CE_Failure, CPLE_AppDefined,
144 : "Cannot guess driver for %s",
145 1 : m_outputDataset.GetName().c_str());
146 1 : return false;
147 : }
148 10 : m_format = aosFormats[0];
149 : }
150 :
151 : auto poOutDrv =
152 18 : GetGDALDriverManager()->GetDriverByName(m_format.c_str());
153 18 : if (!poOutDrv)
154 : {
155 : // shouldn't happen given checks done in GDALAlgorithm unless
156 : // someone deregister the driver between ParseCommandLineArgs() and
157 : // Run()
158 1 : ReportError(CE_Failure, CPLE_AppDefined, "Driver %s does not exist",
159 : m_format.c_str());
160 1 : return false;
161 : }
162 :
163 17 : const CPLStringList aosCreationOptions(m_creationOptions);
164 34 : poDstDSUniquePtr.reset(
165 17 : poOutDrv->Create(m_outputDataset.GetName().c_str(), 0, 0, 0,
166 : GDT_Unknown, aosCreationOptions.List()));
167 17 : poDstDS = poDstDSUniquePtr.get();
168 17 : if (!poDstDS)
169 1 : return false;
170 : }
171 :
172 31 : OGRLayer *poDstLayer = nullptr;
173 :
174 31 : if (m_outputLayerName.empty())
175 : {
176 24 : if (bNewDataset)
177 : {
178 16 : auto poOutDrv = poDstDS->GetDriver();
179 16 : if (poOutDrv && EQUAL(poOutDrv->GetDescription(), "ESRI Shapefile"))
180 : m_outputLayerName =
181 5 : CPLGetBasenameSafe(m_outputDataset.GetName().c_str());
182 : else
183 11 : m_outputLayerName = "output";
184 : }
185 8 : else if (m_appendLayer)
186 : {
187 3 : if (poDstDS->GetLayerCount() == 1)
188 2 : poDstLayer = poDstDS->GetLayer(0);
189 : else
190 : {
191 1 : ReportError(CE_Failure, CPLE_AppDefined,
192 : "--output-layer should be specified");
193 1 : return false;
194 : }
195 : }
196 5 : else if (m_overwriteLayer)
197 : {
198 3 : if (poDstDS->GetLayerCount() == 1)
199 : {
200 2 : if (poDstDS->DeleteLayer(0) != OGRERR_NONE)
201 : {
202 1 : return false;
203 : }
204 : }
205 : else
206 : {
207 1 : ReportError(CE_Failure, CPLE_AppDefined,
208 : "--output-layer should be specified");
209 1 : return false;
210 : }
211 : }
212 : else
213 : {
214 2 : ReportError(CE_Failure, CPLE_AppDefined,
215 : "--output-layer should be specified");
216 2 : return false;
217 : }
218 : }
219 7 : else if (m_overwriteLayer)
220 : {
221 3 : const int nLayerIdx = poDstDS->GetLayerIndex(m_outputLayerName.c_str());
222 3 : if (nLayerIdx < 0)
223 : {
224 1 : ReportError(CE_Failure, CPLE_AppDefined,
225 : "Layer '%s' does not exist", m_outputLayerName.c_str());
226 1 : return false;
227 : }
228 2 : if (poDstDS->DeleteLayer(nLayerIdx) != OGRERR_NONE)
229 : {
230 1 : return false;
231 : }
232 : }
233 4 : else if (m_appendLayer)
234 : {
235 2 : poDstLayer = poDstDS->GetLayerByName(m_outputLayerName.c_str());
236 2 : if (!poDstLayer)
237 : {
238 1 : ReportError(CE_Failure, CPLE_AppDefined,
239 : "Layer '%s' does not exist", m_outputLayerName.c_str());
240 1 : return false;
241 : }
242 : }
243 :
244 23 : if (!bNewDataset && m_update && !m_appendLayer && !m_overwriteLayer)
245 : {
246 2 : poDstLayer = poDstDS->GetLayerByName(m_outputLayerName.c_str());
247 2 : if (poDstLayer)
248 : {
249 1 : ReportError(CE_Failure, CPLE_AppDefined,
250 : "Output layer '%s' already exists. Specify "
251 : "--%s, --%s, --%s or "
252 : "--%s + --output-layer with a different name",
253 : m_outputLayerName.c_str(), GDAL_ARG_NAME_OVERWRITE,
254 : GDAL_ARG_NAME_OVERWRITE_LAYER, GDAL_ARG_NAME_APPEND,
255 : GDAL_ARG_NAME_UPDATE);
256 1 : return false;
257 : }
258 : }
259 :
260 : OGRLayer *poInputLayer;
261 22 : if (m_inputLayerName.empty() && poSrcDS->GetLayerCount() == 1)
262 21 : poInputLayer = poSrcDS->GetLayer(0);
263 : else
264 1 : poInputLayer = poSrcDS->GetLayerByName(m_inputLayerName.c_str());
265 22 : if (!poInputLayer)
266 : {
267 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot get input layer '%s'",
268 : m_inputLayerName.c_str());
269 1 : return false;
270 : }
271 :
272 : OGRLayer *poMethodLayer;
273 21 : if (m_methodLayerName.empty() && poMethodDS->GetLayerCount() == 1)
274 20 : poMethodLayer = poMethodDS->GetLayer(0);
275 : else
276 1 : poMethodLayer = poMethodDS->GetLayerByName(m_methodLayerName.c_str());
277 21 : if (!poMethodLayer)
278 : {
279 1 : ReportError(CE_Failure, CPLE_AppDefined, "Cannot get method layer '%s'",
280 : m_methodLayerName.c_str());
281 1 : return false;
282 : }
283 :
284 20 : if (bNewDataset || !m_appendLayer)
285 : {
286 17 : const CPLStringList aosLayerCreationOptions(m_layerCreationOptions);
287 :
288 : const OGRwkbGeometryType eType =
289 17 : !m_geometryType.empty() ? OGRFromOGCGeomType(m_geometryType.c_str())
290 16 : : poInputLayer->GetGeomType();
291 34 : poDstLayer = poDstDS->CreateLayer(m_outputLayerName.c_str(),
292 17 : poInputLayer->GetSpatialRef(), eType,
293 : aosLayerCreationOptions.List());
294 : }
295 20 : if (!poDstLayer)
296 1 : return false;
297 :
298 38 : CPLStringList aosOptions;
299 :
300 19 : if (m_inputFields.empty() && !m_noInputFields)
301 12 : m_allInputFields = true;
302 :
303 19 : if (m_methodFields.empty() && !m_noMethodFields && !m_allMethodFields)
304 : {
305 22 : if (m_operation == "update" || m_operation == "clip" ||
306 10 : m_operation == "erase")
307 3 : m_noMethodFields = true;
308 : else
309 9 : m_allMethodFields = true;
310 : }
311 :
312 19 : if (m_noInputFields && m_noMethodFields)
313 : {
314 6 : aosOptions.SetNameValue("ADD_INPUT_FIELDS", "NO");
315 6 : aosOptions.SetNameValue("ADD_METHOD_FIELDS", "NO");
316 : }
317 : else
318 : {
319 : // Copy fields from input or method layer to output layer
320 : const auto CopyFields =
321 22 : [poDstLayer](OGRLayer *poSrcLayer, const std::string &prefix,
322 53 : const std::vector<std::string> &srcFields)
323 : {
324 : const auto contains =
325 4 : [](const std::vector<std::string> &v, const std::string &s)
326 4 : { return std::find(v.begin(), v.end(), s) != v.end(); };
327 :
328 22 : const auto poOutFDefn = poDstLayer->GetLayerDefn();
329 22 : const auto poFDefn = poSrcLayer->GetLayerDefn();
330 22 : const int nCount = poFDefn->GetFieldCount();
331 59 : for (int i = 0; i < nCount; ++i)
332 : {
333 39 : const auto poSrcFieldDefn = poFDefn->GetFieldDefn(i);
334 39 : const char *pszName = poSrcFieldDefn->GetNameRef();
335 39 : if (srcFields.empty() || contains(srcFields, pszName))
336 : {
337 37 : OGRFieldDefn oField(*poSrcFieldDefn);
338 37 : const std::string outName = prefix + pszName;
339 37 : whileUnsealing(&oField)->SetName(outName.c_str());
340 68 : if (poOutFDefn->GetFieldIndex(outName.c_str()) < 0 &&
341 31 : poDstLayer->CreateField(&oField) != OGRERR_NONE)
342 : {
343 2 : return false;
344 : }
345 : }
346 : }
347 20 : return true;
348 13 : };
349 :
350 13 : if (!m_noInputFields)
351 : {
352 51 : if (!GetArg("input-prefix")->IsExplicitlySet() &&
353 51 : m_inputPrefix.empty() && !m_noMethodFields)
354 : {
355 9 : m_inputPrefix = "input_";
356 : }
357 13 : if (!m_inputPrefix.empty())
358 : {
359 9 : aosOptions.SetNameValue("INPUT_PREFIX", m_inputPrefix.c_str());
360 : }
361 13 : if (!CopyFields(poInputLayer, m_inputPrefix, m_inputFields))
362 2 : return false;
363 : }
364 :
365 12 : if (!m_noMethodFields)
366 : {
367 35 : if (!GetArg("method-prefix")->IsExplicitlySet() &&
368 35 : m_methodPrefix.empty() && !m_noInputFields)
369 : {
370 8 : m_methodPrefix = "method_";
371 : }
372 9 : if (!m_methodPrefix.empty())
373 : {
374 : aosOptions.SetNameValue("METHOD_PREFIX",
375 8 : m_methodPrefix.c_str());
376 : }
377 9 : if (!CopyFields(poMethodLayer, m_methodPrefix, m_methodFields))
378 1 : return false;
379 : }
380 : }
381 :
382 17 : if (OGR_GT_IsSubClassOf(poDstLayer->GetGeomType(), wkbGeometryCollection))
383 : {
384 1 : aosOptions.SetNameValue("PROMOTE_TO_MULTI", "YES");
385 : }
386 :
387 : const std::map<std::string, decltype(&OGRLayer::Union)>
388 : mapOperationToMethod = {
389 : {"union", &OGRLayer::Union},
390 : {"intersection", &OGRLayer::Intersection},
391 : {"sym-difference", &OGRLayer::SymDifference},
392 : {"identity", &OGRLayer::Identity},
393 : {"update", &OGRLayer::Update},
394 : {"clip", &OGRLayer::Clip},
395 : {"erase", &OGRLayer::Erase},
396 153 : };
397 :
398 17 : const auto oIter = mapOperationToMethod.find(m_operation);
399 17 : CPLAssert(oIter != mapOperationToMethod.end());
400 17 : const auto pFunc = oIter->second;
401 : const bool bOK =
402 17 : (poInputLayer->*pFunc)(poMethodLayer, poDstLayer, aosOptions.List(),
403 17 : pfnProgress, pProgressData) == OGRERR_NONE;
404 17 : if (bOK && !m_outputDataset.GetDatasetRef())
405 : {
406 11 : m_outputDataset.Set(std::move(poDstDSUniquePtr));
407 : }
408 :
409 17 : return bOK;
410 : #else
411 : (void)pfnProgress;
412 : (void)pProgressData;
413 : ReportError(CE_Failure, CPLE_NotSupported,
414 : "This algorithm is only supported for builds against GEOS");
415 : return false;
416 : #endif
417 : }
418 :
419 : //! @endcond
|