LCOV - code coverage report
Current view: top level - autotest/cpp - test_viewshed.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 201 201 100.0 %
Date: 2024-11-21 22:18:42 Functions: 32 32 100.0 %

          Line data    Source code
       1             : ///////////////////////////////////////////////////////////////////////////////
       2             : //
       3             : // Project:  C++ Test Suite for GDAL/OGR
       4             : // Purpose:  Test viewshed algorithm
       5             : // Author:   Andrew Bell
       6             : //
       7             : ///////////////////////////////////////////////////////////////////////////////
       8             : /*
       9             :  * SPDX-License-Identifier: MIT
      10             :  ****************************************************************************/
      11             : 
      12             : #include <algorithm>
      13             : #include <array>
      14             : #include <utility>
      15             : 
      16             : #include "gdal_unit_test.h"
      17             : 
      18             : #include "gtest_include.h"
      19             : 
      20             : #include "viewshed/viewshed.h"
      21             : 
      22             : namespace gdal
      23             : {
      24             : namespace viewshed
      25             : {
      26             : 
      27             : namespace
      28             : {
      29             : using Coord = std::pair<int, int>;
      30             : using DatasetPtr = std::unique_ptr<GDALDataset>;
      31             : using Transform = std::array<double, 6>;
      32             : Transform identity{0, 1, 0, 0, 0, 1};
      33             : 
      34          17 : Options stdOptions(int x, int y)
      35             : {
      36          17 :     Options opts;
      37          17 :     opts.observer.x = x;
      38          17 :     opts.observer.y = y;
      39          17 :     opts.outputFilename = "none";
      40          17 :     opts.outputFormat = "mem";
      41          17 :     opts.curveCoeff = 0;
      42             : 
      43          17 :     return opts;
      44             : }
      45             : 
      46           5 : Options stdOptions(const Coord &observer)
      47             : {
      48           5 :     return stdOptions(observer.first, observer.second);
      49             : }
      50             : 
      51          22 : DatasetPtr runViewshed(const int8_t *in, int xlen, int ylen,
      52             :                        const Options &opts)
      53             : {
      54          44 :     Viewshed v(opts);
      55             : 
      56          22 :     GDALDriver *driver = (GDALDriver *)GDALGetDriverByName("MEM");
      57          22 :     GDALDataset *dataset = driver->Create("", xlen, ylen, 1, GDT_Int8, nullptr);
      58          22 :     EXPECT_TRUE(dataset);
      59          22 :     dataset->SetGeoTransform(identity.data());
      60          22 :     GDALRasterBand *band = dataset->GetRasterBand(1);
      61          22 :     EXPECT_TRUE(band);
      62          22 :     CPLErr err = band->RasterIO(GF_Write, 0, 0, xlen, ylen, (int8_t *)in, xlen,
      63          22 :                                 ylen, GDT_Int8, 0, 0, nullptr);
      64          22 :     EXPECT_EQ(err, CE_None);
      65             : 
      66          22 :     EXPECT_TRUE(v.run(band));
      67          44 :     return v.output();
      68             : }
      69             : 
      70             : }  // namespace
      71             : 
      72           4 : TEST(Viewshed, all_visible)
      73             : {
      74             :     // clang-format off
      75           1 :     const int xlen = 3;
      76           1 :     const int ylen = 3;
      77           1 :     std::array<int8_t, xlen * ylen> in
      78             :     {
      79             :         1, 2, 3,
      80             :         4, 5, 6,
      81             :         3, 2, 1
      82             :     };
      83             :     // clang-format on
      84             : 
      85           2 :     SCOPED_TRACE("all_visible");
      86           2 :     DatasetPtr output = runViewshed(in.data(), xlen, ylen, stdOptions(1, 1));
      87             : 
      88             :     std::array<uint8_t, xlen * ylen> out;
      89           1 :     GDALRasterBand *band = output->GetRasterBand(1);
      90           1 :     CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
      91           1 :                                 ylen, GDT_Int8, 0, 0, nullptr);
      92           1 :     EXPECT_EQ(err, CE_None);
      93             : 
      94          10 :     for (uint8_t o : out)
      95           9 :         EXPECT_EQ(o, 127);
      96           1 : }
      97             : 
      98           4 : TEST(Viewshed, simple_height)
      99             : {
     100             :     // clang-format off
     101           1 :     const int xlen = 5;
     102           1 :     const int ylen = 5;
     103           1 :     std::array<int8_t, xlen * ylen> in
     104             :     {
     105             :         -1, 0, 1, 0, -1,
     106             :         -1, 2, 0, 4, -1,
     107             :         -1, 1, 0, -1, -1,
     108             :          0, 3, 0, 2, 0,
     109             :         -1, 0, 0, 3, -1
     110             :     };
     111             : 
     112           1 :     std::array<double, xlen * ylen> observable
     113             :     {
     114             :         4, 2, 0, 4, 8,
     115             :         3, 2, 0, 4, 3,
     116             :         2, 1, 0, -1, -2,
     117             :         4, 3, 0, 2, 1,
     118             :         6, 3, 0, 2, 4
     119             :     };
     120             :     // clang-format on
     121             : 
     122             :     {
     123           2 :         SCOPED_TRACE("simple_height:normal");
     124             : 
     125             :         DatasetPtr output =
     126           2 :             runViewshed(in.data(), xlen, ylen, stdOptions(2, 2));
     127             : 
     128             :         std::array<int8_t, xlen * ylen> out;
     129           1 :         GDALRasterBand *band = output->GetRasterBand(1);
     130           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     131           1 :                                     ylen, GDT_Int8, 0, 0, nullptr);
     132           1 :         EXPECT_EQ(err, CE_None);
     133             : 
     134             :         // We expect the cell to be observable when the input is higher than the observable
     135             :         // height.
     136             :         std::array<int8_t, xlen * ylen> expected;
     137          26 :         for (size_t i = 0; i < in.size(); ++i)
     138          25 :             expected[i] = (in[i] >= observable[i] ? 127 : 0);
     139             : 
     140           1 :         EXPECT_EQ(expected, out);
     141             :     }
     142             : 
     143             :     {
     144             :         std::array<double, xlen * ylen> dem;
     145           2 :         SCOPED_TRACE("simple_height:dem");
     146           2 :         Options opts = stdOptions(2, 2);
     147           1 :         opts.outputMode = OutputMode::DEM;
     148             : 
     149           2 :         DatasetPtr output = runViewshed(in.data(), xlen, ylen, opts);
     150             : 
     151           1 :         GDALRasterBand *band = output->GetRasterBand(1);
     152           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, dem.data(), xlen,
     153           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     154           1 :         EXPECT_EQ(err, CE_None);
     155             : 
     156             :         // DEM values are observable values clamped at 0. Not sure why.
     157           1 :         std::array<double, xlen *ylen> expected = observable;
     158          26 :         for (double &d : expected)
     159          25 :             d = std::max(0.0, d);
     160             : 
     161             :         // Double equality is fine here as all the values are small integers.
     162           1 :         EXPECT_EQ(dem, expected);
     163             :     }
     164             : 
     165             :     {
     166             :         std::array<double, xlen * ylen> ground;
     167           2 :         SCOPED_TRACE("simple_height:ground");
     168           2 :         Options opts = stdOptions(2, 2);
     169           1 :         opts.outputMode = OutputMode::Ground;
     170           2 :         DatasetPtr output = runViewshed(in.data(), xlen, ylen, opts);
     171             : 
     172           1 :         GDALRasterBand *band = output->GetRasterBand(1);
     173           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, ground.data(),
     174           1 :                                     xlen, ylen, GDT_Float64, 0, 0, nullptr);
     175           1 :         EXPECT_EQ(err, CE_None);
     176             : 
     177             :         std::array<double, xlen * ylen> expected;
     178          26 :         for (size_t i = 0; i < expected.size(); ++i)
     179          25 :             expected[i] = std::max(0.0, observable[i] - in[i]);
     180             : 
     181             :         // Double equality is fine here as all the values are small integers.
     182           1 :         EXPECT_EQ(expected, ground);
     183             :     }
     184           1 : }
     185             : 
     186             : // Addresses cases in #9501
     187           4 : TEST(Viewshed, dem_vs_ground)
     188             : {
     189             :     // Run gdal_viewshed on the input 8 x 1 array in both ground and dem mode and
     190             :     // verify the results are what are expected.
     191           5 :     auto run = [](const std::array<int8_t, 8> &in, Coord observer,
     192             :                   const std::array<double, 8> &ground,
     193             :                   const std::array<double, 8> &dem)
     194             :     {
     195           5 :         const int xlen = 8;
     196           5 :         const int ylen = 1;
     197             : 
     198             :         std::array<double, xlen * ylen> out;
     199          10 :         Options opts = stdOptions(observer);
     200           5 :         opts.outputMode = OutputMode::Ground;
     201             : 
     202             :         // Verify ground mode.
     203          10 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     204           5 :         GDALRasterBand *band = ds->GetRasterBand(1);
     205           5 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     206           5 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     207           5 :         EXPECT_EQ(err, CE_None);
     208          45 :         for (size_t i = 0; i < ground.size(); ++i)
     209          40 :             EXPECT_DOUBLE_EQ(out[i], ground[i]);
     210             : 
     211             :         // Verify DEM mode.
     212           5 :         opts.outputMode = OutputMode::DEM;
     213           5 :         ds = runViewshed(in.data(), xlen, ylen, opts);
     214           5 :         band = ds->GetRasterBand(1);
     215           5 :         err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, ylen,
     216             :                              GDT_Float64, 0, 0, nullptr);
     217           5 :         EXPECT_EQ(err, CE_None);
     218          45 :         for (size_t i = 0; i < dem.size(); ++i)
     219          40 :             EXPECT_DOUBLE_EQ(out[i], dem[i]);
     220           5 :     };
     221             : 
     222             :     // Input / Observer / Minimum expected above ground / Minimum expected above zero
     223           1 :     run({0, 0, 0, 1, 0, 0, 0, 0}, {2, 0}, {0, 0, 0, 0, 2, 3, 4, 5},
     224             :         {0, 0, 0, 1, 2, 3, 4, 5});
     225           1 :     run({1, 1, 0, 1, 0, 1, 2, 2}, {3, 0}, {0, 0, 0, 0, 0, 0, 0, 1 / 3.0},
     226             :         {1, 0, 0, 1, 0, 0, 1, 7 / 3.0});
     227           1 :     run({0, 0, 0, 1, 1, 0, 0, 0}, {0, 0},
     228             :         {0, 0, 0, 0, 1 / 3.0, 5 / 3.0, 6 / 3.0, 7 / 3.0},
     229             :         {0, 0, 0, 0, 4 / 3.0, 5 / 3.0, 6 / 3.0, 7 / 3.0});
     230           1 :     run({0, 0, 1, 2, 3, 4, 5, 6}, {0, 0}, {0, 0, 0, 0, 0, 0, 0, 0},
     231             :         {0, 0, 0, 3 / 2.0, 8 / 3.0, 15 / 4.0, 24 / 5.0, 35 / 6.0});
     232           1 :     run({0, 0, 1, 1, 3, 4, 5, 4}, {0, 0}, {0, 0, 0, .5, 0, 0, 0, 11 / 6.0},
     233             :         {0, 0, 0, 3 / 2.0, 2, 15 / 4.0, 24 / 5.0, 35 / 6.0});
     234           1 : }
     235             : 
     236             : // Test an observer to the right of the raster.
     237           4 : TEST(Viewshed, oor_right)
     238             : {
     239             :     // clang-format off
     240           1 :     const int xlen = 5;
     241           1 :     const int ylen = 3;
     242           1 :     std::array<int8_t, xlen * ylen> in
     243             :     {
     244             :         1, 2, 0, 4, 1,
     245             :         0, 0, 2, 1, 0,
     246             :         1, 0, 0, 3, 3
     247             :     };
     248             :     // clang-format on
     249             : 
     250             :     {
     251           2 :         Options opts = stdOptions(6, 1);
     252           1 :         opts.outputMode = OutputMode::DEM;
     253           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     254           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     255             :         std::array<double, xlen * ylen> out;
     256           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     257           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     258           1 :         EXPECT_EQ(err, CE_None);
     259             : 
     260             :         // clang-format off
     261           1 :         std::array<double, xlen * ylen> expected
     262             :         {
     263             :             16 / 3.0, 29 / 6.0, 13 / 3.0, 1, 1,
     264             :             3,        2.5,      4 / 3.0,  0, 0,
     265             :             13 / 3.0, 23 / 6.0, 10 / 3.0, 3, 3
     266             :         };
     267             :         // clang-format on
     268             : 
     269          16 :         for (size_t i = 0; i < out.size(); ++i)
     270          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     271             :     }
     272             : 
     273             :     {
     274           2 :         Options opts = stdOptions(6, 2);
     275           1 :         opts.outputMode = OutputMode::DEM;
     276           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     277           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     278             :         std::array<double, xlen * ylen> out;
     279           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     280           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     281           1 :         EXPECT_EQ(err, CE_None);
     282             : 
     283             :         // clang-format off
     284           1 :         std::array<double, xlen * ylen> expected
     285             :         {
     286             :             26 / 5.0, 17 / 4.0, 11 / 3.0, .5,  1,
     287             :             6,        4.5,      3,        1.5, 0,
     288             :             9,        7.5,      6,        4.5, 3
     289             :         };
     290             :         // clang-format on
     291             : 
     292          16 :         for (size_t i = 0; i < out.size(); ++i)
     293          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     294             :     }
     295           1 : }
     296             : 
     297             : // Test an observer to the left of the raster.
     298           4 : TEST(Viewshed, oor_left)
     299             : {
     300             :     // clang-format off
     301           1 :     const int xlen = 5;
     302           1 :     const int ylen = 3;
     303           1 :     std::array<int8_t, xlen * ylen> in
     304             :     {
     305             :         1, 2, 0, 4, 1,
     306             :         0, 0, 2, 1, 0,
     307             :         1, 0, 0, 3, 3
     308             :     };
     309             :     // clang-format on
     310             : 
     311             :     {
     312           2 :         Options opts = stdOptions(-2, 1);
     313           1 :         opts.outputMode = OutputMode::DEM;
     314           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     315           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     316             :         std::array<double, xlen * ylen> out;
     317           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     318           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     319           1 :         EXPECT_EQ(err, CE_None);
     320             : 
     321             :         // clang-format off
     322           1 :         std::array<double, xlen * ylen> expected
     323             :         {
     324             :             1, 1, 2, 2.5, 4.5,
     325             :             0, 0, 0, 2.5, 3,
     326             :             1, 1, 1, 1.5, 3.5
     327             :         };
     328             :         // clang-format on
     329             : 
     330          16 :         for (size_t i = 0; i < out.size(); ++i)
     331          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     332             :     }
     333             : 
     334             :     {
     335           2 :         Options opts = stdOptions(-2, 2);
     336           1 :         opts.outputMode = OutputMode::DEM;
     337           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     338           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     339             :         std::array<double, xlen * ylen> out;
     340           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     341           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     342           1 :         EXPECT_EQ(err, CE_None);
     343             : 
     344             :         // clang-format off
     345           1 :         std::array<double, xlen * ylen> expected
     346             :         {
     347             :             1, .5,  5 / 3.0, 2.25, 4.2,
     348             :             0, .5,  1,       2.5,  3.1,
     349             :             1, 1.5, 2,       2.5,  3.6
     350             :         };
     351             :         // clang-format on
     352             : 
     353          16 :         for (size_t i = 0; i < out.size(); ++i)
     354          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     355             :     }
     356           1 : }
     357             : 
     358             : // Test an observer above the raster
     359           4 : TEST(Viewshed, oor_above)
     360             : {
     361             :     // clang-format off
     362           1 :     const int xlen = 5;
     363           1 :     const int ylen = 3;
     364           1 :     std::array<int8_t, xlen * ylen> in
     365             :     {
     366             :         1, 2, 0, 4, 1,
     367             :         0, 0, 2, 1, 0,
     368             :         1, 0, 0, 3, 3
     369             :     };
     370             :     // clang-format on
     371             : 
     372             :     {
     373           2 :         Options opts = stdOptions(2, -2);
     374           1 :         opts.outputMode = OutputMode::DEM;
     375           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     376           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     377             :         std::array<double, xlen * ylen> out;
     378           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     379           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     380             : 
     381           1 :         EXPECT_EQ(err, CE_None);
     382             : 
     383             :         // clang-format off
     384           1 :         std::array<double, xlen * ylen> expected
     385             :         {
     386             :             1,   2,       0,       4,        1,
     387             :             2.5, 2,       0,       4,        4.5,
     388             :             3,   8 / 3.0, 8 / 3.0, 14 / 3.0, 17 / 3.0
     389             :         };
     390             :         // clang-format on
     391             : 
     392          16 :         for (size_t i = 0; i < out.size(); ++i)
     393          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     394             :     }
     395             : 
     396             :     {
     397           2 :         Options opts = stdOptions(-2, -2);
     398           1 :         opts.outputMode = OutputMode::DEM;
     399           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     400           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     401             :         std::array<double, xlen * ylen> out;
     402           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     403           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     404           1 :         EXPECT_EQ(err, CE_None);
     405             : 
     406             :         // clang-format off
     407           1 :         std::array<double, xlen * ylen> expected
     408             :         {
     409             :             1, 2,   0,   4,    1,
     410             :             0, 1.5, 2.5, 1.25, 3.15,
     411             :             1, 0.5, 2,   3,    2.2
     412             :         };
     413             :         // clang-format on
     414             : 
     415          16 :         for (size_t i = 0; i < out.size(); ++i)
     416          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     417             :     }
     418           1 : }
     419             : 
     420             : // Test an observer below the raster
     421           4 : TEST(Viewshed, oor_below)
     422             : {
     423             :     // clang-format off
     424           1 :     const int xlen = 5;
     425           1 :     const int ylen = 3;
     426           1 :     std::array<int8_t, xlen * ylen> in
     427             :     {
     428             :         1, 2, 0, 4, 1,
     429             :         0, 0, 2, 1, 0,
     430             :         1, 0, 0, 3, 3
     431             :     };
     432             :     // clang-format on
     433             : 
     434             :     {
     435           2 :         Options opts = stdOptions(2, 4);
     436           1 :         opts.outputMode = OutputMode::DEM;
     437           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     438           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     439             :         std::array<double, xlen * ylen> out;
     440           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     441           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     442             : 
     443           1 :         EXPECT_EQ(err, CE_None);
     444             : 
     445             :         // clang-format off
     446           1 :         std::array<double, xlen * ylen> expected
     447             :         {
     448             :             1 / 3.0, 2 / 3.0, 8 / 3.0, 11 / 3.0, 5,
     449             :             0.5,     0,       0,       3,        4.5,
     450             :             1,       0,       0,       3,        3
     451             :         };
     452             :         // clang-format on
     453             : 
     454          16 :         for (size_t i = 0; i < out.size(); ++i)
     455          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     456             :     }
     457             : 
     458             :     {
     459           2 :         Options opts = stdOptions(6, 4);
     460           1 :         opts.outputMode = OutputMode::DEM;
     461           2 :         DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts);
     462           1 :         GDALRasterBand *band = ds->GetRasterBand(1);
     463             :         std::array<double, xlen * ylen> out;
     464           1 :         CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen,
     465           1 :                                     ylen, GDT_Float64, 0, 0, nullptr);
     466           1 :         EXPECT_EQ(err, CE_None);
     467             : 
     468             :         // clang-format off
     469           1 :         std::array<double, xlen * ylen> expected
     470             :         {
     471             :             4.2,  6,    6,   1.5, 1,
     472             :             1.35, 2.25, 4.5, 4.5, 0,
     473             :             1,    0,    0,   3,   3
     474             :         };
     475             :         // clang-format on
     476             : 
     477          16 :         for (size_t i = 0; i < out.size(); ++i)
     478          15 :             EXPECT_DOUBLE_EQ(out[i], expected[i]);
     479             :     }
     480           1 : }
     481             : 
     482             : }  // namespace viewshed
     483             : }  // namespace gdal

Generated by: LCOV version 1.14