Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/viewshed/viewshed_executor.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Viewshed Generation
4
 * Purpose:  Core algorithm implementation for viewshed generation.
5
 * Author:   Tamas Szekeres, szekerest@gmail.com
6
 *
7
 * (c) 2024 info@hobu.co
8
 *
9
 ******************************************************************************
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include <algorithm>
15
#include <atomic>
16
#include <cassert>
17
#include <cmath>
18
#include <limits>
19
20
#include "viewshed_executor.h"
21
#include "progress.h"
22
#include "util.h"
23
24
// cppcheck-suppress-begin knownConditionTrueFalse
25
namespace gdal
26
{
27
namespace viewshed
28
{
29
30
//! @cond Doxygen_Suppress
31
CPLErr DummyBand::IReadBlock(int, int, void *)
32
0
{
33
0
    return CE_Failure;
34
0
}
35
36
//! @endcond
37
38
namespace
39
{
40
41
/// Determines whether a value is a valid intersection coordinate.
42
/// @param  i  Value to test.
43
/// @return  True if the value doesn't represent an invalid intersection.
44
bool valid(int i)
45
0
{
46
0
    return i != INVALID_ISECT;
47
0
}
48
49
/// Determines whether a value is an invalid intersection coordinate.
50
/// @param  i  Value to test.
51
/// @return  True if the value represents an invalid intersection.
52
bool invalid(int i)
53
0
{
54
0
    return !valid(i);
55
0
}
56
57
/// Calculate the height at nDistance units along a line through the origin given the height
58
/// at nDistance - 1 units along the line.
59
/// \param nDistance  Distance along the line for the target point.
60
/// \param Za  Height at the line one unit previous to the target point.
61
double CalcHeightLine(int nDistance, double Za)
62
0
{
63
0
    assert(nDistance > 1);
64
0
    return Za * nDistance / (nDistance - 1);
65
0
}
66
67
/// Calculate the height at nDistance units along a line through the origin given the height
68
/// at nDistance - 1 units along the line.
69
/// \param nDistance  Distance along the line for the target point.
70
/// \param Zcur  Height at the line at the target point.
71
/// \param Za    Height at the line one unit previous to the target point.
72
double CalcHeightLine(int nDistance, double Zcur, double Za)
73
0
{
74
0
    nDistance = std::abs(nDistance);
75
0
    assert(nDistance > 0);
76
0
    if (nDistance == 1)
77
0
        return Zcur;
78
0
    return CalcHeightLine(nDistance, Za);
79
0
}
80
81
// Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0)
82
// and passing through the line connecting (i - 1, j, Za) and (i, j - 1, Zb).
83
// In other words, the origin and the two points form a plane and we're calculating Zc
84
// of the point (i, j, Zc), also on the plane.
85
double CalcHeightDiagonal(int i, int j, double Za, double Zb)
86
0
{
87
0
    return (Za * i + Zb * j) / (i + j - 1);
88
0
}
89
90
// Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0)
91
// and through the line connecting (i -1, j - 1, Za) and (i - 1, j, Zb). In other words,
92
// the origin and the other two points form a plane and we're calculating Zc of the
93
// point (i, j, Zc), also on the plane.
94
double CalcHeightEdge(int i, int j, double Za, double Zb)
95
0
{
96
0
    assert(i != j);
97
0
    return (Za * i + Zb * (j - i)) / (j - 1);
98
0
}
99
100
double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset,
101
                  double dfThisPrev, double dfLast,
102
                  [[maybe_unused]] double dfLastPrev)
103
0
{
104
0
    return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast);
105
0
}
106
107
double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
108
              double dfLastPrev)
109
0
{
110
0
    if (nXOffset >= nYOffset)
111
0
        return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev);
112
0
    else
113
0
        return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast);
114
0
}
115
116
double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
117
             double dfLastPrev)
118
0
{
119
0
    double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
120
0
    double dfDiagonal =
121
0
        doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
122
0
    return std::min(dfEdge, dfDiagonal);
123
0
}
124
125
double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
126
             double dfLastPrev)
127
0
{
128
0
    double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
129
0
    double dfDiagonal =
130
0
        doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
131
0
    return std::max(dfEdge, dfDiagonal);
132
0
}
133
134
}  // unnamed namespace
135
136
/// Constructor - the viewshed algorithm executor
137
/// @param srcBand  Source raster band
138
/// @param sdBand  Standard-deviation raster band
139
/// @param dstBand  Destination raster band
140
/// @param nX  X position of observer
141
/// @param nY  Y position of observer
142
/// @param outExtent  Extent of output raster (relative to input)
143
/// @param curExtent  Extent of active raster.
144
/// @param opts  Configuration options.
145
/// @param progress  Reference to the progress tracker.
146
/// @param emitWarningIfNoData  Whether a warning must be emitted if an input
147
///                             pixel is at the nodata value.
148
ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand,
149
                                   GDALRasterBand &sdBand,
150
                                   GDALRasterBand &dstBand, int nX, int nY,
151
                                   const Window &outExtent,
152
                                   const Window &curExtent, const Options &opts,
153
                                   Progress &progress, bool emitWarningIfNoData)
154
0
    : m_pool(4), m_dummyBand(), m_srcBand(srcBand), m_sdBand(sdBand),
155
0
      m_dstBand(dstBand),
156
      // If the standard deviation band isn't a dummy band, we're in SD mode.
157
0
      m_hasSdBand(dynamic_cast<DummyBand *>(&m_sdBand) == nullptr),
158
0
      m_emitWarningIfNoData(emitWarningIfNoData), oOutExtent(outExtent),
159
0
      oCurExtent(curExtent), m_nX(nX - oOutExtent.xStart), m_nY(nY),
160
0
      oOpts(opts), oProgress(progress),
161
0
      m_dfMinDistance2(opts.minDistance * opts.minDistance),
162
0
      m_dfMaxDistance2(opts.maxDistance * opts.maxDistance)
163
0
{
164
0
    if (m_dfMaxDistance2 == 0)
165
0
        m_dfMaxDistance2 = std::numeric_limits<double>::max();
166
0
    if (opts.lowPitch != -90.0)
167
0
        m_lowTanPitch = std::tan(oOpts.lowPitch * (2 * M_PI / 360.0));
168
0
    if (opts.highPitch != 90.0)
169
0
        m_highTanPitch = std::tan(oOpts.highPitch * (2 * M_PI / 360.0));
170
0
    m_srcBand.GetDataset()->GetGeoTransform(m_gt);
171
0
    int hasNoData = false;
172
0
    m_noDataValue = m_srcBand.GetNoDataValue(&hasNoData);
173
0
    m_hasNoData = hasNoData;
174
0
}
175
176
/// Constructor - the viewshed algorithm executor
177
/// @param srcBand  Source raster band
178
/// @param dstBand  Destination raster band
179
/// @param nX  X position of observer
180
/// @param nY  Y position of observer
181
/// @param outExtent  Extent of output raster (relative to input)
182
/// @param curExtent  Extent of active raster.
183
/// @param opts  Configuration options.
184
/// @param progress  Reference to the progress tracker.
185
/// @param emitWarningIfNoData  Whether a warning must be emitted if an input
186
///                             pixel is at the nodata value.
187
ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand,
188
                                   GDALRasterBand &dstBand, int nX, int nY,
189
                                   const Window &outExtent,
190
                                   const Window &curExtent, const Options &opts,
191
                                   Progress &progress, bool emitWarningIfNoData)
192
0
    : ViewshedExecutor(srcBand, m_dummyBand, dstBand, nX, nY, outExtent,
193
0
                       curExtent, opts, progress, emitWarningIfNoData)
194
0
{
195
0
}
196
197
// calculate the height adjustment factor.
198
double ViewshedExecutor::calcHeightAdjFactor()
199
0
{
200
0
    std::lock_guard g(oMutex);
201
202
0
    const OGRSpatialReference *poDstSRS =
203
0
        m_dstBand.GetDataset()->GetSpatialRef();
204
205
0
    if (poDstSRS)
206
0
    {
207
0
        OGRErr eSRSerr;
208
209
        // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR
210
0
        double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr);
211
212
        /* If we fetched the axis from the SRS, use it */
213
0
        if (eSRSerr != OGRERR_FAILURE)
214
0
            return oOpts.curveCoeff / (dfSemiMajor * 2.0);
215
216
0
        CPLDebug("GDALViewshedGenerate",
217
0
                 "Unable to fetch SemiMajor axis from spatial reference");
218
0
    }
219
0
    return 0;
220
0
}
221
222
/// Set the output Z value depending on the observable height and computation mode
223
/// in normal mode.
224
///
225
/// dfResult  Reference to the result cell
226
/// dfCellVal  Reference to the current cell height. Replace with observable height.
227
/// dfZ  Minimum observable height at cell.
228
void ViewshedExecutor::setOutputNormal(Lines &lines, int pos, double dfZ)
229
0
{
230
0
    double &cur = lines.cur[pos];
231
0
    double &result = lines.result[pos];
232
233
0
    if (oOpts.outputMode != OutputMode::Normal)
234
0
    {
235
0
        double adjustment = dfZ - cur;
236
0
        if (adjustment > 0)
237
0
            result += adjustment;
238
0
    }
239
0
    else
240
0
    {
241
0
        double cellHeight = cur + oOpts.targetHeight;
242
0
        result = (cellHeight < dfZ) ? oOpts.invisibleVal : oOpts.visibleVal;
243
0
    }
244
0
    cur = std::max(cur, dfZ);
245
0
}
246
247
/// Set the output Z value depending on the observable height and computation when
248
/// making an standard deviation pass.
249
///
250
/// dfResult  Reference to the result cell
251
/// dfCellVal  Reference to the current cell height. Replace with observable height.
252
/// dfZ  Minimum observable height at cell.
253
void ViewshedExecutor::setOutputSd(Lines &lines, int pos, double dfZ)
254
0
{
255
0
    double &cur = lines.cur[pos];
256
0
    double &result = lines.result[pos];
257
0
    double &sd = lines.sd[pos];
258
259
0
    assert(oOpts.outputMode == OutputMode::Normal);
260
0
    if (result == oOpts.invisibleVal)
261
0
    {
262
0
        double cellHeight = cur + oOpts.targetHeight;
263
0
        if (cellHeight > dfZ)
264
0
            result = oOpts.maybeVisibleVal;
265
0
    }
266
267
0
    if (sd <= 1)
268
0
        cur = std::max(dfZ, cur);
269
0
    else
270
0
        cur = dfZ;
271
0
}
272
273
/// Read a line of raster data.
274
///
275
/// @param  nLine  Line number to read.
276
/// @param  lines  Raster line to fill.
277
/// @return  Success or failure.
278
bool ViewshedExecutor::readLine(int nLine, Lines &lines)
279
0
{
280
0
    std::lock_guard g(iMutex);
281
282
0
    if (GDALRasterIO(&m_srcBand, GF_Read, oOutExtent.xStart, nLine,
283
0
                     oOutExtent.xSize(), 1, lines.cur.data(),
284
0
                     oOutExtent.xSize(), 1, GDT_Float64, 0, 0))
285
0
    {
286
0
        CPLError(CE_Failure, CPLE_AppDefined,
287
0
                 "RasterIO error when reading DEM at position (%d,%d), "
288
0
                 "size (%d,%d)",
289
0
                 oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
290
0
        return false;
291
0
    }
292
293
0
    if (sdMode())
294
0
    {
295
0
        double nodata = m_sdBand.GetNoDataValue();
296
0
        CPLErr sdStatus = m_sdBand.RasterIO(
297
0
            GF_Read, oOutExtent.xStart, nLine, oOutExtent.xSize(), 1,
298
0
            lines.sd.data(), oOutExtent.xSize(), 1, GDT_Float64, 0, 0, nullptr);
299
0
        if (sdStatus != CE_None)
300
0
        {
301
0
            CPLError(CE_Failure, CPLE_AppDefined,
302
0
                     "RasterIO error when reading standard deviation band at "
303
0
                     "position (%d,%d), "
304
0
                     "size (%d,%d)",
305
0
                     oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
306
0
            return false;
307
0
        }
308
        // Set the standard deviation to 1000 if nodata is found.
309
0
        for (size_t i = 0; i < lines.sd.size(); ++i)
310
0
            if (lines.sd[i] == nodata)
311
0
                lines.sd[i] = 1000.0;
312
0
    }
313
314
    // Initialize the result line.
315
    // In DEM mode the base is the pre-adjustment value.  In ground mode the base is zero.
316
0
    if (oOpts.outputMode == OutputMode::DEM)
317
0
        lines.result = lines.cur;
318
0
    else if (oOpts.outputMode == OutputMode::Ground)
319
0
        std::fill(lines.result.begin(), lines.result.end(), 0);
320
321
0
    return true;
322
0
}
323
324
/// Write an output line of either visibility or height data.
325
///
326
/// @param  nLine  Line number being written.
327
/// @param vResult  Result line to write.
328
/// @return  True on success, false otherwise.
329
bool ViewshedExecutor::writeLine(int nLine, std::vector<double> &vResult)
330
0
{
331
    // GDALRasterIO isn't thread-safe.
332
0
    std::lock_guard g(oMutex);
333
334
0
    if (GDALRasterIO(&m_dstBand, GF_Write, 0, nLine - oOutExtent.yStart,
335
0
                     oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(),
336
0
                     1, GDT_Float64, 0, 0))
337
0
    {
338
0
        CPLError(CE_Failure, CPLE_AppDefined,
339
0
                 "RasterIO error when writing target raster at position "
340
0
                 "(%d,%d), size (%d,%d)",
341
0
                 0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1);
342
0
        return false;
343
0
    }
344
0
    return true;
345
0
}
346
347
/// Adjust the height of the line of data by the observer height and the curvature of the
348
/// earth.
349
///
350
/// @param  nYOffset  Y offset of the line being adjusted.
351
/// @param  lines  Raster lines to adjust.
352
/// @return  Processing limits of the line based on min/max distance.
353
LineLimits ViewshedExecutor::adjustHeight(int nYOffset, Lines &lines)
354
0
{
355
0
    LineLimits ll(0, m_nX + 1, m_nX + 1, oCurExtent.xSize());
356
357
    // Find the starting point in the raster (m_nX may be outside)
358
0
    int nXStart = oCurExtent.clampX(m_nX);
359
360
0
    const auto CheckNoData = [this](double val)
361
0
    {
362
0
        if (!m_hasFoundNoData &&
363
0
            ((m_hasNoData && val == m_noDataValue) || std::isnan(val)))
364
0
        {
365
0
            m_hasFoundNoData = true;
366
0
            if (m_emitWarningIfNoData)
367
0
            {
368
0
                CPLError(CE_Warning, CPLE_AppDefined,
369
0
                         "Nodata value found in input DEM. Output will be "
370
0
                         "likely incorrect");
371
0
            }
372
0
        }
373
0
    };
374
375
    // If there is a height adjustment factor other than zero or a max distance,
376
    // calculate the adjusted height of the cell, stopping if we've exceeded the max
377
    // distance.
378
0
    if (static_cast<bool>(m_dfHeightAdjFactor) || oOpts.pitchMasking() ||
379
0
        m_dfMaxDistance2 > 0 || m_dfMinDistance2 > 0)
380
0
    {
381
        // Hoist invariants from the loops.
382
0
        const double dfLineX = m_gt[2] * nYOffset;
383
0
        const double dfLineY = m_gt[5] * nYOffset;
384
385
        // Go left
386
0
        double *pdfHeight = lines.cur.data() + nXStart;
387
0
        for (int nXOffset = nXStart - m_nX; nXOffset >= -m_nX;
388
0
             nXOffset--, pdfHeight--)
389
0
        {
390
0
            double dfX = m_gt[1] * nXOffset + dfLineX;
391
0
            double dfY = m_gt[4] * nXOffset + dfLineY;
392
0
            double dfR2 = dfX * dfX + dfY * dfY;
393
394
0
            if (dfR2 < m_dfMinDistance2)
395
0
                ll.leftMin--;
396
0
            else if (dfR2 > m_dfMaxDistance2)
397
0
            {
398
0
                ll.left = nXOffset + m_nX + 1;
399
0
                break;
400
0
            }
401
402
0
            CheckNoData(*pdfHeight);
403
0
            *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
404
0
            if (oOpts.pitchMasking())
405
0
                calcPitchMask(*pdfHeight, std::sqrt(dfR2),
406
0
                              lines.result[m_nX + nXOffset],
407
0
                              lines.pitchMask[m_nX + nXOffset]);
408
0
        }
409
410
        // Go right.
411
0
        pdfHeight = lines.cur.data() + nXStart + 1;
412
0
        for (int nXOffset = nXStart - m_nX + 1;
413
0
             nXOffset < oCurExtent.xSize() - m_nX; nXOffset++, pdfHeight++)
414
0
        {
415
0
            double dfX = m_gt[1] * nXOffset + dfLineX;
416
0
            double dfY = m_gt[4] * nXOffset + dfLineY;
417
0
            double dfR2 = dfX * dfX + dfY * dfY;
418
419
0
            if (dfR2 < m_dfMinDistance2)
420
0
                ll.rightMin++;
421
0
            else if (dfR2 > m_dfMaxDistance2)
422
0
            {
423
0
                ll.right = nXOffset + m_nX;
424
0
                break;
425
0
            }
426
427
0
            CheckNoData(*pdfHeight);
428
0
            *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
429
0
            if (oOpts.pitchMasking())
430
0
                calcPitchMask(*pdfHeight, std::sqrt(dfR2),
431
0
                              lines.result[m_nX + nXOffset],
432
0
                              lines.pitchMask[m_nX + nXOffset]);
433
0
        }
434
0
    }
435
0
    else
436
0
    {
437
        // No curvature adjustment. Just normalize for the observer height.
438
0
        double *pdfHeight = lines.cur.data();
439
0
        for (int i = 0; i < oCurExtent.xSize(); ++i)
440
0
        {
441
0
            CheckNoData(*pdfHeight);
442
0
            *pdfHeight -= m_dfZObserver;
443
0
            pdfHeight++;
444
0
        }
445
0
    }
446
0
    return ll;
447
0
}
448
449
/// Calculate the pitch masking value to apply after running the viewshed algorithm.
450
///
451
/// @param  dfZ  Adjusted input height.
452
/// @param  dfDist  Distance from observer to cell.
453
/// @param  dfResult  Result value to which adjustment may be added.
454
/// @param  maskVal  Output mask value.
455
void ViewshedExecutor::calcPitchMask(double dfZ, double dfDist, double dfResult,
456
                                     double &maskVal)
457
0
{
458
0
    if (oOpts.lowPitchMasking())
459
0
    {
460
0
        double dfZMask = dfDist * m_lowTanPitch;
461
0
        double adjustment = dfZMask - dfZ;
462
0
        if (adjustment > 0)
463
0
        {
464
0
            maskVal = (oOpts.outputMode == OutputMode::Normal
465
0
                           ? std::numeric_limits<double>::infinity()
466
0
                           : adjustment + dfResult);
467
0
            return;
468
0
        }
469
0
    }
470
0
    if (oOpts.highPitchMasking())
471
0
    {
472
0
        double dfZMask = dfDist * m_highTanPitch;
473
0
        if (dfZ > dfZMask)
474
0
            maskVal = std::numeric_limits<double>::infinity();
475
0
    }
476
0
}
477
478
/// Process the first line (the one with the Y coordinate the same as the observer).
479
///
480
/// @param lines  Raster lines to process.
481
/// @return True on success, false otherwise.
482
bool ViewshedExecutor::processFirstLine(Lines &lines)
483
0
{
484
0
    int nLine = oOutExtent.clampY(m_nY);
485
0
    int nYOffset = nLine - m_nY;
486
487
0
    if (!readLine(nLine, lines))
488
0
        return false;
489
490
    // If the observer is outside of the raster, take the specified value as the Z height,
491
    // otherwise, take it as an offset from the raster height at that location.
492
0
    m_dfZObserver = oOpts.observer.z;
493
0
    if (oCurExtent.containsX(m_nX))
494
0
        m_dfZObserver += lines.cur[m_nX];
495
496
0
    LineLimits ll = adjustHeight(nYOffset, lines);
497
498
0
    std::vector<double> savedInput;
499
0
    if (sdMode())
500
0
        savedInput = lines.cur;
501
502
0
    if (oCurExtent.containsX(m_nX))
503
0
    {
504
0
        if (ll.leftMin != ll.rightMin)
505
0
            lines.result[m_nX] = oOpts.outOfRangeVal;
506
0
        else if (oOpts.outputMode == OutputMode::Normal)
507
0
            lines.result[m_nX] = oOpts.visibleVal;
508
0
    }
509
510
0
    auto process = [this, &ll, &lines](bool sdCalc)
511
0
    {
512
0
        if (!oCurExtent.containsY(m_nY))
513
0
            processFirstLineTopOrBottom(ll, lines);
514
0
        else
515
0
        {
516
0
            CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
517
0
            pQueue->SubmitJob([&]()
518
0
                              { processFirstLineLeft(ll, lines, sdCalc); });
519
0
            pQueue->SubmitJob([&]()
520
0
                              { processFirstLineRight(ll, lines, sdCalc); });
521
0
            pQueue->WaitCompletion();
522
0
        }
523
0
    };
524
525
0
    process(false);
526
0
    lines.prev = lines.cur;
527
0
    if (sdMode())
528
0
    {
529
0
        lines.cur = std::move(savedInput);
530
0
        process(true);
531
0
        lines.prevTmp = lines.cur;
532
0
    }
533
534
0
    if (oOpts.pitchMasking())
535
0
        applyPitchMask(lines.result, lines.pitchMask);
536
0
    if (!writeLine(nLine, lines.result))
537
0
        return false;
538
539
0
    return oProgress.lineComplete();
540
0
}
541
542
/// Set the pitch masked value into the result vector when applicable.
543
///
544
/// @param  vResult  Result vector.
545
/// @param  vPitchMaskVal  Pitch mask values (nan is no masking, inf is out-of-range, else
546
///                        actual value).
547
void ViewshedExecutor::applyPitchMask(std::vector<double> &vResult,
548
                                      const std::vector<double> &vPitchMaskVal)
549
0
{
550
0
    for (size_t i = 0; i < vResult.size(); ++i)
551
0
    {
552
0
        if (std::isnan(vPitchMaskVal[i]))
553
0
            continue;
554
0
        if (std::isinf(vPitchMaskVal[i]))
555
0
            vResult[i] = oOpts.outOfRangeVal;
556
0
        else
557
0
            vResult[i] = vPitchMaskVal[i];
558
0
    }
559
0
}
560
561
/// If the observer is above or below the raster, set all cells in the first line near the
562
/// observer as observable provided they're in range. Mark cells out of range as such.
563
/// @param  ll  Line limits for processing.
564
/// @param  lines  Raster lines to process.
565
void ViewshedExecutor::processFirstLineTopOrBottom(const LineLimits &ll,
566
                                                   Lines &lines)
567
0
{
568
0
    for (int iPixel = ll.left; iPixel < ll.right; ++iPixel)
569
0
    {
570
0
        if (oOpts.outputMode == OutputMode::Normal)
571
0
            lines.result[iPixel] = oOpts.visibleVal;
572
0
        else
573
0
            setOutputNormal(lines, iPixel, lines.cur[iPixel]);
574
0
    }
575
576
0
    std::fill(lines.result.begin(), lines.result.begin() + ll.left,
577
0
              oOpts.outOfRangeVal);
578
0
    std::fill(lines.result.begin() + ll.right,
579
0
              lines.result.begin() + oCurExtent.xStop, oOpts.outOfRangeVal);
580
0
}
581
582
/// Process the part of the first line to the left of the observer.
583
///
584
/// @param ll      Line limits for masking.
585
/// @param sdCalc  True when doing standard deviation calculation.
586
/// @param lines   Raster lines to process.
587
void ViewshedExecutor::processFirstLineLeft(const LineLimits &ll, Lines &lines,
588
                                            bool sdCalc)
589
0
{
590
0
    int iEnd = ll.left - 1;
591
0
    int iStart = m_nX - 1;  // One left of the observer.
592
593
    // If end is to the right of start, everything is taken care of by right processing.
594
0
    if (iEnd >= iStart)
595
0
    {
596
0
        maskLineLeft(lines.result, ll, m_nY);
597
0
        return;
598
0
    }
599
600
0
    iStart = oCurExtent.clampX(iStart);
601
602
    // If the start cell is next to the observer, just mark it visible.
603
0
    if (iStart + 1 == m_nX || iStart + 1 == oCurExtent.xStop)
604
0
    {
605
0
        double dfZ = lines.cur[iStart];
606
0
        if (oOpts.outputMode == OutputMode::Normal)
607
0
        {
608
0
            lines.result[iStart] = oOpts.visibleVal;
609
0
            if (sdCalc)
610
0
                if (lines.sd[iStart] > 1)
611
0
                    lines.cur[iStart] =
612
0
                        m_dfZObserver;  // Should this be a minimum value?
613
0
        }
614
0
        else
615
0
            setOutputNormal(lines, iStart, dfZ);
616
0
        iStart--;
617
0
    }
618
619
    // Go from the observer to the left, calculating Z as we go.
620
0
    for (int iPixel = iStart; iPixel > iEnd; iPixel--)
621
0
    {
622
0
        int nXOffset = std::abs(iPixel - m_nX);
623
0
        double dfZ = CalcHeightLine(nXOffset, lines.cur[iPixel + 1]);
624
0
        if (!sdCalc)
625
0
            setOutputNormal(lines, iPixel, dfZ);
626
0
        else
627
0
            setOutputSd(lines, iPixel, dfZ);
628
0
    }
629
630
0
    maskLineLeft(lines.result, ll, m_nY);
631
0
}
632
633
/// Mask cells based on angle intersection to the left of the observer.
634
///
635
/// @param vResult  Result raaster line.
636
/// @param nLine  Line number.
637
/// @return  True when all cells have been masked.
638
bool ViewshedExecutor::maskAngleLeft(std::vector<double> &vResult, int nLine)
639
0
{
640
0
    auto clamp = [this](int x)
641
0
    { return (x < 0 || x >= m_nX) ? INVALID_ISECT : x; };
642
643
0
    if (!oOpts.angleMasking())
644
0
        return false;
645
646
0
    if (nLine != m_nY)
647
0
    {
648
0
        int startAngleX =
649
0
            clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
650
0
        int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine));
651
        // If neither X intersect is in the quadrant and a ray in the quadrant isn't
652
        // between start and stop, fill it all and return true.  If it is in between
653
        // start and stop, we're done.
654
0
        if (invalid(startAngleX) && invalid(endAngleX))
655
0
        {
656
            // Choose a test angle in quadrant II or III depending on the line.
657
0
            double testAngle = nLine < m_nY ? m_testAngle[2] : m_testAngle[3];
658
0
            if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
659
0
            {
660
0
                std::fill(vResult.begin(), vResult.begin() + m_nX,
661
0
                          oOpts.outOfRangeVal);
662
0
                return true;
663
0
            }
664
0
            return false;
665
0
        }
666
0
        if (nLine > m_nY)
667
0
            std::swap(startAngleX, endAngleX);
668
0
        if (invalid(startAngleX))
669
0
            startAngleX = 0;
670
0
        if (invalid(endAngleX))
671
0
            endAngleX = m_nX - 1;
672
0
        if (startAngleX <= endAngleX)
673
0
        {
674
0
            std::fill(vResult.begin(), vResult.begin() + startAngleX,
675
0
                      oOpts.outOfRangeVal);
676
0
            std::fill(vResult.begin() + endAngleX + 1, vResult.begin() + m_nX,
677
0
                      oOpts.outOfRangeVal);
678
0
        }
679
0
        else
680
0
        {
681
0
            std::fill(vResult.begin() + endAngleX + 1,
682
0
                      vResult.begin() + startAngleX, oOpts.outOfRangeVal);
683
0
        }
684
0
    }
685
    // nLine == m_nY
686
0
    else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI))
687
0
    {
688
0
        std::fill(vResult.begin(), vResult.begin() + m_nX, oOpts.outOfRangeVal);
689
0
        return true;
690
0
    }
691
0
    return false;
692
0
}
693
694
/// Mask cells based on angle intersection to the right of the observer.
695
///
696
/// @param vResult  Result raaster line.
697
/// @param nLine  Line number.
698
/// @return  True when all cells have been masked.
699
bool ViewshedExecutor::maskAngleRight(std::vector<double> &vResult, int nLine)
700
0
{
701
0
    int lineLength = static_cast<int>(vResult.size());
702
703
0
    auto clamp = [this, lineLength](int x)
704
0
    { return (x <= m_nX || x >= lineLength) ? INVALID_ISECT : x; };
705
706
0
    if (oOpts.startAngle == oOpts.endAngle)
707
0
        return false;
708
709
0
    if (nLine != m_nY)
710
0
    {
711
0
        int startAngleX =
712
0
            clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
713
0
        int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine));
714
715
        // If neither X intersect is in the quadrant and a ray in the quadrant isn't
716
        // between start and stop, fill it all and return true.  If it is in between
717
        // start and stop, we're done.
718
0
        if (invalid(startAngleX) && invalid(endAngleX))
719
0
        {
720
            // Choose a test angle in quadrant I or IV depending on the line.
721
0
            double testAngle = nLine < m_nY ? m_testAngle[1] : m_testAngle[4];
722
0
            if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
723
0
            {
724
0
                std::fill(vResult.begin() + m_nX + 1, vResult.end(),
725
0
                          oOpts.outOfRangeVal);
726
0
                return true;
727
0
            }
728
0
            return false;
729
0
        }
730
731
0
        if (nLine > m_nY)
732
0
            std::swap(startAngleX, endAngleX);
733
0
        if (invalid(endAngleX))
734
0
            endAngleX = lineLength - 1;
735
0
        if (invalid(startAngleX))
736
0
            startAngleX = m_nX + 1;
737
0
        if (startAngleX <= endAngleX)
738
0
        {
739
0
            std::fill(vResult.begin() + m_nX + 1, vResult.begin() + startAngleX,
740
0
                      oOpts.outOfRangeVal);
741
0
            std::fill(vResult.begin() + endAngleX + 1, vResult.end(),
742
0
                      oOpts.outOfRangeVal);
743
0
        }
744
0
        else
745
0
        {
746
0
            std::fill(vResult.begin() + endAngleX + 1,
747
0
                      vResult.begin() + startAngleX, oOpts.outOfRangeVal);
748
0
        }
749
0
    }
750
    // nLine == m_nY
751
0
    else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 0))
752
0
    {
753
0
        std::fill(vResult.begin() + m_nX + 1, vResult.end(),
754
0
                  oOpts.outOfRangeVal);
755
0
        return true;
756
0
    }
757
0
    return false;
758
0
}
759
760
/// Perform angle and min/max masking to the left of the observer.
761
///
762
/// @param vResult  Raster line to mask.
763
/// @param ll  Min/max line limits.
764
/// @param nLine  Line number.
765
void ViewshedExecutor::maskLineLeft(std::vector<double> &vResult,
766
                                    const LineLimits &ll, int nLine)
767
0
{
768
    // If we've already masked with angles everything, just return.
769
0
    if (maskAngleLeft(vResult, nLine))
770
0
        return;
771
772
    // Mask cells from the left edge to the left limit.
773
0
    std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal);
774
    // Mask cells from the left min to the observer.
775
0
    if (ll.leftMin < m_nX)
776
0
        std::fill(vResult.begin() + ll.leftMin, vResult.begin() + m_nX,
777
0
                  oOpts.outOfRangeVal);
778
0
}
779
780
/// Perform angle and min/max masking to the right of the observer.
781
///
782
/// @param vResult  Raster line to mask.
783
/// @param ll  Min/max line limits.
784
/// @param nLine  Line number.
785
void ViewshedExecutor::maskLineRight(std::vector<double> &vResult,
786
                                     const LineLimits &ll, int nLine)
787
0
{
788
    // If we've already masked with angles everything, just return.
789
0
    if (maskAngleRight(vResult, nLine))
790
0
        return;
791
792
    // Mask cells from the observer to right min.
793
0
    std::fill(vResult.begin() + m_nX + 1, vResult.begin() + ll.rightMin,
794
0
              oOpts.outOfRangeVal);
795
    // Mask cells from the right limit to the right edge.
796
797
    //
798
    //ABELL - Changed from ll.right + 1
799
    //
800
0
    if (ll.right <= static_cast<int>(vResult.size()))
801
0
        std::fill(vResult.begin() + ll.right, vResult.end(),
802
0
                  oOpts.outOfRangeVal);
803
0
}
804
805
/// Process the part of the first line to the right of the observer.
806
///
807
/// @param ll  Line limits
808
/// @param sdCalc  True when doing standard deviation calcuation.
809
/// @param lines  Raster lines to process.
810
void ViewshedExecutor::processFirstLineRight(const LineLimits &ll, Lines &lines,
811
                                             bool sdCalc)
812
0
{
813
0
    int iStart = m_nX + 1;
814
0
    int iEnd = ll.right;
815
816
    // If start is to the right of end, everything is taken care of by left processing.
817
0
    if (iStart >= iEnd)
818
0
    {
819
0
        maskLineRight(lines.result, ll, m_nY);
820
0
        return;
821
0
    }
822
823
0
    iStart = oCurExtent.clampX(iStart);
824
825
    // If the start cell is next to the observer, just mark it visible.
826
0
    if (iStart - 1 == m_nX || iStart == oCurExtent.xStart)
827
0
    {
828
0
        double dfZ = lines.cur[iStart];
829
0
        if (oOpts.outputMode == OutputMode::Normal)
830
0
        {
831
0
            lines.result[iStart] = oOpts.visibleVal;
832
0
            if (sdCalc)
833
0
                if (lines.sd[iStart] > 1)
834
0
                    lines.cur[iStart] =
835
0
                        m_dfZObserver;  // Use some minimum value instead?
836
0
        }
837
0
        else
838
0
            setOutputNormal(lines, iStart, dfZ);
839
0
        iStart++;
840
0
    }
841
842
    // Go from the observer to the right, calculating Z as we go.
843
0
    for (int iPixel = iStart; iPixel < iEnd; iPixel++)
844
0
    {
845
0
        int nXOffset = std::abs(iPixel - m_nX);
846
0
        double dfZ = CalcHeightLine(nXOffset, lines.cur[iPixel - 1]);
847
0
        if (!sdCalc)
848
0
            setOutputNormal(lines, iPixel, dfZ);
849
0
        else
850
0
            setOutputSd(lines, iPixel, dfZ);
851
0
    }
852
853
0
    maskLineRight(lines.result, ll, m_nY);
854
0
}
855
856
/// Process a line to the left of the observer.
857
///
858
/// @param nYOffset  Offset of the line being processed from the observer
859
/// @param ll  Line limits
860
/// @param lines  Raster lines to process.
861
/// @param sdCalc  standard deviation calculation indicator.
862
void ViewshedExecutor::processLineLeft(int nYOffset, LineLimits &ll,
863
                                       Lines &lines, bool sdCalc)
864
0
{
865
0
    int iStart = m_nX - 1;
866
0
    int iEnd = ll.left - 1;
867
0
    int nLine = m_nY + nYOffset;
868
869
    // If start to the left of end, everything is taken care of by processing right.
870
0
    if (iStart <= iEnd)
871
0
    {
872
0
        maskLineLeft(lines.result, ll, nLine);
873
0
        return;
874
0
    }
875
0
    iStart = oCurExtent.clampX(iStart);
876
877
    // If the observer is to the right of the raster, mark the first cell to the left as
878
    // visible. This may mark an out-of-range cell with a value, but this will be fixed
879
    // with the out of range assignment at the end.
880
0
    if (iStart == oCurExtent.xStop - 1)
881
0
    {
882
0
        if (oOpts.outputMode == OutputMode::Normal)
883
0
            lines.result[iStart] = oOpts.visibleVal;
884
0
        else
885
0
            setOutputNormal(lines, iStart, lines.cur[iStart]);
886
0
        iStart--;
887
0
    }
888
889
    // Go from the observer to the left, calculating Z as we go.
890
0
    nYOffset = std::abs(nYOffset);
891
0
    for (int iPixel = iStart; iPixel > iEnd; iPixel--)
892
0
    {
893
0
        int nXOffset = std::abs(iPixel - m_nX);
894
0
        double dfZ;
895
0
        if (nXOffset == nYOffset)
896
0
            dfZ = CalcHeightLine(nYOffset, lines.cur[iPixel],
897
0
                                 lines.prev[iPixel + 1]);
898
0
        else
899
0
            dfZ = oZcalc(nXOffset, nYOffset, lines.cur[iPixel + 1],
900
0
                         lines.prev[iPixel], lines.prev[iPixel + 1]);
901
0
        if (!sdCalc)
902
0
            setOutputNormal(lines, iPixel, dfZ);
903
0
        else
904
0
            setOutputSd(lines, iPixel, dfZ);
905
0
    }
906
907
0
    maskLineLeft(lines.result, ll, nLine);
908
0
}
909
910
/// Process a line to the right of the observer.
911
///
912
/// @param nYOffset  Offset of the line being processed from the observer
913
/// @param ll  Line limits
914
/// @param lines  Raster lines to process.
915
/// @param sdCalc  standard deviation calculation indicator.
916
void ViewshedExecutor::processLineRight(int nYOffset, LineLimits &ll,
917
                                        Lines &lines, bool sdCalc)
918
0
{
919
0
    int iStart = m_nX + 1;
920
0
    int iEnd = ll.right;
921
0
    int nLine = m_nY + nYOffset;
922
923
    // If start is to the right of end, everything is taken care of by processing left.
924
0
    if (iStart >= iEnd)
925
0
    {
926
0
        maskLineRight(lines.result, ll, nLine);
927
0
        return;
928
0
    }
929
0
    iStart = oCurExtent.clampX(iStart);
930
931
    // If the observer is to the left of the raster, mark the first cell to the right as
932
    // visible. This may mark an out-of-range cell with a value, but this will be fixed
933
    // with the out of range assignment at the end.
934
0
    if (iStart == 0)
935
0
    {
936
0
        if (oOpts.outputMode == OutputMode::Normal)
937
0
            lines.result[iStart] = oOpts.visibleVal;
938
0
        else
939
0
            setOutputNormal(lines, 0, lines.cur[0]);
940
0
        iStart++;
941
0
    }
942
943
    // Go from the observer to the right, calculating Z as we go.
944
0
    nYOffset = std::abs(nYOffset);
945
0
    for (int iPixel = iStart; iPixel < iEnd; iPixel++)
946
0
    {
947
0
        int nXOffset = std::abs(iPixel - m_nX);
948
0
        double dfZ;
949
0
        if (nXOffset == nYOffset)
950
0
        {
951
0
            if (sdCalc && nXOffset == 1)
952
0
            {
953
0
                lines.result[iPixel] = oOpts.visibleVal;
954
0
                if (lines.sd[iPixel] > 1)
955
0
                    lines.cur[iPixel] = m_dfZObserver;
956
0
                continue;
957
0
            }
958
0
            dfZ = CalcHeightLine(nYOffset, lines.cur[iPixel],
959
0
                                 lines.prev[iPixel - 1]);
960
0
        }
961
0
        else
962
0
            dfZ = oZcalc(nXOffset, nYOffset, lines.cur[iPixel - 1],
963
0
                         lines.prev[iPixel], lines.prev[iPixel - 1]);
964
0
        if (!sdCalc)
965
0
            setOutputNormal(lines, iPixel, dfZ);
966
0
        else
967
0
            setOutputSd(lines, iPixel, dfZ);
968
0
    }
969
970
0
    maskLineRight(lines.result, ll, nLine);
971
0
}
972
973
/// Apply angular/distance mask to the initial X position.  Assumes m_nX is in the raster.
974
/// @param vResult  Raster line on which to apply mask.
975
/// @param ll  Line limits.
976
/// @param nLine  Line number.
977
/// @return True if the initial X position was masked.
978
bool ViewshedExecutor::maskInitial(std::vector<double> &vResult,
979
                                   const LineLimits &ll, int nLine)
980
0
{
981
    // Mask min/max.
982
0
    if (ll.left >= ll.right || ll.leftMin != ll.rightMin)
983
0
    {
984
0
        vResult[m_nX] = oOpts.outOfRangeVal;
985
0
        return true;
986
0
    }
987
988
0
    if (!oOpts.angleMasking())
989
0
        return false;
990
991
0
    if (nLine < m_nY)
992
0
    {
993
0
        if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI / 2))
994
0
        {
995
0
            vResult[m_nX] = oOpts.outOfRangeVal;
996
0
            return true;
997
0
        }
998
0
    }
999
0
    else if (nLine > m_nY)
1000
0
    {
1001
0
        if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 3 * M_PI / 2))
1002
0
        {
1003
0
            vResult[m_nX] = oOpts.outOfRangeVal;
1004
0
            return true;
1005
0
        }
1006
0
    }
1007
0
    return false;
1008
0
}
1009
1010
/// Process a line above or below the observer.
1011
///
1012
/// @param nLine  Line number being processed.
1013
/// @param lines  Raster lines to process.
1014
/// @return True on success, false otherwise.
1015
bool ViewshedExecutor::processLine(int nLine, Lines &lines)
1016
0
{
1017
0
    int nYOffset = nLine - m_nY;
1018
1019
0
    if (!readLine(nLine, lines))
1020
0
        return false;
1021
1022
    // Adjust height of the read line.
1023
0
    LineLimits ll = adjustHeight(nYOffset, lines);
1024
1025
0
    std::vector<double> savedLine;
1026
0
    if (sdMode())
1027
0
        savedLine = lines.cur;
1028
1029
0
    auto process = [this, nYOffset, &ll, &lines](bool sdCalc)
1030
0
    {
1031
0
        CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
1032
0
        pQueue->SubmitJob([&]()
1033
0
                          { processLineLeft(nYOffset, ll, lines, sdCalc); });
1034
0
        pQueue->SubmitJob([&]()
1035
0
                          { processLineRight(nYOffset, ll, lines, sdCalc); });
1036
0
        pQueue->WaitCompletion();
1037
0
    };
1038
1039
0
    bool masked = false;
1040
    // Handle initial position on the line.
1041
0
    if (oCurExtent.containsX(m_nX))
1042
0
    {
1043
0
        masked = maskInitial(lines.result, ll, nLine);
1044
0
        if (!masked)
1045
0
        {
1046
0
            double dfZ = CalcHeightLine(std::abs(nYOffset), lines.cur[m_nX],
1047
0
                                        lines.prev[m_nX]);
1048
0
            setOutputNormal(lines, m_nX, dfZ);
1049
0
        }
1050
0
    }
1051
1052
0
    process(false);
1053
1054
    // Process standard deviation mode
1055
0
    if (sdMode())
1056
0
    {
1057
0
        lines.prev = std::move(lines.prevTmp);
1058
0
        lines.prevTmp = std::move(lines.cur);
1059
0
        lines.cur = std::move(savedLine);
1060
        // Handle initial position on the line.
1061
0
        if (!masked && oCurExtent.containsX(m_nX))
1062
0
        {
1063
0
            if (std::abs(nYOffset) == 1)
1064
0
            {
1065
0
                lines.result[m_nX] = oOpts.visibleVal;
1066
0
                if (lines.sd[m_nX] > 1)
1067
0
                    lines.cur[m_nX] = m_dfZObserver;
1068
0
            }
1069
0
            else
1070
0
            {
1071
1072
0
                double dfZ = CalcHeightLine(std::abs(nYOffset), lines.cur[m_nX],
1073
0
                                            lines.prev[m_nX]);
1074
0
                setOutputSd(lines, m_nX, dfZ);
1075
0
            }
1076
0
        }
1077
0
        process(true);
1078
0
        lines.prev = std::move(lines.prevTmp);
1079
0
        lines.prevTmp = lines.cur;
1080
0
    }
1081
0
    else
1082
0
        lines.prev = lines.cur;
1083
1084
0
    if (oOpts.pitchMasking())
1085
0
        applyPitchMask(lines.result, lines.pitchMask);
1086
0
    if (!writeLine(nLine, lines.result))
1087
0
        return false;
1088
1089
0
    return oProgress.lineComplete();
1090
0
}
1091
1092
// Calculate the ray angle from the origin to middle of the top or bottom
1093
// of each quadrant.
1094
void ViewshedExecutor::calcTestAngles()
1095
0
{
1096
    // Quadrant 1.
1097
0
    {
1098
0
        int ysize = m_nY + 1;
1099
0
        int xsize = oCurExtent.xStop - m_nX;
1100
0
        m_testAngle[1] = atan2(ysize, xsize / 2.0);
1101
0
    }
1102
1103
    // Quadrant 2.
1104
0
    {
1105
0
        int ysize = m_nY + 1;
1106
0
        int xsize = m_nX + 1;
1107
0
        m_testAngle[2] = atan2(ysize, -xsize / 2.0);
1108
0
    }
1109
1110
    // Quadrant 3.
1111
0
    {
1112
0
        int ysize = oCurExtent.yStop - m_nY;
1113
0
        int xsize = m_nX + 1;
1114
0
        m_testAngle[3] = atan2(-ysize, -xsize / 2.0);
1115
0
    }
1116
1117
    // Quadrant 4.
1118
0
    {
1119
0
        int ysize = oCurExtent.yStop - m_nY;
1120
0
        int xsize = oCurExtent.xStop - m_nX;
1121
0
        m_testAngle[4] = atan2(-ysize, xsize / 2.0);
1122
0
    }
1123
1124
    // Adjust range to [0, 2 * M_PI)
1125
0
    for (int i = 1; i <= 4; ++i)
1126
0
        if (m_testAngle[i] < 0)
1127
0
            m_testAngle[i] += (2 * M_PI);
1128
0
}
1129
1130
/// Run the viewshed computation
1131
/// @return  Success as true or false.
1132
bool ViewshedExecutor::run()
1133
0
{
1134
    // If we're doing angular masking, calculate the test angles used later.
1135
0
    if (oOpts.angleMasking())
1136
0
        calcTestAngles();
1137
1138
0
    Lines firstLine(oCurExtent.xSize());
1139
0
    if (oOpts.pitchMasking())
1140
0
        firstLine.pitchMask.resize(oOutExtent.xSize(),
1141
0
                                   std::numeric_limits<double>::quiet_NaN());
1142
0
    if (sdMode())
1143
0
        firstLine.sd.resize(oOutExtent.xSize());
1144
1145
0
    m_dfHeightAdjFactor = calcHeightAdjFactor();
1146
1147
0
    if (!processFirstLine(firstLine))
1148
0
        return false;
1149
1150
0
    if (oOpts.cellMode == CellMode::Edge)
1151
0
        oZcalc = doEdge;
1152
0
    else if (oOpts.cellMode == CellMode::Diagonal)
1153
0
        oZcalc = doDiagonal;
1154
0
    else if (oOpts.cellMode == CellMode::Min)
1155
0
        oZcalc = doMin;
1156
0
    else if (oOpts.cellMode == CellMode::Max)
1157
0
        oZcalc = doMax;
1158
1159
    // scan upwards
1160
0
    int yStart = oCurExtent.clampY(m_nY);
1161
0
    std::atomic<bool> err(false);
1162
0
    CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
1163
0
    pQueue->SubmitJob(
1164
0
        [&]()
1165
0
        {
1166
0
            Lines lines(oCurExtent.xSize());
1167
0
            lines.prev = firstLine.prev;
1168
0
            lines.prevTmp = firstLine.prevTmp;
1169
0
            if (oOpts.pitchMasking())
1170
0
                lines.pitchMask.resize(
1171
0
                    oOutExtent.xSize(),
1172
0
                    std::numeric_limits<double>::quiet_NaN());
1173
0
            if (sdMode())
1174
0
                lines.sd.resize(oOutExtent.xSize());
1175
1176
0
            for (int nLine = yStart - 1; nLine >= oCurExtent.yStart && !err;
1177
0
                 nLine--)
1178
0
            {
1179
0
                if (!processLine(nLine, lines))
1180
0
                    err = true;
1181
0
                if (oOpts.pitchMasking())
1182
0
                    std::fill(lines.pitchMask.begin(), lines.pitchMask.end(),
1183
0
                              std::numeric_limits<double>::quiet_NaN());
1184
0
            }
1185
0
        });
1186
1187
    // scan downwards
1188
0
    pQueue->SubmitJob(
1189
0
        [&]()
1190
0
        {
1191
0
            Lines lines(oCurExtent.xSize());
1192
0
            lines.prev = firstLine.prev;
1193
0
            lines.prevTmp = firstLine.prevTmp;
1194
0
            if (oOpts.pitchMasking())
1195
0
                lines.pitchMask.resize(
1196
0
                    oOutExtent.xSize(),
1197
0
                    std::numeric_limits<double>::quiet_NaN());
1198
0
            if (sdMode())
1199
0
                lines.sd.resize(oOutExtent.xSize());
1200
1201
0
            for (int nLine = yStart + 1; nLine < oCurExtent.yStop && !err;
1202
0
                 nLine++)
1203
0
            {
1204
0
                if (!processLine(nLine, lines))
1205
0
                    err = true;
1206
0
                if (oOpts.pitchMasking())
1207
0
                    std::fill(lines.pitchMask.begin(), lines.pitchMask.end(),
1208
0
                              std::numeric_limits<double>::quiet_NaN());
1209
0
            }
1210
0
        });
1211
0
    return true;
1212
0
}
1213
1214
}  // namespace viewshed
1215
}  // namespace gdal
1216
1217
// cppcheck-suppress-end knownConditionTrueFalse