LCOV - code coverage report
Current view: top level - apps - gdal_viewshed.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 161 175 92.0 %
Date: 2025-05-15 13:16:46 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Viewshed Generator
       4             :  * Purpose:  Viewshed Generator mainline.
       5             :  * Author:   Tamas Szekeres <szekerest@gmail.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  *
       9             :  * SPDX-License-Identifier: MIT
      10             :  ****************************************************************************/
      11             : 
      12             : #include <limits>
      13             : 
      14             : #include "commonutils.h"
      15             : #include "gdal.h"
      16             : #include "gdalargumentparser.h"
      17             : 
      18             : #include "viewshed/cumulative.h"
      19             : #include "viewshed/viewshed.h"
      20             : 
      21             : namespace gdal
      22             : {
      23             : 
      24             : namespace
      25             : {
      26             : 
      27             : struct Options
      28             : {
      29             :     viewshed::Options opts;
      30             :     std::string osSrcFilename;
      31             :     int nBandIn{1};
      32             :     bool bQuiet;
      33             : };
      34             : 
      35             : /// Parse arguments into options structure.
      36             : ///
      37             : /// \param argParser  Argument parser
      38             : /// \param aosArgv  Command line options as a string list
      39             : /// \return  Command line parsed as options
      40          21 : Options parseArgs(GDALArgumentParser &argParser, const CPLStringList &aosArgv)
      41             : {
      42          21 :     Options localOpts;
      43             : 
      44          21 :     viewshed::Options &opts = localOpts.opts;
      45             : 
      46          21 :     argParser.add_output_format_argument(opts.outputFormat);
      47          21 :     argParser.add_argument("-ox")
      48          21 :         .store_into(opts.observer.x)
      49          42 :         .metavar("<value>")
      50          21 :         .help(_("The X position of the observer (in SRS units)."));
      51             : 
      52          21 :     argParser.add_argument("-oy")
      53          21 :         .store_into(opts.observer.y)
      54          42 :         .metavar("<value>")
      55          21 :         .help(_("The Y position of the observer (in SRS units)."));
      56             : 
      57          21 :     argParser.add_argument("-oz")
      58          21 :         .default_value(2)
      59          21 :         .store_into(opts.observer.z)
      60          42 :         .metavar("<value>")
      61          21 :         .nargs(1)
      62             :         .help(_("The height of the observer above the DEM surface in the "
      63          21 :                 "height unit of the DEM."));
      64             : 
      65          21 :     argParser.add_argument("-vv")
      66          21 :         .default_value(255)
      67          21 :         .store_into(opts.visibleVal)
      68          42 :         .metavar("<value>")
      69          21 :         .nargs(1)
      70          21 :         .help(_("Pixel value to set for visible areas."));
      71             : 
      72          21 :     argParser.add_argument("-iv")
      73          21 :         .default_value(0)
      74          21 :         .store_into(opts.invisibleVal)
      75          42 :         .metavar("<value>")
      76          21 :         .nargs(1)
      77          21 :         .help(_("Pixel value to set for invisible areas."));
      78             : 
      79          21 :     argParser.add_argument("-ov")
      80          21 :         .default_value(0)
      81          21 :         .store_into(opts.outOfRangeVal)
      82          42 :         .metavar("<value>")
      83          21 :         .nargs(1)
      84             :         .help(
      85             :             _("Pixel value to set for the cells that fall outside of the range "
      86          21 :               "specified by the observer location and the maximum distance."));
      87             : 
      88          21 :     argParser.add_creation_options_argument(opts.creationOpts);
      89             : 
      90          21 :     argParser.add_argument("-a_nodata")
      91          21 :         .default_value(-1.0)
      92          21 :         .store_into(opts.nodataVal)
      93          42 :         .metavar("<value>")
      94          21 :         .nargs(1)
      95             :         .help(_("The value to be set for the cells in the output raster that "
      96          21 :                 "have no data."));
      97             : 
      98          21 :     argParser.add_argument("-tz")
      99          21 :         .default_value(0.0)
     100          21 :         .store_into(opts.targetHeight)
     101          42 :         .metavar("<value>")
     102          21 :         .nargs(1)
     103             :         .help(_("The height of the target above the DEM surface in the height "
     104          21 :                 "unit of the DEM."));
     105             : 
     106          21 :     argParser.add_argument("-md")
     107          21 :         .default_value(0)
     108          21 :         .store_into(opts.maxDistance)
     109          42 :         .metavar("<value>")
     110          21 :         .nargs(1)
     111          21 :         .help(_("Maximum distance from observer to compute visibility."));
     112             : 
     113          21 :     argParser.add_argument("-j")
     114          21 :         .default_value(3)
     115          21 :         .store_into(opts.numJobs)
     116          42 :         .metavar("<value>")
     117          21 :         .nargs(1)
     118             :         .help(_(
     119          21 :             "Number of relative simultaneous jobs to run in cumulative mode"));
     120             : 
     121             :     // Value for standard atmospheric refraction. See
     122             :     // doc/source/programs/gdal_viewshed.rst
     123          21 :     argParser.add_argument("-cc")
     124          21 :         .default_value(0.85714)
     125          21 :         .store_into(opts.curveCoeff)
     126          42 :         .metavar("<value>")
     127          21 :         .nargs(1)
     128             :         .help(_("Coefficient to consider the effect of the curvature and "
     129          21 :                 "refraction."));
     130             : 
     131          21 :     argParser.add_argument("-b")
     132          21 :         .default_value(localOpts.nBandIn)
     133          21 :         .store_into(localOpts.nBandIn)
     134          42 :         .metavar("<value>")
     135          21 :         .nargs(1)
     136          21 :         .help(_("Select an input band band containing the DEM data."));
     137             : 
     138          21 :     argParser.add_argument("-om")
     139          21 :         .choices("NORMAL", "DEM", "GROUND", "ACCUM")
     140          42 :         .metavar("NORMAL|DEM|GROUND|ACCUM")
     141             :         .action(
     142          37 :             [&into = opts.outputMode](const std::string &value)
     143             :             {
     144           8 :                 if (EQUAL(value.c_str(), "DEM"))
     145           1 :                     into = viewshed::OutputMode::DEM;
     146           7 :                 else if (EQUAL(value.c_str(), "GROUND"))
     147           1 :                     into = viewshed::OutputMode::Ground;
     148           6 :                 else if (EQUAL(value.c_str(), "ACCUM"))
     149           1 :                     into = viewshed::OutputMode::Cumulative;
     150             :                 else
     151           5 :                     into = viewshed::OutputMode::Normal;
     152          21 :             })
     153          21 :         .nargs(1)
     154          21 :         .help(_("Sets what information the output contains."));
     155             : 
     156          21 :     argParser.add_argument("-os")
     157          21 :         .default_value(10)
     158          21 :         .store_into(opts.observerSpacing)
     159          42 :         .metavar("<value>")
     160          21 :         .nargs(1)
     161          21 :         .help(_("Spacing between observer cells when using cumulative mode."));
     162             : 
     163          21 :     argParser.add_quiet_argument(&localOpts.bQuiet);
     164             : 
     165          21 :     argParser.add_argument("src_filename")
     166          21 :         .store_into(localOpts.osSrcFilename)
     167          21 :         .metavar("<src_filename>");
     168             : 
     169          21 :     argParser.add_argument("dst_filename")
     170          21 :         .store_into(opts.outputFilename)
     171          21 :         .metavar("<dst_filename>");
     172             : 
     173             :     try
     174             :     {
     175          21 :         argParser.parse_args(aosArgv);
     176             :     }
     177           2 :     catch (const std::exception &err)
     178             :     {
     179           2 :         argParser.display_error_and_usage(err);
     180           2 :         std::exit(1);
     181             :     }
     182          19 :     return localOpts;
     183             : }
     184             : 
     185             : /// Validate specified options.
     186             : ///
     187             : /// \param localOpts  Options to validate
     188             : /// \param argParser  Argument parser
     189          19 : void validateArgs(Options &localOpts, const GDALArgumentParser &argParser)
     190             : {
     191          19 :     viewshed::Options &opts = localOpts.opts;
     192             : 
     193          19 :     if (opts.maxDistance < 0)
     194             :     {
     195           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     196             :                  "Max distance must be non-negative.");
     197           0 :         exit(2);
     198             :     }
     199             : 
     200          19 :     if (opts.outputFormat.empty())
     201             :     {
     202             :         opts.outputFormat =
     203          12 :             GetOutputDriverForRaster(opts.outputFilename.c_str());
     204          12 :         if (opts.outputFormat.empty())
     205             :         {
     206           0 :             exit(2);
     207             :         }
     208             :     }
     209             : 
     210          19 :     if (opts.outputMode != viewshed::OutputMode::Cumulative)
     211             :     {
     212          54 :         for (const char *opt : {"-os", "-j"})
     213          36 :             if (argParser.is_used(opt))
     214             :             {
     215           0 :                 std::string err = "Option " + std::string(opt) +
     216           0 :                                   " can only be used in cumulative mode.";
     217           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     218           0 :                 exit(2);
     219             :             }
     220             :     }
     221             : 
     222          19 :     if (opts.outputMode == viewshed::OutputMode::Cumulative)
     223             :     {
     224           6 :         for (const char *opt : {"-ox", "-oy", "-vv", "-iv", "-md"})
     225           5 :             if (argParser.is_used(opt))
     226             :             {
     227           0 :                 std::string err = "Option " + std::string(opt) +
     228           0 :                                   " can't be used in cumulative mode.";
     229           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     230           0 :                 exit(2);
     231             :             }
     232             :     }
     233             :     else
     234             :     {
     235          51 :         for (const char *opt : {"-ox", "-oy"})
     236          35 :             if (!argParser.is_used(opt))
     237             :             {
     238             :                 std::string err =
     239           4 :                     "Option " + std::string(opt) + " is required.";
     240           2 :                 CPLError(CE_Failure, CPLE_AppDefined, "%s", err.c_str());
     241           2 :                 exit(2);
     242             :             }
     243             :     }
     244             : 
     245             :     // For double values that are out of range for byte raster output,
     246             :     // set to zero.  Values less than zero are sentinel as NULL nodata.
     247          31 :     if (opts.outputMode == viewshed::OutputMode::Normal &&
     248          14 :         opts.nodataVal > std::numeric_limits<uint8_t>::max())
     249           0 :         opts.nodataVal = 0;
     250          17 : }
     251             : 
     252             : }  // unnamed namespace
     253             : }  // namespace gdal
     254             : 
     255             : /************************************************************************/
     256             : /*                                main()                                */
     257             : /************************************************************************/
     258             : 
     259          22 : MAIN_START(argc, argv)
     260             : {
     261             :     using namespace gdal;
     262             : 
     263          22 :     EarlySetConfigOptions(argc, argv);
     264             : 
     265          22 :     GDALAllRegister();
     266             : 
     267          22 :     argc = GDALGeneralCmdLineProcessor(argc, &argv, 0);
     268          37 :     CPLStringList aosArgv;
     269          22 :     aosArgv.Assign(argv, /* bTakeOwnership= */ true);
     270          22 :     if (argc < 1)
     271           1 :         std::exit(-argc);
     272             : 
     273          57 :     GDALArgumentParser argParser(aosArgv[0], /* bForBinary=*/true);
     274             : 
     275             :     argParser.add_description(
     276          21 :         _("Calculates a viewshed raster from an input raster DEM."));
     277             : 
     278             :     argParser.add_epilog(_("For more details, consult "
     279          21 :                            "https://gdal.org/programs/gdal_viewshed.html"));
     280             : 
     281          21 :     Options localOpts = parseArgs(argParser, aosArgv);
     282          19 :     viewshed::Options &opts = localOpts.opts;
     283             : 
     284          19 :     validateArgs(localOpts, argParser);
     285             : 
     286             :     /* -------------------------------------------------------------------- */
     287             :     /*      Open source raster file.                                        */
     288             :     /* -------------------------------------------------------------------- */
     289             :     GDALDatasetH hSrcDS =
     290          17 :         GDALOpen(localOpts.osSrcFilename.c_str(), GA_ReadOnly);
     291          17 :     if (hSrcDS == nullptr)
     292           1 :         exit(2);
     293             : 
     294          16 :     GDALRasterBandH hBand = GDALGetRasterBand(hSrcDS, localOpts.nBandIn);
     295          16 :     if (hBand == nullptr)
     296             :     {
     297           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     298             :                  "Band %d does not exist on dataset.", localOpts.nBandIn);
     299           1 :         exit(2);
     300             :     }
     301             : 
     302          15 :     if (!argParser.is_used("-cc"))
     303          12 :         opts.curveCoeff =
     304          12 :             gdal::viewshed::adjustCurveCoeff(opts.curveCoeff, hSrcDS);
     305             : 
     306             :     /* -------------------------------------------------------------------- */
     307             :     /*      Invoke.                                                         */
     308             :     /* -------------------------------------------------------------------- */
     309             : 
     310             :     GDALDatasetH hDstDS;
     311             : 
     312             :     bool bSuccess;
     313          15 :     if (opts.outputMode == viewshed::OutputMode::Cumulative)
     314             :     {
     315           1 :         viewshed::Cumulative oViewshed(opts);
     316           1 :         bSuccess = oViewshed.run(localOpts.osSrcFilename,
     317           1 :                                  localOpts.bQuiet ? GDALDummyProgress
     318             :                                                   : GDALTermProgress);
     319             :     }
     320             :     else
     321             :     {
     322          28 :         viewshed::Viewshed oViewshed(opts);
     323          14 :         bSuccess = oViewshed.run(hBand, localOpts.bQuiet ? GDALDummyProgress
     324             :                                                          : GDALTermProgress);
     325          14 :         hDstDS = GDALDataset::FromHandle(oViewshed.output().release());
     326          14 :         GDALClose(hSrcDS);
     327          14 :         if (GDALClose(hDstDS) != CE_None)
     328           0 :             bSuccess = false;
     329             :     }
     330             : 
     331          15 :     GDALDestroyDriverManager();
     332          15 :     OGRCleanupAll();
     333             : 
     334          15 :     return bSuccess ? 0 : 1;
     335             : }
     336             : 
     337           0 : MAIN_END

Generated by: LCOV version 1.14