LCOV - code coverage report
Current view: top level - alg/viewshed - viewshed.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 86 118 72.9 %
Date: 2024-11-21 22:18:42 Functions: 5 5 100.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             : 
      16             : #include "gdal_alg.h"
      17             : #include "gdal_priv_templates.hpp"
      18             : 
      19             : #include "progress.h"
      20             : #include "util.h"
      21             : #include "viewshed.h"
      22             : #include "viewshed_executor.h"
      23             : 
      24             : /************************************************************************/
      25             : /*                        GDALViewshedGenerate()                        */
      26             : /************************************************************************/
      27             : 
      28             : /**
      29             :  * Create viewshed from raster DEM.
      30             :  *
      31             :  * This algorithm will generate a viewshed raster from an input DEM raster
      32             :  * by using a modified algorithm of "Generating Viewsheds without Using
      33             :  * Sightlines" published at
      34             :  * https://www.asprs.org/wp-content/uploads/pers/2000journal/january/2000_jan_87-90.pdf
      35             :  * This approach provides a relatively fast calculation, since the output raster
      36             :  * is generated in a single scan. The gdal/apps/gdal_viewshed.cpp mainline can
      37             :  * be used as an example of how to use this function. The output raster will be
      38             :  * of type Byte or Float64.
      39             :  *
      40             :  * \note The algorithm as implemented currently will only output meaningful
      41             :  * results if the georeferencing is in a projected coordinate reference system.
      42             :  *
      43             :  * @param hBand The band to read the DEM data from. Only the part of the raster
      44             :  * within the specified maxdistance around the observer point is processed.
      45             :  *
      46             :  * @param pszDriverName Driver name (GTiff if set to NULL)
      47             :  *
      48             :  * @param pszTargetRasterName The name of the target raster to be generated.
      49             :  * Must not be NULL
      50             :  *
      51             :  * @param papszCreationOptions creation options.
      52             :  *
      53             :  * @param dfObserverX observer X value (in SRS units)
      54             :  *
      55             :  * @param dfObserverY observer Y value (in SRS units)
      56             :  *
      57             :  * @param dfObserverHeight The height of the observer above the DEM surface.
      58             :  *
      59             :  * @param dfTargetHeight The height of the target above the DEM surface.
      60             :  * (default 0)
      61             :  *
      62             :  * @param dfVisibleVal pixel value for visibility (default 255)
      63             :  *
      64             :  * @param dfInvisibleVal pixel value for invisibility (default 0)
      65             :  *
      66             :  * @param dfOutOfRangeVal The value to be set for the cells that fall outside of
      67             :  * the range specified by dfMaxDistance.
      68             :  *
      69             :  * @param dfNoDataVal The value to be set for the cells that have no data.
      70             :  *                    If set to a negative value, nodata is not set.
      71             :  *                    Note: currently, no special processing of input cells at a
      72             :  * nodata value is done (which may result in erroneous results).
      73             :  *
      74             :  * @param dfCurvCoeff Coefficient to consider the effect of the curvature and
      75             :  * refraction. The height of the DEM is corrected according to the following
      76             :  * formula: [Height] -= dfCurvCoeff * [Target Distance]^2 / [Earth Diameter] For
      77             :  * the effect of the atmospheric refraction we can use 0.85714.
      78             :  *
      79             :  * @param eMode The mode of the viewshed calculation.
      80             :  * Possible values GVM_Diagonal = 1, GVM_Edge = 2 (default), GVM_Max = 3,
      81             :  * GVM_Min = 4.
      82             :  *
      83             :  * @param dfMaxDistance maximum distance range to compute viewshed.
      84             :  *                      It is also used to clamp the extent of the output
      85             :  * raster. If set to 0, then unlimited range is assumed, that is to say the
      86             :  *                      computation is performed on the extent of the whole
      87             :  * raster.
      88             :  *
      89             :  * @param pfnProgress A GDALProgressFunc that may be used to report progress
      90             :  * to the user, or to interrupt the algorithm.  May be NULL if not required.
      91             :  *
      92             :  * @param pProgressArg The callback data for the pfnProgress function.
      93             :  *
      94             :  * @param heightMode Type of information contained in output raster. Possible
      95             :  * values GVOT_NORMAL = 1 (default), GVOT_MIN_TARGET_HEIGHT_FROM_DEM = 2,
      96             :  *                   GVOT_MIN_TARGET_HEIGHT_FROM_GROUND = 3
      97             :  *
      98             :  *                   GVOT_NORMAL returns a raster of type Byte containing
      99             :  * visible locations.
     100             :  *
     101             :  *                   GVOT_MIN_TARGET_HEIGHT_FROM_DEM and
     102             :  * GVOT_MIN_TARGET_HEIGHT_FROM_GROUND will return a raster of type Float64
     103             :  * containing the minimum target height for target to be visible from the DEM
     104             :  * surface or ground level respectively. Parameters dfTargetHeight, dfVisibleVal
     105             :  * and dfInvisibleVal will be ignored.
     106             :  *
     107             :  *
     108             :  * @param papszExtraOptions Future extra options. Must be set to NULL currently.
     109             :  *
     110             :  * @return not NULL output dataset on success (to be closed with GDALClose()) or
     111             :  * NULL if an error occurs.
     112             :  *
     113             :  * @since GDAL 3.1
     114             :  */
     115           1 : GDALDatasetH GDALViewshedGenerate(
     116             :     GDALRasterBandH hBand, const char *pszDriverName,
     117             :     const char *pszTargetRasterName, CSLConstList papszCreationOptions,
     118             :     double dfObserverX, double dfObserverY, double dfObserverHeight,
     119             :     double dfTargetHeight, double dfVisibleVal, double dfInvisibleVal,
     120             :     double dfOutOfRangeVal, double dfNoDataVal, double dfCurvCoeff,
     121             :     GDALViewshedMode eMode, double dfMaxDistance, GDALProgressFunc pfnProgress,
     122             :     void *pProgressArg, GDALViewshedOutputType heightMode,
     123             :     [[maybe_unused]] CSLConstList papszExtraOptions)
     124             : {
     125             :     using namespace gdal;
     126             : 
     127           2 :     viewshed::Options oOpts;
     128           1 :     oOpts.outputFormat = pszDriverName;
     129           1 :     oOpts.outputFilename = pszTargetRasterName;
     130           1 :     oOpts.creationOpts = papszCreationOptions;
     131           1 :     oOpts.observer.x = dfObserverX;
     132           1 :     oOpts.observer.y = dfObserverY;
     133           1 :     oOpts.observer.z = dfObserverHeight;
     134           1 :     oOpts.targetHeight = dfTargetHeight;
     135           1 :     oOpts.curveCoeff = dfCurvCoeff;
     136           1 :     oOpts.maxDistance = dfMaxDistance;
     137           1 :     oOpts.nodataVal = dfNoDataVal;
     138             : 
     139           1 :     switch (eMode)
     140             :     {
     141           1 :         case GVM_Edge:
     142           1 :             oOpts.cellMode = viewshed::CellMode::Edge;
     143           1 :             break;
     144           0 :         case GVM_Diagonal:
     145           0 :             oOpts.cellMode = viewshed::CellMode::Diagonal;
     146           0 :             break;
     147           0 :         case GVM_Min:
     148           0 :             oOpts.cellMode = viewshed::CellMode::Min;
     149           0 :             break;
     150           0 :         case GVM_Max:
     151           0 :             oOpts.cellMode = viewshed::CellMode::Max;
     152           0 :             break;
     153             :     }
     154             : 
     155           1 :     switch (heightMode)
     156             :     {
     157           0 :         case GVOT_MIN_TARGET_HEIGHT_FROM_DEM:
     158           0 :             oOpts.outputMode = viewshed::OutputMode::DEM;
     159           0 :             break;
     160           1 :         case GVOT_MIN_TARGET_HEIGHT_FROM_GROUND:
     161           1 :             oOpts.outputMode = viewshed::OutputMode::Ground;
     162           1 :             break;
     163           0 :         case GVOT_NORMAL:
     164           0 :             oOpts.outputMode = viewshed::OutputMode::Normal;
     165           0 :             break;
     166             :     }
     167             : 
     168           1 :     if (!GDALIsValueInRange<uint8_t>(dfVisibleVal))
     169             :     {
     170           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     171             :                  "dfVisibleVal out of range. Must be [0, 255].");
     172           0 :         return nullptr;
     173             :     }
     174           1 :     if (!GDALIsValueInRange<uint8_t>(dfInvisibleVal))
     175             :     {
     176           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     177             :                  "dfInvisibleVal out of range. Must be [0, 255].");
     178           0 :         return nullptr;
     179             :     }
     180           1 :     if (!GDALIsValueInRange<uint8_t>(dfOutOfRangeVal))
     181             :     {
     182           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     183             :                  "dfOutOfRangeVal out of range. Must be [0, 255].");
     184           0 :         return nullptr;
     185             :     }
     186           1 :     oOpts.visibleVal = dfVisibleVal;
     187           1 :     oOpts.invisibleVal = dfInvisibleVal;
     188           1 :     oOpts.outOfRangeVal = dfOutOfRangeVal;
     189             : 
     190           1 :     gdal::viewshed::Viewshed v(oOpts);
     191             : 
     192           1 :     if (!pfnProgress)
     193           1 :         pfnProgress = GDALDummyProgress;
     194           1 :     v.run(hBand, pfnProgress, pProgressArg);
     195             : 
     196           1 :     return GDALDataset::FromHandle(v.output().release());
     197             : }
     198             : 
     199             : namespace gdal
     200             : {
     201             : namespace viewshed
     202             : {
     203             : 
     204             : namespace
     205             : {
     206             : 
     207          37 : bool getTransforms(GDALRasterBand &band, double *pFwdTransform,
     208             :                    double *pRevTransform)
     209             : {
     210          37 :     band.GetDataset()->GetGeoTransform(pFwdTransform);
     211          37 :     if (!GDALInvGeoTransform(pFwdTransform, pRevTransform))
     212             :     {
     213           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform");
     214           0 :         return false;
     215             :     }
     216          37 :     return true;
     217             : }
     218             : 
     219             : }  // unnamed namespace
     220             : 
     221          37 : Viewshed::Viewshed(const Options &opts) : oOpts{opts}
     222             : {
     223          37 : }
     224             : 
     225             : Viewshed::~Viewshed() = default;
     226             : 
     227             : /// Calculate the extent of the output raster in terms of the input raster and
     228             : /// save the input raster extent.
     229             : ///
     230             : /// @return  false on error, true otherwise
     231          37 : bool Viewshed::calcExtents(int nX, int nY,
     232             :                            const std::array<double, 6> &adfInvTransform)
     233             : {
     234             :     // We start with the assumption that the output size matches the input.
     235          37 :     oOutExtent.xStop = GDALGetRasterBandXSize(pSrcBand);
     236          37 :     oOutExtent.yStop = GDALGetRasterBandYSize(pSrcBand);
     237             : 
     238          37 :     if (!oOutExtent.contains(nX, nY))
     239           8 :         CPLError(CE_Warning, CPLE_AppDefined,
     240             :                  "NOTE: The observer location falls outside of the DEM area");
     241             : 
     242          37 :     constexpr double EPSILON = 1e-8;
     243          37 :     if (oOpts.maxDistance > 0)
     244             :     {
     245             :         //ABELL - This assumes that the transformation is only a scaling. Should be fixed.
     246             :         //  Find the distance in the direction of the transformed unit vector in the X and Y
     247             :         //  directions and use those factors to determine the limiting values in the raster space.
     248           2 :         int nXStart = static_cast<int>(
     249           2 :             std::floor(nX - adfInvTransform[1] * oOpts.maxDistance + EPSILON));
     250           2 :         int nXStop = static_cast<int>(
     251           2 :             std::ceil(nX + adfInvTransform[1] * oOpts.maxDistance - EPSILON) +
     252           2 :             1);
     253             :         int nYStart =
     254           2 :             static_cast<int>(std::floor(
     255           2 :                 nY - std::fabs(adfInvTransform[5]) * oOpts.maxDistance +
     256           2 :                 EPSILON)) -
     257           2 :             (adfInvTransform[5] > 0 ? 1 : 0);
     258           2 :         int nYStop = static_cast<int>(
     259           2 :             std::ceil(nY + std::fabs(adfInvTransform[5]) * oOpts.maxDistance -
     260           2 :                       EPSILON) +
     261           2 :             (adfInvTransform[5] < 0 ? 1 : 0));
     262             : 
     263             :         // If the limits are invalid, set the window size to zero to trigger the error below.
     264           2 :         if (nXStart >= oOutExtent.xStop || nXStop < 0 ||
     265           2 :             nYStart >= oOutExtent.yStop || nYStop < 0)
     266             :         {
     267           0 :             oOutExtent = Window();
     268             :         }
     269             :         else
     270             :         {
     271           2 :             oOutExtent.xStart = std::max(nXStart, 0);
     272           2 :             oOutExtent.xStop = std::min(nXStop, oOutExtent.xStop);
     273             : 
     274           2 :             oOutExtent.yStart = std::max(nYStart, 0);
     275           2 :             oOutExtent.yStop = std::min(nYStop, oOutExtent.yStop);
     276             :         }
     277             :     }
     278             : 
     279          37 :     if (oOutExtent.xSize() == 0 || oOutExtent.ySize() == 0)
     280             :     {
     281           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     282             :                  "Invalid target raster size due to transform "
     283             :                  "and/or distance limitation.");
     284           0 :         return false;
     285             :     }
     286             : 
     287             :     // normalize horizontal index to [ 0, oOutExtent.xSize() )
     288          37 :     oCurExtent = oOutExtent;
     289          37 :     oCurExtent.shiftX(-oOutExtent.xStart);
     290             : 
     291          37 :     return true;
     292             : }
     293             : 
     294             : /// Compute the viewshed of a raster band.
     295             : ///
     296             : /// @param band  Pointer to the raster band to be processed.
     297             : /// @param pfnProgress  Pointer to the progress function. Can be null.
     298             : /// @param pProgressArg  Argument passed to the progress function
     299             : /// @return  True on success, false otherwise.
     300          37 : bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress,
     301             :                    void *pProgressArg)
     302             : {
     303          37 :     pSrcBand = static_cast<GDALRasterBand *>(band);
     304             : 
     305             :     std::array<double, 6> adfFwdTransform;
     306             :     std::array<double, 6> adfInvTransform;
     307          37 :     if (!getTransforms(*pSrcBand, adfFwdTransform.data(),
     308             :                        adfInvTransform.data()))
     309           0 :         return false;
     310             : 
     311             :     // calculate observer position
     312             :     double dfX, dfY;
     313          37 :     GDALApplyGeoTransform(adfInvTransform.data(), oOpts.observer.x,
     314             :                           oOpts.observer.y, &dfX, &dfY);
     315          37 :     if (!GDALIsValueInRange<int>(dfX))
     316             :     {
     317           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Observer X value out of range");
     318           0 :         return false;
     319             :     }
     320          37 :     if (!GDALIsValueInRange<int>(dfY))
     321             :     {
     322           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Observer Y value out of range");
     323           0 :         return false;
     324             :     }
     325          37 :     int nX = static_cast<int>(dfX);
     326          37 :     int nY = static_cast<int>(dfY);
     327             : 
     328             :     // Must calculate extents in order to make the output dataset.
     329          37 :     if (!calcExtents(nX, nY, adfInvTransform))
     330           0 :         return false;
     331             : 
     332          37 :     poDstDS = createOutputDataset(*pSrcBand, oOpts, oOutExtent);
     333          37 :     if (!poDstDS)
     334           2 :         return false;
     335             : 
     336             :     // Create the progress reporter.
     337          70 :     Progress oProgress(pfnProgress, pProgressArg, oOutExtent.ySize());
     338             : 
     339             :     // Execute the viewshed algorithm.
     340          35 :     GDALRasterBand *pDstBand = poDstDS->GetRasterBand(1);
     341          35 :     ViewshedExecutor executor(*pSrcBand, *pDstBand, nX, nY, oOutExtent,
     342          70 :                               oCurExtent, oOpts, oProgress);
     343          35 :     executor.run();
     344          35 :     oProgress.emit(1);
     345          35 :     return static_cast<bool>(poDstDS);
     346             : }
     347             : 
     348             : }  // namespace viewshed
     349             : }  // namespace gdal

Generated by: LCOV version 1.14