Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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