LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_viewshed.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 111 123 90.2 %
Date: 2025-06-19 12:30:01 Functions: 2 2 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster viewshed" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_viewshed.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_vsi_virtual.h"
      17             : 
      18             : #include "commonutils.h"
      19             : #include "gdal_priv.h"
      20             : 
      21             : #include "viewshed/cumulative.h"
      22             : #include "viewshed/viewshed.h"
      23             : 
      24             : #include <algorithm>
      25             : 
      26             : //! @cond Doxygen_Suppress
      27             : 
      28             : #ifndef _
      29             : #define _(x) (x)
      30             : #endif
      31             : 
      32             : /************************************************************************/
      33             : /*       GDALRasterViewshedAlgorithm::GDALRasterViewshedAlgorithm()     */
      34             : /************************************************************************/
      35             : 
      36          39 : GDALRasterViewshedAlgorithm::GDALRasterViewshedAlgorithm(bool standaloneStep)
      37             :     : GDALRasterPipelineNonNativelyStreamingAlgorithm(NAME, DESCRIPTION,
      38          39 :                                                       HELP_URL, standaloneStep)
      39             : {
      40          78 :     AddArg("position", 'p', _("Observer position"), &m_observerPos)
      41          78 :         .AddAlias("pos")
      42          78 :         .SetMetaVar("<X,Y> or <X,Y,H>")
      43          39 :         .SetMinCount(2)
      44          39 :         .SetMaxCount(3)
      45          39 :         .SetRepeatedArgAllowed(false);
      46          39 :     AddArg("height", 'z', _("Observer height"), &m_opts.observer.z);
      47             : 
      48             :     AddArg("target-height", 0,
      49             :            _("Height of the target above the DEM surface in the height unit of "
      50             :              "the DEM."),
      51          78 :            &m_opts.targetHeight)
      52          39 :         .SetDefault(m_opts.targetHeight);
      53             :     AddArg("mode", 0, _("Sets what information the output contains."),
      54          78 :            &m_outputMode)
      55          39 :         .SetChoices("normal", "DEM", "ground", "cumulative")
      56          39 :         .SetDefault(m_outputMode);
      57             : 
      58             :     AddArg("max-distance", 0,
      59             :            _("Maximum distance from observer to compute visibility. It is also "
      60             :              "used to clamp the extent of the output raster."),
      61          78 :            &m_opts.maxDistance)
      62          39 :         .SetMinValueIncluded(0);
      63             :     AddArg("min-distance", 0,
      64             :            _("Mask all cells less than this distance from the observer. Must "
      65             :              "be less "
      66             :              "than 'max-distance'."),
      67          78 :            &m_opts.minDistance)
      68          39 :         .SetMinValueIncluded(0);
      69             : 
      70             :     AddArg("start-angle", 0,
      71             :            _("Mask all cells outside of the arc ('start-angle', 'end-angle'). "
      72             :              "Clockwise degrees "
      73             :              "from north. Also used to clamp the extent of the output raster."),
      74          78 :            &m_opts.startAngle)
      75          39 :         .SetMinValueIncluded(0)
      76          39 :         .SetMaxValueExcluded(360);
      77             :     AddArg("end-angle", 0,
      78             :            _("Mask all cells outside of the arc ('start-angle', 'end-angle'). "
      79             :              "Clockwise degrees "
      80             :              "from north. Also used to clamp the extent of the output raster."),
      81          78 :            &m_opts.endAngle)
      82          39 :         .SetMinValueIncluded(0)
      83          39 :         .SetMaxValueExcluded(360);
      84             : 
      85             :     AddArg("high-pitch", 0,
      86             :            _("Mark all cells out-of-range where the observable height would be "
      87             :              "higher than the "
      88             :              "'high-pitch' angle from the observer. Degrees from horizontal - "
      89             :              "positive is up. "
      90             :              "Must be greater than 'low-pitch'."),
      91          78 :            &m_opts.highPitch)
      92          39 :         .SetMaxValueIncluded(90)
      93          39 :         .SetMinValueExcluded(-90);
      94             :     AddArg("low-pitch", 0,
      95             :            _("Bound observable height to be no lower than the 'low-pitch' "
      96             :              "angle from the observer. "
      97             :              "Degrees from horizontal - positive is up. Must be less than "
      98             :              "'high-pitch'."),
      99          78 :            &m_opts.lowPitch)
     100          39 :         .SetMaxValueExcluded(90)
     101          39 :         .SetMinValueIncluded(-90);
     102             : 
     103             :     AddArg("curvature-coefficient", 0,
     104             :            _("Coefficient to consider the effect of the curvature and "
     105             :              "refraction."),
     106          78 :            &m_opts.curveCoeff)
     107          39 :         .SetMinValueIncluded(0);
     108             : 
     109          39 :     AddBandArg(&m_band).SetDefault(m_band);
     110             :     AddArg("visible-value", 0, _("Pixel value to set for visible areas"),
     111          78 :            &m_opts.visibleVal)
     112          39 :         .SetDefault(m_opts.visibleVal)
     113          39 :         .SetMinValueIncluded(0)
     114          39 :         .SetMaxValueIncluded(255);
     115             :     AddArg("invisible-value", 0, _("Pixel value to set for invisible areas"),
     116          78 :            &m_opts.invisibleVal)
     117          39 :         .SetDefault(m_opts.invisibleVal)
     118          39 :         .SetMinValueIncluded(0)
     119          39 :         .SetMaxValueIncluded(255);
     120             :     AddArg("out-of-range-value", 0,
     121             :            _("Pixel value to set for the cells that fall outside of the range "
     122             :              "specified by the observer location and the maximum distance"),
     123          78 :            &m_opts.outOfRangeVal)
     124          39 :         .SetDefault(m_opts.outOfRangeVal)
     125          39 :         .SetMinValueIncluded(0)
     126          39 :         .SetMaxValueIncluded(255);
     127             :     AddArg("dst-nodata", 0,
     128             :            _("The value to be set for the cells in the output raster that have "
     129             :              "no data."),
     130          78 :            &m_opts.nodataVal)
     131          39 :         .SetMinValueIncluded(0)
     132          39 :         .SetMaxValueIncluded(255);
     133             :     AddArg("observer-spacing", 0, _("Cell Spacing between observers"),
     134          78 :            &m_opts.observerSpacing)
     135          39 :         .SetDefault(m_opts.observerSpacing)
     136          39 :         .SetMinValueIncluded(1);
     137             : 
     138          39 :     m_numThreadsStr = std::to_string(m_numThreads);
     139          39 :     AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
     140          39 : }
     141             : 
     142             : /************************************************************************/
     143             : /*                 GDALRasterViewshedAlgorithm::RunStep()               */
     144             : /************************************************************************/
     145             : 
     146          15 : bool GDALRasterViewshedAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     147             : {
     148          15 :     auto pfnProgress = ctxt.m_pfnProgress;
     149          15 :     auto pProgressData = ctxt.m_pProgressData;
     150          15 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     151          15 :     CPLAssert(poSrcDS);
     152          15 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     153             : 
     154          15 :     if (GetArg("height")->IsExplicitlySet())
     155             :     {
     156           3 :         if (m_observerPos.size() == 3)
     157             :         {
     158           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     159             :                         "Height can't be specified in both 'position' and "
     160             :                         "'height' arguments");
     161           0 :             return false;
     162             :         }
     163             :     }
     164             : 
     165          15 :     if (m_observerPos.size())
     166             :     {
     167          12 :         m_opts.observer.x = m_observerPos[0];
     168          12 :         m_opts.observer.y = m_observerPos[1];
     169          12 :         if (m_observerPos.size() == 3)
     170          12 :             m_opts.observer.z = m_observerPos[2];
     171             :         else
     172           0 :             m_opts.observer.z = 2;
     173             :     }
     174             : 
     175          15 :     if (!GetArg("curvature-coefficient")->IsExplicitlySet())
     176             :     {
     177          14 :         m_opts.curveCoeff = gdal::viewshed::adjustCurveCoeff(
     178             :             m_opts.curveCoeff, GDALDataset::ToHandle(poSrcDS));
     179             :     }
     180             : 
     181          15 :     if (m_outputMode == "normal")
     182          10 :         m_opts.outputMode = gdal::viewshed::OutputMode::Normal;
     183           5 :     else if (m_outputMode == "DEM")
     184           1 :         m_opts.outputMode = gdal::viewshed::OutputMode::DEM;
     185           4 :     else if (m_outputMode == "ground")
     186           1 :         m_opts.outputMode = gdal::viewshed::OutputMode::Ground;
     187           3 :     else if (m_outputMode == "cumulative")
     188           3 :         m_opts.outputMode = gdal::viewshed::OutputMode::Cumulative;
     189             : 
     190          15 :     m_opts.numJobs = static_cast<uint8_t>(std::clamp(m_numThreads, 0, 255));
     191             : 
     192             :     m_opts.outputFilename =
     193          15 :         CPLGenerateTempFilenameSafe(
     194          45 :             CPLGetBasenameSafe(poSrcDS->GetDescription()).c_str()) +
     195          15 :         ".tif";
     196          15 :     m_opts.outputFormat = "GTiff";
     197             : 
     198          15 :     if (m_opts.outputMode == gdal::viewshed::OutputMode::Cumulative)
     199             :     {
     200             :         static const std::vector<std::string> badArgs{
     201             :             "visible-value", "invisible-value", "max-distance",
     202             :             "min-distance",  "start-angle",     "end-angle",
     203          12 :             "low-pitch",     "high-pitch",      "position"};
     204             : 
     205          30 :         for (const auto &arg : badArgs)
     206          27 :             if (GetArg(arg)->IsExplicitlySet())
     207             :             {
     208             :                 std::string err =
     209           0 :                     "Option '" + arg + "' can't be used in cumulative mode.";
     210           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     211           0 :                 return false;
     212             :             }
     213             : 
     214           3 :         auto poSrcDriver = poSrcDS->GetDriver();
     215           5 :         if (EQUAL(poSrcDS->GetDescription(), "") || !poSrcDriver ||
     216           2 :             EQUAL(poSrcDriver->GetDescription(), "MEM"))
     217             :         {
     218           1 :             ReportError(
     219             :                 CE_Failure, CPLE_AppDefined,
     220             :                 "In cumulative mode, the input dataset must be opened by name");
     221           1 :             return false;
     222             :         }
     223           4 :         gdal::viewshed::Cumulative oViewshed(m_opts);
     224           4 :         const bool bSuccess = oViewshed.run(
     225           2 :             m_inputDataset[0].GetName().c_str(),
     226             :             pfnProgress ? pfnProgress : GDALDummyProgress, pProgressData);
     227           2 :         if (bSuccess)
     228             :         {
     229           2 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(
     230             :                 GDALDataset::Open(m_opts.outputFilename.c_str(),
     231             :                                   GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     232             :                                   nullptr, nullptr, nullptr)));
     233             :         }
     234             :     }
     235             :     else
     236             :     {
     237             :         static const std::vector<std::string> badArgs{"observer-spacing",
     238          14 :                                                       "num-threads"};
     239          36 :         for (const auto &arg : badArgs)
     240          24 :             if (GetArg(arg)->IsExplicitlySet())
     241             :             {
     242             :                 std::string err =
     243           0 :                     "Option '" + arg + "' can't be used in standard mode.";
     244           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     245           0 :                 return false;
     246             :             }
     247          13 :         static const std::vector<std::string> goodArgs{"position"};
     248          24 :         for (const auto &arg : goodArgs)
     249          12 :             if (!GetArg(arg)->IsExplicitlySet())
     250             :             {
     251             :                 std::string err =
     252           0 :                     "Option '" + arg + "' must be specified in standard mode.";
     253           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     254           0 :                 return false;
     255             :             }
     256             : 
     257          24 :         gdal::viewshed::Viewshed oViewshed(m_opts);
     258          12 :         const bool bSuccess = oViewshed.run(
     259             :             GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(m_band)),
     260             :             pfnProgress ? pfnProgress : GDALDummyProgress, pProgressData);
     261          12 :         if (bSuccess)
     262             :         {
     263          12 :             m_outputDataset.Set(oViewshed.output());
     264             :         }
     265             :     }
     266             : 
     267          14 :     auto poOutDS = m_outputDataset.GetDatasetRef();
     268          14 :     if (poOutDS && poOutDS->GetDescription()[0])
     269             :     {
     270             :         // In file systems that allow it (all but Windows...), we want to
     271             :         // delete the temporary file as soon as soon as possible after
     272             :         // having open it, so that if someone kills the process there are
     273             :         // no temp files left over. If that unlink() doesn't succeed
     274             :         // (on Windows), then the file will eventually be deleted when
     275             :         // poTmpDS is cleaned due to MarkSuppressOnClose().
     276          14 :         VSIUnlink(poOutDS->GetDescription());
     277          14 :         poOutDS->MarkSuppressOnClose();
     278             :     }
     279             : 
     280          14 :     return poOutDS != nullptr;
     281             : }
     282             : 
     283             : GDALRasterViewshedAlgorithmStandalone::
     284             :     ~GDALRasterViewshedAlgorithmStandalone() = default;
     285             : 
     286             : //! @endcond

Generated by: LCOV version 1.14