/src/gdal/apps/gdalalg_raster_neighbors.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: "neighbors" step of "raster pipeline" |
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_neighbors.h" |
14 | | |
15 | | #include "gdal_priv.h" |
16 | | #include "gdal_priv_templates.hpp" |
17 | | #include "vrtdataset.h" |
18 | | |
19 | | #include <algorithm> |
20 | | #include <limits> |
21 | | #include <map> |
22 | | #include <optional> |
23 | | #include <set> |
24 | | #include <utility> |
25 | | #include <vector> |
26 | | |
27 | | //! @cond Doxygen_Suppress |
28 | | |
29 | | #ifndef _ |
30 | 0 | #define _(x) (x) |
31 | | #endif |
32 | | |
33 | | static const std::set<std::string> oSetKernelNames = { |
34 | | "u", "v", "equal", "edge1", |
35 | | "edge2", "sharpen", "gaussian", "unsharp-masking"}; |
36 | | |
37 | | namespace |
38 | | { |
39 | | struct KernelDef |
40 | | { |
41 | | int size = 0; |
42 | | std::vector<double> adfCoefficients{}; |
43 | | }; |
44 | | } // namespace |
45 | | |
46 | | // clang-format off |
47 | | // Cf https://en.wikipedia.org/wiki/Kernel_(image_processing) |
48 | | static const std::map<std::string, std::pair<int, std::vector<int>>> oMapKernelNameToMatrix = { |
49 | | { "u", { 3, { 0, 0, 0, |
50 | | -1, 0, 1, |
51 | | 0, 0, 0 } } }, |
52 | | { "v", { 3, { 0, -1, 0, |
53 | | 0, 0, 0, |
54 | | 0, 1, 0 } } }, |
55 | | { "edge1", { 3, { 0, -1, 0, |
56 | | -1, 4, -1, |
57 | | 0, -1, 0 } } }, |
58 | | { "edge2", { 3, { -1, -1, -1, |
59 | | -1, 8, -1, |
60 | | -1, -1, -1 } } }, |
61 | | { "sharpen", { 3, { 0, -1, 0, |
62 | | -1, 5, -1, |
63 | | 0, -1, 0 } } }, |
64 | | { "gaussian-3x3", { 3, { 1, 2, 1, |
65 | | 2, 4, 2, |
66 | | 1, 2, 1 } } }, |
67 | | { "gaussian-5x5", { 5, { 1, 4, 6, 4, 1, |
68 | | 4, 16, 24, 16, 4, |
69 | | 6, 24, 36, 24, 6, |
70 | | 4, 16, 24, 16, 4, |
71 | | 1, 4, 6, 4, 1, } } }, |
72 | | { "unsharp-masking-5x5", { 5, { 1, 4, 6, 4, 1, |
73 | | 4, 16, 24, 16, 4, |
74 | | 6, 24, -476, 24, 6, |
75 | | 4, 16, 24, 16, 4, |
76 | | 1, 4, 6, 4, 1, } } }, |
77 | | }; |
78 | | |
79 | | // clang-format on |
80 | | |
81 | | /************************************************************************/ |
82 | | /* CreateDerivedBandXML() */ |
83 | | /************************************************************************/ |
84 | | |
85 | | static bool CreateDerivedBandXML(VRTDataset *poVRTDS, GDALRasterBand *poSrcBand, |
86 | | GDALDataType eType, const std::string &noData, |
87 | | const std::string &method, |
88 | | const KernelDef &kernelDef) |
89 | 0 | { |
90 | 0 | poVRTDS->AddBand(eType, nullptr); |
91 | |
|
92 | 0 | std::optional<double> dstNoData; |
93 | 0 | bool autoSelectNoDataValue = false; |
94 | 0 | if (noData.empty()) |
95 | 0 | { |
96 | 0 | autoSelectNoDataValue = true; |
97 | 0 | } |
98 | 0 | else if (noData != "none") |
99 | 0 | { |
100 | | // Already validated to be numeric by the validation action |
101 | 0 | dstNoData = CPLAtof(noData.c_str()); |
102 | 0 | } |
103 | |
|
104 | 0 | auto poVRTBand = cpl::down_cast<VRTSourcedRasterBand *>( |
105 | 0 | poVRTDS->GetRasterBand(poVRTDS->GetRasterCount())); |
106 | |
|
107 | 0 | auto poSource = std::make_unique<VRTKernelFilteredSource>(); |
108 | 0 | poSrcBand->GetDataset()->Reference(); |
109 | 0 | poSource->SetSrcBand(poSrcBand); |
110 | 0 | poSource->SetKernel(kernelDef.size, /* separable = */ false, |
111 | 0 | kernelDef.adfCoefficients); |
112 | 0 | poSource->SetNormalized(method != "sum"); |
113 | 0 | if (method != "sum" && method != "mean") |
114 | 0 | poSource->SetFunction(method.c_str()); |
115 | |
|
116 | 0 | int bSrcHasNoData = false; |
117 | 0 | const double dfNoDataValue = poSrcBand->GetNoDataValue(&bSrcHasNoData); |
118 | 0 | if (bSrcHasNoData) |
119 | 0 | { |
120 | 0 | poSource->SetNoDataValue(dfNoDataValue); |
121 | 0 | if (autoSelectNoDataValue && !dstNoData.has_value()) |
122 | 0 | { |
123 | 0 | dstNoData = dfNoDataValue; |
124 | 0 | } |
125 | 0 | } |
126 | |
|
127 | 0 | if (dstNoData.has_value()) |
128 | 0 | { |
129 | 0 | if (!GDALIsValueExactAs(dstNoData.value(), eType)) |
130 | 0 | { |
131 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
132 | 0 | "Band output type %s cannot represent NoData value %g", |
133 | 0 | GDALGetDataTypeName(eType), dstNoData.value()); |
134 | 0 | return false; |
135 | 0 | } |
136 | | |
137 | 0 | poVRTBand->SetNoDataValue(dstNoData.value()); |
138 | 0 | } |
139 | | |
140 | 0 | poVRTBand->AddSource(std::move(poSource)); |
141 | |
|
142 | 0 | return true; |
143 | 0 | } |
144 | | |
145 | | /************************************************************************/ |
146 | | /* GDALNeighborsCreateVRTDerived() */ |
147 | | /************************************************************************/ |
148 | | |
149 | | static std::unique_ptr<GDALDataset> |
150 | | GDALNeighborsCreateVRTDerived(GDALDataset *poSrcDS, int nBand, |
151 | | GDALDataType eType, const std::string &noData, |
152 | | const std::vector<std::string> &methods, |
153 | | const std::vector<KernelDef> &aKernelDefs) |
154 | 0 | { |
155 | 0 | CPLAssert(methods.size() == aKernelDefs.size()); |
156 | | |
157 | 0 | auto ds = std::make_unique<VRTDataset>(poSrcDS->GetRasterXSize(), |
158 | 0 | poSrcDS->GetRasterYSize()); |
159 | 0 | GDALGeoTransform gt; |
160 | 0 | if (poSrcDS->GetGeoTransform(gt) == CE_None) |
161 | 0 | ds->SetGeoTransform(gt); |
162 | 0 | if (const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef()) |
163 | 0 | { |
164 | 0 | ds->SetSpatialRef(poSRS); |
165 | 0 | } |
166 | |
|
167 | 0 | bool ret = true; |
168 | 0 | if (nBand != 0) |
169 | 0 | { |
170 | 0 | for (size_t i = 0; i < aKernelDefs.size() && ret; ++i) |
171 | 0 | { |
172 | 0 | ret = |
173 | 0 | CreateDerivedBandXML(ds.get(), poSrcDS->GetRasterBand(nBand), |
174 | 0 | eType, noData, methods[i], aKernelDefs[i]); |
175 | 0 | } |
176 | 0 | } |
177 | 0 | else |
178 | 0 | { |
179 | 0 | for (int iBand = 1; iBand <= poSrcDS->GetRasterCount(); ++iBand) |
180 | 0 | { |
181 | 0 | for (size_t i = 0; i < aKernelDefs.size() && ret; ++i) |
182 | 0 | { |
183 | 0 | ret = CreateDerivedBandXML(ds.get(), |
184 | 0 | poSrcDS->GetRasterBand(iBand), eType, |
185 | 0 | noData, methods[i], aKernelDefs[i]); |
186 | 0 | } |
187 | 0 | } |
188 | 0 | } |
189 | 0 | if (!ret) |
190 | 0 | ds.reset(); |
191 | 0 | return ds; |
192 | 0 | } |
193 | | |
194 | | /************************************************************************/ |
195 | | /* GDALRasterNeighborsAlgorithm::GDALRasterNeighborsAlgorithm() */ |
196 | | /************************************************************************/ |
197 | | |
198 | | GDALRasterNeighborsAlgorithm::GDALRasterNeighborsAlgorithm( |
199 | | bool standaloneStep) noexcept |
200 | 0 | : GDALRasterPipelineStepAlgorithm( |
201 | 0 | NAME, DESCRIPTION, HELP_URL, |
202 | 0 | ConstructorOptions().SetStandaloneStep(standaloneStep)) |
203 | 0 | { |
204 | 0 | AddBandArg(&m_band); |
205 | |
|
206 | 0 | AddArg("method", 0, _("Method to combine weighed source pixels"), &m_method) |
207 | 0 | .SetChoices("mean", "sum", "min", "max", "stddev", "median", "mode"); |
208 | |
|
209 | 0 | AddArg("size", 0, _("Neighborhood size"), &m_size) |
210 | 0 | .SetMinValueIncluded(3) |
211 | 0 | .SetMaxValueIncluded(99) |
212 | 0 | .AddValidationAction( |
213 | 0 | [this]() |
214 | 0 | { |
215 | 0 | if ((m_size % 2) != 1) |
216 | 0 | { |
217 | 0 | ReportError(CE_Failure, CPLE_IllegalArg, |
218 | 0 | "The value of 'size' must be an odd number."); |
219 | 0 | return false; |
220 | 0 | } |
221 | 0 | return true; |
222 | 0 | }); |
223 | |
|
224 | 0 | AddArg("kernel", 0, _("Convolution kernel(s) to apply"), &m_kernel) |
225 | 0 | .SetPackedValuesAllowed(false) |
226 | 0 | .SetMinCount(1) |
227 | 0 | .SetMinCharCount(1) |
228 | 0 | .SetRequired() |
229 | 0 | .SetAutoCompleteFunction( |
230 | 0 | [](const std::string &v) |
231 | 0 | { |
232 | 0 | std::vector<std::string> ret; |
233 | 0 | if (v.empty() || v.front() != '[') |
234 | 0 | { |
235 | 0 | ret.insert(ret.end(), oSetKernelNames.begin(), |
236 | 0 | oSetKernelNames.end()); |
237 | 0 | ret.push_back( |
238 | 0 | "[[val00,val10,...,valN0],...,[val0N,val1N,...valNN]]"); |
239 | 0 | } |
240 | 0 | return ret; |
241 | 0 | }) |
242 | 0 | .AddValidationAction( |
243 | 0 | [this]() |
244 | 0 | { |
245 | 0 | for (const std::string &kernel : m_kernel) |
246 | 0 | { |
247 | 0 | if (kernel.front() == '[' && kernel.back() == ']') |
248 | 0 | { |
249 | 0 | const CPLStringList aosValues(CSLTokenizeString2( |
250 | 0 | kernel.c_str(), "[] ,", |
251 | 0 | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); |
252 | 0 | const double dfSize = |
253 | 0 | static_cast<double>(aosValues.size()); |
254 | 0 | const int nSqrt = static_cast<int>(sqrt(dfSize) + 0.5); |
255 | 0 | if (!((aosValues.size() % 2) == 1 && |
256 | 0 | nSqrt * nSqrt == aosValues.size())) |
257 | 0 | { |
258 | 0 | ReportError( |
259 | 0 | CE_Failure, CPLE_IllegalArg, |
260 | 0 | "The number of values in the 'kernel' " |
261 | 0 | "argument must be an odd square number."); |
262 | 0 | return false; |
263 | 0 | } |
264 | 0 | for (int i = 0; i < aosValues.size(); ++i) |
265 | 0 | { |
266 | 0 | if (CPLGetValueType(aosValues[i]) == |
267 | 0 | CPL_VALUE_STRING) |
268 | 0 | { |
269 | 0 | ReportError(CE_Failure, CPLE_IllegalArg, |
270 | 0 | "Non-numeric value found in the " |
271 | 0 | "'kernel' argument"); |
272 | 0 | return false; |
273 | 0 | } |
274 | 0 | } |
275 | 0 | } |
276 | 0 | else if (!cpl::contains(oSetKernelNames, kernel)) |
277 | 0 | { |
278 | 0 | std::string osMsg = |
279 | 0 | "Valid values for 'kernel' argument are: "; |
280 | 0 | bool bFirst = true; |
281 | 0 | for (const auto &name : oSetKernelNames) |
282 | 0 | { |
283 | 0 | if (!bFirst) |
284 | 0 | osMsg += ", "; |
285 | 0 | bFirst = false; |
286 | 0 | osMsg += '\''; |
287 | 0 | osMsg += name; |
288 | 0 | osMsg += '\''; |
289 | 0 | } |
290 | 0 | osMsg += " or " |
291 | 0 | "[[val00,val10,...,valN0],...,[val0N,val1N,..." |
292 | 0 | "valNN]]"; |
293 | 0 | ReportError(CE_Failure, CPLE_IllegalArg, "%s", |
294 | 0 | osMsg.c_str()); |
295 | 0 | return false; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | return true; |
299 | 0 | }); |
300 | |
|
301 | 0 | AddOutputDataTypeArg(&m_type).SetDefault("Float64"); |
302 | |
|
303 | 0 | AddNodataArg(&m_nodata, true); |
304 | |
|
305 | 0 | AddValidationAction( |
306 | 0 | [this]() |
307 | 0 | { |
308 | 0 | if (m_method.size() > 1 && m_method.size() != m_kernel.size()) |
309 | 0 | { |
310 | 0 | ReportError( |
311 | 0 | CE_Failure, CPLE_AppDefined, |
312 | 0 | "The number of values for the 'method' argument should " |
313 | 0 | "be one or exactly the number of values of 'kernel'"); |
314 | 0 | return false; |
315 | 0 | } |
316 | | |
317 | 0 | if (m_size > 0) |
318 | 0 | { |
319 | 0 | for (const std::string &kernel : m_kernel) |
320 | 0 | { |
321 | 0 | if (kernel == "gaussian") |
322 | 0 | { |
323 | 0 | if (m_size != 3 && m_size != 5) |
324 | 0 | { |
325 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
326 | 0 | "Currently only size = 3 or 5 is " |
327 | 0 | "supported for kernel '%s'", |
328 | 0 | kernel.c_str()); |
329 | 0 | return false; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | else if (kernel == "unsharp-masking") |
333 | 0 | { |
334 | 0 | if (m_size != 5) |
335 | 0 | { |
336 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
337 | 0 | "Currently only size = 5 is supported " |
338 | 0 | "for kernel '%s'", |
339 | 0 | kernel.c_str()); |
340 | 0 | return false; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | else if (kernel[0] == '[') |
344 | 0 | { |
345 | 0 | const CPLStringList aosValues(CSLTokenizeString2( |
346 | 0 | kernel.c_str(), "[] ,", |
347 | 0 | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); |
348 | 0 | const double dfSize = |
349 | 0 | static_cast<double>(aosValues.size()); |
350 | 0 | const int size = |
351 | 0 | static_cast<int>(std::floor(sqrt(dfSize) + 0.5)); |
352 | 0 | if (m_size != size) |
353 | 0 | { |
354 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
355 | 0 | "Value of 'size' argument (%d) " |
356 | 0 | "inconsistent with the one deduced " |
357 | 0 | "from the kernel matrix (%d)", |
358 | 0 | m_size, size); |
359 | 0 | return false; |
360 | 0 | } |
361 | 0 | } |
362 | 0 | else if (m_size != 3 && kernel != "equal" && |
363 | 0 | kernel[0] != '[') |
364 | 0 | { |
365 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
366 | 0 | "Currently only size = 3 is supported for " |
367 | 0 | "kernel '%s'", |
368 | 0 | kernel.c_str()); |
369 | 0 | return false; |
370 | 0 | } |
371 | 0 | } |
372 | 0 | } |
373 | | |
374 | 0 | return true; |
375 | 0 | }); |
376 | 0 | } |
377 | | |
378 | | /************************************************************************/ |
379 | | /* GetKernelDef() */ |
380 | | /************************************************************************/ |
381 | | |
382 | | static KernelDef GetKernelDef(const std::string &name, bool normalizeCoefs, |
383 | | double weightIfNotNormalized) |
384 | 0 | { |
385 | 0 | auto it = oMapKernelNameToMatrix.find(name); |
386 | 0 | CPLAssert(it != oMapKernelNameToMatrix.end()); |
387 | 0 | KernelDef def; |
388 | 0 | def.size = it->second.first; |
389 | 0 | int nSum = 0; |
390 | 0 | for (const int nVal : it->second.second) |
391 | 0 | nSum += nVal; |
392 | 0 | const double dfWeight = normalizeCoefs |
393 | 0 | ? 1.0 / (static_cast<double>(nSum) + |
394 | 0 | std::numeric_limits<double>::min()) |
395 | 0 | : weightIfNotNormalized; |
396 | 0 | for (const int nVal : it->second.second) |
397 | 0 | { |
398 | 0 | def.adfCoefficients.push_back(nVal * dfWeight); |
399 | 0 | } |
400 | 0 | return def; |
401 | 0 | } |
402 | | |
403 | | /************************************************************************/ |
404 | | /* GDALRasterNeighborsAlgorithm::RunStep() */ |
405 | | /************************************************************************/ |
406 | | |
407 | | bool GDALRasterNeighborsAlgorithm::RunStep(GDALPipelineStepRunContext &) |
408 | 0 | { |
409 | 0 | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
410 | 0 | CPLAssert(!m_outputDataset.GetDatasetRef()); |
411 | | |
412 | 0 | CPLAssert(m_band <= poSrcDS->GetRasterCount()); |
413 | | |
414 | 0 | auto eType = GDALGetDataTypeByName(m_type.c_str()); |
415 | 0 | if (eType == GDT_Unknown) |
416 | 0 | { |
417 | 0 | eType = GDT_Float64; |
418 | 0 | } |
419 | 0 | std::vector<KernelDef> aKernelDefs(m_kernel.size()); |
420 | 0 | std::vector<bool> abNullCoefficientSum(m_kernel.size()); |
421 | 0 | for (size_t i = 0; i < m_kernel.size(); ++i) |
422 | 0 | { |
423 | 0 | const std::string &kernel = m_kernel[i]; |
424 | 0 | if (!kernel.empty() && kernel[0] == '[') |
425 | 0 | { |
426 | 0 | const CPLStringList aosValues( |
427 | 0 | CSLTokenizeString2(kernel.c_str(), "[] ,", |
428 | 0 | CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); |
429 | 0 | const double dfSize = static_cast<double>(aosValues.size()); |
430 | 0 | KernelDef def; |
431 | 0 | def.size = static_cast<int>(std::floor(sqrt(dfSize) + 0.5)); |
432 | 0 | double dfSum = 0; |
433 | 0 | for (const char *pszC : cpl::Iterate(aosValues)) |
434 | 0 | { |
435 | 0 | const double dfV = CPLAtof(pszC); |
436 | 0 | dfSum += dfV; |
437 | | // Already validated to be numeric by the validation action |
438 | 0 | def.adfCoefficients.push_back(dfV); |
439 | 0 | } |
440 | 0 | abNullCoefficientSum[i] = std::fabs(dfSum) < 1e-10; |
441 | 0 | if (abNullCoefficientSum[i] && m_method.size() == m_kernel.size() && |
442 | 0 | m_method[i] == "mean") |
443 | 0 | { |
444 | 0 | ReportError( |
445 | 0 | CE_Failure, CPLE_AppDefined, |
446 | 0 | "Specifying method = 'mean' for a kernel whose sum of " |
447 | 0 | "coefficients is zero is not allowed. Use 'sum' instead"); |
448 | 0 | return false; |
449 | 0 | } |
450 | 0 | aKernelDefs[i] = std::move(def); |
451 | 0 | } |
452 | 0 | } |
453 | | |
454 | 0 | if (m_method.empty()) |
455 | 0 | { |
456 | 0 | for (size_t i = 0; i < m_kernel.size(); ++i) |
457 | 0 | { |
458 | 0 | const bool bIsZeroSumKernel = |
459 | 0 | m_kernel[i] == "u" || m_kernel[i] == "v" || |
460 | 0 | m_kernel[i] == "edge1" || m_kernel[i] == "edge2" || |
461 | 0 | abNullCoefficientSum[i]; |
462 | 0 | m_method.push_back(bIsZeroSumKernel ? "sum" : "mean"); |
463 | 0 | } |
464 | 0 | } |
465 | 0 | else if (m_method.size() == 1) |
466 | 0 | { |
467 | 0 | const std::string lastValue(m_method.back()); |
468 | 0 | m_method.resize(m_kernel.size(), lastValue); |
469 | 0 | } |
470 | |
|
471 | 0 | if (m_size == 0 && m_kernel[0][0] != '[') |
472 | 0 | m_size = m_kernel[0] == "unsharp-masking" ? 5 : 3; |
473 | |
|
474 | 0 | for (size_t i = 0; i < m_kernel.size(); ++i) |
475 | 0 | { |
476 | 0 | const std::string &kernel = m_kernel[i]; |
477 | 0 | if (aKernelDefs[i].adfCoefficients.empty()) |
478 | 0 | { |
479 | 0 | KernelDef def; |
480 | 0 | if (kernel == "edge1" || kernel == "edge2" || kernel == "sharpen") |
481 | 0 | { |
482 | 0 | CPLAssert(m_size == 3); |
483 | 0 | def = GetKernelDef(kernel, false, 1.0); |
484 | 0 | } |
485 | 0 | else if (kernel == "u" || kernel == "v") |
486 | 0 | { |
487 | 0 | CPLAssert(m_size == 3); |
488 | 0 | def = GetKernelDef(kernel, false, 0.5); |
489 | 0 | } |
490 | 0 | else if (kernel == "equal") |
491 | 0 | { |
492 | 0 | def.size = m_size; |
493 | 0 | const double dfWeight = |
494 | 0 | m_method[i] == "mean" |
495 | 0 | ? 1.0 / (static_cast<double>(m_size) * m_size + |
496 | 0 | std::numeric_limits<double>::min()) |
497 | 0 | : 1.0; |
498 | 0 | def.adfCoefficients.resize(static_cast<size_t>(m_size) * m_size, |
499 | 0 | dfWeight); |
500 | 0 | } |
501 | 0 | else if (kernel == "gaussian") |
502 | 0 | { |
503 | 0 | CPLAssert(m_size == 3 || m_size == 5); |
504 | 0 | def = GetKernelDef( |
505 | 0 | m_size == 3 ? "gaussian-3x3" : "gaussian-5x5", true, 0.0); |
506 | 0 | } |
507 | 0 | else if (kernel == "unsharp-masking") |
508 | 0 | { |
509 | 0 | CPLAssert(m_size == 5); |
510 | 0 | def = GetKernelDef("unsharp-masking-5x5", true, 0.0); |
511 | 0 | } |
512 | 0 | else |
513 | 0 | { |
514 | 0 | CPLAssert(false); |
515 | 0 | } |
516 | 0 | aKernelDefs[i] = std::move(def); |
517 | 0 | } |
518 | 0 | } |
519 | | |
520 | 0 | auto vrt = GDALNeighborsCreateVRTDerived(poSrcDS, m_band, eType, m_nodata, |
521 | 0 | m_method, aKernelDefs); |
522 | 0 | const bool ret = vrt != nullptr; |
523 | 0 | if (vrt) |
524 | 0 | { |
525 | 0 | m_outputDataset.Set(std::move(vrt)); |
526 | 0 | } |
527 | 0 | return ret; |
528 | 0 | } |
529 | | |
530 | | GDALRasterNeighborsAlgorithmStandalone:: |
531 | 0 | ~GDALRasterNeighborsAlgorithmStandalone() = default; |
532 | | |
533 | | //! @endcond |