Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster contour" subcommand
5 : * Author: Alessandro Pasotti <elpaso at itopen dot it>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Alessandro Pasotti <elpaso at itopen dot it>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include <cmath>
14 :
15 : #include "gdalalg_raster_contour.h"
16 :
17 : #include "cpl_conv.h"
18 : #include "gdal_priv.h"
19 : #include "gdal_utils.h"
20 : #include "gdal_alg.h"
21 : #include "gdal_utils_priv.h"
22 :
23 : //! @cond Doxygen_Suppress
24 :
25 : #ifndef _
26 : #define _(x) (x)
27 : #endif
28 :
29 : /************************************************************************/
30 : /* GDALRasterContourAlgorithm::GDALRasterContourAlgorithm() */
31 : /************************************************************************/
32 :
33 37 : GDALRasterContourAlgorithm::GDALRasterContourAlgorithm(bool standaloneStep)
34 : : GDALPipelineStepAlgorithm(
35 : NAME, DESCRIPTION, HELP_URL,
36 0 : ConstructorOptions()
37 37 : .SetStandaloneStep(standaloneStep)
38 37 : .SetAddAppendLayerArgument(false)
39 37 : .SetAddOverwriteLayerArgument(false)
40 37 : .SetAddUpdateArgument(false)
41 37 : .SetAddUpsertArgument(false)
42 37 : .SetAddSkipErrorsArgument(false)
43 74 : .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
44 : {
45 37 : m_outputLayerName = "contour";
46 :
47 37 : AddProgressArg();
48 37 : if (standaloneStep)
49 : {
50 20 : AddRasterInputArgs(false, false);
51 20 : AddVectorOutputArgs(false, false);
52 : }
53 :
54 : // gdal_contour specific options
55 37 : AddBandArg(&m_band).SetDefault(1);
56 :
57 : AddArg("elevation-name", 0, _("Name of the elevation field"),
58 37 : &m_elevAttributeName);
59 37 : AddArg("min-name", 0, _("Name of the minimum elevation field"), &m_amin);
60 37 : AddArg("max-name", 0, _("Name of the maximum elevation field"), &m_amax);
61 37 : AddArg("3d", 0, _("Force production of 3D vectors instead of 2D"), &m_3d);
62 :
63 : AddArg("src-nodata", 0, _("Input pixel value to treat as 'nodata'"),
64 37 : &m_sNodata);
65 74 : AddArg("interval", 0, _("Elevation interval between contours"), &m_interval)
66 74 : .SetMutualExclusionGroup("levels")
67 37 : .SetMinValueExcluded(0);
68 74 : AddArg("levels", 0, _("List of contour levels"), &m_levels)
69 37 : .SetMutualExclusionGroup("levels");
70 : AddArg("exp-base", 'e', _("Base for exponential contour level generation"),
71 74 : &m_expBase)
72 37 : .SetMutualExclusionGroup("levels");
73 74 : AddArg("offset", 0, _("Offset to apply to contour levels"), &m_offset)
74 37 : .AddAlias("off");
75 : AddArg("polygonize", 'p', _("Create polygons instead of lines"),
76 37 : &m_polygonize);
77 : AddArg("group-transactions", 0,
78 : _("Group n features per transaction (default 100 000)"),
79 74 : &m_groupTransactions)
80 37 : .SetMinValueIncluded(0);
81 37 : }
82 :
83 : /************************************************************************/
84 : /* GDALRasterContourAlgorithm::RunImpl() */
85 : /************************************************************************/
86 :
87 13 : bool GDALRasterContourAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
88 : void *pProgressData)
89 : {
90 13 : GDALPipelineStepRunContext stepCtxt;
91 13 : stepCtxt.m_pfnProgress = pfnProgress;
92 13 : stepCtxt.m_pProgressData = pProgressData;
93 13 : return RunPreStepPipelineValidations() && RunStep(stepCtxt);
94 : }
95 :
96 : /************************************************************************/
97 : /* GDALRasterContourAlgorithm::RunStep() */
98 : /************************************************************************/
99 :
100 14 : bool GDALRasterContourAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
101 : {
102 14 : CPLErrorReset();
103 :
104 14 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
105 14 : CPLAssert(poSrcDS);
106 :
107 14 : CPLAssert(!m_outputDataset.GetDatasetRef());
108 :
109 28 : CPLStringList aosOptions;
110 :
111 28 : std::string outputFilename;
112 14 : if (m_standaloneStep)
113 : {
114 13 : outputFilename = m_outputDataset.GetName();
115 13 : if (!m_format.empty())
116 : {
117 0 : aosOptions.AddString("-of");
118 0 : aosOptions.AddString(m_format.c_str());
119 : }
120 :
121 14 : for (const auto &co : m_creationOptions)
122 : {
123 1 : aosOptions.push_back("-co");
124 1 : aosOptions.push_back(co.c_str());
125 : }
126 :
127 14 : for (const auto &co : m_layerCreationOptions)
128 : {
129 1 : aosOptions.push_back("-lco");
130 1 : aosOptions.push_back(co.c_str());
131 : }
132 : }
133 : else
134 : {
135 1 : if (GetGDALDriverManager()->GetDriverByName("GPKG"))
136 : {
137 1 : aosOptions.AddString("-of");
138 1 : aosOptions.AddString("GPKG");
139 :
140 1 : outputFilename = CPLGenerateTempFilenameSafe("_contour") + ".gpkg";
141 : }
142 : else
143 : {
144 0 : aosOptions.AddString("-of");
145 0 : aosOptions.AddString("MEM");
146 : }
147 : }
148 :
149 14 : if (m_band > 0)
150 : {
151 14 : aosOptions.AddString("-b");
152 14 : aosOptions.AddString(CPLSPrintf("%d", m_band));
153 : }
154 14 : if (!m_elevAttributeName.empty())
155 : {
156 5 : aosOptions.AddString("-a");
157 5 : aosOptions.AddString(m_elevAttributeName);
158 : }
159 14 : if (!m_amin.empty())
160 : {
161 6 : aosOptions.AddString("-amin");
162 6 : aosOptions.AddString(m_amin);
163 : }
164 14 : if (!m_amax.empty())
165 : {
166 6 : aosOptions.AddString("-amax");
167 6 : aosOptions.AddString(m_amax);
168 : }
169 14 : if (m_3d)
170 : {
171 0 : aosOptions.AddString("-3d");
172 : }
173 14 : if (!std::isnan(m_sNodata))
174 : {
175 1 : aosOptions.AddString("-snodata");
176 1 : aosOptions.AddString(CPLSPrintf("%.16g", m_sNodata));
177 : }
178 14 : if (m_levels.size() > 0)
179 : {
180 12 : for (const auto &level : m_levels)
181 : {
182 8 : aosOptions.AddString("-fl");
183 8 : aosOptions.AddString(level);
184 : }
185 : }
186 14 : if (!std::isnan(m_interval))
187 : {
188 8 : aosOptions.AddString("-i");
189 8 : aosOptions.AddString(CPLSPrintf("%.16g", m_interval));
190 : }
191 14 : if (m_expBase > 0)
192 : {
193 1 : aosOptions.AddString("-e");
194 1 : aosOptions.AddString(CPLSPrintf("%d", m_expBase));
195 : }
196 14 : if (!std::isnan(m_offset))
197 : {
198 1 : aosOptions.AddString("-off");
199 1 : aosOptions.AddString(CPLSPrintf("%.16g", m_offset));
200 : }
201 14 : if (m_polygonize)
202 : {
203 5 : aosOptions.AddString("-p");
204 : }
205 14 : if (!m_outputLayerName.empty())
206 : {
207 14 : aosOptions.AddString("-nln");
208 14 : aosOptions.AddString(m_outputLayerName);
209 : }
210 :
211 : // Check that one of --interval, --levels, --exp-base is specified
212 14 : if (m_levels.size() == 0 && std::isnan(m_interval) && m_expBase == 0)
213 : {
214 1 : ReportError(
215 : CE_Failure, CPLE_AppDefined,
216 : "One of 'interval', 'levels', 'exp-base' must be specified.");
217 1 : return false;
218 : }
219 :
220 : // Check that interval is not negative
221 13 : if (!std::isnan(m_interval) && m_interval < 0)
222 : {
223 0 : ReportError(CE_Failure, CPLE_AppDefined,
224 : "Interval must be a positive number.");
225 0 : return false;
226 : }
227 :
228 13 : aosOptions.AddString(m_inputDataset[0].GetName());
229 13 : aosOptions.AddString(outputFilename);
230 :
231 13 : bool bRet = false;
232 26 : GDALContourOptionsForBinary optionsForBinary;
233 : std::unique_ptr<GDALContourOptions, decltype(&GDALContourOptionsFree)>
234 : psOptions{GDALContourOptionsNew(aosOptions.List(), &optionsForBinary),
235 13 : GDALContourOptionsFree};
236 13 : if (psOptions)
237 : {
238 13 : GDALDatasetH hSrcDS{poSrcDS};
239 13 : GDALRasterBandH hBand{nullptr};
240 13 : GDALDatasetH hDstDS{m_outputDataset.GetDatasetRef()};
241 13 : OGRLayerH hLayer{nullptr};
242 13 : char **papszStringOptions = nullptr;
243 :
244 13 : bRet = GDALContourProcessOptions(psOptions.get(), &papszStringOptions,
245 : &hSrcDS, &hBand, &hDstDS,
246 : &hLayer) == CE_None;
247 :
248 13 : if (bRet)
249 : {
250 13 : bRet = GDALContourGenerateEx(hBand, hLayer, papszStringOptions,
251 : ctxt.m_pfnProgress,
252 : ctxt.m_pProgressData) == CE_None;
253 : }
254 :
255 13 : CSLDestroy(papszStringOptions);
256 :
257 13 : auto poDstDS = GDALDataset::FromHandle(hDstDS);
258 13 : if (bRet)
259 : {
260 13 : bRet = poDstDS != nullptr;
261 : }
262 13 : if (poDstDS && !m_standaloneStep && !outputFilename.empty())
263 : {
264 1 : poDstDS->MarkSuppressOnClose();
265 1 : if (bRet)
266 1 : bRet = poDstDS->FlushCache() == CE_None;
267 1 : VSIUnlink(outputFilename.c_str());
268 : }
269 13 : m_outputDataset.Set(std::unique_ptr<GDALDataset>(poDstDS));
270 : }
271 :
272 13 : return bRet;
273 : }
274 :
275 : GDALRasterContourAlgorithmStandalone::~GDALRasterContourAlgorithmStandalone() =
276 : default;
277 :
278 : //! @endcond
|