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