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

Generated by: LCOV version 1.14