/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 (oOpts.outputMode == viewshed::OutputMode::Normal) |
190 | 0 | { |
191 | 0 | if (!GDALIsValueInRange<uint8_t>(dfOutOfRangeVal)) |
192 | 0 | { |
193 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
194 | 0 | "dfOutOfRangeVal out of range. Must be [0, 255]."); |
195 | 0 | return nullptr; |
196 | 0 | } |
197 | 0 | } |
198 | 0 | oOpts.visibleVal = dfVisibleVal; |
199 | 0 | oOpts.invisibleVal = dfInvisibleVal; |
200 | 0 | oOpts.outOfRangeVal = dfOutOfRangeVal; |
201 | |
|
202 | 0 | const char *pszStartAngle = |
203 | 0 | CSLFetchNameValue(papszExtraOptions, "START_ANGLE"); |
204 | 0 | if (pszStartAngle) |
205 | 0 | oOpts.startAngle = CPLAtof(pszStartAngle); |
206 | |
|
207 | 0 | const char *pszEndAngle = CSLFetchNameValue(papszExtraOptions, "END_ANGLE"); |
208 | 0 | if (pszEndAngle) |
209 | 0 | oOpts.endAngle = CPLAtof(pszEndAngle); |
210 | |
|
211 | 0 | const char *pszLowPitch = CSLFetchNameValue(papszExtraOptions, "LOW_PITCH"); |
212 | 0 | if (pszLowPitch) |
213 | 0 | oOpts.lowPitch = CPLAtof(pszLowPitch); |
214 | |
|
215 | 0 | const char *pszHighPitch = |
216 | 0 | CSLFetchNameValue(papszExtraOptions, "HIGH_PITCH"); |
217 | 0 | if (pszHighPitch) |
218 | 0 | oOpts.highPitch = CPLAtof(pszHighPitch); |
219 | |
|
220 | 0 | gdal::viewshed::Viewshed v(oOpts); |
221 | |
|
222 | 0 | if (!pfnProgress) |
223 | 0 | pfnProgress = GDALDummyProgress; |
224 | 0 | v.run(hBand, pfnProgress, pProgressArg); |
225 | |
|
226 | 0 | return GDALDataset::FromHandle(v.output().release()); |
227 | 0 | } |
228 | | |
229 | | namespace gdal |
230 | | { |
231 | | namespace viewshed |
232 | | { |
233 | | |
234 | | namespace |
235 | | { |
236 | | |
237 | | bool getTransforms(GDALRasterBand &band, GDALGeoTransform &fwdTransform, |
238 | | GDALGeoTransform &revTransform) |
239 | 0 | { |
240 | 0 | band.GetDataset()->GetGeoTransform(fwdTransform); |
241 | 0 | if (!fwdTransform.GetInverse(revTransform)) |
242 | 0 | { |
243 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform"); |
244 | 0 | return false; |
245 | 0 | } |
246 | 0 | return true; |
247 | 0 | } |
248 | | |
249 | | /// Shrink the extent of a window to just cover the slice defined by rays from |
250 | | /// (nX, nY) and [startAngle, endAngle] |
251 | | /// |
252 | | /// @param oOutExtent Window to modify |
253 | | /// @param nX X coordinate of ray endpoint. |
254 | | /// @param nY Y coordinate of ray endpoint. |
255 | | /// @param startAngle Start angle of slice (standard mathematics notion, in radians) |
256 | | /// @param endAngle End angle of slice (standard mathematics notion, in radians) |
257 | | void shrinkWindowForAngles(Window &oOutExtent, int nX, int nY, |
258 | | double startAngle, double endAngle) |
259 | 0 | { |
260 | | /// NOTE: This probably doesn't work when the observer is outside the raster and |
261 | | /// needs to be enhanced for that case. |
262 | |
|
263 | 0 | if (startAngle == endAngle) |
264 | 0 | return; |
265 | | |
266 | 0 | Window win = oOutExtent; |
267 | | |
268 | | // Set the X boundaries for the angles |
269 | 0 | int startAngleX = hIntersect(startAngle, nX, nY, win); |
270 | 0 | int stopAngleX = hIntersect(endAngle, nX, nY, win); |
271 | |
|
272 | 0 | int xmax = nX; |
273 | 0 | if (!rayBetween(startAngle, endAngle, 0)) |
274 | 0 | { |
275 | 0 | xmax = std::max(xmax, startAngleX); |
276 | 0 | xmax = std::max(xmax, stopAngleX); |
277 | | // Add one to xmax since we want one past the stop. [start, stop) |
278 | 0 | oOutExtent.xStop = std::min(oOutExtent.xStop, xmax + 1); |
279 | 0 | } |
280 | |
|
281 | 0 | int xmin = nX; |
282 | 0 | if (!rayBetween(startAngle, endAngle, M_PI)) |
283 | 0 | { |
284 | 0 | xmin = std::min(xmin, startAngleX); |
285 | 0 | xmin = std::min(xmin, stopAngleX); |
286 | 0 | oOutExtent.xStart = std::max(oOutExtent.xStart, xmin); |
287 | 0 | } |
288 | | |
289 | | // Set the Y boundaries for the angles |
290 | 0 | int startAngleY = vIntersect(startAngle, nX, nY, win); |
291 | 0 | int stopAngleY = vIntersect(endAngle, nX, nY, win); |
292 | |
|
293 | 0 | int ymin = nY; |
294 | 0 | if (!rayBetween(startAngle, endAngle, M_PI / 2)) |
295 | 0 | { |
296 | 0 | ymin = std::min(ymin, startAngleY); |
297 | 0 | ymin = std::min(ymin, stopAngleY); |
298 | 0 | oOutExtent.yStart = std::max(oOutExtent.yStart, ymin); |
299 | 0 | } |
300 | 0 | int ymax = nY; |
301 | 0 | if (!rayBetween(startAngle, endAngle, 3 * M_PI / 2)) |
302 | 0 | { |
303 | 0 | ymax = std::max(ymax, startAngleY); |
304 | 0 | ymax = std::max(ymax, stopAngleY); |
305 | | // Add one to ymax since we want one past the stop. [start, stop) |
306 | 0 | oOutExtent.yStop = std::min(oOutExtent.yStop, ymax + 1); |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | | } // unnamed namespace |
311 | | |
312 | 0 | Viewshed::Viewshed(const Options &opts) : oOpts{opts} |
313 | 0 | { |
314 | 0 | } |
315 | | |
316 | 0 | Viewshed::~Viewshed() = default; |
317 | | |
318 | | /// Calculate the extent of the output raster in terms of the input raster and |
319 | | /// save the input raster extent. |
320 | | /// |
321 | | /// @return false on error, true otherwise |
322 | | bool Viewshed::calcExtents(int nX, int nY, const GDALGeoTransform &invGT) |
323 | 0 | { |
324 | | // We start with the assumption that the output size matches the input. |
325 | 0 | oOutExtent.xStop = GDALGetRasterBandXSize(pSrcBand); |
326 | 0 | oOutExtent.yStop = GDALGetRasterBandYSize(pSrcBand); |
327 | |
|
328 | 0 | if (!oOutExtent.contains(nX, nY)) |
329 | 0 | { |
330 | 0 | if (oOpts.startAngle != oOpts.endAngle) |
331 | 0 | { |
332 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
333 | 0 | "Angle masking is not supported with an out-of-raster " |
334 | 0 | "observer."); |
335 | 0 | return false; |
336 | 0 | } |
337 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
338 | 0 | "NOTE: The observer location falls outside of the DEM area"); |
339 | 0 | } |
340 | | |
341 | 0 | constexpr double EPSILON = 1e-8; |
342 | 0 | if (oOpts.maxDistance > 0) |
343 | 0 | { |
344 | | //ABELL - This assumes that the transformation is only a scaling. Should be fixed. |
345 | | // Find the distance in the direction of the transformed unit vector in the X and Y |
346 | | // directions and use those factors to determine the limiting values in the raster space. |
347 | 0 | int nXStart = static_cast<int>( |
348 | 0 | std::floor(nX - invGT[1] * oOpts.maxDistance + EPSILON)); |
349 | 0 | int nXStop = static_cast<int>( |
350 | 0 | std::ceil(nX + invGT[1] * oOpts.maxDistance - EPSILON) + 1); |
351 | | //ABELL - These seem to be wrong. The transform of 1 is no transform, so not |
352 | | // sure why we're adding one in the first case. Really, the transformed distance |
353 | | // should add EPSILON. Not sure what the change should be for a negative transform, |
354 | | // which is what I think is being handled with the 1/0 addition/subtraction. |
355 | 0 | int nYStart = |
356 | 0 | static_cast<int>(std::floor( |
357 | 0 | nY - std::fabs(invGT[5]) * oOpts.maxDistance + EPSILON)) - |
358 | 0 | (invGT[5] > 0 ? 1 : 0); |
359 | 0 | int nYStop = static_cast<int>( |
360 | 0 | std::ceil(nY + std::fabs(invGT[5]) * oOpts.maxDistance - EPSILON) + |
361 | 0 | (invGT[5] < 0 ? 1 : 0)); |
362 | | |
363 | | // If the limits are invalid, set the window size to zero to trigger the error below. |
364 | 0 | if (nXStart >= oOutExtent.xStop || nXStop < 0 || |
365 | 0 | nYStart >= oOutExtent.yStop || nYStop < 0) |
366 | 0 | { |
367 | 0 | oOutExtent = Window(); |
368 | 0 | } |
369 | 0 | else |
370 | 0 | { |
371 | 0 | oOutExtent.xStart = std::max(nXStart, 0); |
372 | 0 | oOutExtent.xStop = std::min(nXStop, oOutExtent.xStop); |
373 | |
|
374 | 0 | oOutExtent.yStart = std::max(nYStart, 0); |
375 | 0 | oOutExtent.yStop = std::min(nYStop, oOutExtent.yStop); |
376 | 0 | } |
377 | 0 | } |
378 | |
|
379 | 0 | if (oOutExtent.xSize() == 0 || oOutExtent.ySize() == 0) |
380 | 0 | { |
381 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
382 | 0 | "Invalid target raster size due to transform " |
383 | 0 | "and/or distance limitation."); |
384 | 0 | return false; |
385 | 0 | } |
386 | | |
387 | 0 | shrinkWindowForAngles(oOutExtent, nX, nY, oOpts.startAngle, oOpts.endAngle); |
388 | | |
389 | | // normalize horizontal index to [ 0, oOutExtent.xSize() ) |
390 | 0 | oCurExtent = oOutExtent; |
391 | 0 | oCurExtent.shiftX(-oOutExtent.xStart); |
392 | |
|
393 | 0 | return true; |
394 | 0 | } |
395 | | |
396 | | /// Compute the viewshed of a raster band. |
397 | | /// |
398 | | /// @param band Pointer to the raster band to be processed. |
399 | | /// @param pfnProgress Pointer to the progress function. Can be null. |
400 | | /// @param pProgressArg Argument passed to the progress function |
401 | | /// @return True on success, false otherwise. |
402 | | bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, |
403 | | void *pProgressArg) |
404 | 0 | { |
405 | 0 | return run(band, nullptr, pfnProgress, pProgressArg); |
406 | 0 | } |
407 | | |
408 | | /// Compute the viewshed of a raster band with . |
409 | | /// |
410 | | /// @param band Pointer to the raster band to be processed. |
411 | | /// @param sdBand Pointer to the standard deviation (SD) raster band to be processed. |
412 | | /// @param pfnProgress Pointer to the progress function. Can be null. |
413 | | /// @param pProgressArg Argument passed to the progress function |
414 | | /// @return True on success, false otherwise. |
415 | | bool Viewshed::run(GDALRasterBandH band, GDALRasterBandH sdBand, |
416 | | GDALProgressFunc pfnProgress, void *pProgressArg) |
417 | 0 | { |
418 | 0 | if (sdBand) |
419 | 0 | pSdBand = static_cast<GDALRasterBand *>(sdBand); |
420 | 0 | pSrcBand = static_cast<GDALRasterBand *>(band); |
421 | |
|
422 | 0 | GDALGeoTransform fwdTransform, invTransform; |
423 | 0 | if (!getTransforms(*pSrcBand, fwdTransform, invTransform)) |
424 | 0 | return false; |
425 | | |
426 | | // calculate observer position |
427 | 0 | double dfX, dfY; |
428 | 0 | invTransform.Apply(oOpts.observer.x, oOpts.observer.y, &dfX, &dfY); |
429 | 0 | if (!GDALIsValueInRange<int>(dfX)) |
430 | 0 | { |
431 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Observer X value out of range"); |
432 | 0 | return false; |
433 | 0 | } |
434 | 0 | if (!GDALIsValueInRange<int>(dfY)) |
435 | 0 | { |
436 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Observer Y value out of range"); |
437 | 0 | return false; |
438 | 0 | } |
439 | 0 | int nX = static_cast<int>(dfX); |
440 | 0 | int nY = static_cast<int>(dfY); |
441 | |
|
442 | 0 | if (oOpts.startAngle < 0 || oOpts.startAngle >= 360) |
443 | 0 | { |
444 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
445 | 0 | "Start angle out of range. Must be [0, 360)."); |
446 | 0 | return false; |
447 | 0 | } |
448 | 0 | if (oOpts.endAngle < 0 || oOpts.endAngle >= 360) |
449 | 0 | { |
450 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
451 | 0 | "End angle out of range. Must be [0, 360)."); |
452 | 0 | return false; |
453 | 0 | } |
454 | 0 | if (oOpts.highPitch > 90) |
455 | 0 | { |
456 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
457 | 0 | "Invalid highPitch. Cannot be greater than 90."); |
458 | 0 | return false; |
459 | 0 | } |
460 | 0 | if (oOpts.lowPitch < -90) |
461 | 0 | { |
462 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
463 | 0 | "Invalid lowPitch. Cannot be less than -90."); |
464 | 0 | return false; |
465 | 0 | } |
466 | 0 | if (oOpts.highPitch <= oOpts.lowPitch) |
467 | 0 | { |
468 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
469 | 0 | "Invalid pitch. highPitch must be > lowPitch"); |
470 | 0 | return false; |
471 | 0 | } |
472 | | |
473 | | // Normalize angle to radians and standard math arrangement. |
474 | 0 | oOpts.startAngle = normalizeAngle(oOpts.startAngle); |
475 | 0 | oOpts.endAngle = normalizeAngle(oOpts.endAngle); |
476 | | |
477 | | // Must calculate extents in order to make the output dataset. |
478 | 0 | if (!calcExtents(nX, nY, invTransform)) |
479 | 0 | return false; |
480 | | |
481 | 0 | poDstDS = createOutputDataset(*pSrcBand, oOpts, oOutExtent); |
482 | 0 | if (!poDstDS) |
483 | 0 | return false; |
484 | | |
485 | | // Create the progress reporter. |
486 | 0 | Progress oProgress(pfnProgress, pProgressArg, oOutExtent.ySize()); |
487 | | |
488 | | // Execute the viewshed algorithm. |
489 | 0 | GDALRasterBand *pDstBand = poDstDS->GetRasterBand(1); |
490 | 0 | if (pSdBand) |
491 | 0 | { |
492 | 0 | ViewshedExecutor executor(*pSrcBand, *pSdBand, *pDstBand, nX, nY, |
493 | 0 | oOutExtent, oCurExtent, oOpts, oProgress, |
494 | 0 | /* emitWarningIfNoData = */ true); |
495 | 0 | executor.run(); |
496 | 0 | } |
497 | 0 | else |
498 | 0 | { |
499 | 0 | ViewshedExecutor executor(*pSrcBand, *pDstBand, nX, nY, oOutExtent, |
500 | 0 | oCurExtent, oOpts, oProgress, |
501 | 0 | /* emitWarningIfNoData = */ true); |
502 | 0 | executor.run(); |
503 | 0 | } |
504 | 0 | oProgress.emit(1); |
505 | 0 | return static_cast<bool>(poDstDS); |
506 | 0 | } |
507 | | |
508 | | // Adjust the coefficient of curvature for non-earth SRS. |
509 | | /// \param curveCoeff Current curve coefficient |
510 | | /// \param hSrcDS Source dataset |
511 | | /// \return Adjusted curve coefficient. |
512 | | double adjustCurveCoeff(double curveCoeff, GDALDatasetH hSrcDS) |
513 | 0 | { |
514 | 0 | const OGRSpatialReference *poSRS = |
515 | 0 | GDALDataset::FromHandle(hSrcDS)->GetSpatialRef(); |
516 | 0 | if (poSRS) |
517 | 0 | { |
518 | 0 | OGRErr eSRSerr; |
519 | 0 | const double dfSemiMajor = poSRS->GetSemiMajor(&eSRSerr); |
520 | 0 | if (eSRSerr != OGRERR_FAILURE && |
521 | 0 | fabs(dfSemiMajor - SRS_WGS84_SEMIMAJOR) > |
522 | 0 | 0.05 * SRS_WGS84_SEMIMAJOR) |
523 | 0 | { |
524 | 0 | curveCoeff = 1.0; |
525 | 0 | CPLDebug("gdal_viewshed", |
526 | 0 | "Using -cc=1.0 as a non-Earth CRS has been detected"); |
527 | 0 | } |
528 | 0 | } |
529 | 0 | return curveCoeff; |
530 | 0 | } |
531 | | |
532 | | #if defined(__clang__) || defined(__GNUC__) |
533 | | #pragma GCC diagnostic ignored "-Wmissing-declarations" |
534 | | #endif |
535 | | |
536 | | void testShrinkWindowForAngles(Window &oOutExtent, int nX, int nY, |
537 | | double startAngle, double endAngle) |
538 | 0 | { |
539 | 0 | shrinkWindowForAngles(oOutExtent, nX, nY, startAngle, endAngle); |
540 | 0 | } |
541 | | |
542 | | } // namespace viewshed |
543 | | } // namespace gdal |