Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_hillshade.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "hillshade" 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_hillshade.h"
14
#include "gdalalg_raster_write.h"
15
16
#include "gdal_priv.h"
17
#include "gdal_utils.h"
18
19
#include <cmath>
20
21
//! @cond Doxygen_Suppress
22
23
#ifndef _
24
0
#define _(x) (x)
25
#endif
26
27
/************************************************************************/
28
/*     GDALRasterHillshadeAlgorithm::GDALRasterHillshadeAlgorithm()     */
29
/************************************************************************/
30
31
GDALRasterHillshadeAlgorithm::GDALRasterHillshadeAlgorithm(bool standaloneStep)
32
0
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
33
0
                                      standaloneStep)
34
0
{
35
0
    SetOutputVRTCompatible(false);
36
37
0
    AddBandArg(&m_band).SetDefault(m_band);
38
0
    AddArg("zfactor", 'z',
39
0
           _("Vertical exaggeration used to pre-multiply the elevations"),
40
0
           &m_zfactor)
41
0
        .SetMinValueExcluded(0);
42
0
    AddArg("xscale", 0, _("Ratio of vertical units to horizontal X axis units"),
43
0
           &m_xscale)
44
0
        .SetMinValueExcluded(0);
45
0
    AddArg("yscale", 0, _("Ratio of vertical units to horizontal Y axis units"),
46
0
           &m_yscale)
47
0
        .SetMinValueExcluded(0);
48
0
    AddArg("azimuth", 0, _("Azimuth of the light, in degrees"), &m_azimuth)
49
0
        .SetDefault(m_azimuth);
50
0
    AddArg("altitude", 0, _("Altitude of the light, in degrees"), &m_altitude)
51
0
        .SetDefault(m_altitude)
52
0
        .SetMinValueIncluded(0)
53
0
        .SetMaxValueIncluded(90);
54
0
    AddArg("gradient-alg", 0, _("Algorithm used to compute terrain gradient"),
55
0
           &m_gradientAlg)
56
0
        .SetChoices("Horn", "ZevenbergenThorne")
57
0
        .SetDefault(m_gradientAlg);
58
0
    AddArg("variant", 0, _("Variant of the hillshading algorithm"), &m_variant)
59
0
        .SetChoices("regular", "combined", "multidirectional", "Igor")
60
0
        .SetDefault(m_variant);
61
0
    AddArg("no-edges", 0,
62
0
           _("Do not try to interpolate values at dataset edges or close to "
63
0
             "nodata values"),
64
0
           &m_noEdges);
65
0
}
66
67
/************************************************************************/
68
/*          GDALRasterHillshadeAlgorithm::CanHandleNextStep()           */
69
/************************************************************************/
70
71
bool GDALRasterHillshadeAlgorithm::CanHandleNextStep(
72
    GDALPipelineStepAlgorithm *poNextStep) const
73
0
{
74
0
    return poNextStep->GetName() == GDALRasterWriteAlgorithm::NAME &&
75
0
           poNextStep->GetOutputFormat() != "stream";
76
0
}
77
78
/************************************************************************/
79
/*               GDALRasterHillshadeAlgorithm::RunStep()                */
80
/************************************************************************/
81
82
bool GDALRasterHillshadeAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
83
0
{
84
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
85
0
    CPLAssert(poSrcDS);
86
0
    CPLAssert(m_outputDataset.GetName().empty());
87
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
88
89
0
    CPLStringList aosOptions;
90
0
    std::string outputFilename;
91
0
    if (ctxt.m_poNextUsableStep)
92
0
    {
93
0
        CPLAssert(CanHandleNextStep(ctxt.m_poNextUsableStep));
94
0
        outputFilename = ctxt.m_poNextUsableStep->GetOutputDataset().GetName();
95
0
        const auto &format = ctxt.m_poNextUsableStep->GetOutputFormat();
96
0
        if (!format.empty())
97
0
        {
98
0
            aosOptions.AddString("-of");
99
0
            aosOptions.AddString(format.c_str());
100
0
        }
101
102
0
        for (const std::string &co :
103
0
             ctxt.m_poNextUsableStep->GetCreationOptions())
104
0
        {
105
0
            aosOptions.AddString("-co");
106
0
            aosOptions.AddString(co.c_str());
107
0
        }
108
0
    }
109
0
    else
110
0
    {
111
0
        aosOptions.AddString("-of");
112
0
        aosOptions.AddString("stream");
113
0
    }
114
115
0
    aosOptions.AddString("-b");
116
0
    aosOptions.AddString(CPLSPrintf("%d", m_band));
117
0
    aosOptions.AddString("-z");
118
0
    aosOptions.AddString(CPLSPrintf("%.17g", m_zfactor));
119
0
    if (!std::isnan(m_xscale))
120
0
    {
121
0
        aosOptions.AddString("-xscale");
122
0
        aosOptions.AddString(CPLSPrintf("%.17g", m_xscale));
123
0
    }
124
0
    if (!std::isnan(m_yscale))
125
0
    {
126
0
        aosOptions.AddString("-yscale");
127
0
        aosOptions.AddString(CPLSPrintf("%.17g", m_yscale));
128
0
    }
129
0
    if (m_variant == "multidirectional")
130
0
    {
131
0
        if (GetArg("azimuth")->IsExplicitlySet())
132
0
        {
133
0
            CPLError(CE_Failure, CPLE_AppDefined,
134
0
                     "'azimuth' argument cannot be used with multidirectional "
135
0
                     "variant");
136
0
            return false;
137
0
        }
138
0
    }
139
0
    else
140
0
    {
141
0
        aosOptions.AddString("-az");
142
0
        aosOptions.AddString(CPLSPrintf("%.17g", m_azimuth));
143
0
    }
144
0
    if (m_variant == "Igor")
145
0
    {
146
0
        if (GetArg("altitude")->IsExplicitlySet())
147
0
        {
148
0
            CPLError(CE_Failure, CPLE_AppDefined,
149
0
                     "'altitude' argument cannot be used with Igor variant");
150
0
            return false;
151
0
        }
152
0
    }
153
0
    else
154
0
    {
155
0
        aosOptions.AddString("-alt");
156
0
        aosOptions.AddString(CPLSPrintf("%.17g", m_altitude));
157
0
    }
158
0
    aosOptions.AddString("-alg");
159
0
    aosOptions.AddString(m_gradientAlg.c_str());
160
161
0
    if (m_variant == "combined")
162
0
        aosOptions.AddString("-combined");
163
0
    else if (m_variant == "multidirectional")
164
0
        aosOptions.AddString("-multidirectional");
165
0
    else if (m_variant == "Igor")
166
0
        aosOptions.AddString("-igor");
167
168
0
    if (!m_noEdges)
169
0
        aosOptions.AddString("-compute_edges");
170
171
0
    GDALDEMProcessingOptions *psOptions =
172
0
        GDALDEMProcessingOptionsNew(aosOptions.List(), nullptr);
173
0
    bool bOK = false;
174
0
    if (psOptions)
175
0
    {
176
0
        if (ctxt.m_poNextUsableStep)
177
0
        {
178
0
            GDALDEMProcessingOptionsSetProgress(psOptions, ctxt.m_pfnProgress,
179
0
                                                ctxt.m_pProgressData);
180
0
        }
181
0
        auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(
182
0
            GDALDEMProcessing(outputFilename.c_str(),
183
0
                              GDALDataset::ToHandle(poSrcDS), "hillshade",
184
0
                              nullptr, psOptions, nullptr)));
185
0
        GDALDEMProcessingOptionsFree(psOptions);
186
0
        bOK = poOutDS != nullptr;
187
0
        if (poOutDS)
188
0
        {
189
0
            m_outputDataset.Set(std::move(poOutDS));
190
0
        }
191
0
    }
192
193
0
    return bOK;
194
0
}
195
196
GDALRasterHillshadeAlgorithmStandalone::
197
0
    ~GDALRasterHillshadeAlgorithmStandalone() = default;
198
199
//! @endcond