Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/viewshed/cumulative.cpp
Line
Count
Source
1
/******************************************************************************
2
 * (c) 2024 info@hobu.co
3
 *
4
 * SPDX-License-Identifier: MIT
5
 ****************************************************************************/
6
7
#include <algorithm>
8
#include <limits>
9
#include <thread>
10
11
#include "cpl_worker_thread_pool.h"
12
#include "memdataset.h"
13
14
#include "combiner.h"
15
#include "cumulative.h"
16
#include "notifyqueue.h"
17
#include "util.h"
18
#include "viewshed_executor.h"
19
20
namespace gdal
21
{
22
namespace viewshed
23
{
24
25
/// Constructor
26
///
27
/// @param opts  Options for viewshed generation.
28
0
Cumulative::Cumulative(const Options &opts) : m_opts(opts)
29
0
{
30
0
}
31
32
/// Destructor
33
///
34
0
Cumulative::~Cumulative() = default;
35
36
/// Compute the cumulative viewshed of a raster band.
37
///
38
/// @param srcFilename  Source filename.
39
/// @param pfnProgress  Pointer to the progress function. Can be null.
40
/// @param pProgressArg  Argument passed to the progress function
41
/// @return  True on success, false otherwise.
42
bool Cumulative::run(const std::string &srcFilename,
43
                     GDALProgressFunc pfnProgress, void *pProgressArg)
44
0
{
45
    // In cumulative mode, we run the executors in normal mode and want "1" where things
46
    // are visible.
47
0
    m_opts.outputMode = OutputMode::Normal;
48
0
    m_opts.visibleVal = 1;
49
50
0
    DatasetPtr srcDS(
51
0
        GDALDataset::FromHandle(GDALOpen(srcFilename.c_str(), GA_ReadOnly)));
52
0
    if (!srcDS)
53
0
    {
54
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unable open source file.");
55
0
        return false;
56
0
    }
57
58
0
    GDALRasterBand *pSrcBand = srcDS->GetRasterBand(1);
59
60
    // In cumulative mode, the output extent is always the entire source raster.
61
0
    m_extent.xStop = GDALGetRasterBandXSize(pSrcBand);
62
0
    m_extent.yStop = GDALGetRasterBandYSize(pSrcBand);
63
64
    // Make a bunch of observer locations based on the spacing and stick them on a queue
65
    // to be handled by viewshed executors.
66
0
    for (int x = 0; x < m_extent.xStop; x += m_opts.observerSpacing)
67
0
        for (int y = 0; y < m_extent.yStop; y += m_opts.observerSpacing)
68
0
            m_observerQueue.push({x, y});
69
0
    m_observerQueue.done();
70
71
    // Run executors.
72
0
    const int numThreads = m_opts.numJobs;
73
0
    std::atomic<bool> err = false;
74
0
    std::atomic<int> running = numThreads;
75
0
    std::atomic<bool> hasFoundNoData = false;
76
0
    Progress progress(pfnProgress, pProgressArg,
77
0
                      m_observerQueue.size() * m_extent.ySize());
78
0
    CPLWorkerThreadPool executorPool(numThreads);
79
0
    for (int i = 0; i < numThreads; ++i)
80
0
        executorPool.SubmitJob(
81
0
            [this, &srcFilename, &progress, &err, &running, &hasFoundNoData]
82
0
            {
83
0
                runExecutor(srcFilename, progress, err, running,
84
0
                            hasFoundNoData);
85
0
            });
86
87
    // Run combiners that create 8-bit sums of executor jobs.
88
0
    CPLWorkerThreadPool combinerPool(numThreads);
89
0
    std::vector<Combiner> combiners(numThreads,
90
0
                                    Combiner(m_datasetQueue, m_rollupQueue));
91
0
    for (Combiner &c : combiners)
92
0
        combinerPool.SubmitJob([&c] { c.run(); });
93
94
    // Run 32-bit rollup job that combines the 8-bit results from the combiners.
95
0
    std::thread sum([this] { rollupRasters(); });
96
97
    // When the combiner jobs are done, all the data is in the rollup queue.
98
0
    combinerPool.WaitCompletion();
99
0
    if (m_datasetQueue.isStopped())
100
0
        return false;
101
0
    m_rollupQueue.done();
102
103
    // Wait for finalBuf to be fully filled.
104
0
    sum.join();
105
    // The executors should exit naturally, but we wait here so that we don't outrun their
106
    // completion and exit with outstanding threads.
107
0
    executorPool.WaitCompletion();
108
109
0
    if (hasFoundNoData)
110
0
    {
111
0
        CPLError(
112
0
            CE_Warning, CPLE_AppDefined,
113
0
            "Nodata value found in input DEM. Output will be likely incorrect");
114
0
    }
115
116
    // Scale the data so that we can write an 8-bit raster output.
117
0
    scaleOutput();
118
0
    if (!writeOutput(createOutputDataset(*pSrcBand, m_opts, m_extent)))
119
0
    {
120
0
        CPLError(CE_Failure, CPLE_AppDefined,
121
0
                 "Unable to write to output file.");
122
0
        return false;
123
0
    }
124
0
    progress.emit(1);
125
126
0
    return true;
127
0
}
128
129
/// Run an executor (single viewshed)
130
/// @param srcFilename  Source filename
131
/// @param progress  Progress supporting support.
132
/// @param err  Shared error flag.
133
/// @param running  Shared count of number of executors running.
134
/// @param hasFoundNoData Shared flag to indicate if a point at nodata has been encountered.
135
void Cumulative::runExecutor(const std::string &srcFilename, Progress &progress,
136
                             std::atomic<bool> &err, std::atomic<int> &running,
137
                             std::atomic<bool> &hasFoundNoData)
138
0
{
139
0
    DatasetPtr srcDs(GDALDataset::Open(srcFilename.c_str(), GA_ReadOnly));
140
0
    if (!srcDs)
141
0
    {
142
0
        err = true;
143
0
    }
144
0
    else
145
0
    {
146
0
        Location loc;
147
0
        while (!err && m_observerQueue.pop(loc))
148
0
        {
149
0
            DatasetPtr dstDs(MEMDataset::Create(
150
0
                "", m_extent.xSize(), m_extent.ySize(), 1, GDT_UInt8, nullptr));
151
0
            if (!dstDs)
152
0
            {
153
0
                err = true;
154
0
            }
155
0
            else
156
0
            {
157
0
                ViewshedExecutor executor(
158
0
                    *srcDs->GetRasterBand(1), *dstDs->GetRasterBand(1), loc.x,
159
0
                    loc.y, m_extent, m_extent, m_opts, progress,
160
0
                    /* emitWarningIfNoData = */ false);
161
0
                err = !executor.run();
162
0
                if (!err)
163
0
                    m_datasetQueue.push(std::move(dstDs));
164
0
                if (executor.hasFoundNoData())
165
0
                {
166
0
                    hasFoundNoData = true;
167
0
                }
168
0
            }
169
0
        }
170
0
    }
171
172
    // Job done. Set the output queue state.  If all the executor jobs have completed,
173
    // set the dataset output queue done.
174
0
    if (err)
175
0
        m_datasetQueue.stop();
176
0
    else
177
0
    {
178
0
        running--;
179
0
        if (!running)
180
0
            m_datasetQueue.done();
181
0
    }
182
0
}
183
184
// Add 8-bit rasters into the 32-bit raster buffer.
185
void Cumulative::rollupRasters()
186
0
{
187
0
    DatasetPtr pDS;
188
189
0
    m_finalBuf.resize(m_extent.size());
190
0
    while (m_rollupQueue.pop(pDS))
191
0
    {
192
0
        uint8_t *srcP =
193
0
            static_cast<uint8_t *>(pDS->GetInternalHandle("MEMORY1"));
194
0
        for (size_t i = 0; i < m_extent.size(); ++i)
195
0
            m_finalBuf[i] += srcP[i];
196
0
    }
197
0
}
198
199
/// Scale the output so that it's fully spread in 8 bits. Perhaps this shouldn't happen if
200
/// the max is less than 255?
201
void Cumulative::scaleOutput()
202
0
{
203
0
    uint32_t m = 0;  // This gathers all the bits set.
204
0
    for (uint32_t &val : m_finalBuf)
205
0
        m = std::max(val, m);
206
207
0
    if (m == 0)
208
0
        return;
209
210
0
    double factor =
211
0
        std::numeric_limits<uint8_t>::max() / static_cast<double>(m);
212
0
    for (uint32_t &val : m_finalBuf)
213
0
        val = static_cast<uint32_t>(std::floor(factor * val));
214
0
}
215
216
/// Write the output dataset.
217
/// @param pDstDS  Pointer to the destination dataset.
218
/// @return True if the write was successful, false otherwise.
219
bool Cumulative::writeOutput(DatasetPtr pDstDS)
220
0
{
221
0
    if (!pDstDS)
222
0
        return false;
223
224
0
    GDALRasterBand *pDstBand = pDstDS->GetRasterBand(1);
225
0
    return (pDstBand->RasterIO(GF_Write, 0, 0, m_extent.xSize(),
226
0
                               m_extent.ySize(), m_finalBuf.data(),
227
0
                               m_extent.xSize(), m_extent.ySize(), GDT_UInt32,
228
0
                               0, 0, nullptr) == 0);
229
0
}
230
231
}  // namespace viewshed
232
}  // namespace gdal