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