LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_viewshed.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 121 133 91.0 %
Date: 2026-04-03 14:38:35 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("dst-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          98 :         .SetMinValueIncluded(0)
     141          98 :         .SetMaxValueIncluded(255);
     142             :     AddArg("observer-spacing", 0, _("Cell Spacing between observers"),
     143         196 :            &m_opts.observerSpacing)
     144          98 :         .SetDefault(m_opts.observerSpacing)
     145          98 :         .SetMinValueIncluded(1);
     146             : 
     147          98 :     m_numThreadsStr = std::to_string(m_numThreads);
     148          98 :     AddNumThreadsArg(&m_numThreads, &m_numThreadsStr);
     149          98 : }
     150             : 
     151             : /************************************************************************/
     152             : /*                GDALRasterViewshedAlgorithm::RunStep()                */
     153             : /************************************************************************/
     154             : 
     155          18 : bool GDALRasterViewshedAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     156             : {
     157          18 :     auto pfnProgress = ctxt.m_pfnProgress;
     158          18 :     auto pProgressData = ctxt.m_pProgressData;
     159          18 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     160          18 :     CPLAssert(poSrcDS);
     161          18 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     162             : 
     163          18 :     GDALRasterBandH sdBand = nullptr;
     164          18 :     if (auto sdDataset = m_sdFilename.GetDatasetRef())
     165             :     {
     166           2 :         if (sdDataset->GetRasterCount() == 0)
     167             :         {
     168           1 :             ReportError(
     169             :                 CE_Failure, CPLE_AppDefined,
     170             :                 "The standard deviation dataset must have one raster band");
     171           1 :             return false;
     172             :         }
     173           1 :         sdBand = GDALRasterBand::FromHandle(sdDataset->GetRasterBand(1));
     174             :     }
     175             : 
     176          17 :     if (GetArg("height")->IsExplicitlySet())
     177             :     {
     178           3 :         if (m_observerPos.size() == 3)
     179             :         {
     180           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     181             :                         "Height can't be specified in both 'position' and "
     182             :                         "'height' arguments");
     183           0 :             return false;
     184             :         }
     185             :     }
     186             : 
     187          17 :     if (m_observerPos.size())
     188             :     {
     189          14 :         m_opts.observer.x = m_observerPos[0];
     190          14 :         m_opts.observer.y = m_observerPos[1];
     191          14 :         if (m_observerPos.size() == 3)
     192          14 :             m_opts.observer.z = m_observerPos[2];
     193             :         else
     194           0 :             m_opts.observer.z = 2;
     195             :     }
     196             : 
     197          17 :     if (!GetArg("curvature-coefficient")->IsExplicitlySet())
     198             :     {
     199          16 :         m_opts.curveCoeff = gdal::viewshed::adjustCurveCoeff(
     200             :             m_opts.curveCoeff, GDALDataset::ToHandle(poSrcDS));
     201             :     }
     202             : 
     203          17 :     if (m_outputMode == "normal")
     204          11 :         m_opts.outputMode = gdal::viewshed::OutputMode::Normal;
     205           6 :     else if (m_outputMode == "DEM")
     206           1 :         m_opts.outputMode = gdal::viewshed::OutputMode::DEM;
     207           5 :     else if (m_outputMode == "ground")
     208           2 :         m_opts.outputMode = gdal::viewshed::OutputMode::Ground;
     209           3 :     else if (m_outputMode == "cumulative")
     210           3 :         m_opts.outputMode = gdal::viewshed::OutputMode::Cumulative;
     211             : 
     212          17 :     m_opts.numJobs = static_cast<uint8_t>(std::clamp(m_numThreads, 0, 255));
     213             : 
     214             :     m_opts.outputFilename =
     215          17 :         CPLGenerateTempFilenameSafe(
     216          51 :             CPLGetBasenameSafe(poSrcDS->GetDescription()).c_str()) +
     217          17 :         ".tif";
     218          17 :     m_opts.outputFormat = "GTiff";
     219             : 
     220          17 :     if (m_opts.outputMode == gdal::viewshed::OutputMode::Cumulative)
     221             :     {
     222             :         static const std::vector<std::string> badArgs{
     223             :             "visible-value", "invisible-value", "max-distance",
     224             :             "min-distance",  "start-angle",     "end-angle",
     225          12 :             "low-pitch",     "high-pitch",      "position"};
     226             : 
     227          30 :         for (const auto &arg : badArgs)
     228          27 :             if (GetArg(arg)->IsExplicitlySet())
     229             :             {
     230             :                 std::string err =
     231           0 :                     "Option '" + arg + "' can't be used in cumulative mode.";
     232           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     233           0 :                 return false;
     234             :             }
     235             : 
     236           3 :         auto poSrcDriver = poSrcDS->GetDriver();
     237           5 :         if (EQUAL(poSrcDS->GetDescription(), "") || !poSrcDriver ||
     238           2 :             EQUAL(poSrcDriver->GetDescription(), "MEM"))
     239             :         {
     240           1 :             ReportError(
     241             :                 CE_Failure, CPLE_AppDefined,
     242             :                 "In cumulative mode, the input dataset must be opened by name");
     243           1 :             return false;
     244             :         }
     245           4 :         gdal::viewshed::Cumulative oViewshed(m_opts);
     246           4 :         const bool bSuccess = oViewshed.run(
     247           2 :             m_inputDataset[0].GetName().c_str(),
     248             :             pfnProgress ? pfnProgress : GDALDummyProgress, pProgressData);
     249           2 :         if (bSuccess)
     250             :         {
     251           2 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(
     252             :                 GDALDataset::Open(m_opts.outputFilename.c_str(),
     253             :                                   GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     254             :                                   nullptr, nullptr, nullptr)));
     255             :         }
     256             :     }
     257             :     else
     258             :     {
     259             :         static const std::vector<std::string> badArgs{
     260          16 :             "observer-spacing", GDAL_ARG_NAME_NUM_THREADS};
     261          42 :         for (const auto &arg : badArgs)
     262          28 :             if (GetArg(arg)->IsExplicitlySet())
     263             :             {
     264             :                 std::string err =
     265           0 :                     "Option '" + arg + "' can't be used in standard mode.";
     266           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     267           0 :                 return false;
     268             :             }
     269          15 :         static const std::vector<std::string> goodArgs{"position"};
     270          28 :         for (const auto &arg : goodArgs)
     271          14 :             if (!GetArg(arg)->IsExplicitlySet())
     272             :             {
     273             :                 std::string err =
     274           0 :                     "Option '" + arg + "' must be specified in standard mode.";
     275           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     276           0 :                 return false;
     277             :             }
     278             : 
     279          28 :         gdal::viewshed::Viewshed oViewshed(m_opts);
     280          14 :         const bool bSuccess = oViewshed.run(
     281             :             GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(m_band)), sdBand,
     282             :             pfnProgress ? pfnProgress : GDALDummyProgress, pProgressData);
     283          14 :         if (bSuccess)
     284             :         {
     285          14 :             m_outputDataset.Set(oViewshed.output());
     286             :         }
     287             :     }
     288             : 
     289          16 :     auto poOutDS = m_outputDataset.GetDatasetRef();
     290          16 :     if (poOutDS && poOutDS->GetDescription()[0])
     291             :     {
     292             :         // In file systems that allow it (all but Windows...), we want to
     293             :         // delete the temporary file as soon as soon as possible after
     294             :         // having open it, so that if someone kills the process there are
     295             :         // no temp files left over. If that unlink() doesn't succeed
     296             :         // (on Windows), then the file will eventually be deleted when
     297             :         // poTmpDS is cleaned due to MarkSuppressOnClose().
     298          16 :         VSIUnlink(poOutDS->GetDescription());
     299          16 :         poOutDS->MarkSuppressOnClose();
     300             :     }
     301             : 
     302          16 :     return poOutDS != nullptr;
     303             : }
     304             : 
     305             : GDALRasterViewshedAlgorithmStandalone::
     306             :     ~GDALRasterViewshedAlgorithmStandalone() = default;
     307             : 
     308             : //! @endcond

Generated by: LCOV version 1.14