LCOV - code coverage report
Current view: top level - alg/viewshed - viewshed_executor.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 502 564 89.0 %
Date: 2026-01-20 17:06:23 Functions: 41 46 89.1 %

          Line data    Source code
       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           0 : CPLErr DummyBand::IReadBlock(int, int, void *)
      32             : {
      33           0 :     return CE_Failure;
      34             : }
      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          94 : bool valid(int i)
      45             : {
      46          94 :     return i != INVALID_ISECT;
      47             : }
      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          94 : bool invalid(int i)
      53             : {
      54          94 :     return !valid(i);
      55             : }
      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       80988 : double CalcHeightLine(int nDistance, double Za)
      62             : {
      63       80988 :     assert(nDistance > 1);
      64       80988 :     return Za * nDistance / (nDistance - 1);
      65             : }
      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       61202 : double CalcHeightLine(int nDistance, double Zcur, double Za)
      73             : {
      74       61202 :     nDistance = std::abs(nDistance);
      75       61202 :     assert(nDistance > 0);
      76       61202 :     if (nDistance == 1)
      77        1225 :         return Zcur;
      78       59977 :     return CalcHeightLine(nDistance, Za);
      79             : }
      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           0 : double CalcHeightDiagonal(int i, int j, double Za, double Zb)
      86             : {
      87           0 :     return (Za * i + Zb * j) / (i + j - 1);
      88             : }
      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     2917460 : double CalcHeightEdge(int i, int j, double Za, double Zb)
      95             : {
      96     2917460 :     assert(i != j);
      97     2917460 :     return (Za * i + Zb * (j - i)) / (j - 1);
      98             : }
      99             : 
     100           0 : double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset,
     101             :                   double dfThisPrev, double dfLast,
     102             :                   [[maybe_unused]] double dfLastPrev)
     103             : {
     104           0 :     return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast);
     105             : }
     106             : 
     107     2917460 : double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
     108             :               double dfLastPrev)
     109             : {
     110     2917460 :     if (nXOffset >= nYOffset)
     111     1192650 :         return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev);
     112             :     else
     113     1724820 :         return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast);
     114             : }
     115             : 
     116           0 : double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
     117             :              double dfLastPrev)
     118             : {
     119           0 :     double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     120             :     double dfDiagonal =
     121           0 :         doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     122           0 :     return std::min(dfEdge, dfDiagonal);
     123             : }
     124             : 
     125           0 : double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
     126             :              double dfLastPrev)
     127             : {
     128           0 :     double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     129             :     double dfDiagonal =
     130           0 :         doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     131           0 :     return std::max(dfEdge, dfDiagonal);
     132             : }
     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         244 : 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         244 :                                    Progress &progress, bool emitWarningIfNoData)
     154             :     : m_pool(4), m_dummyBand(), m_srcBand(srcBand), m_sdBand(sdBand),
     155             :       m_dstBand(dstBand),
     156             :       // If the standard deviation band isn't a dummy band, we're in SD mode.
     157         244 :       m_hasSdBand(dynamic_cast<DummyBand *>(&m_sdBand) == nullptr),
     158             :       m_emitWarningIfNoData(emitWarningIfNoData), oOutExtent(outExtent),
     159         244 :       oCurExtent(curExtent), m_nX(nX - oOutExtent.xStart), m_nY(nY),
     160             :       oOpts(opts), oProgress(progress),
     161         244 :       m_dfMinDistance2(opts.minDistance * opts.minDistance),
     162         488 :       m_dfMaxDistance2(opts.maxDistance * opts.maxDistance)
     163             : {
     164         244 :     if (m_dfMaxDistance2 == 0)
     165         241 :         m_dfMaxDistance2 = std::numeric_limits<double>::max();
     166         244 :     if (opts.lowPitch != -90.0)
     167           1 :         m_lowTanPitch = std::tan(oOpts.lowPitch * (2 * M_PI / 360.0));
     168         244 :     if (opts.highPitch != 90.0)
     169           1 :         m_highTanPitch = std::tan(oOpts.highPitch * (2 * M_PI / 360.0));
     170         244 :     m_srcBand.GetDataset()->GetGeoTransform(m_gt);
     171         244 :     int hasNoData = false;
     172         244 :     m_noDataValue = m_srcBand.GetNoDataValue(&hasNoData);
     173         244 :     m_hasNoData = hasNoData;
     174         244 : }
     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         235 : ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand,
     188             :                                    GDALRasterBand &dstBand, int nX, int nY,
     189             :                                    const Window &outExtent,
     190             :                                    const Window &curExtent, const Options &opts,
     191         235 :                                    Progress &progress, bool emitWarningIfNoData)
     192             :     : ViewshedExecutor(srcBand, m_dummyBand, dstBand, nX, nY, outExtent,
     193         235 :                        curExtent, opts, progress, emitWarningIfNoData)
     194             : {
     195         235 : }
     196             : 
     197             : // calculate the height adjustment factor.
     198         244 : double ViewshedExecutor::calcHeightAdjFactor()
     199             : {
     200         488 :     std::lock_guard g(oMutex);
     201             : 
     202             :     const OGRSpatialReference *poDstSRS =
     203         244 :         m_dstBand.GetDataset()->GetSpatialRef();
     204             : 
     205         244 :     if (poDstSRS)
     206             :     {
     207             :         OGRErr eSRSerr;
     208             : 
     209             :         // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR
     210          13 :         double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr);
     211             : 
     212             :         /* If we fetched the axis from the SRS, use it */
     213          13 :         if (eSRSerr != OGRERR_FAILURE)
     214          13 :             return oOpts.curveCoeff / (dfSemiMajor * 2.0);
     215             : 
     216           0 :         CPLDebug("GDALViewshedGenerate",
     217             :                  "Unable to fetch SemiMajor axis from spatial reference");
     218             :     }
     219         231 :     return 0;
     220             : }
     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     2985250 : void ViewshedExecutor::setOutputNormal(Lines &lines, int pos, double dfZ)
     229             : {
     230     2985250 :     double &cur = lines.cur[pos];
     231     2985250 :     double &result = lines.result[pos];
     232             : 
     233     2985250 :     if (oOpts.outputMode != OutputMode::Normal)
     234             :     {
     235       29100 :         double adjustment = dfZ - cur;
     236       29100 :         if (adjustment > 0)
     237       15777 :             result += adjustment;
     238             :     }
     239             :     else
     240             :     {
     241     2956150 :         double cellHeight = cur + oOpts.targetHeight;
     242     2956150 :         result = (cellHeight < dfZ) ? oOpts.invisibleVal : oOpts.visibleVal;
     243             :     }
     244     2985250 :     cur = std::max(cur, dfZ);
     245     2985250 : }
     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       14487 : void ViewshedExecutor::setOutputSd(Lines &lines, int pos, double dfZ)
     254             : {
     255       14487 :     double &cur = lines.cur[pos];
     256       14487 :     double &result = lines.result[pos];
     257       14487 :     double &sd = lines.sd[pos];
     258             : 
     259       14487 :     assert(oOpts.outputMode == OutputMode::Normal);
     260       14487 :     if (result == oOpts.invisibleVal)
     261             :     {
     262        7887 :         double cellHeight = cur + oOpts.targetHeight;
     263        7887 :         if (cellHeight > dfZ)
     264        2056 :             result = oOpts.maybeVisibleVal;
     265             :     }
     266             : 
     267       14487 :     if (sd <= 1)
     268        2323 :         cur = std::max(dfZ, cur);
     269             :     else
     270       12164 :         cur = dfZ;
     271       14487 : }
     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       29125 : bool ViewshedExecutor::readLine(int nLine, Lines &lines)
     279             : {
     280       58250 :     std::lock_guard g(iMutex);
     281             : 
     282       58250 :     if (GDALRasterIO(&m_srcBand, GF_Read, oOutExtent.xStart, nLine,
     283       29125 :                      oOutExtent.xSize(), 1, lines.cur.data(),
     284       29125 :                      oOutExtent.xSize(), 1, GDT_Float64, 0, 0))
     285             :     {
     286           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     287             :                  "RasterIO error when reading DEM at position (%d,%d), "
     288             :                  "size (%d,%d)",
     289           0 :                  oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
     290           0 :         return false;
     291             :     }
     292             : 
     293       29125 :     if (sdMode())
     294             :     {
     295         166 :         double nodata = m_sdBand.GetNoDataValue();
     296         332 :         CPLErr sdStatus = m_sdBand.RasterIO(
     297         166 :             GF_Read, oOutExtent.xStart, nLine, oOutExtent.xSize(), 1,
     298         166 :             lines.sd.data(), oOutExtent.xSize(), 1, GDT_Float64, 0, 0, nullptr);
     299         166 :         if (sdStatus != CE_None)
     300             :         {
     301           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     302             :                      "RasterIO error when reading standard deviation band at "
     303             :                      "position (%d,%d), "
     304             :                      "size (%d,%d)",
     305           0 :                      oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
     306           0 :             return false;
     307             :         }
     308             :         // Set the standard deviation to 1000 if nodata is found.
     309       14682 :         for (size_t i = 0; i < lines.sd.size(); ++i)
     310       14516 :             if (lines.sd[i] == nodata)
     311         476 :                 lines.sd[i] = 1000.0;
     312             :     }
     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       29125 :     if (oOpts.outputMode == OutputMode::DEM)
     317         179 :         lines.result = lines.cur;
     318       28946 :     else if (oOpts.outputMode == OutputMode::Ground)
     319         150 :         std::fill(lines.result.begin(), lines.result.end(), 0);
     320             : 
     321       29125 :     return true;
     322             : }
     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       29125 : bool ViewshedExecutor::writeLine(int nLine, std::vector<double> &vResult)
     330             : {
     331             :     // GDALRasterIO isn't thread-safe.
     332       58250 :     std::lock_guard g(oMutex);
     333             : 
     334       58250 :     if (GDALRasterIO(&m_dstBand, GF_Write, 0, nLine - oOutExtent.yStart,
     335       29125 :                      oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(),
     336       29125 :                      1, GDT_Float64, 0, 0))
     337             :     {
     338           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     339             :                  "RasterIO error when writing target raster at position "
     340             :                  "(%d,%d), size (%d,%d)",
     341           0 :                  0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1);
     342           0 :         return false;
     343             :     }
     344       29125 :     return true;
     345             : }
     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       29125 : LineLimits ViewshedExecutor::adjustHeight(int nYOffset, Lines &lines)
     354             : {
     355       29125 :     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       29125 :     int nXStart = oCurExtent.clampX(m_nX);
     359             : 
     360     5971800 :     const auto CheckNoData = [this](double val)
     361             :     {
     362     5971800 :         if (!m_hasFoundNoData &&
     363     2985900 :             ((m_hasNoData && val == m_noDataValue) || std::isnan(val)))
     364             :         {
     365           0 :             m_hasFoundNoData = true;
     366           0 :             if (m_emitWarningIfNoData)
     367             :             {
     368           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     369             :                          "Nodata value found in input DEM. Output will be "
     370             :                          "likely incorrect");
     371             :             }
     372             :         }
     373     3015020 :     };
     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       27571 :     if (static_cast<bool>(m_dfHeightAdjFactor) || oOpts.pitchMasking() ||
     379       56696 :         m_dfMaxDistance2 > 0 || m_dfMinDistance2 > 0)
     380             :     {
     381             :         // Hoist invariants from the loops.
     382       29125 :         const double dfLineX = m_gt[2] * nYOffset;
     383       29125 :         const double dfLineY = m_gt[5] * nYOffset;
     384             : 
     385             :         // Go left
     386       29125 :         double *pdfHeight = lines.cur.data() + nXStart;
     387     1509090 :         for (int nXOffset = nXStart - m_nX; nXOffset >= -m_nX;
     388             :              nXOffset--, pdfHeight--)
     389             :         {
     390     1479990 :             double dfX = m_gt[1] * nXOffset + dfLineX;
     391     1479990 :             double dfY = m_gt[4] * nXOffset + dfLineY;
     392     1479990 :             double dfR2 = dfX * dfX + dfY * dfY;
     393             : 
     394     1479990 :             if (dfR2 < m_dfMinDistance2)
     395           6 :                 ll.leftMin--;
     396     1479980 :             else if (dfR2 > m_dfMaxDistance2)
     397             :             {
     398          26 :                 ll.left = nXOffset + m_nX + 1;
     399          26 :                 break;
     400             :             }
     401             : 
     402     1479960 :             CheckNoData(*pdfHeight);
     403     1479960 :             *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
     404     1479960 :             if (oOpts.pitchMasking())
     405          75 :                 calcPitchMask(*pdfHeight, std::sqrt(dfR2),
     406          75 :                               lines.result[m_nX + nXOffset],
     407          75 :                               lines.pitchMask[m_nX + nXOffset]);
     408             :         }
     409             : 
     410             :         // Go right.
     411       29125 :         pdfHeight = lines.cur.data() + nXStart + 1;
     412     1535060 :         for (int nXOffset = nXStart - m_nX + 1;
     413     1535060 :              nXOffset < oCurExtent.xSize() - m_nX; nXOffset++, pdfHeight++)
     414             :         {
     415     1505960 :             double dfX = m_gt[1] * nXOffset + dfLineX;
     416     1505960 :             double dfY = m_gt[4] * nXOffset + dfLineY;
     417     1505960 :             double dfR2 = dfX * dfX + dfY * dfY;
     418             : 
     419     1505960 :             if (dfR2 < m_dfMinDistance2)
     420           3 :                 ll.rightMin++;
     421     1505960 :             else if (dfR2 > m_dfMaxDistance2)
     422             :             {
     423          26 :                 ll.right = nXOffset + m_nX;
     424          26 :                 break;
     425             :             }
     426             : 
     427     1505940 :             CheckNoData(*pdfHeight);
     428     1505940 :             *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
     429     1505940 :             if (oOpts.pitchMasking())
     430         175 :                 calcPitchMask(*pdfHeight, std::sqrt(dfR2),
     431         175 :                               lines.result[m_nX + nXOffset],
     432         175 :                               lines.pitchMask[m_nX + nXOffset]);
     433             :         }
     434             :     }
     435             :     else
     436             :     {
     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             :         {
     441           0 :             CheckNoData(*pdfHeight);
     442           0 :             *pdfHeight -= m_dfZObserver;
     443           0 :             pdfHeight++;
     444             :         }
     445             :     }
     446       29125 :     return ll;
     447             : }
     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         250 : void ViewshedExecutor::calcPitchMask(double dfZ, double dfDist, double dfResult,
     456             :                                      double &maskVal)
     457             : {
     458         250 :     if (oOpts.lowPitchMasking())
     459             :     {
     460          25 :         double dfZMask = dfDist * m_lowTanPitch;
     461          25 :         double adjustment = dfZMask - dfZ;
     462          25 :         if (adjustment > 0)
     463             :         {
     464          48 :             maskVal = (oOpts.outputMode == OutputMode::Normal
     465          24 :                            ? std::numeric_limits<double>::infinity()
     466             :                            : adjustment + dfResult);
     467          24 :             return;
     468             :         }
     469             :     }
     470         226 :     if (oOpts.highPitchMasking())
     471             :     {
     472         225 :         double dfZMask = dfDist * m_highTanPitch;
     473         225 :         if (dfZ > dfZMask)
     474           7 :             maskVal = std::numeric_limits<double>::infinity();
     475             :     }
     476             : }
     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         244 : bool ViewshedExecutor::processFirstLine(Lines &lines)
     483             : {
     484         244 :     int nLine = oOutExtent.clampY(m_nY);
     485         244 :     int nYOffset = nLine - m_nY;
     486             : 
     487         244 :     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         244 :     m_dfZObserver = oOpts.observer.z;
     493         244 :     if (oCurExtent.containsX(m_nX))
     494         238 :         m_dfZObserver += lines.cur[m_nX];
     495             : 
     496         244 :     LineLimits ll = adjustHeight(nYOffset, lines);
     497             : 
     498         488 :     std::vector<double> savedInput;
     499         244 :     if (sdMode())
     500           9 :         savedInput = lines.cur;
     501             : 
     502         244 :     if (oCurExtent.containsX(m_nX))
     503             :     {
     504         238 :         if (ll.leftMin != ll.rightMin)
     505           1 :             lines.result[m_nX] = oOpts.outOfRangeVal;
     506         237 :         else if (oOpts.outputMode == OutputMode::Normal)
     507         220 :             lines.result[m_nX] = oOpts.visibleVal;
     508             :     }
     509             : 
     510        1004 :     auto process = [this, &ll, &lines](bool sdCalc)
     511             :     {
     512         253 :         if (!oCurExtent.containsY(m_nY))
     513           4 :             processFirstLineTopOrBottom(ll, lines);
     514             :         else
     515             :         {
     516         498 :             CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     517         249 :             pQueue->SubmitJob([&]()
     518         249 :                               { processFirstLineLeft(ll, lines, sdCalc); });
     519         249 :             pQueue->SubmitJob([&]()
     520         249 :                               { processFirstLineRight(ll, lines, sdCalc); });
     521         249 :             pQueue->WaitCompletion();
     522             :         }
     523         253 :     };
     524             : 
     525         244 :     process(false);
     526         244 :     lines.prev = lines.cur;
     527         244 :     if (sdMode())
     528             :     {
     529           9 :         lines.cur = std::move(savedInput);
     530           9 :         process(true);
     531           9 :         lines.prevTmp = lines.cur;
     532             :     }
     533             : 
     534         244 :     if (oOpts.pitchMasking())
     535           2 :         applyPitchMask(lines.result, lines.pitchMask);
     536         244 :     if (!writeLine(nLine, lines.result))
     537           0 :         return false;
     538             : 
     539         244 :     return oProgress.lineComplete();
     540             : }
     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          20 : void ViewshedExecutor::applyPitchMask(std::vector<double> &vResult,
     548             :                                       const std::vector<double> &vPitchMaskVal)
     549             : {
     550         270 :     for (size_t i = 0; i < vResult.size(); ++i)
     551             :     {
     552         250 :         if (std::isnan(vPitchMaskVal[i]))
     553         219 :             continue;
     554          31 :         if (std::isinf(vPitchMaskVal[i]))
     555           7 :             vResult[i] = oOpts.outOfRangeVal;
     556             :         else
     557          24 :             vResult[i] = vPitchMaskVal[i];
     558             :     }
     559          20 : }
     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           4 : void ViewshedExecutor::processFirstLineTopOrBottom(const LineLimits &ll,
     566             :                                                    Lines &lines)
     567             : {
     568          24 :     for (int iPixel = ll.left; iPixel < ll.right; ++iPixel)
     569             :     {
     570          20 :         if (oOpts.outputMode == OutputMode::Normal)
     571           0 :             lines.result[iPixel] = oOpts.visibleVal;
     572             :         else
     573          20 :             setOutputNormal(lines, iPixel, lines.cur[iPixel]);
     574             :     }
     575             : 
     576           4 :     std::fill(lines.result.begin(), lines.result.begin() + ll.left,
     577           4 :               oOpts.outOfRangeVal);
     578           4 :     std::fill(lines.result.begin() + ll.right,
     579           4 :               lines.result.begin() + oCurExtent.xStop, oOpts.outOfRangeVal);
     580           4 : }
     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         249 : void ViewshedExecutor::processFirstLineLeft(const LineLimits &ll, Lines &lines,
     588             :                                             bool sdCalc)
     589             : {
     590         249 :     int iEnd = ll.left - 1;
     591         249 :     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         249 :     if (iEnd >= iStart)
     595             :     {
     596          40 :         maskLineLeft(lines.result, ll, m_nY);
     597          40 :         return;
     598             :     }
     599             : 
     600         209 :     iStart = oCurExtent.clampX(iStart);
     601             : 
     602             :     // If the start cell is next to the observer, just mark it visible.
     603         209 :     if (iStart + 1 == m_nX || iStart + 1 == oCurExtent.xStop)
     604             :     {
     605         209 :         double dfZ = lines.cur[iStart];
     606         209 :         if (oOpts.outputMode == OutputMode::Normal)
     607             :         {
     608         198 :             lines.result[iStart] = oOpts.visibleVal;
     609         198 :             if (sdCalc)
     610           4 :                 if (lines.sd[iStart] > 1)
     611           3 :                     lines.cur[iStart] =
     612           3 :                         m_dfZObserver;  // Should this be a minimum value?
     613             :         }
     614             :         else
     615          11 :             setOutputNormal(lines, iStart, dfZ);
     616         209 :         iStart--;
     617             :     }
     618             : 
     619             :     // Go from the observer to the left, calculating Z as we go.
     620       10501 :     for (int iPixel = iStart; iPixel > iEnd; iPixel--)
     621             :     {
     622       10292 :         int nXOffset = std::abs(iPixel - m_nX);
     623       10292 :         double dfZ = CalcHeightLine(nXOffset, lines.cur[iPixel + 1]);
     624       10292 :         if (!sdCalc)
     625       10224 :             setOutputNormal(lines, iPixel, dfZ);
     626             :         else
     627          68 :             setOutputSd(lines, iPixel, dfZ);
     628             :     }
     629             : 
     630         209 :     maskLineLeft(lines.result, ll, m_nY);
     631             : }
     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       29287 : bool ViewshedExecutor::maskAngleLeft(std::vector<double> &vResult, int nLine)
     639             : {
     640          70 :     auto clamp = [this](int x)
     641          36 :     { return (x < 0 || x >= m_nX) ? INVALID_ISECT : x; };
     642             : 
     643       29287 :     if (!oOpts.angleMasking())
     644       29267 :         return false;
     645             : 
     646          20 :     if (nLine != m_nY)
     647             :     {
     648             :         int startAngleX =
     649          18 :             clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
     650          18 :         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          18 :         if (invalid(startAngleX) && invalid(endAngleX))
     655             :         {
     656             :             // Choose a test angle in quadrant II or III depending on the line.
     657          15 :             double testAngle = nLine < m_nY ? m_testAngle[2] : m_testAngle[3];
     658          15 :             if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
     659             :             {
     660          10 :                 std::fill(vResult.begin(), vResult.begin() + m_nX,
     661          10 :                           oOpts.outOfRangeVal);
     662          15 :                 return true;
     663             :             }
     664           5 :             return false;
     665             :         }
     666           3 :         if (nLine > m_nY)
     667           0 :             std::swap(startAngleX, endAngleX);
     668           3 :         if (invalid(startAngleX))
     669           3 :             startAngleX = 0;
     670           3 :         if (invalid(endAngleX))
     671           0 :             endAngleX = m_nX - 1;
     672           3 :         if (startAngleX <= endAngleX)
     673             :         {
     674           3 :             std::fill(vResult.begin(), vResult.begin() + startAngleX,
     675           3 :                       oOpts.outOfRangeVal);
     676           3 :             std::fill(vResult.begin() + endAngleX + 1, vResult.begin() + m_nX,
     677           3 :                       oOpts.outOfRangeVal);
     678             :         }
     679             :         else
     680             :         {
     681           0 :             std::fill(vResult.begin() + endAngleX + 1,
     682           0 :                       vResult.begin() + startAngleX, oOpts.outOfRangeVal);
     683             :         }
     684             :     }
     685             :     // nLine == m_nY
     686           2 :     else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI))
     687             :     {
     688           1 :         std::fill(vResult.begin(), vResult.begin() + m_nX, oOpts.outOfRangeVal);
     689           1 :         return true;
     690             :     }
     691           4 :     return false;
     692             : }
     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       29287 : bool ViewshedExecutor::maskAngleRight(std::vector<double> &vResult, int nLine)
     700             : {
     701       29287 :     int lineLength = static_cast<int>(vResult.size());
     702             : 
     703          54 :     auto clamp = [this, lineLength](int x)
     704          36 :     { return (x <= m_nX || x >= lineLength) ? INVALID_ISECT : x; };
     705             : 
     706       29287 :     if (oOpts.startAngle == oOpts.endAngle)
     707       29267 :         return false;
     708             : 
     709          20 :     if (nLine != m_nY)
     710             :     {
     711             :         int startAngleX =
     712          18 :             clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
     713          18 :         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          18 :         if (invalid(startAngleX) && invalid(endAngleX))
     719             :         {
     720             :             // Choose a test angle in quadrant I or IV depending on the line.
     721          10 :             double testAngle = nLine < m_nY ? m_testAngle[1] : m_testAngle[4];
     722          10 :             if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
     723             :             {
     724           0 :                 std::fill(vResult.begin() + m_nX + 1, vResult.end(),
     725           0 :                           oOpts.outOfRangeVal);
     726          10 :                 return true;
     727             :             }
     728          10 :             return false;
     729             :         }
     730             : 
     731           8 :         if (nLine > m_nY)
     732           0 :             std::swap(startAngleX, endAngleX);
     733           8 :         if (invalid(endAngleX))
     734           0 :             endAngleX = lineLength - 1;
     735           8 :         if (invalid(startAngleX))
     736           8 :             startAngleX = m_nX + 1;
     737           8 :         if (startAngleX <= endAngleX)
     738             :         {
     739           8 :             std::fill(vResult.begin() + m_nX + 1, vResult.begin() + startAngleX,
     740           8 :                       oOpts.outOfRangeVal);
     741           8 :             std::fill(vResult.begin() + endAngleX + 1, vResult.end(),
     742           8 :                       oOpts.outOfRangeVal);
     743             :         }
     744             :         else
     745             :         {
     746           0 :             std::fill(vResult.begin() + endAngleX + 1,
     747           0 :                       vResult.begin() + startAngleX, oOpts.outOfRangeVal);
     748             :         }
     749             :     }
     750             :     // nLine == m_nY
     751           2 :     else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 0))
     752             :     {
     753           1 :         std::fill(vResult.begin() + m_nX + 1, vResult.end(),
     754           1 :                   oOpts.outOfRangeVal);
     755           1 :         return true;
     756             :     }
     757           9 :     return false;
     758             : }
     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       29287 : void ViewshedExecutor::maskLineLeft(std::vector<double> &vResult,
     766             :                                     const LineLimits &ll, int nLine)
     767             : {
     768             :     // If we've already masked with angles everything, just return.
     769       29287 :     if (maskAngleLeft(vResult, nLine))
     770          11 :         return;
     771             : 
     772             :     // Mask cells from the left edge to the left limit.
     773       29276 :     std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal);
     774             :     // Mask cells from the left min to the observer.
     775       29276 :     if (ll.leftMin < m_nX)
     776           3 :         std::fill(vResult.begin() + ll.leftMin, vResult.begin() + m_nX,
     777           3 :                   oOpts.outOfRangeVal);
     778             : }
     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       29287 : void ViewshedExecutor::maskLineRight(std::vector<double> &vResult,
     786             :                                      const LineLimits &ll, int nLine)
     787             : {
     788             :     // If we've already masked with angles everything, just return.
     789       29287 :     if (maskAngleRight(vResult, nLine))
     790           1 :         return;
     791             : 
     792             :     // Mask cells from the observer to right min.
     793       29286 :     std::fill(vResult.begin() + m_nX + 1, vResult.begin() + ll.rightMin,
     794       29286 :               oOpts.outOfRangeVal);
     795             :     // Mask cells from the right limit to the right edge.
     796             : 
     797             :     //
     798             :     //ABELL - Changed from ll.right + 1
     799             :     //
     800       29286 :     if (ll.right <= static_cast<int>(vResult.size()))
     801       29286 :         std::fill(vResult.begin() + ll.right, vResult.end(),
     802       29286 :                   oOpts.outOfRangeVal);
     803             : }
     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         249 : void ViewshedExecutor::processFirstLineRight(const LineLimits &ll, Lines &lines,
     811             :                                              bool sdCalc)
     812             : {
     813         249 :     int iStart = m_nX + 1;
     814         249 :     int iEnd = ll.right;
     815             : 
     816             :     // If start is to the right of end, everything is taken care of by left processing.
     817         249 :     if (iStart >= iEnd)
     818             :     {
     819          12 :         maskLineRight(lines.result, ll, m_nY);
     820          12 :         return;
     821             :     }
     822             : 
     823         237 :     iStart = oCurExtent.clampX(iStart);
     824             : 
     825             :     // If the start cell is next to the observer, just mark it visible.
     826         237 :     if (iStart - 1 == m_nX || iStart == oCurExtent.xStart)
     827             :     {
     828         237 :         double dfZ = lines.cur[iStart];
     829         237 :         if (oOpts.outputMode == OutputMode::Normal)
     830             :         {
     831         220 :             lines.result[iStart] = oOpts.visibleVal;
     832         220 :             if (sdCalc)
     833           4 :                 if (lines.sd[iStart] > 1)
     834           4 :                     lines.cur[iStart] =
     835           4 :                         m_dfZObserver;  // Use some minimum value instead?
     836             :         }
     837             :         else
     838          17 :             setOutputNormal(lines, iStart, dfZ);
     839         237 :         iStart++;
     840             :     }
     841             : 
     842             :     // Go from the observer to the right, calculating Z as we go.
     843       10956 :     for (int iPixel = iStart; iPixel < iEnd; iPixel++)
     844             :     {
     845       10719 :         int nXOffset = std::abs(iPixel - m_nX);
     846       10719 :         double dfZ = CalcHeightLine(nXOffset, lines.cur[iPixel - 1]);
     847       10719 :         if (!sdCalc)
     848       10651 :             setOutputNormal(lines, iPixel, dfZ);
     849             :         else
     850          68 :             setOutputSd(lines, iPixel, dfZ);
     851             :     }
     852             : 
     853         237 :     maskLineRight(lines.result, ll, m_nY);
     854             : }
     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       29038 : void ViewshedExecutor::processLineLeft(int nYOffset, LineLimits &ll,
     863             :                                        Lines &lines, bool sdCalc)
     864             : {
     865       29038 :     int iStart = m_nX - 1;
     866       29038 :     int iEnd = ll.left - 1;
     867       29038 :     int nLine = m_nY + nYOffset;
     868             : 
     869             :     // If start to the left of end, everything is taken care of by processing right.
     870       29038 :     if (iStart <= iEnd)
     871             :     {
     872        2971 :         maskLineLeft(lines.result, ll, nLine);
     873        2971 :         return;
     874             :     }
     875       26067 :     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       26067 :     if (iStart == oCurExtent.xStop - 1)
     881             :     {
     882           6 :         if (oOpts.outputMode == OutputMode::Normal)
     883           0 :             lines.result[iStart] = oOpts.visibleVal;
     884             :         else
     885           6 :             setOutputNormal(lines, iStart, lines.cur[iStart]);
     886           6 :         iStart--;
     887             :     }
     888             : 
     889             :     // Go from the observer to the left, calculating Z as we go.
     890       26067 :     nYOffset = std::abs(nYOffset);
     891     1473580 :     for (int iPixel = iStart; iPixel > iEnd; iPixel--)
     892             :     {
     893     1447510 :         int nXOffset = std::abs(iPixel - m_nX);
     894             :         double dfZ;
     895     1447510 :         if (nXOffset == nYOffset)
     896       15856 :             dfZ = CalcHeightLine(nYOffset, lines.cur[iPixel],
     897       15856 :                                  lines.prev[iPixel + 1]);
     898             :         else
     899     1431650 :             dfZ = oZcalc(nXOffset, nYOffset, lines.cur[iPixel + 1],
     900     1431650 :                          lines.prev[iPixel], lines.prev[iPixel + 1]);
     901     1447510 :         if (!sdCalc)
     902     1440410 :             setOutputNormal(lines, iPixel, dfZ);
     903             :         else
     904        7103 :             setOutputSd(lines, iPixel, dfZ);
     905             :     }
     906             : 
     907       26067 :     maskLineLeft(lines.result, ll, nLine);
     908             : }
     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       29038 : void ViewshedExecutor::processLineRight(int nYOffset, LineLimits &ll,
     917             :                                         Lines &lines, bool sdCalc)
     918             : {
     919       29038 :     int iStart = m_nX + 1;
     920       29038 :     int iEnd = ll.right;
     921       29038 :     int nLine = m_nY + nYOffset;
     922             : 
     923             :     // If start is to the right of end, everything is taken care of by processing left.
     924       29038 :     if (iStart >= iEnd)
     925             :     {
     926          44 :         maskLineRight(lines.result, ll, nLine);
     927          44 :         return;
     928             :     }
     929       28994 :     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       28994 :     if (iStart == 0)
     935             :     {
     936           6 :         if (oOpts.outputMode == OutputMode::Normal)
     937           0 :             lines.result[iStart] = oOpts.visibleVal;
     938             :         else
     939           6 :             setOutputNormal(lines, 0, lines.cur[0]);
     940           6 :         iStart++;
     941             :     }
     942             : 
     943             :     // Go from the observer to the right, calculating Z as we go.
     944       28994 :     nYOffset = std::abs(nYOffset);
     945     1531140 :     for (int iPixel = iStart; iPixel < iEnd; iPixel++)
     946             :     {
     947     1502150 :         int nXOffset = std::abs(iPixel - m_nX);
     948             :         double dfZ;
     949     1502150 :         if (nXOffset == nYOffset)
     950             :         {
     951       16339 :             if (sdCalc && nXOffset == 1)
     952             :             {
     953           4 :                 lines.result[iPixel] = oOpts.visibleVal;
     954           4 :                 if (lines.sd[iPixel] > 1)
     955           4 :                     lines.cur[iPixel] = m_dfZObserver;
     956           4 :                 continue;
     957             :             }
     958       16335 :             dfZ = CalcHeightLine(nYOffset, lines.cur[iPixel],
     959       16335 :                                  lines.prev[iPixel - 1]);
     960             :         }
     961             :         else
     962     1485810 :             dfZ = oZcalc(nXOffset, nYOffset, lines.cur[iPixel - 1],
     963     1485810 :                          lines.prev[iPixel], lines.prev[iPixel - 1]);
     964     1502150 :         if (!sdCalc)
     965     1495050 :             setOutputNormal(lines, iPixel, dfZ);
     966             :         else
     967        7099 :             setOutputSd(lines, iPixel, dfZ);
     968             :     }
     969             : 
     970       28994 :     maskLineRight(lines.result, ll, nLine);
     971             : }
     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       28869 : bool ViewshedExecutor::maskInitial(std::vector<double> &vResult,
     979             :                                    const LineLimits &ll, int nLine)
     980             : {
     981             :     // Mask min/max.
     982       28869 :     if (ll.left >= ll.right || ll.leftMin != ll.rightMin)
     983             :     {
     984           7 :         vResult[m_nX] = oOpts.outOfRangeVal;
     985           7 :         return true;
     986             :     }
     987             : 
     988       28862 :     if (!oOpts.angleMasking())
     989       28844 :         return false;
     990             : 
     991          18 :     if (nLine < m_nY)
     992             :     {
     993          13 :         if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI / 2))
     994             :         {
     995           0 :             vResult[m_nX] = oOpts.outOfRangeVal;
     996           0 :             return true;
     997             :         }
     998             :     }
     999           5 :     else if (nLine > m_nY)
    1000             :     {
    1001           5 :         if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 3 * M_PI / 2))
    1002             :         {
    1003           0 :             vResult[m_nX] = oOpts.outOfRangeVal;
    1004           0 :             return true;
    1005             :         }
    1006             :     }
    1007          18 :     return false;
    1008             : }
    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       28881 : bool ViewshedExecutor::processLine(int nLine, Lines &lines)
    1016             : {
    1017       28881 :     int nYOffset = nLine - m_nY;
    1018             : 
    1019       28881 :     if (!readLine(nLine, lines))
    1020           0 :         return false;
    1021             : 
    1022             :     // Adjust height of the read line.
    1023       28881 :     LineLimits ll = adjustHeight(nYOffset, lines);
    1024             : 
    1025       57762 :     std::vector<double> savedLine;
    1026       28881 :     if (sdMode())
    1027         157 :         savedLine = lines.cur;
    1028             : 
    1029       87114 :     auto process = [this, nYOffset, &ll, &lines](bool sdCalc)
    1030             :     {
    1031       58076 :         CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
    1032       29038 :         pQueue->SubmitJob([&]()
    1033       29038 :                           { processLineLeft(nYOffset, ll, lines, sdCalc); });
    1034       29038 :         pQueue->SubmitJob([&]()
    1035       29038 :                           { processLineRight(nYOffset, ll, lines, sdCalc); });
    1036       29038 :         pQueue->WaitCompletion();
    1037       29038 :     };
    1038             : 
    1039       28881 :     bool masked = false;
    1040             :     // Handle initial position on the line.
    1041       28881 :     if (oCurExtent.containsX(m_nX))
    1042             :     {
    1043       28869 :         masked = maskInitial(lines.result, ll, nLine);
    1044       28869 :         if (!masked)
    1045             :         {
    1046       28862 :             double dfZ = CalcHeightLine(std::abs(nYOffset), lines.cur[m_nX],
    1047       28862 :                                         lines.prev[m_nX]);
    1048       28862 :             setOutputNormal(lines, m_nX, dfZ);
    1049             :         }
    1050             :     }
    1051             : 
    1052       28881 :     process(false);
    1053             : 
    1054             :     // Process standard deviation mode
    1055       28881 :     if (sdMode())
    1056             :     {
    1057         157 :         lines.prev = std::move(lines.prevTmp);
    1058         157 :         lines.prevTmp = std::move(lines.cur);
    1059         157 :         lines.cur = std::move(savedLine);
    1060             :         // Handle initial position on the line.
    1061         157 :         if (!masked && oCurExtent.containsX(m_nX))
    1062             :         {
    1063         157 :             if (std::abs(nYOffset) == 1)
    1064             :             {
    1065           8 :                 lines.result[m_nX] = oOpts.visibleVal;
    1066           8 :                 if (lines.sd[m_nX] > 1)
    1067           3 :                     lines.cur[m_nX] = m_dfZObserver;
    1068             :             }
    1069             :             else
    1070             :             {
    1071             : 
    1072         149 :                 double dfZ = CalcHeightLine(std::abs(nYOffset), lines.cur[m_nX],
    1073         149 :                                             lines.prev[m_nX]);
    1074         149 :                 setOutputSd(lines, m_nX, dfZ);
    1075             :             }
    1076             :         }
    1077         157 :         process(true);
    1078         157 :         lines.prev = std::move(lines.prevTmp);
    1079         157 :         lines.prevTmp = lines.cur;
    1080             :     }
    1081             :     else
    1082       28724 :         lines.prev = lines.cur;
    1083             : 
    1084       28881 :     if (oOpts.pitchMasking())
    1085          18 :         applyPitchMask(lines.result, lines.pitchMask);
    1086       28881 :     if (!writeLine(nLine, lines.result))
    1087           0 :         return false;
    1088             : 
    1089       28881 :     return oProgress.lineComplete();
    1090             : }
    1091             : 
    1092             : // Calculate the ray angle from the origin to middle of the top or bottom
    1093             : // of each quadrant.
    1094           2 : void ViewshedExecutor::calcTestAngles()
    1095             : {
    1096             :     // Quadrant 1.
    1097             :     {
    1098           2 :         int ysize = m_nY + 1;
    1099           2 :         int xsize = oCurExtent.xStop - m_nX;
    1100           2 :         m_testAngle[1] = atan2(ysize, xsize / 2.0);
    1101             :     }
    1102             : 
    1103             :     // Quadrant 2.
    1104             :     {
    1105           2 :         int ysize = m_nY + 1;
    1106           2 :         int xsize = m_nX + 1;
    1107           2 :         m_testAngle[2] = atan2(ysize, -xsize / 2.0);
    1108             :     }
    1109             : 
    1110             :     // Quadrant 3.
    1111             :     {
    1112           2 :         int ysize = oCurExtent.yStop - m_nY;
    1113           2 :         int xsize = m_nX + 1;
    1114           2 :         m_testAngle[3] = atan2(-ysize, -xsize / 2.0);
    1115             :     }
    1116             : 
    1117             :     // Quadrant 4.
    1118             :     {
    1119           2 :         int ysize = oCurExtent.yStop - m_nY;
    1120           2 :         int xsize = oCurExtent.xStop - m_nX;
    1121           2 :         m_testAngle[4] = atan2(-ysize, xsize / 2.0);
    1122             :     }
    1123             : 
    1124             :     // Adjust range to [0, 2 * M_PI)
    1125          10 :     for (int i = 1; i <= 4; ++i)
    1126           8 :         if (m_testAngle[i] < 0)
    1127           4 :             m_testAngle[i] += (2 * M_PI);
    1128           2 : }
    1129             : 
    1130             : /// Run the viewshed computation
    1131             : /// @return  Success as true or false.
    1132         244 : bool ViewshedExecutor::run()
    1133             : {
    1134             :     // If we're doing angular masking, calculate the test angles used later.
    1135         244 :     if (oOpts.angleMasking())
    1136           2 :         calcTestAngles();
    1137             : 
    1138         488 :     Lines firstLine(oCurExtent.xSize());
    1139         244 :     if (oOpts.pitchMasking())
    1140           2 :         firstLine.pitchMask.resize(oOutExtent.xSize(),
    1141           4 :                                    std::numeric_limits<double>::quiet_NaN());
    1142         244 :     if (sdMode())
    1143           9 :         firstLine.sd.resize(oOutExtent.xSize());
    1144             : 
    1145         244 :     m_dfHeightAdjFactor = calcHeightAdjFactor();
    1146             : 
    1147         244 :     if (!processFirstLine(firstLine))
    1148           0 :         return false;
    1149             : 
    1150         244 :     if (oOpts.cellMode == CellMode::Edge)
    1151         244 :         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         244 :     int yStart = oCurExtent.clampY(m_nY);
    1161         244 :     std::atomic<bool> err(false);
    1162         244 :     CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
    1163         244 :     pQueue->SubmitJob(
    1164         244 :         [&]()
    1165             :         {
    1166         488 :             Lines lines(oCurExtent.xSize());
    1167         244 :             lines.prev = firstLine.prev;
    1168         244 :             lines.prevTmp = firstLine.prevTmp;
    1169         244 :             if (oOpts.pitchMasking())
    1170           2 :                 lines.pitchMask.resize(
    1171           2 :                     oOutExtent.xSize(),
    1172           4 :                     std::numeric_limits<double>::quiet_NaN());
    1173         244 :             if (sdMode())
    1174           9 :                 lines.sd.resize(oOutExtent.xSize());
    1175             : 
    1176       13605 :             for (int nLine = yStart - 1; nLine >= oCurExtent.yStart && !err;
    1177             :                  nLine--)
    1178             :             {
    1179       13361 :                 if (!processLine(nLine, lines))
    1180           0 :                     err = true;
    1181       13361 :                 if (oOpts.pitchMasking())
    1182           9 :                     std::fill(lines.pitchMask.begin(), lines.pitchMask.end(),
    1183          18 :                               std::numeric_limits<double>::quiet_NaN());
    1184             :             }
    1185         244 :         });
    1186             : 
    1187             :     // scan downwards
    1188         244 :     pQueue->SubmitJob(
    1189         244 :         [&]()
    1190             :         {
    1191         488 :             Lines lines(oCurExtent.xSize());
    1192         244 :             lines.prev = firstLine.prev;
    1193         244 :             lines.prevTmp = firstLine.prevTmp;
    1194         244 :             if (oOpts.pitchMasking())
    1195           2 :                 lines.pitchMask.resize(
    1196           2 :                     oOutExtent.xSize(),
    1197           4 :                     std::numeric_limits<double>::quiet_NaN());
    1198         244 :             if (sdMode())
    1199           9 :                 lines.sd.resize(oOutExtent.xSize());
    1200             : 
    1201       15764 :             for (int nLine = yStart + 1; nLine < oCurExtent.yStop && !err;
    1202             :                  nLine++)
    1203             :             {
    1204       15520 :                 if (!processLine(nLine, lines))
    1205           0 :                     err = true;
    1206       15520 :                 if (oOpts.pitchMasking())
    1207           9 :                     std::fill(lines.pitchMask.begin(), lines.pitchMask.end(),
    1208          18 :                               std::numeric_limits<double>::quiet_NaN());
    1209             :             }
    1210         244 :         });
    1211         244 :     return true;
    1212             : }
    1213             : 
    1214             : }  // namespace viewshed
    1215             : }  // namespace gdal
    1216             : 
    1217             : // cppcheck-suppress-end knownConditionTrueFalse

Generated by: LCOV version 1.14