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