/src/gdal/apps/gdalalg_raster_proximity.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: gdal "raster proximity" 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 "gdalalg_raster_proximity.h" |
14 | | |
15 | | #include <cmath> |
16 | | #include <limits> |
17 | | |
18 | | #include "cpl_conv.h" |
19 | | |
20 | | #include "gdal_alg.h" |
21 | | #include "gdal_priv.h" |
22 | | |
23 | | //! @cond Doxygen_Suppress |
24 | | |
25 | | #ifndef _ |
26 | 0 | #define _(x) (x) |
27 | | #endif |
28 | | |
29 | | /************************************************************************/ |
30 | | /* GDALRasterProximityAlgorithm::GDALRasterProximityAlgorithm() */ |
31 | | /************************************************************************/ |
32 | | |
33 | | GDALRasterProximityAlgorithm::GDALRasterProximityAlgorithm(bool standaloneStep) |
34 | 0 | : GDALRasterPipelineNonNativelyStreamingAlgorithm(NAME, DESCRIPTION, |
35 | 0 | HELP_URL, standaloneStep) |
36 | 0 | { |
37 | 0 | AddOutputDataTypeArg(&m_outputDataType) |
38 | 0 | .SetChoices("Int8", "UInt16", "Int16", "UInt32", "Int32", "Float32", |
39 | 0 | "Float64") |
40 | 0 | .SetHiddenChoices("Byte") |
41 | 0 | .SetDefault(m_outputDataType); |
42 | 0 | AddBandArg(&m_inputBand); |
43 | 0 | AddArg("target-values", 0, _("Target pixel values"), &m_targetPixelValues); |
44 | 0 | AddArg("distance-units", 0, _("Distance units"), &m_distanceUnits) |
45 | 0 | .SetChoices("pixel", "geo") |
46 | 0 | .SetDefault(m_distanceUnits); |
47 | 0 | AddArg("max-distance", 0, |
48 | 0 | _("Maximum distance. The nodata value will be used for pixels " |
49 | 0 | "beyond this distance"), |
50 | 0 | &m_maxDistance) |
51 | 0 | .SetDefault(m_maxDistance); |
52 | 0 | AddArg("fixed-value", 0, |
53 | 0 | _("Fixed value for the pixels that are within the " |
54 | 0 | "maximum distance (instead of the actual distance)"), |
55 | 0 | &m_fixedBufferValue) |
56 | 0 | .SetMinValueIncluded(0) |
57 | 0 | .SetDefault(m_fixedBufferValue); |
58 | 0 | AddArg("output-nodata", 0, |
59 | 0 | _("Specify a nodata value to use for pixels that are beyond the " |
60 | 0 | "maximum distance"), |
61 | 0 | &m_noDataValue) |
62 | 0 | .AddHiddenAlias("nodata"); |
63 | 0 | } |
64 | | |
65 | | /************************************************************************/ |
66 | | /* GDALRasterProximityAlgorithm::RunStep() */ |
67 | | /************************************************************************/ |
68 | | |
69 | | bool GDALRasterProximityAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
70 | 0 | { |
71 | 0 | auto pfnProgress = ctxt.m_pfnProgress; |
72 | 0 | auto pProgressData = ctxt.m_pProgressData; |
73 | |
|
74 | 0 | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
75 | 0 | CPLAssert(poSrcDS); |
76 | | |
77 | 0 | GDALDataType outputType = GDT_Float32; |
78 | 0 | if (!m_outputDataType.empty()) |
79 | 0 | { |
80 | 0 | outputType = GDALGetDataTypeByName(m_outputDataType.c_str()); |
81 | 0 | } |
82 | |
|
83 | 0 | auto poTmpDS = CreateTemporaryDataset( |
84 | 0 | poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), 1, outputType, |
85 | 0 | /* bTiledIfPossible = */ true, poSrcDS, /* bCopyMetadata = */ false); |
86 | 0 | if (!poTmpDS) |
87 | 0 | return false; |
88 | | |
89 | 0 | const auto srcBand = poSrcDS->GetRasterBand(m_inputBand); |
90 | 0 | CPLAssert(srcBand); |
91 | | |
92 | 0 | const auto dstBand = poTmpDS->GetRasterBand(1); |
93 | 0 | CPLAssert(dstBand); |
94 | | |
95 | | // Build options for GDALComputeProximity |
96 | 0 | CPLStringList proximityOptions; |
97 | |
|
98 | 0 | if (GetArg("max-distance")->IsExplicitlySet()) |
99 | 0 | { |
100 | 0 | proximityOptions.AddString(CPLSPrintf("MAXDIST=%.17g", m_maxDistance)); |
101 | 0 | } |
102 | |
|
103 | 0 | if (GetArg("distance-units")->IsExplicitlySet()) |
104 | 0 | { |
105 | 0 | proximityOptions.AddString( |
106 | 0 | CPLSPrintf("DISTUNITS=%s", m_distanceUnits.c_str())); |
107 | 0 | } |
108 | |
|
109 | 0 | if (GetArg("fixed-value")->IsExplicitlySet()) |
110 | 0 | { |
111 | 0 | proximityOptions.AddString( |
112 | 0 | CPLSPrintf("FIXED_BUF_VAL=%.17g", m_fixedBufferValue)); |
113 | 0 | } |
114 | |
|
115 | 0 | if (GetArg("output-nodata")->IsExplicitlySet()) |
116 | 0 | { |
117 | | // Because GDALComputeProximity does all of its work with 32-bit floats, |
118 | | // we need to check that a manually-specified NoData value can be represented |
119 | | // as a 32-bit float. The default NoData value for some integer types |
120 | | // also cannot be represented as 32-bit floats, but they still round-trip OK, |
121 | | // so we only perform the runtime check for manually-specified values. |
122 | 0 | if (!std::isnan(m_noDataValue)) |
123 | 0 | { |
124 | 0 | const float fNoData = static_cast<float>(m_noDataValue); |
125 | 0 | if (static_cast<double>(fNoData) != m_noDataValue) |
126 | 0 | { |
127 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
128 | 0 | "gdal raster proximity requires the output NoData " |
129 | 0 | "value to be representable as a 32-bit floating point " |
130 | 0 | "number, but the specified value of %g cannot.", |
131 | 0 | m_noDataValue); |
132 | 0 | return false; |
133 | 0 | } |
134 | 0 | } |
135 | 0 | dstBand->SetNoDataValue(m_noDataValue); |
136 | 0 | } |
137 | 0 | else if (outputType == GDT_Float16 || outputType == GDT_Float32 || |
138 | 0 | outputType == GDT_Float64) |
139 | 0 | { |
140 | 0 | dstBand->SetNoDataValue(std::numeric_limits<double>::quiet_NaN()); |
141 | 0 | } |
142 | 0 | else |
143 | 0 | { |
144 | 0 | double dfNoData; |
145 | 0 | if (GDALGetDataTypeMinMaxAsDouble(outputType, nullptr, &dfNoData)) |
146 | 0 | { |
147 | 0 | dstBand->SetNoDataValue(dfNoData); |
148 | 0 | } |
149 | 0 | else |
150 | 0 | { |
151 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
152 | 0 | "No default NoData value for output data type %s", |
153 | 0 | GDALGetDataTypeName(outputType)); |
154 | 0 | return false; |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | // Always set this to YES. Note that this was NOT the |
159 | | // default behavior in the python implementation of the utility. |
160 | 0 | proximityOptions.AddString("USE_INPUT_NODATA=YES"); |
161 | |
|
162 | 0 | if (GetArg("target-values")->IsExplicitlySet()) |
163 | 0 | { |
164 | 0 | std::string targetPixelValues; |
165 | 0 | for (const auto &value : m_targetPixelValues) |
166 | 0 | { |
167 | 0 | if (!targetPixelValues.empty()) |
168 | 0 | targetPixelValues += ","; |
169 | 0 | targetPixelValues += CPLSPrintf("%.17g", value); |
170 | 0 | } |
171 | 0 | proximityOptions.AddString( |
172 | 0 | CPLSPrintf("VALUES=%s", targetPixelValues.c_str())); |
173 | 0 | } |
174 | |
|
175 | 0 | const auto error = GDALComputeProximity(srcBand, dstBand, proximityOptions, |
176 | 0 | pfnProgress, pProgressData); |
177 | 0 | if (error == CE_None) |
178 | 0 | { |
179 | 0 | if (pfnProgress) |
180 | 0 | pfnProgress(1.0, "", pProgressData); |
181 | 0 | m_outputDataset.Set(std::move(poTmpDS)); |
182 | 0 | } |
183 | |
|
184 | 0 | return error == CE_None; |
185 | 0 | } |
186 | | |
187 | | GDALRasterProximityAlgorithmStandalone:: |
188 | 0 | ~GDALRasterProximityAlgorithmStandalone() = default; |
189 | | |
190 | | //! @endcond |