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