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