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