LCOV - code coverage report
Current view: top level - alg/viewshed - viewshed_executor.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 239 278 86.0 %
Date: 2024-11-21 22:18:42 Functions: 23 27 85.2 %

          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 <limits>
      18             : 
      19             : #include "viewshed_executor.h"
      20             : #include "progress.h"
      21             : 
      22             : namespace gdal
      23             : {
      24             : namespace viewshed
      25             : {
      26             : 
      27             : namespace
      28             : {
      29             : 
      30             : /// Calculate the height at nDistance units along a line through the origin given the height
      31             : /// at nDistance - 1 units along the line.
      32             : /// \param nDistance  Distance along the line for the target point.
      33             : /// \param Za  Height at the line one unit previous to the target point.
      34      231037 : double CalcHeightLine(int nDistance, double Za)
      35             : {
      36      231037 :     nDistance = std::abs(nDistance);
      37      231037 :     assert(nDistance != 1);
      38      231037 :     return Za * nDistance / (nDistance - 1);
      39             : }
      40             : 
      41             : // Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0)
      42             : // and passing through the line connecting (i - 1, j, Za) and (i, j - 1, Zb).
      43             : // In other words, the origin and the two points form a plane and we're calculating Zc
      44             : // of the point (i, j, Zc), also on the plane.
      45           0 : double CalcHeightDiagonal(int i, int j, double Za, double Zb)
      46             : {
      47           0 :     return (Za * i + Zb * j) / (i + j - 1);
      48             : }
      49             : 
      50             : // Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0)
      51             : // and through the line connecting (i -1, j - 1, Za) and (i - 1, j, Zb). In other words,
      52             : // the origin and the other two points form a plane and we're calculating Zc of the
      53             : // point (i, j, Zc), also on the plane.
      54     8292380 : double CalcHeightEdge(int i, int j, double Za, double Zb)
      55             : {
      56     8292380 :     assert(i != j);
      57     8292380 :     return (Za * i + Zb * (j - i)) / (j - 1);
      58             : }
      59             : 
      60           0 : double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset,
      61             :                   double dfThisPrev, double dfLast,
      62             :                   [[maybe_unused]] double dfLastPrev)
      63             : {
      64           0 :     return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast);
      65             : }
      66             : 
      67     8274870 : double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
      68             :               double dfLastPrev)
      69             : {
      70     8274870 :     if (nXOffset >= nYOffset)
      71     3332280 :         return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev);
      72             :     else
      73     4942590 :         return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast);
      74             : }
      75             : 
      76           0 : double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
      77             :              double dfLastPrev)
      78             : {
      79           0 :     double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
      80             :     double dfDiagonal =
      81           0 :         doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
      82           0 :     return std::min(dfEdge, dfDiagonal);
      83             : }
      84             : 
      85           0 : double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast,
      86             :              double dfLastPrev)
      87             : {
      88           0 :     double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
      89             :     double dfDiagonal =
      90           0 :         doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev);
      91           0 :     return std::max(dfEdge, dfDiagonal);
      92             : }
      93             : 
      94             : }  // unnamed namespace
      95             : 
      96             : /// Constructor -- the viewshed algorithm executor
      97             : /// @param srcBand  Source raster band
      98             : /// @param dstBand  Destination raster band
      99             : /// @param nX  X position of observer
     100             : /// @param nY  Y position of observer
     101             : /// @param outExtent  Extent of output raster (relative to input)
     102             : /// @param curExtent  Extent of active raster.
     103             : /// @param opts  Configuration options.
     104             : /// @param progress  Reference to the progress tracker.
     105         623 : ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand,
     106             :                                    GDALRasterBand &dstBand, int nX, int nY,
     107             :                                    const Window &outExtent,
     108             :                                    const Window &curExtent, const Options &opts,
     109         623 :                                    Progress &progress)
     110             :     : m_pool(4), m_srcBand(srcBand), m_dstBand(dstBand), oOutExtent(outExtent),
     111         623 :       oCurExtent(curExtent), m_nX(nX - oOutExtent.xStart), m_nY(nY),
     112             :       oOpts(opts), oProgress(progress),
     113         623 :       m_dfMaxDistance2(opts.maxDistance * opts.maxDistance)
     114             : {
     115         623 :     if (m_dfMaxDistance2 == 0)
     116         621 :         m_dfMaxDistance2 = std::numeric_limits<double>::max();
     117         623 :     m_srcBand.GetDataset()->GetGeoTransform(m_adfTransform.data());
     118         623 : }
     119             : 
     120             : // calculate the height adjustment factor.
     121         623 : double ViewshedExecutor::calcHeightAdjFactor()
     122             : {
     123        1246 :     std::lock_guard g(oMutex);
     124             : 
     125             :     const OGRSpatialReference *poDstSRS =
     126         623 :         m_dstBand.GetDataset()->GetSpatialRef();
     127             : 
     128         623 :     if (poDstSRS)
     129             :     {
     130             :         OGRErr eSRSerr;
     131             : 
     132             :         // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR
     133          11 :         double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr);
     134             : 
     135             :         /* If we fetched the axis from the SRS, use it */
     136          11 :         if (eSRSerr != OGRERR_FAILURE)
     137          11 :             return oOpts.curveCoeff / (dfSemiMajor * 2.0);
     138             : 
     139           0 :         CPLDebug("GDALViewshedGenerate",
     140             :                  "Unable to fetch SemiMajor axis from spatial reference");
     141             :     }
     142         612 :     return 0;
     143             : }
     144             : 
     145             : /// Set the output Z value depending on the observable height and computation mode.
     146             : ///
     147             : /// dfResult  Reference to the result cell
     148             : /// dfCellVal  Reference to the current cell height. Replace with observable height.
     149             : /// dfZ  Minimum observable height at cell.
     150     8465900 : void ViewshedExecutor::setOutput(double &dfResult, double &dfCellVal,
     151             :                                  double dfZ)
     152             : {
     153     8465900 :     if (oOpts.outputMode != OutputMode::Normal)
     154             :     {
     155       43346 :         dfResult += (dfZ - dfCellVal);
     156       43346 :         dfResult = std::max(0.0, dfResult);
     157             :     }
     158             :     else
     159     8422550 :         dfResult = (dfCellVal + oOpts.targetHeight < dfZ) ? oOpts.invisibleVal
     160             :                                                           : oOpts.visibleVal;
     161     8465610 :     dfCellVal = std::max(dfCellVal, dfZ);
     162     8508830 : }
     163             : 
     164             : /// Read a line of raster data.
     165             : ///
     166             : /// @param  nLine  Line number to read.
     167             : /// @param  data  Pointer to location in which to store data.
     168             : /// @return  Success or failure.
     169       83721 : bool ViewshedExecutor::readLine(int nLine, double *data)
     170             : {
     171      167371 :     std::lock_guard g(iMutex);
     172             : 
     173       83728 :     if (GDALRasterIO(&m_srcBand, GF_Read, oOutExtent.xStart, nLine,
     174             :                      oOutExtent.xSize(), 1, data, oOutExtent.xSize(), 1,
     175       83650 :                      GDT_Float64, 0, 0))
     176             :     {
     177           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     178             :                  "RasterIO error when reading DEM at position (%d,%d), "
     179             :                  "size (%d,%d)",
     180           0 :                  oOutExtent.xStart, nLine, oOutExtent.xSize(), 1);
     181           0 :         return false;
     182             :     }
     183       83650 :     return true;
     184             : }
     185             : 
     186             : /// Write an output line of either visibility or height data.
     187             : ///
     188             : /// @param  nLine  Line number being written.
     189             : /// @param vResult  Result line to write.
     190             : /// @return  True on success, false otherwise.
     191       83517 : bool ViewshedExecutor::writeLine(int nLine, std::vector<double> &vResult)
     192             : {
     193             :     // GDALRasterIO isn't thread-safe.
     194      167171 :     std::lock_guard g(oMutex);
     195             : 
     196      166946 :     if (GDALRasterIO(&m_dstBand, GF_Write, 0, nLine - oOutExtent.yStart,
     197       83473 :                      oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(),
     198       83654 :                      1, GDT_Float64, 0, 0))
     199             :     {
     200           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     201             :                  "RasterIO error when writing target raster at position "
     202             :                  "(%d,%d), size (%d,%d)",
     203           0 :                  0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1);
     204           0 :         return false;
     205             :     }
     206       83654 :     return true;
     207             : }
     208             : 
     209             : /// Adjust the height of the line of data by the observer height and the curvature of the
     210             : /// earth.
     211             : ///
     212             : /// @param  nYOffset  Y offset of the line being adjusted.
     213             : /// @param  vThisLineVal  Line height data.
     214             : /// @return [left, right)  Leftmost and one past the rightmost cell in the line within
     215             : ///    the max distance. Indices are limited to the raster extent (right may be just
     216             : ///    outside the raster).
     217             : std::pair<int, int>
     218       83676 : ViewshedExecutor::adjustHeight(int nYOffset, std::vector<double> &vThisLineVal)
     219             : {
     220       83676 :     int nLeft = 0;
     221       83676 :     int nRight = oCurExtent.xSize();
     222             : 
     223             :     // Find the starting point in the raster (m_nX may be outside)
     224       83586 :     int nXStart = oCurExtent.clampX(m_nX);
     225             : 
     226             :     // If there is a height adjustment factor other than zero or a max distance,
     227             :     // calculate the adjusted height of the cell, stopping if we've exceeded the max
     228             :     // distance.
     229       83631 :     if (static_cast<bool>(m_dfHeightAdjFactor) || m_dfMaxDistance2 > 0)
     230             :     {
     231             :         // Hoist invariants from the loops.
     232       83631 :         const double dfLineX = m_adfTransform[2] * nYOffset;
     233       83594 :         const double dfLineY = m_adfTransform[5] * nYOffset;
     234             : 
     235             :         // Go left
     236       83613 :         double *pdfHeight = vThisLineVal.data() + nXStart;
     237     4271430 :         for (int nXOffset = nXStart - m_nX; nXOffset >= -m_nX;
     238             :              nXOffset--, pdfHeight--)
     239             :         {
     240     4193890 :             double dfX = m_adfTransform[1] * nXOffset + dfLineX;
     241     4182150 :             double dfY = m_adfTransform[4] * nXOffset + dfLineY;
     242     4187970 :             double dfR2 = dfX * dfX + dfY * dfY;
     243     4187970 :             if (dfR2 > m_dfMaxDistance2)
     244             :             {
     245         102 :                 nLeft = nXOffset + m_nX + 1;
     246         102 :                 break;
     247             :             }
     248     4187860 :             *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
     249             :         }
     250             : 
     251             :         // Go right.
     252       77637 :         pdfHeight = vThisLineVal.data() + nXStart + 1;
     253       83679 :         for (int nXOffset = nXStart - m_nX + 1;
     254     4377040 :              nXOffset < oCurExtent.xSize() - m_nX; nXOffset++, pdfHeight++)
     255             :         {
     256     4290210 :             double dfX = m_adfTransform[1] * nXOffset + dfLineX;
     257     4284530 :             double dfY = m_adfTransform[4] * nXOffset + dfLineY;
     258     4293460 :             double dfR2 = dfX * dfX + dfY * dfY;
     259     4293460 :             if (dfR2 > m_dfMaxDistance2)
     260             :             {
     261         102 :                 nRight = nXOffset + m_nX;
     262         102 :                 break;
     263             :             }
     264     4293360 :             *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver;
     265       83702 :         }
     266             :     }
     267             :     else
     268             :     {
     269             :         // No curvature adjustment. Just normalize for the observer height.
     270           0 :         double *pdfHeight = vThisLineVal.data();
     271           0 :         for (int i = 0; i < oCurExtent.xSize(); ++i)
     272             :         {
     273           0 :             *pdfHeight -= m_dfZObserver;
     274           0 :             pdfHeight++;
     275             :         }
     276             :     }
     277       83702 :     return {nLeft, nRight};
     278             : }
     279             : 
     280             : /// Process the first line (the one with the Y coordinate the same as the observer).
     281             : ///
     282             : /// @param vLastLineVal  Vector in which to store the read line. Becomes the last line
     283             : ///    in further processing.
     284             : /// @return True on success, false otherwise.
     285         623 : bool ViewshedExecutor::processFirstLine(std::vector<double> &vLastLineVal)
     286             : {
     287         623 :     int nLine = oOutExtent.clampY(m_nY);
     288         623 :     int nYOffset = nLine - m_nY;
     289             : 
     290        1246 :     std::vector<double> vResult(oOutExtent.xSize());
     291        1246 :     std::vector<double> vThisLineVal(oOutExtent.xSize());
     292             : 
     293         623 :     if (!readLine(nLine, vThisLineVal.data()))
     294           0 :         return false;
     295             : 
     296             :     // If the observer is outside of the raster, take the specified value as the Z height,
     297             :     // otherwise, take it as an offset from the raster height at that location.
     298         623 :     m_dfZObserver = oOpts.observer.z;
     299         623 :     if (oCurExtent.containsX(m_nX))
     300             :     {
     301         617 :         m_dfZObserver += vThisLineVal[m_nX];
     302         617 :         if (oOpts.outputMode == OutputMode::Normal)
     303         600 :             vResult[m_nX] = oOpts.visibleVal;
     304             :     }
     305         623 :     m_dfHeightAdjFactor = calcHeightAdjFactor();
     306             : 
     307             :     // In DEM mode the base is the pre-adjustment value.  In ground mode the base is zero.
     308         622 :     if (oOpts.outputMode == OutputMode::DEM)
     309          15 :         vResult = vThisLineVal;
     310             : 
     311             :     // iLeft and iRight are the processing limits for the line.
     312         622 :     const auto [iLeft, iRight] = adjustHeight(nYOffset, vThisLineVal);
     313             : 
     314         622 :     if (!oCurExtent.containsY(m_nY))
     315           4 :         processFirstLineTopOrBottom(iLeft, iRight, vResult, vThisLineVal);
     316             :     else
     317             :     {
     318        1237 :         CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     319        1237 :         pQueue->SubmitJob(
     320         618 :             [&, left = iLeft]() {
     321         618 :                 processFirstLineLeft(m_nX - 1, left - 1, vResult, vThisLineVal);
     322         618 :             });
     323             : 
     324        1237 :         pQueue->SubmitJob(
     325         618 :             [&, right = iRight]()
     326         619 :             { processFirstLineRight(m_nX + 1, right, vResult, vThisLineVal); });
     327         618 :         pQueue->WaitCompletion();
     328             :     }
     329             : 
     330             :     // Make the current line the last line.
     331         623 :     vLastLineVal = std::move(vThisLineVal);
     332             : 
     333             :     // Create the output writer.
     334         623 :     if (!writeLine(nLine, vResult))
     335           0 :         return false;
     336             : 
     337         623 :     return oProgress.lineComplete();
     338             : }
     339             : 
     340             : // If the observer is above or below the raster, set all cells in the first line near the
     341             : // observer as observable provided they're in range. Mark cells out of range as such.
     342             : /// @param  iLeft  Leftmost observable raster position in range of the target line.
     343             : /// @param  iRight One past the rightmost observable raster position of the target line.
     344             : /// @param  vResult  Result line.
     345             : /// @param  vThisLineVal  Heights of the cells in the target line
     346           4 : void ViewshedExecutor::processFirstLineTopOrBottom(
     347             :     int iLeft, int iRight, std::vector<double> &vResult,
     348             :     std::vector<double> &vThisLineVal)
     349             : {
     350           4 :     double *pResult = vResult.data() + iLeft;
     351           4 :     double *pThis = vThisLineVal.data() + iLeft;
     352          24 :     for (int iPixel = iLeft; iPixel < iRight; ++iPixel, ++pResult, pThis++)
     353             :     {
     354          20 :         if (oOpts.outputMode == OutputMode::Normal)
     355           0 :             *pResult = oOpts.visibleVal;
     356             :         else
     357          20 :             setOutput(*pResult, *pThis, *pThis);
     358             :     }
     359           4 :     std::fill(vResult.begin(), vResult.begin() + iLeft, oOpts.outOfRangeVal);
     360           4 :     std::fill(vResult.begin() + iRight, vResult.begin() + oCurExtent.xStop,
     361           4 :               oOpts.outOfRangeVal);
     362           4 : }
     363             : 
     364             : /// Process the part of the first line to the left of the observer.
     365             : ///
     366             : /// @param iStart  X coordinate of the first cell to the left of the observer to be procssed.
     367             : /// @param iEnd  X coordinate one past the last cell to be processed.
     368             : /// @param vResult  Vector in which to store the visibility/height results.
     369             : /// @param vThisLineVal  Height of each cell in the line being processed.
     370         619 : void ViewshedExecutor::processFirstLineLeft(int iStart, int iEnd,
     371             :                                             std::vector<double> &vResult,
     372             :                                             std::vector<double> &vThisLineVal)
     373             : {
     374             :     // If end is to the right of start, everything is taken care of by right processing.
     375         619 :     if (iEnd >= iStart)
     376          36 :         return;
     377             : 
     378         583 :     iStart = oCurExtent.clampX(iStart);
     379             : 
     380         583 :     double *pThis = vThisLineVal.data() + iStart;
     381             : 
     382             :     // If the start cell is next to the observer, just mark it visible.
     383         583 :     if (iStart + 1 == m_nX || iStart + 1 == oCurExtent.xStop)
     384             :     {
     385         583 :         if (oOpts.outputMode == OutputMode::Normal)
     386         572 :             vResult[iStart] = oOpts.visibleVal;
     387             :         else
     388          11 :             setOutput(vResult[iStart], *pThis, *pThis);
     389         583 :         iStart--;
     390         583 :         pThis--;
     391             :     }
     392             : 
     393             :     // Go from the observer to the left, calculating Z as we go.
     394       29940 :     for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--)
     395             :     {
     396       29357 :         int nXOffset = std::abs(iPixel - m_nX);
     397       29357 :         double dfZ = CalcHeightLine(nXOffset, *(pThis + 1));
     398       29357 :         setOutput(vResult[iPixel], *pThis, dfZ);
     399             :     }
     400             :     // For cells outside of the [start, end) range, set the outOfRange value.
     401         583 :     std::fill(vResult.begin(), vResult.begin() + iEnd + 1, oOpts.outOfRangeVal);
     402             : }
     403             : 
     404             : /// Process the part of the first line to the right of the observer.
     405             : ///
     406             : /// @param iStart  X coordinate of the first cell to the right of the observer to be processed.
     407             : /// @param iEnd  X coordinate one past the last cell to be processed.
     408             : /// @param vResult  Vector in which to store the visibility/height results.
     409             : /// @param vThisLineVal  Height of each cell in the line being processed.
     410         618 : void ViewshedExecutor::processFirstLineRight(int iStart, int iEnd,
     411             :                                              std::vector<double> &vResult,
     412             :                                              std::vector<double> &vThisLineVal)
     413             : {
     414             :     // If start is to the right of end, everything is taken care of by left processing.
     415         618 :     if (iStart >= iEnd)
     416           2 :         return;
     417             : 
     418         616 :     iStart = oCurExtent.clampX(iStart);
     419             : 
     420         616 :     double *pThis = vThisLineVal.data() + iStart;
     421             : 
     422             :     // If the start cell is next to the observer, just mark it visible.
     423         616 :     if (iStart - 1 == m_nX || iStart == oCurExtent.xStart)
     424             :     {
     425         616 :         if (oOpts.outputMode == OutputMode::Normal)
     426         599 :             vResult[iStart] = oOpts.visibleVal;
     427             :         else
     428          17 :             setOutput(vResult[iStart], *pThis, *pThis);
     429         616 :         iStart++;
     430         616 :         pThis++;
     431             :     }
     432             : 
     433             :     // Go from the observer to the right, calculating Z as we go.
     434       31141 :     for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++)
     435             :     {
     436       30524 :         int nXOffset = std::abs(iPixel - m_nX);
     437       30524 :         double dfZ = CalcHeightLine(nXOffset, *(pThis - 1));
     438       30521 :         setOutput(vResult[iPixel], *pThis, dfZ);
     439             :     }
     440             :     // For cells outside of the [start, end) range, set the outOfRange value.
     441         617 :     std::fill(vResult.begin() + iEnd, vResult.end(), oOpts.outOfRangeVal);
     442             : }
     443             : 
     444             : /// Process a line to the left of the observer.
     445             : ///
     446             : /// @param nYOffset  Offset of the line being processed from the observer
     447             : /// @param iStart  X coordinate of the first cell to the left of the observer to be processed.
     448             : /// @param iEnd  X coordinate one past the last cell to be processed.
     449             : /// @param vResult  Vector in which to store the visibility/height results.
     450             : /// @param vThisLineVal  Height of each cell in the line being processed.
     451             : /// @param vLastLineVal  Observable height of each cell in the previous line processed.
     452       82936 : void ViewshedExecutor::processLineLeft(int nYOffset, int iStart, int iEnd,
     453             :                                        std::vector<double> &vResult,
     454             :                                        std::vector<double> &vThisLineVal,
     455             :                                        std::vector<double> &vLastLineVal)
     456             : {
     457             :     // If start to the left of end, everything is taken care of by processing right.
     458       82936 :     if (iStart <= iEnd)
     459        3891 :         return;
     460       79045 :     iStart = oCurExtent.clampX(iStart);
     461             : 
     462       79046 :     nYOffset = std::abs(nYOffset);
     463       79046 :     double *pThis = vThisLineVal.data() + iStart;
     464       78975 :     double *pLast = vLastLineVal.data() + iStart;
     465             : 
     466             :     // If the observer is to the right of the raster, mark the first cell to the left as
     467             :     // visible. This may mark an out-of-range cell with a value, but this will be fixed
     468             :     // with the out of range assignment at the end.
     469       78969 :     if (iStart == oCurExtent.xStop - 1)
     470             :     {
     471           6 :         if (oOpts.outputMode == OutputMode::Normal)
     472           0 :             vResult[iStart] = oOpts.visibleVal;
     473             :         else
     474           6 :             setOutput(vResult[iStart], *pThis, *pThis);
     475           6 :         iStart--;
     476           6 :         pThis--;
     477           6 :         pLast--;
     478             :     }
     479             : 
     480             :     // Go from the observer to the left, calculating Z as we go.
     481     4190130 :     for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--, pLast--)
     482             :     {
     483     4110930 :         int nXOffset = std::abs(iPixel - m_nX);
     484             :         double dfZ;
     485     4110930 :         if (nXOffset == nYOffset)
     486             :         {
     487       45244 :             if (nXOffset == 1)
     488        1134 :                 dfZ = *pThis;
     489             :             else
     490       44110 :                 dfZ = CalcHeightLine(nXOffset, *(pLast + 1));
     491             :         }
     492             :         else
     493             :             dfZ =
     494     4065690 :                 oZcalc(nXOffset, nYOffset, *(pThis + 1), *pLast, *(pLast + 1));
     495     4113890 :         setOutput(vResult[iPixel], *pThis, dfZ);
     496             :     }
     497             : 
     498             :     // For cells outside of the [start, end) range, set the outOfRange value.
     499       79199 :     std::fill(vResult.begin(), vResult.begin() + iEnd + 1, oOpts.outOfRangeVal);
     500             : }
     501             : 
     502             : /// Process a line to the right of the observer.
     503             : ///
     504             : /// @param nYOffset  Offset of the line being processed from the observer
     505             : /// @param iStart  X coordinate of the first cell to the right of the observer to be processed.
     506             : /// @param iEnd  X coordinate one past the last cell to be processed.
     507             : /// @param vResult  Vector in which to store the visibility/height results.
     508             : /// @param vThisLineVal  Height of each cell in the line being processed.
     509             : /// @param vLastLineVal  Observable height of each cell in the previous line processed.
     510       82873 : void ViewshedExecutor::processLineRight(int nYOffset, int iStart, int iEnd,
     511             :                                         std::vector<double> &vResult,
     512             :                                         std::vector<double> &vThisLineVal,
     513             :                                         std::vector<double> &vLastLineVal)
     514             : {
     515             :     // If start is to the right of end, everything is taken care of by processing left.
     516       82873 :     if (iStart >= iEnd)
     517          10 :         return;
     518       82863 :     iStart = oCurExtent.clampX(iStart);
     519             : 
     520       82936 :     nYOffset = std::abs(nYOffset);
     521       82936 :     double *pThis = vThisLineVal.data() + iStart;
     522       82870 :     double *pLast = vLastLineVal.data() + iStart;
     523             : 
     524             :     // If the observer is to the left of the raster, mark the first cell to the right as
     525             :     // visible. This may mark an out-of-range cell with a value, but this will be fixed
     526             :     // with the out of range assignment at the end.
     527       82896 :     if (iStart == 0)
     528             :     {
     529           6 :         if (oOpts.outputMode == OutputMode::Normal)
     530           0 :             vResult[iStart] = oOpts.visibleVal;
     531             :         else
     532           6 :             setOutput(vResult[0], *pThis, *pThis);
     533           6 :         iStart++;
     534           6 :         pThis++;
     535           6 :         pLast++;
     536             :     }
     537             : 
     538             :     // Go from the observer to the right, calculating Z as we go.
     539     4371950 :     for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++, pLast++)
     540             :     {
     541     4288880 :         int nXOffset = std::abs(iPixel - m_nX);
     542             :         double dfZ;
     543     4288880 :         if (nXOffset == nYOffset)
     544             :         {
     545       46714 :             if (nXOffset == 1)
     546        1189 :                 dfZ = *pThis;
     547             :             else
     548       45525 :                 dfZ = CalcHeightLine(nXOffset, *(pLast - 1));
     549             :         }
     550             :         else
     551             :             dfZ =
     552     4242170 :                 oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1));
     553     4282150 :         setOutput(vResult[iPixel], *pThis, dfZ);
     554             :     }
     555             : 
     556             :     // For cells outside of the [start, end) range, set the outOfRange value.
     557       83069 :     std::fill(vResult.begin() + iEnd, vResult.end(), oOpts.outOfRangeVal);
     558             : }
     559             : 
     560             : /// Process a line above or below the observer.
     561             : ///
     562             : /// @param nLine  Line number being processed.
     563             : /// @param vLastLineVal  Vector in which to store the read line. Becomes the last line
     564             : ///    in further processing.
     565             : /// @return True on success, false otherwise.
     566       83104 : bool ViewshedExecutor::processLine(int nLine, std::vector<double> &vLastLineVal)
     567             : {
     568       83104 :     int nYOffset = nLine - m_nY;
     569      166210 :     std::vector<double> vResult(oOutExtent.xSize());
     570      166220 :     std::vector<double> vThisLineVal(oOutExtent.xSize());
     571             : 
     572       83114 :     if (!readLine(nLine, vThisLineVal.data()))
     573           0 :         return false;
     574             : 
     575             :     // In DEM mode the base is the input DEM value.
     576             :     // In ground mode the base is zero.
     577       83038 :     if (oOpts.outputMode == OutputMode::DEM)
     578         159 :         vResult = vThisLineVal;
     579             : 
     580             :     // Adjust height of the read line.
     581       83038 :     const auto [iLeft, iRight] = adjustHeight(nYOffset, vThisLineVal);
     582             : 
     583             :     // Handle the initial position on the line.
     584       82940 :     if (oCurExtent.containsX(m_nX))
     585             :     {
     586       82849 :         if (iLeft < iRight)
     587             :         {
     588             :             double dfZ;
     589       82845 :             if (std::abs(nYOffset) == 1)
     590        1189 :                 dfZ = vThisLineVal[m_nX];
     591             :             else
     592       81656 :                 dfZ = CalcHeightLine(nYOffset, vLastLineVal[m_nX]);
     593       82919 :             setOutput(vResult[m_nX], vThisLineVal[m_nX], dfZ);
     594             :         }
     595             :         else
     596           4 :             vResult[m_nX] = oOpts.outOfRangeVal;
     597             :     }
     598             : 
     599             :     // process left half then right half of line
     600      166025 :     CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     601      165696 :     pQueue->SubmitJob(
     602       82872 :         [&, left = iLeft]()
     603             :         {
     604       82983 :             processLineLeft(nYOffset, m_nX - 1, left - 1, vResult, vThisLineVal,
     605       82983 :                             vLastLineVal);
     606       82959 :         });
     607      165874 :     pQueue->SubmitJob(
     608       82905 :         [&, right = iRight]()
     609             :         {
     610       82922 :             processLineRight(nYOffset, m_nX + 1, right, vResult, vThisLineVal,
     611       82922 :                              vLastLineVal);
     612       82999 :         });
     613       83015 :     pQueue->WaitCompletion();
     614             : 
     615             :     // Make the current line the last line.
     616       82987 :     vLastLineVal = std::move(vThisLineVal);
     617             : 
     618       82891 :     if (!writeLine(nLine, vResult))
     619           0 :         return false;
     620             : 
     621       83009 :     return oProgress.lineComplete();
     622             : }
     623             : 
     624             : /// Run the viewshed computation
     625             : /// @return  Success as true or false.
     626         623 : bool ViewshedExecutor::run()
     627             : {
     628        1246 :     std::vector<double> vFirstLineVal(oCurExtent.xSize());
     629         623 :     if (!processFirstLine(vFirstLineVal))
     630           0 :         return false;
     631             : 
     632         623 :     if (oOpts.cellMode == CellMode::Edge)
     633         623 :         oZcalc = doEdge;
     634           0 :     else if (oOpts.cellMode == CellMode::Diagonal)
     635           0 :         oZcalc = doDiagonal;
     636           0 :     else if (oOpts.cellMode == CellMode::Min)
     637           0 :         oZcalc = doMin;
     638           0 :     else if (oOpts.cellMode == CellMode::Max)
     639           0 :         oZcalc = doMax;
     640             : 
     641             :     // scan upwards
     642         623 :     int yStart = oCurExtent.clampY(m_nY);
     643         623 :     std::atomic<bool> err(false);
     644         623 :     CPLJobQueuePtr pQueue = m_pool.CreateJobQueue();
     645         623 :     pQueue->SubmitJob(
     646         623 :         [&]()
     647             :         {
     648        1245 :             std::vector<double> vLastLineVal = vFirstLineVal;
     649             : 
     650       41008 :             for (int nLine = yStart - 1; nLine >= oCurExtent.yStart && !err;
     651             :                  nLine--)
     652       40385 :                 if (!processLine(nLine, vLastLineVal))
     653           0 :                     err = true;
     654         623 :         });
     655             : 
     656             :     // scan downwards
     657         623 :     pQueue->SubmitJob(
     658         622 :         [&]()
     659             :         {
     660        1253 :             std::vector<double> vLastLineVal = vFirstLineVal;
     661             : 
     662       43346 :             for (int nLine = yStart + 1; nLine < oCurExtent.yStop && !err;
     663             :                  nLine++)
     664       42714 :                 if (!processLine(nLine, vLastLineVal))
     665           0 :                     err = true;
     666         623 :         });
     667         623 :     return true;
     668             : }
     669             : 
     670             : }  // namespace viewshed
     671             : }  // namespace gdal

Generated by: LCOV version 1.14