/src/gdal/alg/viewshed/viewshed.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Viewshed Generation |
4 | | * Purpose: Core algorithm implementation for viewshed generation. |
5 | | * Author: Tamas Szekeres, szekerest@gmail.com |
6 | | * |
7 | | * (c) 2024 info@hobu.co |
8 | | * |
9 | | ****************************************************************************** |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include <algorithm> |
15 | | #include <array> |
16 | | |
17 | | #include "gdal_alg.h" |
18 | | #include "gdal_priv_templates.hpp" |
19 | | |
20 | | #include "progress.h" |
21 | | #include "util.h" |
22 | | #include "viewshed.h" |
23 | | #include "viewshed_executor.h" |
24 | | |
25 | | /************************************************************************/ |
26 | | /* GDALViewshedGenerate() */ |
27 | | /************************************************************************/ |
28 | | |
29 | | /** |
30 | | * Create viewshed from raster DEM. |
31 | | * |
32 | | * This algorithm will generate a viewshed raster from an input DEM raster |
33 | | * by using a modified algorithm of "Generating Viewsheds without Using |
34 | | * Sightlines" published at |
35 | | * https://www.asprs.org/wp-content/uploads/pers/2000journal/january/2000_jan_87-90.pdf |
36 | | * This approach provides a relatively fast calculation, since the output raster |
37 | | * is generated in a single scan. The gdal/apps/gdal_viewshed.cpp mainline can |
38 | | * be used as an example of how to use this function. The output raster will be |
39 | | * of type Byte or Float64. |
40 | | * |
41 | | * \note The algorithm as implemented currently will only output meaningful |
42 | | * results if the georeferencing is in a projected coordinate reference system. |
43 | | * |
44 | | * @param hBand The band to read the DEM data from. Only the part of the raster |
45 | | * within the specified maxdistance around the observer point is processed. |
46 | | * |
47 | | * @param pszDriverName Driver name (GTiff if set to NULL) |
48 | | * |
49 | | * @param pszTargetRasterName The name of the target raster to be generated. |
50 | | * Must not be NULL |
51 | | * |
52 | | * @param papszCreationOptions creation options. |
53 | | * |
54 | | * @param dfObserverX observer X value (in SRS units) |
55 | | * |
56 | | * @param dfObserverY observer Y value (in SRS units) |
57 | | * |
58 | | * @param dfObserverHeight The height of the observer above the DEM surface. |
59 | | * |
60 | | * @param dfTargetHeight The height of the target above the DEM surface. |
61 | | * (default 0) |
62 | | * |
63 | | * @param dfVisibleVal pixel value for visibility (default 255) |
64 | | * |
65 | | * @param dfInvisibleVal pixel value for invisibility (default 0) |
66 | | * |
67 | | * @param dfOutOfRangeVal The value to be set for the cells that fall outside of |
68 | | * the range specified by dfMaxDistance. |
69 | | * |
70 | | * @param dfNoDataVal The value to be set for the cells that have no data. |
71 | | * If set to a negative value, nodata is not set. |
72 | | * Note: currently, no special processing of input cells at a |
73 | | * nodata value is done (which may result in erroneous results). |
74 | | * |
75 | | * @param dfCurvCoeff Coefficient to consider the effect of the curvature and |
76 | | * refraction. The height of the DEM is corrected according to the following |
77 | | * formula: [Height] -= dfCurvCoeff * [Target Distance]^2 / [Earth Diameter] For |
78 | | * the effect of the atmospheric refraction we can use 0.85714. |
79 | | * |
80 | | * @param eMode The mode of the viewshed calculation. |
81 | | * Possible values GVM_Diagonal = 1, GVM_Edge = 2 (default), GVM_Max = 3, |
82 | | * GVM_Min = 4. |
83 | | * |
84 | | * @param dfMaxDistance maximum distance range to compute viewshed. |
85 | | * It is also used to clamp the extent of the output |
86 | | * raster. If set to 0, then unlimited range is assumed, that is to say the |
87 | | * computation is performed on the extent of the whole |
88 | | * raster. |
89 | | * |
90 | | * @param pfnProgress A GDALProgressFunc that may be used to report progress |
91 | | * to the user, or to interrupt the algorithm. May be NULL if not required. |
92 | | * |
93 | | * @param pProgressArg The callback data for the pfnProgress function. |
94 | | * |
95 | | * @param heightMode Type of information contained in output raster. Possible |
96 | | * values GVOT_NORMAL = 1 (default), GVOT_MIN_TARGET_HEIGHT_FROM_DEM = 2, |
97 | | * GVOT_MIN_TARGET_HEIGHT_FROM_GROUND = 3 |
98 | | * |
99 | | * GVOT_NORMAL returns a raster of type Byte containing |
100 | | * visible locations. |
101 | | * |
102 | | * GVOT_MIN_TARGET_HEIGHT_FROM_DEM and |
103 | | * GVOT_MIN_TARGET_HEIGHT_FROM_GROUND will return a raster of type Float64 |
104 | | * containing the minimum target height for target to be visible from the DEM |
105 | | * surface or ground level respectively. Parameters dfTargetHeight, dfVisibleVal |
106 | | * and dfInvisibleVal will be ignored. |
107 | | * |
108 | | * @param papszExtraOptions Extra options to control the viewshed analysis. |
109 | | * This is a NULL-terminated list of strings in "KEY=VALUE" format, or NULL for no options. |
110 | | * The following keys are supported: |
111 | | * <ul> |
112 | | * <li><b>START_ANGLE</b>: Mask all cells outside of the arc ('start-angle', 'end-angle'). Clockwise degrees from north. Also used to clamp the extent of the output raster.</li> |
113 | | * <li><b>END_ANGLE</b>: Mask all cells outside of the arc ('start-angle', 'end-angle'). Clockwise degrees from north. Also used to clamp the extent of the output raster.</li> |
114 | | * <li><b>LOW_PITCH</b>: Bound observable height to be no lower than the 'low-pitch' angle from the observer. Degrees from horizontal - positive is up. Must be less than 'high-pitch'.</li> |
115 | | * <li><b>HIGH_PITCH</b>: Mark all cells out-of-range where the observable height would be higher than the 'high-pitch' angle from the observer. Degrees from horizontal - positive is up. Must be greater than 'low-pitch'.</li> |
116 | | * </ul> |
117 | | * If NULL, a 360-degree viewshed is calculated. |
118 | | * |
119 | | * @return not NULL output dataset on success (to be closed with GDALClose()) or |
120 | | * NULL if an error occurs. |
121 | | * |
122 | | * @since GDAL 3.1 |
123 | | */ |
124 | | GDALDatasetH GDALViewshedGenerate( |
125 | | GDALRasterBandH hBand, const char *pszDriverName, |
126 | | const char *pszTargetRasterName, CSLConstList papszCreationOptions, |
127 | | double dfObserverX, double dfObserverY, double dfObserverHeight, |
128 | | double dfTargetHeight, double dfVisibleVal, double dfInvisibleVal, |
129 | | double dfOutOfRangeVal, double dfNoDataVal, double dfCurvCoeff, |
130 | | GDALViewshedMode eMode, double dfMaxDistance, GDALProgressFunc pfnProgress, |
131 | | void *pProgressArg, GDALViewshedOutputType heightMode, |
132 | | [[maybe_unused]] CSLConstList papszExtraOptions) |
133 | 0 | { |
134 | 0 | using namespace gdal; |
135 | |
|
136 | 0 | viewshed::Options oOpts; |
137 | 0 | oOpts.outputFormat = pszDriverName; |
138 | 0 | oOpts.outputFilename = pszTargetRasterName; |
139 | 0 | oOpts.creationOpts = papszCreationOptions; |
140 | 0 | oOpts.observer.x = dfObserverX; |
141 | 0 | oOpts.observer.y = dfObserverY; |
142 | 0 | oOpts.observer.z = dfObserverHeight; |
143 | 0 | oOpts.targetHeight = dfTargetHeight; |
144 | 0 | oOpts.curveCoeff = dfCurvCoeff; |
145 | 0 | oOpts.maxDistance = dfMaxDistance; |
146 | 0 | oOpts.nodataVal = dfNoDataVal; |
147 | |
|
148 | 0 | switch (eMode) |
149 | 0 | { |
150 | 0 | case GVM_Edge: |
151 | 0 | oOpts.cellMode = viewshed::CellMode::Edge; |
152 | 0 | break; |
153 | 0 | case GVM_Diagonal: |
154 | 0 | oOpts.cellMode = viewshed::CellMode::Diagonal; |
155 | 0 | break; |
156 | 0 | case GVM_Min: |
157 | 0 | oOpts.cellMode = viewshed::CellMode::Min; |
158 | 0 | break; |
159 | 0 | case GVM_Max: |
160 | 0 | oOpts.cellMode = viewshed::CellMode::Max; |
161 | 0 | break; |
162 | 0 | } |
163 | | |
164 | 0 | switch (heightMode) |
165 | 0 | { |
166 | 0 | case GVOT_MIN_TARGET_HEIGHT_FROM_DEM: |
167 | 0 | oOpts.outputMode = viewshed::OutputMode::DEM; |
168 | 0 | break; |
169 | 0 | case GVOT_MIN_TARGET_HEIGHT_FROM_GROUND: |
170 | 0 | oOpts.outputMode = viewshed::OutputMode::Ground; |
171 | 0 | break; |
172 | 0 | case GVOT_NORMAL: |
173 | 0 | oOpts.outputMode = viewshed::OutputMode::Normal; |
174 | 0 | break; |
175 | 0 | } |
176 | | |
177 | 0 | if (!GDALIsValueInRange<uint8_t>(dfVisibleVal)) |
178 | 0 | { |
179 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
180 | 0 | "dfVisibleVal out of range. Must be [0, 255]."); |
181 | 0 | return nullptr; |
182 | 0 | } |
183 | 0 | if (!GDALIsValueInRange<uint8_t>(dfInvisibleVal)) |
184 | 0 | { |
185 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
186 | 0 | "dfInvisibleVal out of range. Must be [0, 255]."); |
187 | 0 | return nullptr; |
188 | 0 | } |
189 | 0 | if (!GDALIsValueInRange<uint8_t>(dfOutOfRangeVal)) |
190 | 0 | { |
191 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
192 | 0 | "dfOutOfRangeVal out of range. Must be [0, 255]."); |
193 | 0 | return nullptr; |
194 | 0 | } |
195 | 0 | oOpts.visibleVal = dfVisibleVal; |
196 | 0 | oOpts.invisibleVal = dfInvisibleVal; |
197 | 0 | oOpts.outOfRangeVal = dfOutOfRangeVal; |
198 | |
|
199 | 0 | const char *pszStartAngle = |
200 | 0 | CSLFetchNameValue(papszExtraOptions, "START_ANGLE"); |
201 | 0 | if (pszStartAngle) |
202 | 0 | oOpts.startAngle = CPLAtof(pszStartAngle); |
203 | |
|
204 | 0 | const char *pszEndAngle = CSLFetchNameValue(papszExtraOptions, "END_ANGLE"); |
205 | 0 | if (pszEndAngle) |
206 | 0 | oOpts.endAngle = CPLAtof(pszEndAngle); |
207 | |
|
208 | 0 | const char *pszLowPitch = CSLFetchNameValue(papszExtraOptions, "LOW_PITCH"); |
209 | 0 | if (pszLowPitch) |
210 | 0 | oOpts.lowPitch = CPLAtof(pszLowPitch); |
211 | |
|
212 | 0 | const char *pszHighPitch = |
213 | 0 | CSLFetchNameValue(papszExtraOptions, "HIGH_PITCH"); |
214 | 0 | if (pszHighPitch) |
215 | 0 | oOpts.highPitch = CPLAtof(pszHighPitch); |
216 | |
|
217 | 0 | gdal::viewshed::Viewshed v(oOpts); |
218 | |
|
219 | 0 | if (!pfnProgress) |
220 | 0 | pfnProgress = GDALDummyProgress; |
221 | 0 | v.run(hBand, pfnProgress, pProgressArg); |
222 | |
|
223 | 0 | return GDALDataset::FromHandle(v.output().release()); |
224 | 0 | } |
225 | | |
226 | | namespace gdal |
227 | | { |
228 | | namespace viewshed |
229 | | { |
230 | | |
231 | | namespace |
232 | | { |
233 | | |
234 | | bool getTransforms(GDALRasterBand &band, GDALGeoTransform &fwdTransform, |
235 | | GDALGeoTransform &revTransform) |
236 | 0 | { |
237 | 0 | band.GetDataset()->GetGeoTransform(fwdTransform); |
238 | 0 | if (!fwdTransform.GetInverse(revTransform)) |
239 | 0 | { |
240 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform"); |
241 | 0 | return false; |
242 | 0 | } |
243 | 0 | return true; |
244 | 0 | } |
245 | | |
246 | | /// Shrink the extent of a window to just cover the slice defined by rays from |
247 | | /// (nX, nY) and [startAngle, endAngle] |
248 | | /// |
249 | | /// @param oOutExtent Window to modify |
250 | | /// @param nX X coordinate of ray endpoint. |
251 | | /// @param nY Y coordinate of ray endpoint. |
252 | | /// @param startAngle Start angle of slice (standard mathematics notion, in radians) |
253 | | /// @param endAngle End angle of slice (standard mathematics notion, in radians) |
254 | | void shrinkWindowForAngles(Window &oOutExtent, int nX, int nY, |
255 | | double startAngle, double endAngle) |
256 | 0 | { |
257 | | /// NOTE: This probably doesn't work when the observer is outside the raster and |
258 | | /// needs to be enhanced for that case. |
259 | |
|
260 | 0 | if (startAngle == endAngle) |
261 | 0 | return; |
262 | | |
263 | 0 | Window win = oOutExtent; |
264 | | |
265 | | // Set the X boundaries for the angles |
266 | 0 | int startAngleX = hIntersect(startAngle, nX, nY, win); |
267 | 0 | int stopAngleX = hIntersect(endAngle, nX, nY, win); |
268 | |
|
269 | 0 | int xmax = nX; |
270 | 0 | if (!rayBetween(startAngle, endAngle, 0)) |
271 | 0 | { |
272 | 0 | xmax = std::max(xmax, startAngleX); |
273 | 0 | xmax = std::max(xmax, stopAngleX); |
274 | | // Add one to xmax since we want one past the stop. [start, stop) |
275 | 0 | oOutExtent.xStop = std::min(oOutExtent.xStop, xmax + 1); |
276 | 0 | } |
277 | |
|
278 | 0 | int xmin = nX; |
279 | 0 | if (!rayBetween(startAngle, endAngle, M_PI)) |
280 | 0 | { |
281 | 0 | xmin = std::min(xmin, startAngleX); |
282 | 0 | xmin = std::min(xmin, stopAngleX); |
283 | 0 | oOutExtent.xStart = std::max(oOutExtent.xStart, xmin); |
284 | 0 | } |
285 | | |
286 | | // Set the Y boundaries for the angles |
287 | 0 | int startAngleY = vIntersect(startAngle, nX, nY, win); |
288 | 0 | int stopAngleY = vIntersect(endAngle, nX, nY, win); |
289 | |
|
290 | 0 | int ymin = nY; |
291 | 0 | if (!rayBetween(startAngle, endAngle, M_PI / 2)) |
292 | 0 | { |
293 | 0 | ymin = std::min(ymin, startAngleY); |
294 | 0 | ymin = std::min(ymin, stopAngleY); |
295 | 0 | oOutExtent.yStart = std::max(oOutExtent.yStart, ymin); |
296 | 0 | } |
297 | 0 | int ymax = nY; |
298 | 0 | if (!rayBetween(startAngle, endAngle, 3 * M_PI / 2)) |
299 | 0 | { |
300 | 0 | ymax = std::max(ymax, startAngleY); |
301 | 0 | ymax = std::max(ymax, stopAngleY); |
302 | | // Add one to ymax since we want one past the stop. [start, stop) |
303 | 0 | oOutExtent.yStop = std::min(oOutExtent.yStop, ymax + 1); |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | | } // unnamed namespace |
308 | | |
309 | 0 | Viewshed::Viewshed(const Options &opts) : oOpts{opts} |
310 | 0 | { |
311 | 0 | } |
312 | | |
313 | 0 | Viewshed::~Viewshed() = default; |
314 | | |
315 | | /// Calculate the extent of the output raster in terms of the input raster and |
316 | | /// save the input raster extent. |
317 | | /// |
318 | | /// @return false on error, true otherwise |
319 | | bool Viewshed::calcExtents(int nX, int nY, const GDALGeoTransform &invGT) |
320 | 0 | { |
321 | | // We start with the assumption that the output size matches the input. |
322 | 0 | oOutExtent.xStop = GDALGetRasterBandXSize(pSrcBand); |
323 | 0 | oOutExtent.yStop = GDALGetRasterBandYSize(pSrcBand); |
324 | |
|
325 | 0 | if (!oOutExtent.contains(nX, nY)) |
326 | 0 | { |
327 | 0 | if (oOpts.startAngle != oOpts.endAngle) |
328 | 0 | { |
329 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
330 | 0 | "Angle masking is not supported with an out-of-raster " |
331 | 0 | "observer."); |
332 | 0 | return false; |
333 | 0 | } |
334 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
335 | 0 | "NOTE: The observer location falls outside of the DEM area"); |
336 | 0 | } |
337 | | |
338 | 0 | constexpr double EPSILON = 1e-8; |
339 | 0 | if (oOpts.maxDistance > 0) |
340 | 0 | { |
341 | | //ABELL - This assumes that the transformation is only a scaling. Should be fixed. |
342 | | // Find the distance in the direction of the transformed unit vector in the X and Y |
343 | | // directions and use those factors to determine the limiting values in the raster space. |
344 | 0 | int nXStart = static_cast<int>( |
345 | 0 | std::floor(nX - invGT[1] * oOpts.maxDistance + EPSILON)); |
346 | 0 | int nXStop = static_cast<int>( |
347 | 0 | std::ceil(nX + invGT[1] * oOpts.maxDistance - EPSILON) + 1); |
348 | | //ABELL - These seem to be wrong. The transform of 1 is no transform, so not |
349 | | // sure why we're adding one in the first case. Really, the transformed distance |
350 | | // should add EPSILON. Not sure what the change should be for a negative transform, |
351 | | // which is what I think is being handled with the 1/0 addition/subtraction. |
352 | 0 | int nYStart = |
353 | 0 | static_cast<int>(std::floor( |
354 | 0 | nY - std::fabs(invGT[5]) * oOpts.maxDistance + EPSILON)) - |
355 | 0 | (invGT[5] > 0 ? 1 : 0); |
356 | 0 | int nYStop = static_cast<int>( |
357 | 0 | std::ceil(nY + std::fabs(invGT[5]) * oOpts.maxDistance - EPSILON) + |
358 | 0 | (invGT[5] < 0 ? 1 : 0)); |
359 | | |
360 | | // If the limits are invalid, set the window size to zero to trigger the error below. |
361 | 0 | if (nXStart >= oOutExtent.xStop || nXStop < 0 || |
362 | 0 | nYStart >= oOutExtent.yStop || nYStop < 0) |
363 | 0 | { |
364 | 0 | oOutExtent = Window(); |
365 | 0 | } |
366 | 0 | else |
367 | 0 | { |
368 | 0 | oOutExtent.xStart = std::max(nXStart, 0); |
369 | 0 | oOutExtent.xStop = std::min(nXStop, oOutExtent.xStop); |
370 | |
|
371 | 0 | oOutExtent.yStart = std::max(nYStart, 0); |
372 | 0 | oOutExtent.yStop = std::min(nYStop, oOutExtent.yStop); |
373 | 0 | } |
374 | 0 | } |
375 | |
|
376 | 0 | if (oOutExtent.xSize() == 0 || oOutExtent.ySize() == 0) |
377 | 0 | { |
378 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
379 | 0 | "Invalid target raster size due to transform " |
380 | 0 | "and/or distance limitation."); |
381 | 0 | return false; |
382 | 0 | } |
383 | | |
384 | 0 | shrinkWindowForAngles(oOutExtent, nX, nY, oOpts.startAngle, oOpts.endAngle); |
385 | | |
386 | | // normalize horizontal index to [ 0, oOutExtent.xSize() ) |
387 | 0 | oCurExtent = oOutExtent; |
388 | 0 | oCurExtent.shiftX(-oOutExtent.xStart); |
389 | |
|
390 | 0 | return true; |
391 | 0 | } |
392 | | |
393 | | /// Compute the viewshed of a raster band. |
394 | | /// |
395 | | /// @param band Pointer to the raster band to be processed. |
396 | | /// @param pfnProgress Pointer to the progress function. Can be null. |
397 | | /// @param pProgressArg Argument passed to the progress function |
398 | | /// @return True on success, false otherwise. |
399 | | bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, |
400 | | void *pProgressArg) |
401 | 0 | { |
402 | 0 | return run(band, nullptr, pfnProgress, pProgressArg); |
403 | 0 | } |
404 | | |
405 | | /// Compute the viewshed of a raster band with . |
406 | | /// |
407 | | /// @param band Pointer to the raster band to be processed. |
408 | | /// @param sdBand Pointer to the standard deviation (SD) raster band to be processed. |
409 | | /// @param pfnProgress Pointer to the progress function. Can be null. |
410 | | /// @param pProgressArg Argument passed to the progress function |
411 | | /// @return True on success, false otherwise. |
412 | | bool Viewshed::run(GDALRasterBandH band, GDALRasterBandH sdBand, |
413 | | GDALProgressFunc pfnProgress, void *pProgressArg) |
414 | 0 | { |
415 | 0 | if (sdBand) |
416 | 0 | pSdBand = static_cast<GDALRasterBand *>(sdBand); |
417 | 0 | pSrcBand = static_cast<GDALRasterBand *>(band); |
418 | |
|
419 | 0 | GDALGeoTransform fwdTransform, invTransform; |
420 | 0 | if (!getTransforms(*pSrcBand, fwdTransform, invTransform)) |
421 | 0 | return false; |
422 | | |
423 | | // calculate observer position |
424 | 0 | double dfX, dfY; |
425 | 0 | invTransform.Apply(oOpts.observer.x, oOpts.observer.y, &dfX, &dfY); |
426 | 0 | if (!GDALIsValueInRange<int>(dfX)) |
427 | 0 | { |
428 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Observer X value out of range"); |
429 | 0 | return false; |
430 | 0 | } |
431 | 0 | if (!GDALIsValueInRange<int>(dfY)) |
432 | 0 | { |
433 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Observer Y value out of range"); |
434 | 0 | return false; |
435 | 0 | } |
436 | 0 | int nX = static_cast<int>(dfX); |
437 | 0 | int nY = static_cast<int>(dfY); |
438 | |
|
439 | 0 | if (oOpts.startAngle < 0 || oOpts.startAngle >= 360) |
440 | 0 | { |
441 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
442 | 0 | "Start angle out of range. Must be [0, 360)."); |
443 | 0 | return false; |
444 | 0 | } |
445 | 0 | if (oOpts.endAngle < 0 || oOpts.endAngle >= 360) |
446 | 0 | { |
447 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
448 | 0 | "End angle out of range. Must be [0, 360)."); |
449 | 0 | return false; |
450 | 0 | } |
451 | 0 | if (oOpts.highPitch > 90) |
452 | 0 | { |
453 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
454 | 0 | "Invalid highPitch. Cannot be greater than 90."); |
455 | 0 | return false; |
456 | 0 | } |
457 | 0 | if (oOpts.lowPitch < -90) |
458 | 0 | { |
459 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
460 | 0 | "Invalid lowPitch. Cannot be less than -90."); |
461 | 0 | return false; |
462 | 0 | } |
463 | 0 | if (oOpts.highPitch <= oOpts.lowPitch) |
464 | 0 | { |
465 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
466 | 0 | "Invalid pitch. highPitch must be > lowPitch"); |
467 | 0 | return false; |
468 | 0 | } |
469 | | |
470 | | // Normalize angle to radians and standard math arrangement. |
471 | 0 | oOpts.startAngle = normalizeAngle(oOpts.startAngle); |
472 | 0 | oOpts.endAngle = normalizeAngle(oOpts.endAngle); |
473 | | |
474 | | // Must calculate extents in order to make the output dataset. |
475 | 0 | if (!calcExtents(nX, nY, invTransform)) |
476 | 0 | return false; |
477 | | |
478 | 0 | poDstDS = createOutputDataset(*pSrcBand, oOpts, oOutExtent); |
479 | 0 | if (!poDstDS) |
480 | 0 | return false; |
481 | | |
482 | | // Create the progress reporter. |
483 | 0 | Progress oProgress(pfnProgress, pProgressArg, oOutExtent.ySize()); |
484 | | |
485 | | // Execute the viewshed algorithm. |
486 | 0 | GDALRasterBand *pDstBand = poDstDS->GetRasterBand(1); |
487 | 0 | if (pSdBand) |
488 | 0 | { |
489 | 0 | ViewshedExecutor executor(*pSrcBand, *pSdBand, *pDstBand, nX, nY, |
490 | 0 | oOutExtent, oCurExtent, oOpts, oProgress, |
491 | 0 | /* emitWarningIfNoData = */ true); |
492 | 0 | executor.run(); |
493 | 0 | } |
494 | 0 | else |
495 | 0 | { |
496 | 0 | ViewshedExecutor executor(*pSrcBand, *pDstBand, nX, nY, oOutExtent, |
497 | 0 | oCurExtent, oOpts, oProgress, |
498 | 0 | /* emitWarningIfNoData = */ true); |
499 | 0 | executor.run(); |
500 | 0 | } |
501 | 0 | oProgress.emit(1); |
502 | 0 | return static_cast<bool>(poDstDS); |
503 | 0 | } |
504 | | |
505 | | // Adjust the coefficient of curvature for non-earth SRS. |
506 | | /// \param curveCoeff Current curve coefficient |
507 | | /// \param hSrcDS Source dataset |
508 | | /// \return Adjusted curve coefficient. |
509 | | double adjustCurveCoeff(double curveCoeff, GDALDatasetH hSrcDS) |
510 | 0 | { |
511 | 0 | const OGRSpatialReference *poSRS = |
512 | 0 | GDALDataset::FromHandle(hSrcDS)->GetSpatialRef(); |
513 | 0 | if (poSRS) |
514 | 0 | { |
515 | 0 | OGRErr eSRSerr; |
516 | 0 | const double dfSemiMajor = poSRS->GetSemiMajor(&eSRSerr); |
517 | 0 | if (eSRSerr != OGRERR_FAILURE && |
518 | 0 | fabs(dfSemiMajor - SRS_WGS84_SEMIMAJOR) > |
519 | 0 | 0.05 * SRS_WGS84_SEMIMAJOR) |
520 | 0 | { |
521 | 0 | curveCoeff = 1.0; |
522 | 0 | CPLDebug("gdal_viewshed", |
523 | 0 | "Using -cc=1.0 as a non-Earth CRS has been detected"); |
524 | 0 | } |
525 | 0 | } |
526 | 0 | return curveCoeff; |
527 | 0 | } |
528 | | |
529 | | #if defined(__clang__) || defined(__GNUC__) |
530 | | #pragma GCC diagnostic ignored "-Wmissing-declarations" |
531 | | #endif |
532 | | |
533 | | void testShrinkWindowForAngles(Window &oOutExtent, int nX, int nY, |
534 | | double startAngle, double endAngle) |
535 | 0 | { |
536 | 0 | shrinkWindowForAngles(oOutExtent, nX, nY, startAngle, endAngle); |
537 | 0 | } |
538 | | |
539 | | } // namespace viewshed |
540 | | } // namespace gdal |