• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15899162844

26 Jun 2025 10:14AM UTC coverage: 71.088% (+0.004%) from 71.084%
15899162844

Pull #12623

github

web-flow
Merge c704a8392 into f5cb024d4
Pull Request #12623: gdal raster overview add: add a --overview-src option

209 of 244 new or added lines in 5 files covered. (85.66%)

96 existing lines in 44 files now uncovered.

574014 of 807474 relevant lines covered (71.09%)

250815.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.83
/alg/viewshed/viewshed_executor.cpp
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
namespace gdal
25
{
26
namespace viewshed
27
{
28

29
namespace
30
{
31

32
/// Determines whether a value is a valid intersection coordinate.
33
/// @param  i  Value to test.
34
/// @return  True if the value doesn't represent an invalid intersection.
35
bool valid(int i)
78✔
36
{
37
    return i != INVALID_ISECT;
78✔
38
}
39

40
/// Determines whether a value is an invalid intersection coordinate.
41
/// @param  i  Value to test.
42
/// @return  True if the value represents an invalid intersection.
43
bool invalid(int i)
78✔
44
{
45
    return !valid(i);
78✔
46
}
47

48
/// Calculate the height at nDistance units along a line through the origin given the height
49
/// at nDistance - 1 units along the line.
50
/// \param nDistance  Distance along the line for the target point.
51
/// \param Za  Height at the line one unit previous to the target point.
52
double CalcHeightLine(int nDistance, double Za)
79,879✔
53
{
54
    nDistance = std::abs(nDistance);
79,879✔
55
    assert(nDistance != 1);
79,879✔
56
    return Za * nDistance / (nDistance - 1);
79,879✔
57
}
58

59
// Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0)
60
// and passing through the line connecting (i - 1, j, Za) and (i, j - 1, Zb).
61
// In other words, the origin and the two points form a plane and we're calculating Zc
62
// of the point (i, j, Zc), also on the plane.
63
double CalcHeightDiagonal(int i, int j, double Za, double Zb)
×
64
{
65
    return (Za * i + Zb * j) / (i + j - 1);
×
66
}
67

68
// Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0)
69
// and through the line connecting (i -1, j - 1, Za) and (i - 1, j, Zb). In other words,
70
// the origin and the other two points form a plane and we're calculating Zc of the
71
// point (i, j, Zc), also on the plane.
72
double CalcHeightEdge(int i, int j, double Za, double Zb)
2,826,630✔
73
{
74
    assert(i != j);
2,826,630✔
75
    return (Za * i + Zb * (j - i)) / (j - 1);
2,826,630✔
76
}
77

78
double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset,
×
79
                  double dfThisPrev, double dfLast,
80
                  [[maybe_unused]] double dfLastPrev)
81
{
82
    return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast);
×
83
}
84

85
double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
2,823,580✔
86
              double dfLastPrev)
87
{
88
    if (nXOffset >= nYOffset)
2,823,580✔
89
        return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev);
1,169,380✔
90
    else
91
        return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast);
1,654,200✔
92
}
93

94
double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
×
95
             double dfLastPrev)
96
{
97
    double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
×
98
    double dfDiagonal =
99
        doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
×
100
    return std::min(dfEdge, dfDiagonal);
×
101
}
102

103
double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
×
104
             double dfLastPrev)
105
{
106
    double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
×
107
    double dfDiagonal =
108
        doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
×
109
    return std::max(dfEdge, dfDiagonal);
×
110
}
111

112
}  // unnamed namespace
113

114
/// Constructor - the viewshed algorithm executor
115
/// @param srcBand  Source raster band
116
/// @param dstBand  Destination raster band
117
/// @param nX  X position of observer
118
/// @param nY  Y position of observer
119
/// @param outExtent  Extent of output raster (relative to input)
120
/// @param curExtent  Extent of active raster.
121
/// @param opts  Configuration options.
122
/// @param progress  Reference to the progress tracker.
123
/// @param emitWarningIfNoData  Whether a warning must be emitted if an input
124
///                             pixel is at the nodata value.
125
ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand,
235✔
126
                                   GDALRasterBand &dstBand, int nX, int nY,
127
                                   const Window &outExtent,
128
                                   const Window &curExtent, const Options &opts,
129
                                   Progress &progress, bool emitWarningIfNoData)
235✔
130
    : m_pool(4), m_srcBand(srcBand), m_dstBand(dstBand),
131
      m_emitWarningIfNoData(emitWarningIfNoData), oOutExtent(outExtent),
132
      oCurExtent(curExtent), m_nX(nX - oOutExtent.xStart), m_nY(nY),
235✔
133
      oOpts(opts), oProgress(progress),
134
      m_dfMinDistance2(opts.minDistance * opts.minDistance),
235✔
135
      m_dfMaxDistance2(opts.maxDistance * opts.maxDistance)
235✔
136
{
137
    if (m_dfMaxDistance2 == 0)
235✔
138
        m_dfMaxDistance2 = std::numeric_limits<double>::max();
232✔
139
    if (opts.lowPitch != -90.0)
235✔
140
        m_lowTanPitch = std::tan(oOpts.lowPitch * (2 * M_PI / 360.0));
1✔
141
    if (opts.highPitch != 90.0)
235✔
142
        m_highTanPitch = std::tan(oOpts.highPitch * (2 * M_PI / 360.0));
1✔
143
    m_srcBand.GetDataset()->GetGeoTransform(m_gt);
235✔
144
    int hasNoData = false;
235✔
145
    m_noDataValue = m_srcBand.GetNoDataValue(&hasNoData);
235✔
146
    m_hasNoData = hasNoData;
235✔
147
}
235✔
148

149
// calculate the height adjustment factor.
150
double ViewshedExecutor::calcHeightAdjFactor()
235✔
151
{
152
    std::lock_guard g(oMutex);
470✔
153

154
    const OGRSpatialReference *poDstSRS =
155
        m_dstBand.GetDataset()->GetSpatialRef();
235✔
156

157
    if (poDstSRS)
235✔
158
    {
159
        OGRErr eSRSerr;
160

161
        // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR
162
        double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr);
12✔
163

164
        /* If we fetched the axis from the SRS, use it */
165
        if (eSRSerr != OGRERR_FAILURE)
12✔
166
            return oOpts.curveCoeff / (dfSemiMajor * 2.0);
12✔
167

168
        CPLDebug("GDALViewshedGenerate",
×
169
                 "Unable to fetch SemiMajor axis from spatial reference");
170
    }
171
    return 0;
223✔
172
}
173

174
/// Set the output Z value depending on the observable height and computation mode.
175
///
176
/// dfResult  Reference to the result cell
177
/// dfCellVal  Reference to the current cell height. Replace with observable height.
178
/// dfZ  Minimum observable height at cell.
179
void ViewshedExecutor::setOutput(double &dfResult, double &dfCellVal,
2,879,710✔
180
                                 double dfZ)
181
{
182
    if (oOpts.outputMode != OutputMode::Normal)
2,879,710✔
183
    {
184
        dfResult += (dfZ - dfCellVal);
28,211✔
185
        dfResult = std::max(0.0, dfResult);
28,211✔
186
    }
187
    else
188
        dfResult = (dfCellVal + oOpts.targetHeight < dfZ) ? oOpts.invisibleVal
2,851,500✔
189
                                                          : oOpts.visibleVal;
190
    dfCellVal = std::max(dfCellVal, dfZ);
2,879,660✔
191
}
2,885,300✔
192

193
/// Read a line of raster data.
194
///
195
/// @param  nLine  Line number to read.
196
/// @param  data  Pointer to location in which to store data.
197
/// @return  Success or failure.
198
bool ViewshedExecutor::readLine(int nLine, double *data)
28,953✔
199
{
200
    std::lock_guard g(iMutex);
57,863✔
201

202
    if (GDALRasterIO(&m_srcBand, GF_Read, oOutExtent.xStart, nLine,
28,952✔
203
                     oOutExtent.xSize(), 1, data, oOutExtent.xSize(), 1,
204
                     GDT_Float64, 0, 0))
28,910✔
205
    {
206
        CPLError(CE_Failure, CPLE_AppDefined,
×
207
                 "RasterIO error when reading DEM at position (%d,%d), "
208
                 "size (%d,%d)",
209
                 oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
×
210
        return false;
×
211
    }
212
    return true;
28,910✔
213
}
214

215
/// Write an output line of either visibility or height data.
216
///
217
/// @param  nLine  Line number being written.
218
/// @param vResult  Result line to write.
219
/// @return  True on success, false otherwise.
220
bool ViewshedExecutor::writeLine(int nLine, std::vector<double> &vResult)
28,839✔
221
{
222
    // GDALRasterIO isn't thread-safe.
223
    std::lock_guard g(oMutex);
57,696✔
224

225
    if (GDALRasterIO(&m_dstBand, GF_Write, 0, nLine - oOutExtent.yStart,
57,627✔
226
                     oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(),
28,831✔
227
                     1, GDT_Float64, 0, 0))
28,857✔
228
    {
229
        CPLError(CE_Failure, CPLE_AppDefined,
×
230
                 "RasterIO error when writing target raster at position "
231
                 "(%d,%d), size (%d,%d)",
232
                 0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1);
×
233
        return false;
×
234
    }
235
    return true;
28,857✔
236
}
237

238
/// Adjust the height of the line of data by the observer height and the curvature of the
239
/// earth.
240
///
241
/// @param  nYOffset  Y offset of the line being adjusted.
242
/// @param  vThisLineVal  Line height data.
243
/// @return  Processing limits of the line based on min/max distance.
244
LineLimits ViewshedExecutor::adjustHeight(int nYOffset,
28,889✔
245
                                          std::vector<double> &vThisLineVal)
246
{
247
    LineLimits ll(0, m_nX + 1, m_nX + 1, oCurExtent.xSize());
28,889✔
248

249
    // Find the starting point in the raster (m_nX may be outside)
250
    int nXStart = oCurExtent.clampX(m_nX);
28,849✔
251

252
    const auto CheckNoData = [this](double val)
5,807,160✔
253
    {
254
        if (!m_hasFoundNoData &&
5,799,370✔
255
            ((m_hasNoData && val == m_noDataValue) || std::isnan(val)))
2,909,450✔
256
        {
257
            m_hasFoundNoData = true;
×
258
            if (m_emitWarningIfNoData)
×
259
            {
260
                CPLError(CE_Warning, CPLE_AppDefined,
×
261
                         "Nodata value found in input DEM. Output will be "
262
                         "likely incorrect");
263
            }
264
        }
265
    };
2,918,790✔
266

267
    // If there is a height adjustment factor other than zero or a max distance,
268
    // calculate the adjusted height of the cell, stopping if we've exceeded the max
269
    // distance.
270
    if (static_cast<bool>(m_dfHeightAdjFactor) || m_dfMaxDistance2 > 0 ||
28,868✔
271
        m_dfMinDistance2 > 0)
×
272
    {
273
        // Hoist invariants from the loops.
274
        const double dfLineX = m_gt[2] * nYOffset;
28,868✔
275
        const double dfLineY = m_gt[5] * nYOffset;
28,843✔
276

277
        // Go left
278
        double *pdfHeight = vThisLineVal.data() + nXStart;
28,833✔
279
        for (int nXOffset = nXStart - m_nX; nXOffset >= -m_nX;
1,477,980✔
280
             nXOffset--, pdfHeight--)
281
        {
282
            double dfX = m_gt[1] * nXOffset + dfLineX;
1,450,580✔
283
            double dfY = m_gt[4] * nXOffset + dfLineY;
1,446,870✔
284
            double dfR2 = dfX * dfX + dfY * dfY;
1,455,650✔
285

286
            if (dfR2 < m_dfMinDistance2)
1,455,650✔
287
                ll.leftMin--;
6✔
288
            else if (dfR2 > m_dfMaxDistance2)
1,455,640✔
289
            {
290
                ll.left = nXOffset + m_nX + 1;
26✔
291
                break;
26✔
292
            }
293

294
            CheckNoData(*pdfHeight);
1,455,620✔
295
            *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
1,449,110✔
296
        }
297

298
        // Go right.
299
        pdfHeight = vThisLineVal.data() + nXStart + 1;
27,431✔
300
        for (int nXOffset = nXStart - m_nX + 1;
28,939✔
301
             nXOffset < oCurExtent.xSize() - m_nX; nXOffset++, pdfHeight++)
1,503,020✔
302
        {
303
            double dfX = m_gt[1] * nXOffset + dfLineX;
1,477,280✔
304
            double dfY = m_gt[4] * nXOffset + dfLineY;
1,475,870✔
305
            double dfR2 = dfX * dfX + dfY * dfY;
1,482,760✔
306
            if (dfR2 < m_dfMinDistance2)
1,482,760✔
307
                ll.rightMin++;
3✔
308
            else if (dfR2 > m_dfMaxDistance2)
1,482,760✔
309
            {
310
                ll.right = nXOffset + m_nX;
26✔
311
                break;
26✔
312
            }
313

314
            CheckNoData(*pdfHeight);
1,482,740✔
315
            *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
1,474,090✔
316
        }
28,942✔
317
    }
318
    else
319
    {
320
        // No curvature adjustment. Just normalize for the observer height.
321
        double *pdfHeight = vThisLineVal.data();
×
322
        for (int i = 0; i < oCurExtent.xSize(); ++i)
×
323
        {
324
            CheckNoData(*pdfHeight);
×
325
            *pdfHeight -= m_dfZObserver;
×
326
            pdfHeight++;
×
327
        }
328
    }
329
    return ll;
28,942✔
330
}
331

332
/// Process the first line (the one with the Y coordinate the same as the observer).
333
///
334
/// @param vLastLineVal  Vector in which to store the read line. Becomes the last line
335
///    in further processing.
336
/// @return True on success, false otherwise.
337
bool ViewshedExecutor::processFirstLine(std::vector<double> &vLastLineVal)
235✔
338
{
339
    int nLine = oOutExtent.clampY(m_nY);
235✔
340
    int nYOffset = nLine - m_nY;
235✔
341

342
    std::vector<double> vResult(oOutExtent.xSize());
470✔
343
    std::vector<double> vThisLineVal(oOutExtent.xSize());
470✔
344

345
    if (!readLine(nLine, vThisLineVal.data()))
235✔
346
        return false;
×
347

348
    // If the observer is outside of the raster, take the specified value as the Z height,
349
    // otherwise, take it as an offset from the raster height at that location.
350
    m_dfZObserver = oOpts.observer.z;
235✔
351
    if (oCurExtent.containsX(m_nX))
235✔
352
    {
353
        m_dfZObserver += vThisLineVal[m_nX];
229✔
354
        if (oOpts.outputMode == OutputMode::Normal)
229✔
355
            vResult[m_nX] = oOpts.visibleVal;
212✔
356
    }
357
    m_dfHeightAdjFactor = calcHeightAdjFactor();
235✔
358

359
    // In DEM mode the base is the pre-adjustment value.  In ground mode the base is zero.
360
    if (oOpts.outputMode == OutputMode::DEM)
235✔
361
        vResult = vThisLineVal;
16✔
362

363
    LineLimits ll = adjustHeight(nYOffset, vThisLineVal);
235✔
364
    if (oCurExtent.containsX(m_nX) && ll.leftMin != ll.rightMin)
235✔
365
        vResult[m_nX] = oOpts.outOfRangeVal;
1✔
366

367
    if (!oCurExtent.containsY(m_nY))
235✔
368
        processFirstLineTopOrBottom(ll, vResult, vThisLineVal);
4✔
369
    else
370
    {
371
        CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
462✔
372
        pQueue->SubmitJob([&]()
231✔
373
                          { processFirstLineLeft(ll, vResult, vThisLineVal); });
231✔
374

375
        pQueue->SubmitJob(
231✔
376
            [&]() { processFirstLineRight(ll, vResult, vThisLineVal); });
231✔
377
        pQueue->WaitCompletion();
231✔
378
    }
379

380
    // Make the current line the last line.
381
    vLastLineVal = std::move(vThisLineVal);
235✔
382

383
    // Create the output writer.
384
    if (!writeLine(nLine, vResult))
235✔
385
        return false;
×
386

387
    return oProgress.lineComplete();
235✔
388
}
389

390
// If the observer is above or below the raster, set all cells in the first line near the
391
// observer as observable provided they're in range. Mark cells out of range as such.
392
/// @param  ll  Line limits for processing.
393
/// @param  vResult  Result line.
394
/// @param  vThisLineVal  Heights of the cells in the target line
395
void ViewshedExecutor::processFirstLineTopOrBottom(
4✔
396
    const LineLimits &ll, std::vector<double> &vResult,
397
    std::vector<double> &vThisLineVal)
398
{
399
    double *pResult = vResult.data() + ll.left;
4✔
400
    double *pThis = vThisLineVal.data() + ll.left;
4✔
401
    for (int iPixel = ll.left; iPixel < ll.right; ++iPixel, ++pResult, pThis++)
24✔
402
    {
403
        if (oOpts.outputMode == OutputMode::Normal)
20✔
404
            *pResult = oOpts.visibleVal;
×
405
        else
406
            setOutput(*pResult, *pThis, *pThis);
20✔
407
    }
408

409
    std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal);
4✔
410
    std::fill(vResult.begin() + ll.right, vResult.begin() + oCurExtent.xStop,
4✔
411
              oOpts.outOfRangeVal);
4✔
412
}
4✔
413

414
/// Process the part of the first line to the left of the observer.
415
///
416
/// @param ll  Line limits for masking.
417
/// @param vResult  Vector in which to store the visibility/height results.
418
/// @param vThisLineVal  Height of each cell in the line being processed.
419
void ViewshedExecutor::processFirstLineLeft(const LineLimits &ll,
231✔
420
                                            std::vector<double> &vResult,
421
                                            std::vector<double> &vThisLineVal)
422
{
423
    int iEnd = ll.left - 1;
231✔
424
    int iStart = m_nX - 1;  // One left of the observer.
231✔
425

426
    // If end is to the right of start, everything is taken care of by right processing.
427
    if (iEnd >= iStart)
231✔
428
        return;
30✔
429

430
    iStart = oCurExtent.clampX(iStart);
201✔
431

432
    double *pThis = vThisLineVal.data() + iStart;
201✔
433

434
    // If the start cell is next to the observer, just mark it visible.
435
    if (iStart + 1 == m_nX || iStart + 1 == oCurExtent.xStop)
201✔
436
    {
437
        double dfZ = *pThis;
201✔
438
        if (oOpts.outputMode == OutputMode::Normal)
201✔
439
            vResult[iStart] = oOpts.visibleVal;
190✔
440
        else
441
        {
442
            maskLowPitch(dfZ, 1, 0);
11✔
443
            setOutput(vResult[iStart], *pThis, dfZ);
11✔
444
        }
445
        maskHighPitch(vResult[iStart], dfZ, 1, 0);
201✔
446
        iStart--;
201✔
447
        pThis--;
201✔
448
    }
449

450
    // Go from the observer to the left, calculating Z as we go.
451
    for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--)
10,339✔
452
    {
453
        int nXOffset = std::abs(iPixel - m_nX);
10,138✔
454
        double dfZ = CalcHeightLine(nXOffset, *(pThis + 1));
10,138✔
455
        maskLowPitch(dfZ, nXOffset, 0);
10,139✔
456
        setOutput(vResult[iPixel], *pThis, dfZ);
10,143✔
457
        maskHighPitch(vResult[iPixel], dfZ, nXOffset, 0);
10,142✔
458
    }
459

460
    maskLineLeft(vResult, ll, m_nY);
201✔
461
}
462

463
/// Mask cells based on angle intersection to the left of the observer.
464
///
465
/// @param vResult  Result raaster line.
466
/// @param nLine  Line number.
467
/// @return  True when all cells have been masked.
468
bool ViewshedExecutor::maskAngleLeft(std::vector<double> &vResult, int nLine)
25,957✔
469
{
470
    auto clamp = [this](int x)
38✔
471
    { return (x < 0 || x >= m_nX) ? INVALID_ISECT : x; };
20✔
472

473
    if (!oOpts.angleMasking())
25,957✔
474
        return false;
25,889✔
475

476
    if (nLine != m_nY)
7✔
477
    {
478
        int startAngleX =
479
            clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
10✔
480
        int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine));
10✔
481
        // If neither X intersect is in the quadrant and a ray in the quadrant isn't
482
        // between start and stop, fill it all and return true.  If it is in between
483
        // start and stop, we're done.
484
        if (invalid(startAngleX) && invalid(endAngleX))
10✔
485
        {
486
            // Choose a test angle in quadrant II or III depending on the line.
487
            double testAngle = nLine < m_nY ? m_testAngle[2] : m_testAngle[3];
7✔
488
            if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
7✔
489
            {
490
                std::fill(vResult.begin(), vResult.begin() + m_nX,
2✔
491
                          oOpts.outOfRangeVal);
2✔
492
                return true;
7✔
493
            }
494
            return false;
5✔
495
        }
496
        if (nLine > m_nY)
3✔
497
            std::swap(startAngleX, endAngleX);
×
498
        if (invalid(startAngleX))
3✔
499
            startAngleX = 0;
3✔
500
        if (invalid(endAngleX))
3✔
501
            endAngleX = m_nX - 1;
×
502
        if (startAngleX <= endAngleX)
3✔
503
        {
504
            std::fill(vResult.begin(), vResult.begin() + startAngleX,
3✔
505
                      oOpts.outOfRangeVal);
3✔
506
            std::fill(vResult.begin() + endAngleX + 1, vResult.begin() + m_nX,
3✔
507
                      oOpts.outOfRangeVal);
3✔
508
        }
509
        else
510
        {
511
            std::fill(vResult.begin() + endAngleX + 1,
×
512
                      vResult.begin() + startAngleX, oOpts.outOfRangeVal);
×
513
        }
514
    }
515
    // nLine == m_nY
UNCOV
516
    else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI))
×
517
    {
518
        std::fill(vResult.begin(), vResult.begin() + m_nX, oOpts.outOfRangeVal);
×
519
        return true;
×
520
    }
521
    return false;
4✔
522
}
523

524
/// Mask cells based on angle intersection to the right of the observer.
525
///
526
/// @param vResult  Result raaster line.
527
/// @param nLine  Line number.
528
/// @return  True when all cells have been masked.
529
bool ViewshedExecutor::maskAngleRight(std::vector<double> &vResult, int nLine)
28,916✔
530
{
531
    int lineLength = static_cast<int>(vResult.size());
28,916✔
532

533
    auto clamp = [this, lineLength](int x)
54✔
534
    { return (x <= m_nX || x >= lineLength) ? INVALID_ISECT : x; };
36✔
535

536
    if (oOpts.startAngle == oOpts.endAngle)
28,909✔
537
        return false;
28,884✔
538

539
    if (nLine != m_nY)
25✔
540
    {
541
        int startAngleX =
542
            clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
18✔
543
        int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine));
18✔
544

545
        // If neither X intersect is in the quadrant and a ray in the quadrant isn't
546
        // between start and stop, fill it all and return true.  If it is in between
547
        // start and stop, we're done.
548
        if (invalid(startAngleX) && invalid(endAngleX))
18✔
549
        {
550
            // Choose a test angle in quadrant I or IV depending on the line.
551
            double testAngle = nLine < m_nY ? m_testAngle[1] : m_testAngle[4];
10✔
552
            if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
10✔
553
            {
554
                std::fill(vResult.begin() + m_nX + 1, vResult.end(),
×
555
                          oOpts.outOfRangeVal);
×
556
                return true;
10✔
557
            }
558
            return false;
10✔
559
        }
560

561
        if (nLine > m_nY)
8✔
562
            std::swap(startAngleX, endAngleX);
×
563
        if (invalid(endAngleX))
8✔
564
            endAngleX = lineLength - 1;
×
565
        if (invalid(startAngleX))
8✔
566
            startAngleX = m_nX + 1;
8✔
567
        if (startAngleX <= endAngleX)
8✔
568
        {
569
            std::fill(vResult.begin() + m_nX + 1, vResult.begin() + startAngleX,
8✔
570
                      oOpts.outOfRangeVal);
8✔
571
            std::fill(vResult.begin() + endAngleX + 1, vResult.end(),
8✔
572
                      oOpts.outOfRangeVal);
8✔
573
        }
574
        else
575
        {
576
            std::fill(vResult.begin() + endAngleX + 1,
×
577
                      vResult.begin() + startAngleX, oOpts.outOfRangeVal);
×
578
        }
579
    }
580
    // nLine == m_nY
581
    else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 0))
7✔
582
    {
583
        std::fill(vResult.begin() + m_nX + 1, vResult.end(),
1✔
584
                  oOpts.outOfRangeVal);
1✔
585
        return true;
1✔
586
    }
587
    return false;
9✔
588
}
589

590
/// Perform angle and min/max masking to the left of the observer.
591
///
592
/// @param vResult  Raster line to mask.
593
/// @param ll  Min/max line limits.
594
/// @param nLine  Line number.
595
void ViewshedExecutor::maskLineLeft(std::vector<double> &vResult,
25,963✔
596
                                    const LineLimits &ll, int nLine)
597
{
598
    // If we've already masked with angles everything, just return.
599
    if (maskAngleLeft(vResult, nLine))
25,963✔
600
        return;
2✔
601

602
    // Mask cells from the left edge to the left limit.
603
    std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal);
25,894✔
604
    // Mask cells from the left min to the observer.
605
    if (ll.leftMin < m_nX)
25,871✔
606
        std::fill(vResult.begin() + ll.leftMin, vResult.begin() + m_nX,
3✔
607
                  oOpts.outOfRangeVal);
3✔
608
}
609

610
/// Perform angle and min/max masking to the right of the observer.
611
///
612
/// @param vResult  Raster line to mask.
613
/// @param ll  Min/max line limits.
614
/// @param nLine  Line number.
615
void ViewshedExecutor::maskLineRight(std::vector<double> &vResult,
28,917✔
616
                                     const LineLimits &ll, int nLine)
617
{
618
    // If we've already masked with angles everything, just return.
619
    if (maskAngleRight(vResult, nLine))
28,917✔
620
        return;
1✔
621

622
    // Mask cells from the observer to right min.
623
    std::fill(vResult.begin() + m_nX + 1, vResult.begin() + ll.rightMin,
28,882✔
624
              oOpts.outOfRangeVal);
28,912✔
625
    // Mask cells from the right limit to the right edge.
626
    if (ll.right + 1 < static_cast<int>(vResult.size()))
28,845✔
627
        std::fill(vResult.begin() + ll.right + 1, vResult.end(),
8✔
628
                  oOpts.outOfRangeVal);
8✔
629
}
630

631
/// Process the part of the first line to the right of the observer.
632
///
633
/// @param ll  Line limits
634
/// @param vResult  Vector in which to store the visibility/height results.
635
/// @param vThisLineVal  Height of each cell in the line being processed.
636
void ViewshedExecutor::processFirstLineRight(const LineLimits &ll,
231✔
637
                                             std::vector<double> &vResult,
638
                                             std::vector<double> &vThisLineVal)
639
{
640
    int iStart = m_nX + 1;
231✔
641
    int iEnd = ll.right;
231✔
642

643
    // If start is to the right of end, everything is taken care of by left processing.
644
    if (iStart >= iEnd)
231✔
645
        return;
2✔
646

647
    iStart = oCurExtent.clampX(iStart);
229✔
648

649
    double *pThis = vThisLineVal.data() + iStart;
229✔
650

651
    // If the start cell is next to the observer, just mark it visible.
652
    if (iStart - 1 == m_nX || iStart == oCurExtent.xStart)
229✔
653
    {
654
        double dfZ = *pThis;
229✔
655
        if (oOpts.outputMode == OutputMode::Normal)
229✔
656
            vResult[iStart] = oOpts.visibleVal;
212✔
657
        else
658
        {
659
            maskLowPitch(dfZ, 1, 0);
17✔
660
            setOutput(vResult[iStart], *pThis, dfZ);
17✔
661
        }
662
        maskHighPitch(vResult[iStart], dfZ, 1, 0);
229✔
663
        iStart++;
229✔
664
        pThis++;
229✔
665
    }
666

667
    // Go from the observer to the right, calculating Z as we go.
668
    for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++)
10,812✔
669
    {
670
        int nXOffset = std::abs(iPixel - m_nX);
10,583✔
671
        double dfZ = CalcHeightLine(nXOffset, *(pThis - 1));
10,583✔
672
        maskLowPitch(dfZ, nXOffset, 0);
10,583✔
673
        setOutput(vResult[iPixel], *pThis, dfZ);
10,583✔
674
        maskHighPitch(vResult[iPixel], dfZ, nXOffset, 0);
10,583✔
675
    }
676

677
    maskLineRight(vResult, ll, m_nY);
229✔
678
}
679

680
/// Process a line to the left of the observer.
681
///
682
/// @param nYOffset  Offset of the line being processed from the observer
683
/// @param ll  Line limits
684
/// @param vResult  Vector in which to store the visibility/height results.
685
/// @param vThisLineVal  Height of each cell in the line being processed.
686
/// @param vLastLineVal  Observable height of each cell in the previous line processed.
687
void ViewshedExecutor::processLineLeft(int nYOffset, LineLimits &ll,
28,624✔
688
                                       std::vector<double> &vResult,
689
                                       std::vector<double> &vThisLineVal,
690
                                       std::vector<double> &vLastLineVal)
691
{
692
    int iStart = m_nX - 1;
28,624✔
693
    int iEnd = ll.left - 1;
28,624✔
694
    int nLine = m_nY + nYOffset;
28,624✔
695
    // If start to the left of end, everything is taken care of by processing right.
696
    if (iStart <= iEnd)
28,624✔
697
        return;
2,927✔
698
    iStart = oCurExtent.clampX(iStart);
25,697✔
699

700
    nYOffset = std::abs(nYOffset);
25,654✔
701
    double *pThis = vThisLineVal.data() + iStart;
25,654✔
702
    double *pLast = vLastLineVal.data() + iStart;
25,610✔
703

704
    // If the observer is to the right of the raster, mark the first cell to the left as
705
    // visible. This may mark an out-of-range cell with a value, but this will be fixed
706
    // with the out of range assignment at the end.
707

708
    if (iStart == oCurExtent.xStop - 1)
25,669✔
709
    {
710
        if (oOpts.outputMode == OutputMode::Normal)
6✔
711
            vResult[iStart] = oOpts.visibleVal;
×
712
        else
713
        {
714
            maskLowPitch(*pThis, m_nX - iStart, nYOffset);
6✔
715
            setOutput(vResult[iStart], *pThis, *pThis);
6✔
716
            maskHighPitch(vResult[iStart], *pThis, m_nX - iStart, nYOffset);
6✔
717
        }
718
        iStart--;
6✔
719
        pThis--;
6✔
720
        pLast--;
6✔
721
    }
722

723
    // Go from the observer to the left, calculating Z as we go.
724
    for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--, pLast--)
1,428,100✔
725
    {
726
        int nXOffset = std::abs(iPixel - m_nX);
1,402,330✔
727
        double dfZ;
728
        if (nXOffset == nYOffset)
1,402,330✔
729
        {
730
            if (nXOffset == 1)
15,634✔
731
                dfZ = *pThis;
375✔
732
            else
733
                dfZ = CalcHeightLine(nXOffset, *(pLast + 1));
15,259✔
734
        }
735
        else
736
            dfZ =
1,396,130✔
737
                oZcalc(nXOffset, nYOffset, *(pThis + 1), *pLast, *(pLast + 1));
1,386,700✔
738
        maskLowPitch(dfZ, nXOffset, nYOffset);
1,411,780✔
739
        setOutput(vResult[iPixel], *pThis, dfZ);
1,399,370✔
740
        maskHighPitch(vResult[iPixel], dfZ, nXOffset, nYOffset);
1,402,390✔
741
    }
742

743
    maskLineLeft(vResult, ll, nLine);
25,773✔
744
}
745

746
/// Process a line to the right of the observer.
747
///
748
/// @param nYOffset  Offset of the line being processed from the observer
749
/// @param ll  Line limits
750
/// @param vResult  Vector in which to store the visibility/height results.
751
/// @param vThisLineVal  Height of each cell in the line being processed.
752
/// @param vLastLineVal  Observable height of each cell in the previous line processed.
753
void ViewshedExecutor::processLineRight(int nYOffset, LineLimits &ll,
28,643✔
754
                                        std::vector<double> &vResult,
755
                                        std::vector<double> &vThisLineVal,
756
                                        std::vector<double> &vLastLineVal)
757
{
758
    int iStart = m_nX + 1;
28,643✔
759
    int iEnd = ll.right;
28,643✔
760
    int nLine = m_nY + nYOffset;
28,643✔
761

762
    // If start is to the right of end, everything is taken care of by processing left.
763
    if (iStart >= iEnd)
28,643✔
764
        return;
12✔
765
    iStart = oCurExtent.clampX(iStart);
28,631✔
766

767
    nYOffset = std::abs(nYOffset);
28,623✔
768
    double *pThis = vThisLineVal.data() + iStart;
28,623✔
769
    double *pLast = vLastLineVal.data() + iStart;
28,584✔
770

771
    // If the observer is to the left of the raster, mark the first cell to the right as
772
    // visible. This may mark an out-of-range cell with a value, but this will be fixed
773
    // with the out of range assignment at the end.
774
    if (iStart == 0)
28,650✔
775
    {
776
        if (oOpts.outputMode == OutputMode::Normal)
6✔
777
            vResult[iStart] = oOpts.visibleVal;
×
778
        else
779
        {
780
            maskLowPitch(*pThis, m_nX, nYOffset);
6✔
781
            setOutput(vResult[0], *pThis, *pThis);
6✔
782
            maskHighPitch(vResult[0], *pThis, m_nX, nYOffset);
6✔
783
        }
784
        iStart++;
6✔
785
        pThis++;
6✔
786
        pLast++;
6✔
787
    }
788

789
    // Go from the observer to the right, calculating Z as we go.
790
    for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++, pLast++)
1,485,500✔
791
    {
792
        int nXOffset = std::abs(iPixel - m_nX);
1,456,810✔
793
        double dfZ;
794
        if (nXOffset == nYOffset)
1,456,810✔
795
        {
796
            if (nXOffset == 1)
16,127✔
797
                dfZ = *pThis;
416✔
798
            else
799
                dfZ = CalcHeightLine(nXOffset, *(pLast - 1));
15,711✔
800
        }
801
        else
802
            dfZ =
1,450,980✔
803
                oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1));
1,440,680✔
804
        maskLowPitch(dfZ, nXOffset, nYOffset);
1,467,110✔
805
        setOutput(vResult[iPixel], *pThis, dfZ);
1,458,660✔
806
        maskHighPitch(vResult[iPixel], dfZ, nXOffset, nYOffset);
1,458,420✔
807
    }
808

809
    maskLineRight(vResult, ll, nLine);
28,694✔
810
}
811

812
/// Apply angular mask to the initial X position.  Assumes m_nX is in the raster.
813
/// @param vResult  Raster line on which to apply mask.
814
/// @param nLine  Line number.
815
void ViewshedExecutor::maskInitial(std::vector<double> &vResult, int nLine)
28,632✔
816
{
817
    if (!oOpts.angleMasking())
28,632✔
818
        return;
28,572✔
819

820
    if (nLine < m_nY)
25✔
821
    {
822
        if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI / 2))
13✔
823
            vResult[m_nX] = oOpts.outOfRangeVal;
×
824
    }
825
    else if (nLine > m_nY)
12✔
826
    {
827
        if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 3 * M_PI / 2))
5✔
828
            vResult[m_nX] = oOpts.outOfRangeVal;
×
829
    }
830
}
831

832
/// Process a line above or below the observer.
833
///
834
/// @param nLine  Line number being processed.
835
/// @param vLastLineVal  Vector in which to store the read line. Becomes the last line
836
///    in further processing.
837
/// @return True on success, false otherwise.
838
bool ViewshedExecutor::processLine(int nLine, std::vector<double> &vLastLineVal)
28,719✔
839
{
840
    int nYOffset = nLine - m_nY;
28,719✔
841
    std::vector<double> vResult(oOutExtent.xSize());
57,439✔
842
    std::vector<double> vThisLineVal(oOutExtent.xSize());
57,435✔
843

844
    if (!readLine(nLine, vThisLineVal.data()))
28,723✔
845
        return false;
×
846

847
    // In DEM mode the base is the input DEM value.
848
    // In ground mode the base is zero.
849
    if (oOpts.outputMode == OutputMode::DEM)
28,682✔
850
        vResult = vThisLineVal;
163✔
851

852
    // Adjust height of the read line.
853
    LineLimits ll = adjustHeight(nYOffset, vThisLineVal);
28,682✔
854

855
    // Handle the initial position on the line.
856
    if (oCurExtent.containsX(m_nX))
28,697✔
857
    {
858
        if (ll.left < ll.right && ll.leftMin == ll.rightMin)
28,668✔
859
        {
860
            double dfZ;
861
            if (std::abs(nYOffset) == 1)
28,656✔
862
                dfZ = vThisLineVal[m_nX];
414✔
863
            else
864
                dfZ = CalcHeightLine(nYOffset, vLastLineVal[m_nX]);
28,242✔
865
            maskLowPitch(dfZ, 0, nYOffset);
28,651✔
866
            setOutput(vResult[m_nX], vThisLineVal[m_nX], dfZ);
28,639✔
867
            maskHighPitch(vResult[m_nX], dfZ, 0, nYOffset);
28,612✔
868
        }
869
        else
870
            vResult[m_nX] = oOpts.outOfRangeVal;
12✔
871

872
        maskInitial(vResult, nLine);
28,625✔
873
    }
874

875
    // process left half then right half of line
876
    CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
57,339✔
877
    pQueue->SubmitJob(
28,587✔
878
        [&]() {
28,638✔
879
            processLineLeft(nYOffset, ll, vResult, vThisLineVal, vLastLineVal);
28,638✔
880
        });
28,590✔
881
    pQueue->SubmitJob(
28,640✔
882
        [&]() {
28,644✔
883
            processLineRight(nYOffset, ll, vResult, vThisLineVal, vLastLineVal);
28,644✔
884
        });
28,670✔
885
    pQueue->WaitCompletion();
28,649✔
886
    // Make the current line the last line.
887
    vLastLineVal = std::move(vThisLineVal);
28,674✔
888

889
    if (!writeLine(nLine, vResult))
28,605✔
890
        return false;
×
891

892
    return oProgress.lineComplete();
28,581✔
893
}
894

895
// Calculate the ray angle from the origin to middle of the top or bottom
896
// of each quadrant.
897
void ViewshedExecutor::calcTestAngles()
2✔
898
{
899
    // Quadrant 1.
900
    {
901
        int ysize = m_nY + 1;
2✔
902
        int xsize = oCurExtent.xStop - m_nX;
2✔
903
        m_testAngle[1] = atan2(ysize, xsize / 2.0);
2✔
904
    }
905

906
    // Quadrant 2.
907
    {
908
        int ysize = m_nY + 1;
2✔
909
        int xsize = m_nX + 1;
2✔
910
        m_testAngle[2] = atan2(ysize, -xsize / 2.0);
2✔
911
    }
912

913
    // Quadrant 3.
914
    {
915
        int ysize = oCurExtent.yStop - m_nY;
2✔
916
        int xsize = m_nX + 1;
2✔
917
        m_testAngle[3] = atan2(-ysize, -xsize / 2.0);
2✔
918
    }
919

920
    // Quadrant 4.
921
    {
922
        int ysize = oCurExtent.yStop - m_nY;
2✔
923
        int xsize = oCurExtent.xStop - m_nX;
2✔
924
        m_testAngle[4] = atan2(-ysize, xsize / 2.0);
2✔
925
    }
926

927
    // Adjust range to [0, 2 * M_PI)
928
    for (int i = 1; i <= 4; ++i)
10✔
929
        if (m_testAngle[i] < 0)
8✔
930
            m_testAngle[i] += (2 * M_PI);
4✔
931
}
2✔
932

933
/// Run the viewshed computation
934
/// @return  Success as true or false.
935
bool ViewshedExecutor::run()
235✔
936
{
937
    // If we're doing angular masking, calculate the test angles used later.
938
    if (oOpts.angleMasking())
235✔
939
        calcTestAngles();
2✔
940

941
    std::vector<double> vFirstLineVal(oCurExtent.xSize());
470✔
942
    if (!processFirstLine(vFirstLineVal))
235✔
943
        return false;
×
944

945
    if (oOpts.cellMode == CellMode::Edge)
235✔
946
        oZcalc = doEdge;
235✔
947
    else if (oOpts.cellMode == CellMode::Diagonal)
×
948
        oZcalc = doDiagonal;
×
949
    else if (oOpts.cellMode == CellMode::Min)
×
950
        oZcalc = doMin;
×
951
    else if (oOpts.cellMode == CellMode::Max)
×
952
        oZcalc = doMax;
×
953

954
    // scan upwards
955
    int yStart = oCurExtent.clampY(m_nY);
235✔
956
    std::atomic<bool> err(false);
235✔
957
    CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
235✔
958
    pQueue->SubmitJob(
235✔
959
        [&]()
235✔
960
        {
961
            std::vector<double> vLastLineVal = vFirstLineVal;
470✔
962

963
            for (int nLine = yStart - 1; nLine >= oCurExtent.yStart && !err;
13,515✔
964
                 nLine--)
965
                if (!processLine(nLine, vLastLineVal))
13,278✔
966
                    err = true;
×
967
        });
235✔
968

969
    // scan downwards
970
    pQueue->SubmitJob(
235✔
971
        [&]()
235✔
972
        {
973
            std::vector<double> vLastLineVal = vFirstLineVal;
470✔
974

975
            for (int nLine = yStart + 1; nLine < oCurExtent.yStop && !err;
15,676✔
976
                 nLine++)
977
                if (!processLine(nLine, vLastLineVal))
15,440✔
978
                    err = true;
×
979
        });
235✔
980
    return true;
235✔
981
}
982

983
/// Mask cells lower than the low pitch angle of intersection by setting the value
984
/// to the intersection value.
985
///
986
/// @param dfZ  Initial/modified cell height.
987
/// @param nXOffset  Cell X offset from observer.
988
/// @param nYOffset  Cell Y offset from observer.
989
void ViewshedExecutor::maskLowPitch(double &dfZ, int nXOffset, int nYOffset)
2,901,620✔
990
{
991
    if (std::isnan(m_lowTanPitch))
2,901,620✔
992
        return;
2,883,730✔
993

994
    double dfX = m_gt[1] * nXOffset + m_gt[2] * nYOffset;
×
995
    double dfY = m_gt[4] * nXOffset + m_gt[5] * nYOffset;
24✔
996
    double dfR2 = dfX * dfX + dfY * dfY;
24✔
997
    double dfDist = std::sqrt(dfR2);
24✔
998
    double dfZmask = dfDist * m_lowTanPitch;
24✔
999
    if (dfZmask > dfZ)
24✔
1000
        dfZ = dfZmask;
16✔
1001
}
1002

1003
/// Mask cells higher than the high pitch angle of intersection by setting the value
1004
/// to out-of-range.
1005
///
1006
/// @param dfResult  Result value.
1007
/// @param dfZ  Cell height.
1008
/// @param nXOffset  Cell X offset from observer.
1009
/// @param nYOffset  Cell Y offset from observer.
1010
void ViewshedExecutor::maskHighPitch(double &dfResult, double dfZ, int nXOffset,
2,877,900✔
1011
                                     int nYOffset)
1012
{
1013
    if (std::isnan(m_highTanPitch))
2,877,900✔
1014
        return;
2,875,780✔
1015

1016
    double dfX = m_gt[1] * nXOffset + m_gt[2] * nYOffset;
×
1017
    double dfY = m_gt[4] * nXOffset + m_gt[5] * nYOffset;
224✔
1018
    double dfR2 = dfX * dfX + dfY * dfY;
224✔
1019
    double dfDist = std::sqrt(dfR2);
224✔
1020
    double dfZmask = dfDist * m_highTanPitch;
224✔
1021
    if (dfZmask < dfZ)
224✔
1022
        dfResult = oOpts.outOfRangeVal;
3✔
1023
}
1024

1025
}  // namespace viewshed
1026
}  // namespace gdal
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc