LCOV - code coverage report
Current view: top level - alg/viewshed - viewshed_executor.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 404 463 87.3 %
Date: 2025-05-31 00:00:17 Functions: 36 40 90.0 %

          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             : 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          78 : bool valid(int i)
      36             : {
      37          78 :     return i != INVALID_ISECT;
      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          78 : bool invalid(int i)
      44             : {
      45          78 :     return !valid(i);
      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       79893 : double CalcHeightLine(int nDistance, double Za)
      53             : {
      54       79893 :     nDistance = std::abs(nDistance);
      55       79893 :     assert(nDistance != 1);
      56       79893 :     return Za * nDistance / (nDistance - 1);
      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           0 : double CalcHeightDiagonal(int i, int j, double Za, double Zb)
      64             : {
      65           0 :     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     2802530 : double CalcHeightEdge(int i, int j, double Za, double Zb)
      73             : {
      74     2802530 :     assert(i != j);
      75     2802530 :     return (Za * i + Zb * (j - i)) / (j - 1);
      76             : }
      77             : 
      78           0 : double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset,
      79             :                   double dfThisPrev, double dfLast,
      80             :                   [[maybe_unused]] double dfLastPrev)
      81             : {
      82           0 :     return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast);
      83             : }
      84             : 
      85     2796330 : double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
      86             :               double dfLastPrev)
      87             : {
      88     2796330 :     if (nXOffset >= nYOffset)
      89     1164280 :         return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev);
      90             :     else
      91     1632060 :         return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast);
      92             : }
      93             : 
      94           0 : double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
      95             :              double dfLastPrev)
      96             : {
      97           0 :     double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
      98             :     double dfDiagonal =
      99           0 :         doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     100           0 :     return std::min(dfEdge, dfDiagonal);
     101             : }
     102             : 
     103           0 : double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
     104             :              double dfLastPrev)
     105             : {
     106           0 :     double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     107             :     double dfDiagonal =
     108           0 :         doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
     109           0 :     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         235 : ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand,
     126             :                                    GDALRasterBand &dstBand, int nX, int nY,
     127             :                                    const Window &outExtent,
     128             :                                    const Window &curExtent, const Options &opts,
     129         235 :                                    Progress &progress, bool emitWarningIfNoData)
     130             :     : m_pool(4), m_srcBand(srcBand), m_dstBand(dstBand),
     131             :       m_emitWarningIfNoData(emitWarningIfNoData), oOutExtent(outExtent),
     132         235 :       oCurExtent(curExtent), m_nX(nX - oOutExtent.xStart), m_nY(nY),
     133             :       oOpts(opts), oProgress(progress),
     134         235 :       m_dfMinDistance2(opts.minDistance * opts.minDistance),
     135         235 :       m_dfMaxDistance2(opts.maxDistance * opts.maxDistance)
     136             : {
     137         235 :     if (m_dfMaxDistance2 == 0)
     138         232 :         m_dfMaxDistance2 = std::numeric_limits<double>::max();
     139         235 :     if (opts.lowPitch != -90.0)
     140           1 :         m_lowTanPitch = std::tan(oOpts.lowPitch * (2 * M_PI / 360.0));
     141         235 :     if (opts.highPitch != 90.0)
     142           1 :         m_highTanPitch = std::tan(oOpts.highPitch * (2 * M_PI / 360.0));
     143         235 :     m_srcBand.GetDataset()->GetGeoTransform(m_adfTransform.data());
     144         235 :     int hasNoData = false;
     145         235 :     m_noDataValue = m_srcBand.GetNoDataValue(&hasNoData);
     146         235 :     m_hasNoData = hasNoData;
     147         235 : }
     148             : 
     149             : // calculate the height adjustment factor.
     150         235 : double ViewshedExecutor::calcHeightAdjFactor()
     151             : {
     152         470 :     std::lock_guard g(oMutex);
     153             : 
     154             :     const OGRSpatialReference *poDstSRS =
     155         235 :         m_dstBand.GetDataset()->GetSpatialRef();
     156             : 
     157         235 :     if (poDstSRS)
     158             :     {
     159             :         OGRErr eSRSerr;
     160             : 
     161             :         // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR
     162          12 :         double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr);
     163             : 
     164             :         /* If we fetched the axis from the SRS, use it */
     165          12 :         if (eSRSerr != OGRERR_FAILURE)
     166          12 :             return oOpts.curveCoeff / (dfSemiMajor * 2.0);
     167             : 
     168           0 :         CPLDebug("GDALViewshedGenerate",
     169             :                  "Unable to fetch SemiMajor axis from spatial reference");
     170             :     }
     171         223 :     return 0;
     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     2859900 : void ViewshedExecutor::setOutput(double &dfResult, double &dfCellVal,
     180             :                                  double dfZ)
     181             : {
     182     2859900 :     if (oOpts.outputMode != OutputMode::Normal)
     183             :     {
     184       28435 :         dfResult += (dfZ - dfCellVal);
     185       28435 :         dfResult = std::max(0.0, dfResult);
     186             :     }
     187             :     else
     188     2831460 :         dfResult = (dfCellVal + oOpts.targetHeight < dfZ) ? oOpts.invisibleVal
     189             :                                                           : oOpts.visibleVal;
     190     2859820 :     dfCellVal = std::max(dfCellVal, dfZ);
     191     2872260 : }
     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       28952 : bool ViewshedExecutor::readLine(int nLine, double *data)
     199             : {
     200       57851 :     std::lock_guard g(iMutex);
     201             : 
     202       28954 :     if (GDALRasterIO(&m_srcBand, GF_Read, oOutExtent.xStart, nLine,
     203             :                      oOutExtent.xSize(), 1, data, oOutExtent.xSize(), 1,
     204       28899 :                      GDT_Float64, 0, 0))
     205             :     {
     206           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     207             :                  "RasterIO error when reading DEM at position (%d,%d), "
     208             :                  "size (%d,%d)",
     209           0 :                  oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
     210           0 :         return false;
     211             :     }
     212       28899 :     return true;
     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       28805 : bool ViewshedExecutor::writeLine(int nLine, std::vector<double> &vResult)
     221             : {
     222             :     // GDALRasterIO isn't thread-safe.
     223       57706 :     std::lock_guard g(oMutex);
     224             : 
     225       57597 :     if (GDALRasterIO(&m_dstBand, GF_Write, 0, nLine - oOutExtent.yStart,
     226       28830 :                      oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(),
     227       28901 :                      1, GDT_Float64, 0, 0))
     228             :     {
     229           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     230             :                  "RasterIO error when writing target raster at position "
     231             :                  "(%d,%d), size (%d,%d)",
     232           0 :                  0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1);
     233           0 :         return false;
     234             :     }
     235       28901 :     return true;
     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       28899 : LineLimits ViewshedExecutor::adjustHeight(int nYOffset,
     245             :                                           std::vector<double> &vThisLineVal)
     246             : {
     247       28899 :     LineLimits ll(0, m_nX + 1, m_nX + 1, oCurExtent.xSize());
     248             : 
     249             :     // Find the starting point in the raster (m_nX may be outside)
     250       28820 :     int nXStart = oCurExtent.clampX(m_nX);
     251             : 
     252     5782650 :     const auto CheckNoData = [this](double val)
     253             :     {
     254     5770360 :         if (!m_hasFoundNoData &&
     255     2894010 :             ((m_hasNoData && val == m_noDataValue) || std::isnan(val)))
     256             :         {
     257           0 :             m_hasFoundNoData = true;
     258           0 :             if (m_emitWarningIfNoData)
     259             :             {
     260           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     261             :                          "Nodata value found in input DEM. Output will be "
     262             :                          "likely incorrect");
     263             :             }
     264             :         }
     265     2905200 :     };
     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       28845 :     if (static_cast<bool>(m_dfHeightAdjFactor) || m_dfMaxDistance2 > 0 ||
     271           0 :         m_dfMinDistance2 > 0)
     272             :     {
     273             :         // Hoist invariants from the loops.
     274       28845 :         const double dfLineX = m_adfTransform[2] * nYOffset;
     275       28787 :         const double dfLineY = m_adfTransform[5] * nYOffset;
     276             : 
     277             :         // Go left
     278       28809 :         double *pdfHeight = vThisLineVal.data() + nXStart;
     279     1469590 :         for (int nXOffset = nXStart - m_nX; nXOffset >= -m_nX;
     280             :              nXOffset--, pdfHeight--)
     281             :         {
     282     1441610 :             double dfX = m_adfTransform[1] * nXOffset + dfLineX;
     283     1441560 :             double dfY = m_adfTransform[4] * nXOffset + dfLineY;
     284     1445840 :             double dfR2 = dfX * dfX + dfY * dfY;
     285             : 
     286     1445840 :             if (dfR2 < m_dfMinDistance2)
     287           6 :                 ll.leftMin--;
     288     1445840 :             else if (dfR2 > m_dfMaxDistance2)
     289             :             {
     290          26 :                 ll.left = nXOffset + m_nX + 1;
     291          26 :                 break;
     292             :             }
     293             : 
     294     1445820 :             CheckNoData(*pdfHeight);
     295     1440780 :             *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
     296             :         }
     297             : 
     298             :         // Go right.
     299       28008 :         pdfHeight = vThisLineVal.data() + nXStart + 1;
     300       28911 :         for (int nXOffset = nXStart - m_nX + 1;
     301     1500350 :              nXOffset < oCurExtent.xSize() - m_nX; nXOffset++, pdfHeight++)
     302             :         {
     303     1473710 :             double dfX = m_adfTransform[1] * nXOffset + dfLineX;
     304     1475650 :             double dfY = m_adfTransform[4] * nXOffset + dfLineY;
     305     1475580 :             double dfR2 = dfX * dfX + dfY * dfY;
     306     1475580 :             if (dfR2 < m_dfMinDistance2)
     307           3 :                 ll.rightMin++;
     308     1475580 :             else if (dfR2 > m_dfMaxDistance2)
     309             :             {
     310          26 :                 ll.right = nXOffset + m_nX;
     311          26 :                 break;
     312             :             }
     313             : 
     314     1475560 :             CheckNoData(*pdfHeight);
     315     1471440 :             *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
     316       28945 :         }
     317             :     }
     318             :     else
     319             :     {
     320             :         // No curvature adjustment. Just normalize for the observer height.
     321           0 :         double *pdfHeight = vThisLineVal.data();
     322           0 :         for (int i = 0; i < oCurExtent.xSize(); ++i)
     323             :         {
     324           4 :             CheckNoData(*pdfHeight);
     325           0 :             *pdfHeight -= m_dfZObserver;
     326           0 :             pdfHeight++;
     327             :         }
     328             :     }
     329       28941 :     return ll;
     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         235 : bool ViewshedExecutor::processFirstLine(std::vector<double> &vLastLineVal)
     338             : {
     339         235 :     int nLine = oOutExtent.clampY(m_nY);
     340         235 :     int nYOffset = nLine - m_nY;
     341             : 
     342         470 :     std::vector<double> vResult(oOutExtent.xSize());
     343         470 :     std::vector<double> vThisLineVal(oOutExtent.xSize());
     344             : 
     345         235 :     if (!readLine(nLine, vThisLineVal.data()))
     346           0 :         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         235 :     m_dfZObserver = oOpts.observer.z;
     351         235 :     if (oCurExtent.containsX(m_nX))
     352             :     {
     353         229 :         m_dfZObserver += vThisLineVal[m_nX];
     354         229 :         if (oOpts.outputMode == OutputMode::Normal)
     355         212 :             vResult[m_nX] = oOpts.visibleVal;
     356             :     }
     357         235 :     m_dfHeightAdjFactor = calcHeightAdjFactor();
     358             : 
     359             :     // In DEM mode the base is the pre-adjustment value.  In ground mode the base is zero.
     360         235 :     if (oOpts.outputMode == OutputMode::DEM)
     361          16 :         vResult = vThisLineVal;
     362             : 
     363         235 :     LineLimits ll = adjustHeight(nYOffset, vThisLineVal);
     364         235 :     if (oCurExtent.containsX(m_nX) && ll.leftMin != ll.rightMin)
     365           1 :         vResult[m_nX] = oOpts.outOfRangeVal;
     366             : 
     367         235 :     if (!oCurExtent.containsY(m_nY))
     368           4 :         processFirstLineTopOrBottom(ll, vResult, vThisLineVal);
     369             :     else
     370             :     {
     371         462 :         CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     372         231 :         pQueue->SubmitJob([&]()
     373         231 :                           { processFirstLineLeft(ll, vResult, vThisLineVal); });
     374             : 
     375         231 :         pQueue->SubmitJob(
     376         231 :             [&]() { processFirstLineRight(ll, vResult, vThisLineVal); });
     377         231 :         pQueue->WaitCompletion();
     378             :     }
     379             : 
     380             :     // Make the current line the last line.
     381         235 :     vLastLineVal = std::move(vThisLineVal);
     382             : 
     383             :     // Create the output writer.
     384         235 :     if (!writeLine(nLine, vResult))
     385           0 :         return false;
     386             : 
     387         235 :     return oProgress.lineComplete();
     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           4 : void ViewshedExecutor::processFirstLineTopOrBottom(
     396             :     const LineLimits &ll, std::vector<double> &vResult,
     397             :     std::vector<double> &vThisLineVal)
     398             : {
     399           4 :     double *pResult = vResult.data() + ll.left;
     400           4 :     double *pThis = vThisLineVal.data() + ll.left;
     401          24 :     for (int iPixel = ll.left; iPixel < ll.right; ++iPixel, ++pResult, pThis++)
     402             :     {
     403          20 :         if (oOpts.outputMode == OutputMode::Normal)
     404           0 :             *pResult = oOpts.visibleVal;
     405             :         else
     406          20 :             setOutput(*pResult, *pThis, *pThis);
     407             :     }
     408             : 
     409           4 :     std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal);
     410           4 :     std::fill(vResult.begin() + ll.right, vResult.begin() + oCurExtent.xStop,
     411           4 :               oOpts.outOfRangeVal);
     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         231 : void ViewshedExecutor::processFirstLineLeft(const LineLimits &ll,
     420             :                                             std::vector<double> &vResult,
     421             :                                             std::vector<double> &vThisLineVal)
     422             : {
     423         231 :     int iEnd = ll.left - 1;
     424         231 :     int iStart = m_nX - 1;  // One left of the observer.
     425             : 
     426             :     // If end is to the right of start, everything is taken care of by right processing.
     427         231 :     if (iEnd >= iStart)
     428          30 :         return;
     429             : 
     430         201 :     iStart = oCurExtent.clampX(iStart);
     431             : 
     432         201 :     double *pThis = vThisLineVal.data() + iStart;
     433             : 
     434             :     // If the start cell is next to the observer, just mark it visible.
     435         201 :     if (iStart + 1 == m_nX || iStart + 1 == oCurExtent.xStop)
     436             :     {
     437         201 :         double dfZ = *pThis;
     438         201 :         if (oOpts.outputMode == OutputMode::Normal)
     439         190 :             vResult[iStart] = oOpts.visibleVal;
     440             :         else
     441             :         {
     442          11 :             maskLowPitch(dfZ, 1, 0);
     443          11 :             setOutput(vResult[iStart], *pThis, dfZ);
     444             :         }
     445         201 :         maskHighPitch(vResult[iStart], dfZ, 1, 0);
     446         201 :         iStart--;
     447         201 :         pThis--;
     448             :     }
     449             : 
     450             :     // Go from the observer to the left, calculating Z as we go.
     451       10357 :     for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--)
     452             :     {
     453       10156 :         int nXOffset = std::abs(iPixel - m_nX);
     454       10156 :         double dfZ = CalcHeightLine(nXOffset, *(pThis + 1));
     455       10156 :         maskLowPitch(dfZ, nXOffset, 0);
     456       10156 :         setOutput(vResult[iPixel], *pThis, dfZ);
     457       10156 :         maskHighPitch(vResult[iPixel], dfZ, nXOffset, 0);
     458             :     }
     459             : 
     460         201 :     maskLineLeft(vResult, ll, m_nY);
     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       25958 : bool ViewshedExecutor::maskAngleLeft(std::vector<double> &vResult, int nLine)
     469             : {
     470          38 :     auto clamp = [this](int x)
     471          20 :     { return (x < 0 || x >= m_nX) ? INVALID_ISECT : x; };
     472             : 
     473       25958 :     if (!oOpts.angleMasking())
     474       25893 :         return false;
     475             : 
     476          13 :     if (nLine != m_nY)
     477             :     {
     478             :         int startAngleX =
     479          10 :             clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
     480          10 :         int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine));
     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          10 :         if (invalid(startAngleX) && invalid(endAngleX))
     485             :         {
     486             :             // Choose a test angle in quadrant II or III depending on the line.
     487           7 :             double testAngle = nLine < m_nY ? m_testAngle[2] : m_testAngle[3];
     488           7 :             if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
     489             :             {
     490           2 :                 std::fill(vResult.begin(), vResult.begin() + m_nX,
     491           2 :                           oOpts.outOfRangeVal);
     492           7 :                 return true;
     493             :             }
     494           5 :             return false;
     495             :         }
     496           3 :         if (nLine > m_nY)
     497           0 :             std::swap(startAngleX, endAngleX);
     498           3 :         if (invalid(startAngleX))
     499           3 :             startAngleX = 0;
     500           3 :         if (invalid(endAngleX))
     501           0 :             endAngleX = m_nX - 1;
     502           3 :         if (startAngleX <= endAngleX)
     503             :         {
     504           3 :             std::fill(vResult.begin(), vResult.begin() + startAngleX,
     505           3 :                       oOpts.outOfRangeVal);
     506           3 :             std::fill(vResult.begin() + endAngleX + 1, vResult.begin() + m_nX,
     507           3 :                       oOpts.outOfRangeVal);
     508             :         }
     509             :         else
     510             :         {
     511           0 :             std::fill(vResult.begin() + endAngleX + 1,
     512           0 :                       vResult.begin() + startAngleX, oOpts.outOfRangeVal);
     513             :         }
     514             :     }
     515             :     // nLine == m_nY
     516           3 :     else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI))
     517             :     {
     518           0 :         std::fill(vResult.begin(), vResult.begin() + m_nX, oOpts.outOfRangeVal);
     519           0 :         return true;
     520             :     }
     521           4 :     return false;
     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       28907 : bool ViewshedExecutor::maskAngleRight(std::vector<double> &vResult, int nLine)
     530             : {
     531       28907 :     int lineLength = static_cast<int>(vResult.size());
     532             : 
     533          54 :     auto clamp = [this, lineLength](int x)
     534          36 :     { return (x <= m_nX || x >= lineLength) ? INVALID_ISECT : x; };
     535             : 
     536       28877 :     if (oOpts.startAngle == oOpts.endAngle)
     537       28845 :         return false;
     538             : 
     539          32 :     if (nLine != m_nY)
     540             :     {
     541             :         int startAngleX =
     542          18 :             clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine));
     543          18 :         int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine));
     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          18 :         if (invalid(startAngleX) && invalid(endAngleX))
     549             :         {
     550             :             // Choose a test angle in quadrant I or IV depending on the line.
     551          10 :             double testAngle = nLine < m_nY ? m_testAngle[1] : m_testAngle[4];
     552          10 :             if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle))
     553             :             {
     554           0 :                 std::fill(vResult.begin() + m_nX + 1, vResult.end(),
     555           0 :                           oOpts.outOfRangeVal);
     556          10 :                 return true;
     557             :             }
     558          10 :             return false;
     559             :         }
     560             : 
     561           8 :         if (nLine > m_nY)
     562           0 :             std::swap(startAngleX, endAngleX);
     563           8 :         if (invalid(endAngleX))
     564           0 :             endAngleX = lineLength - 1;
     565           8 :         if (invalid(startAngleX))
     566           8 :             startAngleX = m_nX + 1;
     567           8 :         if (startAngleX <= endAngleX)
     568             :         {
     569           8 :             std::fill(vResult.begin() + m_nX + 1, vResult.begin() + startAngleX,
     570           8 :                       oOpts.outOfRangeVal);
     571           8 :             std::fill(vResult.begin() + endAngleX + 1, vResult.end(),
     572           8 :                       oOpts.outOfRangeVal);
     573             :         }
     574             :         else
     575             :         {
     576           0 :             std::fill(vResult.begin() + endAngleX + 1,
     577           0 :                       vResult.begin() + startAngleX, oOpts.outOfRangeVal);
     578             :         }
     579             :     }
     580             :     // nLine == m_nY
     581          14 :     else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 0))
     582             :     {
     583           1 :         std::fill(vResult.begin() + m_nX + 1, vResult.end(),
     584           1 :                   oOpts.outOfRangeVal);
     585           1 :         return true;
     586             :     }
     587           9 :     return false;
     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       25947 : void ViewshedExecutor::maskLineLeft(std::vector<double> &vResult,
     596             :                                     const LineLimits &ll, int nLine)
     597             : {
     598             :     // If we've already masked with angles everything, just return.
     599       25947 :     if (maskAngleLeft(vResult, nLine))
     600           2 :         return;
     601             : 
     602             :     // Mask cells from the left edge to the left limit.
     603       25923 :     std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal);
     604             :     // Mask cells from the left min to the observer.
     605       25874 :     if (ll.leftMin < m_nX)
     606           3 :         std::fill(vResult.begin() + ll.leftMin, vResult.begin() + m_nX,
     607           3 :                   oOpts.outOfRangeVal);
     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       28898 : void ViewshedExecutor::maskLineRight(std::vector<double> &vResult,
     616             :                                      const LineLimits &ll, int nLine)
     617             : {
     618             :     // If we've already masked with angles everything, just return.
     619       28898 :     if (maskAngleRight(vResult, nLine))
     620           1 :         return;
     621             : 
     622             :     // Mask cells from the observer to right min.
     623       28879 :     std::fill(vResult.begin() + m_nX + 1, vResult.begin() + ll.rightMin,
     624       28892 :               oOpts.outOfRangeVal);
     625             :     // Mask cells from the right limit to the right edge.
     626       28888 :     if (ll.right + 1 < static_cast<int>(vResult.size()))
     627           8 :         std::fill(vResult.begin() + ll.right + 1, vResult.end(),
     628           8 :                   oOpts.outOfRangeVal);
     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         231 : void ViewshedExecutor::processFirstLineRight(const LineLimits &ll,
     637             :                                              std::vector<double> &vResult,
     638             :                                              std::vector<double> &vThisLineVal)
     639             : {
     640         231 :     int iStart = m_nX + 1;
     641         231 :     int iEnd = ll.right;
     642             : 
     643             :     // If start is to the right of end, everything is taken care of by left processing.
     644         231 :     if (iStart >= iEnd)
     645           2 :         return;
     646             : 
     647         229 :     iStart = oCurExtent.clampX(iStart);
     648             : 
     649         229 :     double *pThis = vThisLineVal.data() + iStart;
     650             : 
     651             :     // If the start cell is next to the observer, just mark it visible.
     652         229 :     if (iStart - 1 == m_nX || iStart == oCurExtent.xStart)
     653             :     {
     654         229 :         double dfZ = *pThis;
     655         229 :         if (oOpts.outputMode == OutputMode::Normal)
     656         212 :             vResult[iStart] = oOpts.visibleVal;
     657             :         else
     658             :         {
     659          17 :             maskLowPitch(dfZ, 1, 0);
     660          17 :             setOutput(vResult[iStart], *pThis, dfZ);
     661             :         }
     662         229 :         maskHighPitch(vResult[iStart], dfZ, 1, 0);
     663         229 :         iStart++;
     664         229 :         pThis++;
     665             :     }
     666             : 
     667             :     // Go from the observer to the right, calculating Z as we go.
     668       10812 :     for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++)
     669             :     {
     670       10583 :         int nXOffset = std::abs(iPixel - m_nX);
     671       10583 :         double dfZ = CalcHeightLine(nXOffset, *(pThis - 1));
     672       10583 :         maskLowPitch(dfZ, nXOffset, 0);
     673       10583 :         setOutput(vResult[iPixel], *pThis, dfZ);
     674       10583 :         maskHighPitch(vResult[iPixel], dfZ, nXOffset, 0);
     675             :     }
     676             : 
     677         229 :     maskLineRight(vResult, ll, m_nY);
     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       28619 : void ViewshedExecutor::processLineLeft(int nYOffset, LineLimits &ll,
     688             :                                        std::vector<double> &vResult,
     689             :                                        std::vector<double> &vThisLineVal,
     690             :                                        std::vector<double> &vLastLineVal)
     691             : {
     692       28619 :     int iStart = m_nX - 1;
     693       28619 :     int iEnd = ll.left - 1;
     694       28619 :     int nLine = m_nY + nYOffset;
     695             :     // If start to the left of end, everything is taken care of by processing right.
     696       28619 :     if (iStart <= iEnd)
     697        2926 :         return;
     698       25693 :     iStart = oCurExtent.clampX(iStart);
     699             : 
     700       25638 :     nYOffset = std::abs(nYOffset);
     701       25638 :     double *pThis = vThisLineVal.data() + iStart;
     702       25570 :     double *pLast = vLastLineVal.data() + iStart;
     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       25629 :     if (iStart == oCurExtent.xStop - 1)
     709             :     {
     710           6 :         if (oOpts.outputMode == OutputMode::Normal)
     711           0 :             vResult[iStart] = oOpts.visibleVal;
     712             :         else
     713             :         {
     714           6 :             maskLowPitch(*pThis, m_nX - iStart, nYOffset);
     715           6 :             setOutput(vResult[iStart], *pThis, *pThis);
     716           6 :             maskHighPitch(vResult[iStart], *pThis, m_nX - iStart, nYOffset);
     717             :         }
     718           6 :         iStart--;
     719           6 :         pThis--;
     720           6 :         pLast--;
     721             :     }
     722             : 
     723             :     // Go from the observer to the left, calculating Z as we go.
     724     1416400 :     for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--, pLast--)
     725             :     {
     726     1390640 :         int nXOffset = std::abs(iPixel - m_nX);
     727             :         double dfZ;
     728     1390640 :         if (nXOffset == nYOffset)
     729             :         {
     730       15644 :             if (nXOffset == 1)
     731         375 :                 dfZ = *pThis;
     732             :             else
     733       15269 :                 dfZ = CalcHeightLine(nXOffset, *(pLast + 1));
     734             :         }
     735             :         else
     736     1391850 :             dfZ =
     737     1375000 :                 oZcalc(nXOffset, nYOffset, *(pThis + 1), *pLast, *(pLast + 1));
     738     1407500 :         maskLowPitch(dfZ, nXOffset, nYOffset);
     739     1399520 :         setOutput(vResult[iPixel], *pThis, dfZ);
     740     1393600 :         maskHighPitch(vResult[iPixel], dfZ, nXOffset, nYOffset);
     741             :     }
     742             : 
     743       25755 :     maskLineLeft(vResult, ll, nLine);
     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       28644 : void ViewshedExecutor::processLineRight(int nYOffset, LineLimits &ll,
     754             :                                         std::vector<double> &vResult,
     755             :                                         std::vector<double> &vThisLineVal,
     756             :                                         std::vector<double> &vLastLineVal)
     757             : {
     758       28644 :     int iStart = m_nX + 1;
     759       28644 :     int iEnd = ll.right;
     760       28644 :     int nLine = m_nY + nYOffset;
     761             : 
     762             :     // If start is to the right of end, everything is taken care of by processing left.
     763       28644 :     if (iStart >= iEnd)
     764          12 :         return;
     765       28632 :     iStart = oCurExtent.clampX(iStart);
     766             : 
     767       28626 :     nYOffset = std::abs(nYOffset);
     768       28626 :     double *pThis = vThisLineVal.data() + iStart;
     769       28569 :     double *pLast = vLastLineVal.data() + iStart;
     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       28634 :     if (iStart == 0)
     775             :     {
     776           6 :         if (oOpts.outputMode == OutputMode::Normal)
     777           0 :             vResult[iStart] = oOpts.visibleVal;
     778             :         else
     779             :         {
     780           6 :             maskLowPitch(*pThis, m_nX, nYOffset);
     781           6 :             setOutput(vResult[0], *pThis, *pThis);
     782           6 :             maskHighPitch(vResult[0], *pThis, m_nX, nYOffset);
     783             :         }
     784           6 :         iStart++;
     785           6 :         pThis++;
     786           6 :         pLast++;
     787             :     }
     788             : 
     789             :     // Go from the observer to the right, calculating Z as we go.
     790     1475280 :     for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++, pLast++)
     791             :     {
     792     1446600 :         int nXOffset = std::abs(iPixel - m_nX);
     793             :         double dfZ;
     794     1446600 :         if (nXOffset == nYOffset)
     795             :         {
     796       16127 :             if (nXOffset == 1)
     797         416 :                 dfZ = *pThis;
     798             :             else
     799       15711 :                 dfZ = CalcHeightLine(nXOffset, *(pLast - 1));
     800             :         }
     801             :         else
     802     1444900 :             dfZ =
     803     1430480 :                 oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1));
     804     1461020 :         maskLowPitch(dfZ, nXOffset, nYOffset);
     805     1456480 :         setOutput(vResult[iPixel], *pThis, dfZ);
     806     1457750 :         maskHighPitch(vResult[iPixel], dfZ, nXOffset, nYOffset);
     807             :     }
     808             : 
     809       28680 :     maskLineRight(vResult, ll, nLine);
     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       28667 : void ViewshedExecutor::maskInitial(std::vector<double> &vResult, int nLine)
     816             : {
     817       28667 :     if (!oOpts.angleMasking())
     818       28561 :         return;
     819             : 
     820          19 :     if (nLine < m_nY)
     821             :     {
     822          13 :         if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI / 2))
     823           0 :             vResult[m_nX] = oOpts.outOfRangeVal;
     824             :     }
     825           6 :     else if (nLine > m_nY)
     826             :     {
     827           5 :         if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 3 * M_PI / 2))
     828           0 :             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       28724 : bool ViewshedExecutor::processLine(int nLine, std::vector<double> &vLastLineVal)
     839             : {
     840       28724 :     int nYOffset = nLine - m_nY;
     841       57442 :     std::vector<double> vResult(oOutExtent.xSize());
     842       57434 :     std::vector<double> vThisLineVal(oOutExtent.xSize());
     843             : 
     844       28723 :     if (!readLine(nLine, vThisLineVal.data()))
     845           0 :         return false;
     846             : 
     847             :     // In DEM mode the base is the input DEM value.
     848             :     // In ground mode the base is zero.
     849       28691 :     if (oOpts.outputMode == OutputMode::DEM)
     850         163 :         vResult = vThisLineVal;
     851             : 
     852             :     // Adjust height of the read line.
     853       28691 :     LineLimits ll = adjustHeight(nYOffset, vThisLineVal);
     854             : 
     855             :     // Handle the initial position on the line.
     856       28706 :     if (oCurExtent.containsX(m_nX))
     857             :     {
     858       28672 :         if (ll.left < ll.right && ll.leftMin == ll.rightMin)
     859             :         {
     860             :             double dfZ;
     861       28663 :             if (std::abs(nYOffset) == 1)
     862         414 :                 dfZ = vThisLineVal[m_nX];
     863             :             else
     864       28249 :                 dfZ = CalcHeightLine(nYOffset, vLastLineVal[m_nX]);
     865       28628 :             maskLowPitch(dfZ, 0, nYOffset);
     866       28613 :             setOutput(vResult[m_nX], vThisLineVal[m_nX], dfZ);
     867       28609 :             maskHighPitch(vResult[m_nX], dfZ, 0, nYOffset);
     868             :         }
     869             :         else
     870           9 :             vResult[m_nX] = oOpts.outOfRangeVal;
     871             : 
     872       28603 :         maskInitial(vResult, nLine);
     873             :     }
     874             : 
     875             :     // process left half then right half of line
     876       57317 :     CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     877       28571 :     pQueue->SubmitJob(
     878       28615 :         [&]() {
     879       28615 :             processLineLeft(nYOffset, ll, vResult, vThisLineVal, vLastLineVal);
     880       28556 :         });
     881       28634 :     pQueue->SubmitJob(
     882       28642 :         [&]() {
     883       28642 :             processLineRight(nYOffset, ll, vResult, vThisLineVal, vLastLineVal);
     884       28676 :         });
     885       28641 :     pQueue->WaitCompletion();
     886             :     // Make the current line the last line.
     887       28650 :     vLastLineVal = std::move(vThisLineVal);
     888             : 
     889       28606 :     if (!writeLine(nLine, vResult))
     890           0 :         return false;
     891             : 
     892       28661 :     return oProgress.lineComplete();
     893             : }
     894             : 
     895             : // Calculate the ray angle from the origin to middle of the top or bottom
     896             : // of each quadrant.
     897           2 : void ViewshedExecutor::calcTestAngles()
     898             : {
     899             :     // Quadrant 1.
     900             :     {
     901           2 :         int ysize = m_nY + 1;
     902           2 :         int xsize = oCurExtent.xStop - m_nX;
     903           2 :         m_testAngle[1] = atan2(ysize, xsize / 2.0);
     904             :     }
     905             : 
     906             :     // Quadrant 2.
     907             :     {
     908           2 :         int ysize = m_nY + 1;
     909           2 :         int xsize = m_nX + 1;
     910           2 :         m_testAngle[2] = atan2(ysize, -xsize / 2.0);
     911             :     }
     912             : 
     913             :     // Quadrant 3.
     914             :     {
     915           2 :         int ysize = oCurExtent.yStop - m_nY;
     916           2 :         int xsize = m_nX + 1;
     917           2 :         m_testAngle[3] = atan2(-ysize, -xsize / 2.0);
     918             :     }
     919             : 
     920             :     // Quadrant 4.
     921             :     {
     922           2 :         int ysize = oCurExtent.yStop - m_nY;
     923           2 :         int xsize = oCurExtent.xStop - m_nX;
     924           2 :         m_testAngle[4] = atan2(-ysize, xsize / 2.0);
     925             :     }
     926             : 
     927             :     // Adjust range to [0, 2 * M_PI)
     928          10 :     for (int i = 1; i <= 4; ++i)
     929           8 :         if (m_testAngle[i] < 0)
     930           4 :             m_testAngle[i] += (2 * M_PI);
     931           2 : }
     932             : 
     933             : /// Run the viewshed computation
     934             : /// @return  Success as true or false.
     935         235 : bool ViewshedExecutor::run()
     936             : {
     937             :     // If we're doing angular masking, calculate the test angles used later.
     938         235 :     if (oOpts.angleMasking())
     939           2 :         calcTestAngles();
     940             : 
     941         470 :     std::vector<double> vFirstLineVal(oCurExtent.xSize());
     942         235 :     if (!processFirstLine(vFirstLineVal))
     943           0 :         return false;
     944             : 
     945         235 :     if (oOpts.cellMode == CellMode::Edge)
     946         235 :         oZcalc = doEdge;
     947           0 :     else if (oOpts.cellMode == CellMode::Diagonal)
     948           0 :         oZcalc = doDiagonal;
     949           0 :     else if (oOpts.cellMode == CellMode::Min)
     950           0 :         oZcalc = doMin;
     951           0 :     else if (oOpts.cellMode == CellMode::Max)
     952           0 :         oZcalc = doMax;
     953             : 
     954             :     // scan upwards
     955         235 :     int yStart = oCurExtent.clampY(m_nY);
     956         235 :     std::atomic<bool> err(false);
     957         235 :     CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     958         235 :     pQueue->SubmitJob(
     959         235 :         [&]()
     960             :         {
     961         470 :             std::vector<double> vLastLineVal = vFirstLineVal;
     962             : 
     963       13515 :             for (int nLine = yStart - 1; nLine >= oCurExtent.yStart && !err;
     964             :                  nLine--)
     965       13281 :                 if (!processLine(nLine, vLastLineVal))
     966           0 :                     err = true;
     967         235 :         });
     968             : 
     969             :     // scan downwards
     970         235 :     pQueue->SubmitJob(
     971         235 :         [&]()
     972             :         {
     973         470 :             std::vector<double> vLastLineVal = vFirstLineVal;
     974             : 
     975       15677 :             for (int nLine = yStart + 1; nLine < oCurExtent.yStop && !err;
     976             :                  nLine++)
     977       15442 :                 if (!processLine(nLine, vLastLineVal))
     978           0 :                     err = true;
     979         235 :         });
     980         235 :     return true;
     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     2893160 : void ViewshedExecutor::maskLowPitch(double &dfZ, int nXOffset, int nYOffset)
     990             : {
     991     2893160 :     if (std::isnan(m_lowTanPitch))
     992     2883890 :         return;
     993             : 
     994           0 :     double dfX = m_adfTransform[1] * nXOffset + m_adfTransform[2] * nYOffset;
     995          24 :     double dfY = m_adfTransform[4] * nXOffset + m_adfTransform[5] * nYOffset;
     996          24 :     double dfR2 = dfX * dfX + dfY * dfY;
     997          24 :     double dfDist = std::sqrt(dfR2);
     998          24 :     double dfZmask = dfDist * m_lowTanPitch;
     999          24 :     if (dfZmask > dfZ)
    1000          16 :         dfZ = dfZmask;
    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     2860390 : void ViewshedExecutor::maskHighPitch(double &dfResult, double dfZ, int nXOffset,
    1011             :                                      int nYOffset)
    1012             : {
    1013     2860390 :     if (std::isnan(m_highTanPitch))
    1014     2852720 :         return;
    1015             : 
    1016           0 :     double dfX = m_adfTransform[1] * nXOffset + m_adfTransform[2] * nYOffset;
    1017         224 :     double dfY = m_adfTransform[4] * nXOffset + m_adfTransform[5] * nYOffset;
    1018         223 :     double dfR2 = dfX * dfX + dfY * dfY;
    1019         223 :     double dfDist = std::sqrt(dfR2);
    1020         223 :     double dfZmask = dfDist * m_highTanPitch;
    1021         223 :     if (dfZmask < dfZ)
    1022           3 :         dfResult = oOpts.outOfRangeVal;
    1023             : }
    1024             : 
    1025             : }  // namespace viewshed
    1026             : }  // namespace gdal

Generated by: LCOV version 1.14