Skip to content

Commit c029b88

Browse files
committed
Add 'gdal raster viewshed'
1 parent 138cd3f commit c029b88

14 files changed

+822
-26
lines changed

alg/viewshed/viewshed.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -345,5 +345,29 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress,
345345
return static_cast<bool>(poDstDS);
346346
}
347347

348+
// Adjust the coefficient of curvature for non-earth SRS.
349+
/// \param curveCoeff Current curve coefficient
350+
/// \param hSrcDS Source dataset
351+
/// \return Adjusted curve coefficient.
352+
double adjustCurveCoeff(double curveCoeff, GDALDatasetH hSrcDS)
353+
{
354+
const OGRSpatialReference *poSRS =
355+
GDALDataset::FromHandle(hSrcDS)->GetSpatialRef();
356+
if (poSRS)
357+
{
358+
OGRErr eSRSerr;
359+
const double dfSemiMajor = poSRS->GetSemiMajor(&eSRSerr);
360+
if (eSRSerr != OGRERR_FAILURE &&
361+
fabs(dfSemiMajor - SRS_WGS84_SEMIMAJOR) >
362+
0.05 * SRS_WGS84_SEMIMAJOR)
363+
{
364+
curveCoeff = 1.0;
365+
CPLDebug("gdal_viewshed",
366+
"Using -cc=1.0 as a non-Earth CRS has been detected");
367+
}
368+
}
369+
return curveCoeff;
370+
}
371+
348372
} // namespace viewshed
349373
} // namespace gdal

alg/viewshed/viewshed.h

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class Viewshed
8383
Viewshed &operator=(const Viewshed &) = delete;
8484
};
8585

86+
double CPL_DLL adjustCurveCoeff(double curveCoeff, GDALDatasetH hSrcDS);
87+
8688
} // namespace viewshed
8789
} // namespace gdal
8890

apps/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ add_library(
3939
gdalalg_raster_tpi.cpp
4040
gdalalg_raster_tri.cpp
4141
gdalalg_raster_unscale.cpp
42+
gdalalg_raster_viewshed.cpp
4243
gdalalg_raster_write.cpp
4344
gdalalg_vector.cpp
4445
gdalalg_vector_info.cpp

apps/gdal_viewshed.cpp

+2-25
Original file line numberDiff line numberDiff line change
@@ -249,30 +249,6 @@ void validateArgs(Options &localOpts, const GDALArgumentParser &argParser)
249249
opts.nodataVal = 0;
250250
}
251251

252-
// Adjust the coefficient of curvature for non-earth SRS.
253-
/// \param curveCoeff Current curve coefficient
254-
/// \param hSrcDS Source dataset
255-
/// \return Adjusted curve coefficient.
256-
double adjustCurveCoeff(double curveCoeff, GDALDatasetH hSrcDS)
257-
{
258-
const OGRSpatialReference *poSRS =
259-
GDALDataset::FromHandle(hSrcDS)->GetSpatialRef();
260-
if (poSRS)
261-
{
262-
OGRErr eSRSerr;
263-
const double dfSemiMajor = poSRS->GetSemiMajor(&eSRSerr);
264-
if (eSRSerr != OGRERR_FAILURE &&
265-
fabs(dfSemiMajor - SRS_WGS84_SEMIMAJOR) >
266-
0.05 * SRS_WGS84_SEMIMAJOR)
267-
{
268-
curveCoeff = 1.0;
269-
CPLDebug("gdal_viewshed",
270-
"Using -cc=1.0 as a non-Earth CRS has been detected");
271-
}
272-
}
273-
return curveCoeff;
274-
}
275-
276252
} // unnamed namespace
277253
} // namespace gdal
278254

@@ -324,7 +300,8 @@ MAIN_START(argc, argv)
324300
}
325301

326302
if (!argParser.is_used("-cc"))
327-
opts.curveCoeff = adjustCurveCoeff(opts.curveCoeff, hSrcDS);
303+
opts.curveCoeff =
304+
gdal::viewshed::adjustCurveCoeff(opts.curveCoeff, hSrcDS);
328305

329306
/* -------------------------------------------------------------------- */
330307
/* Invoke. */

apps/gdalalg_raster.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "gdalalg_raster_tpi.h"
3636
#include "gdalalg_raster_tri.h"
3737
#include "gdalalg_raster_unscale.h"
38+
#include "gdalalg_raster_viewshed.h"
3839

3940
/************************************************************************/
4041
/* GDALRasterAlgorithm */
@@ -72,6 +73,7 @@ class GDALRasterAlgorithm final : public GDALAlgorithm
7273
RegisterSubAlgorithm<GDALRasterTPIAlgorithmStandalone>();
7374
RegisterSubAlgorithm<GDALRasterTRIAlgorithmStandalone>();
7475
RegisterSubAlgorithm<GDALRasterUnscaleAlgorithmStandalone>();
76+
RegisterSubAlgorithm<GDALRasterViewshedAlgorithm>();
7577
}
7678

7779
private:

apps/gdalalg_raster_viewshed.cpp

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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+
//! @cond Doxygen_Suppress
25+
26+
#ifndef _
27+
#define _(x) (x)
28+
#endif
29+
30+
/************************************************************************/
31+
/* GDALRasterViewshedAlgorithm::GDALRasterViewshedAlgorithm() */
32+
/************************************************************************/
33+
34+
GDALRasterViewshedAlgorithm::GDALRasterViewshedAlgorithm()
35+
: GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
36+
{
37+
AddProgressArg();
38+
39+
AddOpenOptionsArg(&m_openOptions);
40+
AddInputFormatsArg(&m_inputFormats)
41+
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
42+
AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER);
43+
44+
AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER);
45+
AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
46+
/* bGDALGAllowed = */ false)
47+
.AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
48+
{GDAL_DCAP_RASTER, GDAL_DCAP_CREATE});
49+
AddCreationOptionsArg(&m_creationOptions);
50+
AddOverwriteArg(&m_overwrite);
51+
52+
AddArg("position", 'p', _("Observer position"), &m_observerPos)
53+
.AddAlias("pos")
54+
.SetMetaVar("<X,Y> or <X,Y,H>")
55+
.SetMinCount(2)
56+
.SetMaxCount(3)
57+
.SetRequired()
58+
.SetRepeatedArgAllowed(false);
59+
AddArg("target-height", 0,
60+
_("Height of the target above the DEM surface in the height unit of "
61+
"the DEM."),
62+
&m_targetHeight)
63+
.SetDefault(m_targetHeight);
64+
AddArg("mode", 0, _("Sets what information the output contains."),
65+
&m_outputMode)
66+
.SetChoices("normal", "DEM", "ground", "cumulative")
67+
.SetDefault(m_outputMode);
68+
69+
AddArg("max-distance", 0,
70+
_("Maximum distance from observer to compute visibility. It is also "
71+
"used to clamp the extent of the output raster."),
72+
&m_maxDistance)
73+
.SetMinValueIncluded(0);
74+
AddArg("curvature-coefficient", 0,
75+
_("Coefficient to consider the effect of the curvature and "
76+
"refraction."),
77+
&m_curveCoefficient)
78+
.SetMinValueIncluded(0);
79+
80+
AddBandArg(&m_band).SetDefault(m_band);
81+
AddArg("visible-value", 0, _("Pixel value to set for visible areas"),
82+
&m_visibleVal)
83+
.SetDefault(m_visibleVal)
84+
.SetMinValueIncluded(0)
85+
.SetMaxValueIncluded(255);
86+
AddArg("invisible-value", 0, _("Pixel value to set for invisible areas"),
87+
&m_invisibleVal)
88+
.SetDefault(m_invisibleVal)
89+
.SetMinValueIncluded(0)
90+
.SetMaxValueIncluded(255);
91+
AddArg("out-of-range-value", 0,
92+
_("Pixel value to set for the cells that fall outside of the range "
93+
"specified by the observer location and the maximum distance"),
94+
&m_outOfRangeVal)
95+
.SetDefault(m_outOfRangeVal)
96+
.SetMinValueIncluded(0)
97+
.SetMaxValueIncluded(255);
98+
AddArg("dstnodata", 0,
99+
_("The value to be set for the cells in the output raster that have "
100+
"no data."),
101+
&m_dstNoData)
102+
.SetMinValueIncluded(0)
103+
.SetMaxValueIncluded(255);
104+
AddArg("observer-spacing", 0, _("Cell Spacing between observers"),
105+
&m_observerSpacing)
106+
.SetDefault(m_observerSpacing)
107+
.SetMinValueIncluded(1);
108+
AddArg("num-threads", 'j', _("Number of computation threads to use"),
109+
&m_numThreads)
110+
.SetDefault(m_numThreads)
111+
.SetMinValueIncluded(1)
112+
.SetMaxValueIncluded(255);
113+
}
114+
115+
/************************************************************************/
116+
/* GDALRasterViewshedAlgorithm::RunImpl() */
117+
/************************************************************************/
118+
119+
bool GDALRasterViewshedAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
120+
void *pProgressData)
121+
{
122+
if (m_outputDataset.GetDatasetRef())
123+
{
124+
ReportError(CE_Failure, CPLE_NotSupported,
125+
"gdal raster viewshed does not support outputting to an "
126+
"already opened output dataset");
127+
return false;
128+
}
129+
130+
VSIStatBufL sStat;
131+
if (!m_overwrite && !m_outputDataset.GetName().empty() &&
132+
(VSIStatL(m_outputDataset.GetName().c_str(), &sStat) == 0 ||
133+
std::unique_ptr<GDALDataset>(
134+
GDALDataset::Open(m_outputDataset.GetName().c_str()))))
135+
{
136+
ReportError(CE_Failure, CPLE_AppDefined,
137+
"File '%s' already exists. Specify the --overwrite "
138+
"option to overwrite it.",
139+
m_outputDataset.GetName().c_str());
140+
return false;
141+
}
142+
143+
gdal::viewshed::Options opts;
144+
145+
opts.observer.x = m_observerPos[0];
146+
opts.observer.y = m_observerPos[1];
147+
if (m_observerPos.size() == 3)
148+
opts.observer.z = m_observerPos[2];
149+
else
150+
opts.observer.z = 2;
151+
152+
opts.targetHeight = m_targetHeight;
153+
154+
opts.maxDistance = m_maxDistance;
155+
156+
opts.curveCoeff = m_curveCoefficient;
157+
if (!GetArg("curvature-coefficient")->IsExplicitlySet())
158+
{
159+
opts.curveCoeff = gdal::viewshed::adjustCurveCoeff(
160+
opts.curveCoeff,
161+
GDALDataset::ToHandle(m_inputDataset.GetDatasetRef()));
162+
}
163+
164+
opts.visibleVal = m_visibleVal;
165+
opts.invisibleVal = m_invisibleVal;
166+
opts.outOfRangeVal = m_outOfRangeVal;
167+
opts.nodataVal = m_dstNoData;
168+
169+
if (m_outputMode == "normal")
170+
opts.outputMode = gdal::viewshed::OutputMode::Normal;
171+
else if (m_outputMode == "DEM")
172+
opts.outputMode = gdal::viewshed::OutputMode::DEM;
173+
else if (m_outputMode == "ground")
174+
opts.outputMode = gdal::viewshed::OutputMode::Ground;
175+
else if (m_outputMode == "cumulative")
176+
opts.outputMode = gdal::viewshed::OutputMode::Cumulative;
177+
178+
opts.observerSpacing = m_observerSpacing;
179+
opts.numJobs = static_cast<uint8_t>(m_numThreads);
180+
181+
opts.outputFilename = m_outputDataset.GetName();
182+
opts.outputFormat = m_format;
183+
if (opts.outputFormat.empty())
184+
{
185+
opts.outputFormat =
186+
GetOutputDriverForRaster(opts.outputFilename.c_str());
187+
if (opts.outputFormat.empty())
188+
{
189+
ReportError(CE_Failure, CPLE_AppDefined,
190+
"Cannot guess output driver from output filename");
191+
return false;
192+
}
193+
}
194+
195+
opts.creationOpts = CPLStringList(m_creationOptions);
196+
197+
if (opts.outputMode == gdal::viewshed::OutputMode::Cumulative)
198+
{
199+
auto poSrcDS = m_inputDataset.GetDatasetRef();
200+
auto poSrcDriver = poSrcDS->GetDriver();
201+
if (EQUAL(poSrcDS->GetDescription(), "") || !poSrcDriver ||
202+
EQUAL(poSrcDriver->GetDescription(), "MEM"))
203+
{
204+
ReportError(
205+
CE_Failure, CPLE_AppDefined,
206+
"In cumulative mode, the input dataset must be opened by name");
207+
return false;
208+
}
209+
if (EQUAL(opts.outputFormat.c_str(), "MEM"))
210+
{
211+
ReportError(CE_Failure, CPLE_AppDefined,
212+
"In cumulative mode, the output dataset cannot be a "
213+
"MEM dataset");
214+
return false;
215+
}
216+
gdal::viewshed::Cumulative oViewshed(opts);
217+
const bool bSuccess = oViewshed.run(
218+
m_inputDataset.GetName().c_str(),
219+
pfnProgress ? pfnProgress : GDALDummyProgress, pProgressData);
220+
if (bSuccess)
221+
{
222+
m_outputDataset.Set(std::unique_ptr<GDALDataset>(
223+
GDALDataset::Open(opts.outputFilename.c_str(),
224+
GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
225+
nullptr, nullptr, nullptr)));
226+
}
227+
return bSuccess;
228+
}
229+
else
230+
{
231+
gdal::viewshed::Viewshed oViewshed(opts);
232+
const bool bSuccess = oViewshed.run(
233+
GDALRasterBand::ToHandle(
234+
m_inputDataset.GetDatasetRef()->GetRasterBand(m_band)),
235+
pfnProgress ? pfnProgress : GDALDummyProgress, pProgressData);
236+
if (bSuccess)
237+
{
238+
m_outputDataset.Set(oViewshed.output());
239+
}
240+
241+
return bSuccess;
242+
}
243+
}
244+
245+
//! @endcond

0 commit comments

Comments
 (0)