Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster footprint" 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_raster_footprint.h"
14 :
15 : #include "cpl_conv.h"
16 :
17 : #include "gdal_priv.h"
18 : #include "gdal_utils.h"
19 :
20 : //! @cond Doxygen_Suppress
21 :
22 : #ifndef _
23 : #define _(x) (x)
24 : #endif
25 :
26 : /************************************************************************/
27 : /* GDALRasterFootprintAlgorithm::GDALRasterFootprintAlgorithm() */
28 : /************************************************************************/
29 :
30 94 : GDALRasterFootprintAlgorithm::GDALRasterFootprintAlgorithm(bool standaloneStep)
31 : : GDALPipelineStepAlgorithm(
32 : NAME, DESCRIPTION, HELP_URL,
33 0 : ConstructorOptions()
34 94 : .SetStandaloneStep(standaloneStep)
35 188 : .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
36 : {
37 94 : AddProgressArg();
38 :
39 94 : if (standaloneStep)
40 : {
41 79 : AddOpenOptionsArg(&m_openOptions).SetAvailableInPipelineStep(false);
42 79 : AddInputFormatsArg(&m_inputFormats)
43 237 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER})
44 79 : .SetAvailableInPipelineStep(false);
45 79 : AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER)
46 79 : .SetAvailableInPipelineStep(false);
47 :
48 79 : AddOutputDatasetArg(&m_outputDataset, GDAL_OF_VECTOR)
49 79 : .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT)
50 79 : .SetAvailableInPipelineStep(false);
51 : AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
52 79 : /* bGDALGAllowed = */ false)
53 : .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
54 316 : {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE})
55 79 : .SetAvailableInPipelineStep(false);
56 79 : AddCreationOptionsArg(&m_creationOptions)
57 79 : .SetAvailableInPipelineStep(false);
58 79 : AddLayerCreationOptionsArg(&m_layerCreationOptions)
59 79 : .SetAvailableInPipelineStep(false);
60 79 : AddUpdateArg(&m_update)
61 79 : .SetAvailableInPipelineStep(false)
62 79 : .SetHidden(); // needed for correct append execution
63 79 : AddAppendLayerArg(&m_appendLayer).SetAvailableInPipelineStep(false);
64 79 : AddOverwriteArg(&m_overwrite).SetAvailableInPipelineStep(false);
65 : }
66 : else
67 : {
68 15 : AddRasterHiddenInputDatasetArg();
69 : }
70 :
71 94 : m_outputLayerName = "footprint";
72 : AddArg(GDAL_ARG_NAME_OUTPUT_LAYER, 0, _("Output layer name"),
73 188 : &m_outputLayerName)
74 94 : .SetDefault(m_outputLayerName);
75 :
76 94 : AddBandArg(&m_bands);
77 : AddArg("combine-bands", 0,
78 : _("Defines how the mask bands of the selected bands are combined to "
79 : "generate a single mask band, before being vectorized."),
80 188 : &m_combineBands)
81 94 : .SetChoices("union", "intersection")
82 94 : .SetDefault(m_combineBands);
83 : AddArg("overview", 0, _("Which overview level of source file must be used"),
84 188 : &m_overview)
85 188 : .SetMutualExclusionGroup("overview-srcnodata")
86 94 : .SetMinValueIncluded(0);
87 : AddArg("input-nodata", 0, _("Set nodata values for input bands."),
88 188 : &m_srcNoData)
89 94 : .SetMinCount(1)
90 94 : .SetRepeatedArgAllowed(false)
91 188 : .AddHiddenAlias("src-nodata")
92 94 : .SetMutualExclusionGroup("overview-srcnodata");
93 : AddArg("coordinate-system", 0, _("Target coordinate system"),
94 188 : &m_coordinateSystem)
95 94 : .SetChoices("georeferenced", "pixel");
96 188 : AddArg(GDAL_ARG_NAME_OUTPUT_CRS, 0, _("Output CRS"), &m_dstCrs)
97 188 : .SetIsCRSArg()
98 188 : .AddHiddenAlias("dst-crs")
99 94 : .AddHiddenAlias("t_srs");
100 : AddArg("split-multipolygons", 0,
101 : _("Whether to split multipolygons as several features each with one "
102 : "single polygon"),
103 94 : &m_splitMultiPolygons);
104 : AddArg("convex-hull", 0,
105 : _("Whether to compute the convex hull of the footprint"),
106 94 : &m_convexHull);
107 : AddArg("densify-distance", 0,
108 : _("Maximum distance between 2 consecutive points of the output "
109 : "geometry."),
110 188 : &m_densifyVal)
111 94 : .SetMinValueExcluded(0);
112 : AddArg(
113 : "simplify-tolerance", 0,
114 : _("Tolerance used to merge consecutive points of the output geometry."),
115 188 : &m_simplifyVal)
116 94 : .SetMinValueExcluded(0);
117 : AddArg("min-ring-area", 0, _("Minimum value for the area of a ring"),
118 188 : &m_minRingArea)
119 94 : .SetMinValueIncluded(0);
120 : AddArg("max-points", 0,
121 188 : _("Maximum number of points of each output geometry"), &m_maxPoints)
122 94 : .SetDefault(m_maxPoints)
123 : .AddValidationAction(
124 11 : [this]()
125 : {
126 3 : if (m_maxPoints != "unlimited")
127 : {
128 3 : char *endptr = nullptr;
129 : const auto nVal =
130 3 : std::strtoll(m_maxPoints.c_str(), &endptr, 10);
131 5 : if (nVal < 4 ||
132 2 : endptr != m_maxPoints.c_str() + m_maxPoints.size())
133 : {
134 1 : ReportError(
135 : CE_Failure, CPLE_IllegalArg,
136 : "Value of 'max-points' should be a positive "
137 : "integer greater or equal to 4, or 'unlimited'");
138 1 : return false;
139 : }
140 : }
141 2 : return true;
142 94 : });
143 : AddArg("location-field", 0,
144 : _("Name of the field where the path of the input dataset will be "
145 : "stored."),
146 188 : &m_locationField)
147 94 : .SetDefault(m_locationField)
148 94 : .SetMutualExclusionGroup("location");
149 : AddArg("no-location-field", 0,
150 : _("Disable creating a field with the path of the input dataset"),
151 188 : &m_noLocation)
152 94 : .SetMutualExclusionGroup("location");
153 94 : AddAbsolutePathArg(&m_writeAbsolutePaths);
154 :
155 94 : AddValidationAction(
156 108 : [this]
157 : {
158 35 : if (m_inputDataset.size() == 1)
159 : {
160 34 : if (auto poSrcDS = m_inputDataset[0].GetDatasetRef())
161 : {
162 : const int nOvrCount =
163 34 : poSrcDS->GetRasterBand(1)->GetOverviewCount();
164 37 : if (m_overview >= 0 && poSrcDS->GetRasterCount() > 0 &&
165 3 : m_overview >= nOvrCount)
166 : {
167 2 : if (nOvrCount == 0)
168 : {
169 1 : ReportError(
170 : CE_Failure, CPLE_IllegalArg,
171 : "Source dataset has no overviews. "
172 : "Argument 'overview' should not be specified.");
173 : }
174 : else
175 : {
176 1 : ReportError(
177 : CE_Failure, CPLE_IllegalArg,
178 : "Source dataset has only %d overview levels. "
179 : "'overview' "
180 : "value should be strictly lower than this "
181 : "number.",
182 : nOvrCount);
183 : }
184 2 : return false;
185 : }
186 : }
187 : }
188 33 : return true;
189 : });
190 94 : }
191 :
192 : /************************************************************************/
193 : /* GDALRasterFootprintAlgorithm::RunImpl() */
194 : /************************************************************************/
195 :
196 30 : bool GDALRasterFootprintAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
197 : void *pProgressData)
198 : {
199 30 : GDALPipelineStepRunContext stepCtxt;
200 30 : stepCtxt.m_pfnProgress = pfnProgress;
201 30 : stepCtxt.m_pProgressData = pProgressData;
202 30 : return RunPreStepPipelineValidations() && RunStep(stepCtxt);
203 : }
204 :
205 : /************************************************************************/
206 : /* GDALRasterFootprintAlgorithm::RunStep() */
207 : /************************************************************************/
208 :
209 31 : bool GDALRasterFootprintAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
210 : {
211 31 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
212 31 : CPLAssert(poSrcDS);
213 :
214 62 : CPLStringList aosOptions;
215 :
216 62 : std::string outputFilename;
217 31 : if (m_standaloneStep)
218 : {
219 30 : outputFilename = m_outputDataset.GetName();
220 30 : if (!m_format.empty())
221 : {
222 25 : aosOptions.AddString("-of");
223 25 : aosOptions.AddString(m_format.c_str());
224 : }
225 :
226 31 : for (const auto &co : m_creationOptions)
227 : {
228 1 : aosOptions.push_back("-dsco");
229 1 : aosOptions.push_back(co.c_str());
230 : }
231 :
232 31 : for (const auto &co : m_layerCreationOptions)
233 : {
234 1 : aosOptions.push_back("-lco");
235 1 : aosOptions.push_back(co.c_str());
236 : }
237 : }
238 : else
239 : {
240 1 : if (GetGDALDriverManager()->GetDriverByName("GPKG"))
241 : {
242 1 : aosOptions.AddString("-of");
243 1 : aosOptions.AddString("GPKG");
244 :
245 : outputFilename =
246 1 : CPLGenerateTempFilenameSafe("_footprint") + ".gpkg";
247 : }
248 : else
249 : {
250 0 : aosOptions.AddString("-of");
251 0 : aosOptions.AddString("MEM");
252 : }
253 : }
254 :
255 37 : for (int band : m_bands)
256 : {
257 6 : aosOptions.push_back("-b");
258 6 : aosOptions.push_back(CPLSPrintf("%d", band));
259 : }
260 :
261 31 : aosOptions.push_back("-combine_bands");
262 31 : aosOptions.push_back(m_combineBands);
263 :
264 31 : if (m_overview >= 0)
265 : {
266 1 : aosOptions.push_back("-ovr");
267 1 : aosOptions.push_back(CPLSPrintf("%d", m_overview));
268 : }
269 :
270 31 : if (!m_srcNoData.empty())
271 : {
272 2 : aosOptions.push_back("-srcnodata");
273 4 : std::string s;
274 5 : for (double v : m_srcNoData)
275 : {
276 3 : if (!s.empty())
277 1 : s += " ";
278 3 : s += CPLSPrintf("%.17g", v);
279 : }
280 2 : aosOptions.push_back(s);
281 : }
282 :
283 31 : if (m_coordinateSystem == "pixel")
284 : {
285 1 : aosOptions.push_back("-t_cs");
286 1 : aosOptions.push_back("pixel");
287 : }
288 30 : else if (m_coordinateSystem == "georeferenced")
289 : {
290 1 : aosOptions.push_back("-t_cs");
291 1 : aosOptions.push_back("georef");
292 : }
293 :
294 31 : if (!m_dstCrs.empty())
295 : {
296 1 : aosOptions.push_back("-t_srs");
297 1 : aosOptions.push_back(m_dstCrs);
298 : }
299 :
300 31 : if (GetArg(GDAL_ARG_NAME_OUTPUT_LAYER)->IsExplicitlySet())
301 : {
302 5 : aosOptions.push_back("-lyr_name");
303 5 : aosOptions.push_back(m_outputLayerName.c_str());
304 : }
305 :
306 31 : if (m_splitMultiPolygons)
307 1 : aosOptions.push_back("-split_polys");
308 :
309 31 : if (m_convexHull)
310 1 : aosOptions.push_back("-convex_hull");
311 :
312 31 : if (m_densifyVal > 0)
313 : {
314 1 : aosOptions.push_back("-densify");
315 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_densifyVal));
316 : }
317 :
318 31 : if (m_simplifyVal > 0)
319 : {
320 1 : aosOptions.push_back("-simplify");
321 1 : aosOptions.push_back(CPLSPrintf("%.17g", m_simplifyVal));
322 : }
323 :
324 31 : aosOptions.push_back("-min_ring_area");
325 31 : aosOptions.push_back(CPLSPrintf("%.17g", m_minRingArea));
326 :
327 31 : aosOptions.push_back("-max_points");
328 31 : aosOptions.push_back(m_maxPoints);
329 :
330 31 : if (m_noLocation)
331 : {
332 1 : aosOptions.push_back("-no_location");
333 : }
334 : else
335 : {
336 30 : aosOptions.push_back("-location_field_name");
337 30 : aosOptions.push_back(m_locationField);
338 :
339 30 : if (m_writeAbsolutePaths)
340 1 : aosOptions.push_back("-write_absolute_path");
341 : }
342 :
343 31 : bool bOK = false;
344 : std::unique_ptr<GDALFootprintOptions, decltype(&GDALFootprintOptionsFree)>
345 : psOptions{GDALFootprintOptionsNew(aosOptions.List(), nullptr),
346 31 : GDALFootprintOptionsFree};
347 31 : if (psOptions)
348 : {
349 31 : GDALFootprintOptionsSetProgress(psOptions.get(), ctxt.m_pfnProgress,
350 : ctxt.m_pProgressData);
351 :
352 31 : GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
353 : GDALDatasetH hDstDS =
354 31 : GDALDataset::ToHandle(m_outputDataset.GetDatasetRef());
355 31 : auto poRetDS = GDALDataset::FromHandle(GDALFootprint(
356 31 : outputFilename.c_str(), hDstDS, hSrcDS, psOptions.get(), nullptr));
357 31 : bOK = poRetDS != nullptr;
358 31 : if (bOK && !hDstDS)
359 : {
360 28 : if (poRetDS && !m_standaloneStep && !outputFilename.empty())
361 : {
362 1 : bOK = poRetDS->FlushCache() == CE_None;
363 : #if !defined(__APPLE__)
364 : // For some unknown reason, unlinking the file on MacOSX
365 : // leads to later "disk I/O error". See https://github.com/OSGeo/gdal/issues/13794
366 1 : VSIUnlink(outputFilename.c_str());
367 : #endif
368 1 : poRetDS->MarkSuppressOnClose();
369 : }
370 28 : m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
371 : }
372 : }
373 :
374 62 : return bOK;
375 : }
376 :
377 : GDALRasterFootprintAlgorithmStandalone::
378 : ~GDALRasterFootprintAlgorithmStandalone() = default;
379 :
380 : //! @endcond
|