/src/gdal/alg/viewshed/viewshed_executor.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 <atomic> |
16 | | #include <cassert> |
17 | | #include <cmath> |
18 | | #include <limits> |
19 | | |
20 | | #include "viewshed_executor.h" |
21 | | #include "progress.h" |
22 | | #include "util.h" |
23 | | |
24 | | // cppcheck-suppress-begin knownConditionTrueFalse |
25 | | namespace gdal |
26 | | { |
27 | | namespace viewshed |
28 | | { |
29 | | |
30 | | //! @cond Doxygen_Suppress |
31 | | CPLErr DummyBand::IReadBlock(int, int, void *) |
32 | 0 | { |
33 | 0 | return CE_Failure; |
34 | 0 | } |
35 | | |
36 | | //! @endcond |
37 | | |
38 | | namespace |
39 | | { |
40 | | |
41 | | /// Determines whether a value is a valid intersection coordinate. |
42 | | /// @param i Value to test. |
43 | | /// @return True if the value doesn't represent an invalid intersection. |
44 | | bool valid(int i) |
45 | 0 | { |
46 | 0 | return i != INVALID_ISECT; |
47 | 0 | } |
48 | | |
49 | | /// Determines whether a value is an invalid intersection coordinate. |
50 | | /// @param i Value to test. |
51 | | /// @return True if the value represents an invalid intersection. |
52 | | bool invalid(int i) |
53 | 0 | { |
54 | 0 | return !valid(i); |
55 | 0 | } |
56 | | |
57 | | /// Calculate the height at nDistance units along a line through the origin given the height |
58 | | /// at nDistance - 1 units along the line. |
59 | | /// \param nDistance Distance along the line for the target point. |
60 | | /// \param Za Height at the line one unit previous to the target point. |
61 | | double CalcHeightLine(int nDistance, double Za) |
62 | 0 | { |
63 | 0 | assert(nDistance > 1); |
64 | 0 | return Za * nDistance / (nDistance - 1); |
65 | 0 | } |
66 | | |
67 | | /// Calculate the height at nDistance units along a line through the origin given the height |
68 | | /// at nDistance - 1 units along the line. |
69 | | /// \param nDistance Distance along the line for the target point. |
70 | | /// \param Zcur Height at the line at the target point. |
71 | | /// \param Za Height at the line one unit previous to the target point. |
72 | | double CalcHeightLine(int nDistance, double Zcur, double Za) |
73 | 0 | { |
74 | 0 | nDistance = std::abs(nDistance); |
75 | 0 | assert(nDistance > 0); |
76 | 0 | if (nDistance == 1) |
77 | 0 | return Zcur; |
78 | 0 | return CalcHeightLine(nDistance, Za); |
79 | 0 | } |
80 | | |
81 | | // Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) |
82 | | // and passing through the line connecting (i - 1, j, Za) and (i, j - 1, Zb). |
83 | | // In other words, the origin and the two points form a plane and we're calculating Zc |
84 | | // of the point (i, j, Zc), also on the plane. |
85 | | double CalcHeightDiagonal(int i, int j, double Za, double Zb) |
86 | 0 | { |
87 | 0 | return (Za * i + Zb * j) / (i + j - 1); |
88 | 0 | } |
89 | | |
90 | | // Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) |
91 | | // and through the line connecting (i -1, j - 1, Za) and (i - 1, j, Zb). In other words, |
92 | | // the origin and the other two points form a plane and we're calculating Zc of the |
93 | | // point (i, j, Zc), also on the plane. |
94 | | double CalcHeightEdge(int i, int j, double Za, double Zb) |
95 | 0 | { |
96 | 0 | assert(i != j); |
97 | 0 | return (Za * i + Zb * (j - i)) / (j - 1); |
98 | 0 | } |
99 | | |
100 | | double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset, |
101 | | double dfThisPrev, double dfLast, |
102 | | [[maybe_unused]] double dfLastPrev) |
103 | 0 | { |
104 | 0 | return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast); |
105 | 0 | } |
106 | | |
107 | | double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, |
108 | | double dfLastPrev) |
109 | 0 | { |
110 | 0 | if (nXOffset >= nYOffset) |
111 | 0 | return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev); |
112 | 0 | else |
113 | 0 | return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast); |
114 | 0 | } |
115 | | |
116 | | double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, |
117 | | double dfLastPrev) |
118 | 0 | { |
119 | 0 | double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); |
120 | 0 | double dfDiagonal = |
121 | 0 | doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); |
122 | 0 | return std::min(dfEdge, dfDiagonal); |
123 | 0 | } |
124 | | |
125 | | double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, |
126 | | double dfLastPrev) |
127 | 0 | { |
128 | 0 | double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); |
129 | 0 | double dfDiagonal = |
130 | 0 | doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); |
131 | 0 | return std::max(dfEdge, dfDiagonal); |
132 | 0 | } |
133 | | |
134 | | } // unnamed namespace |
135 | | |
136 | | /// Constructor - the viewshed algorithm executor |
137 | | /// @param srcBand Source raster band |
138 | | /// @param sdBand Standard-deviation raster band |
139 | | /// @param dstBand Destination raster band |
140 | | /// @param nX X position of observer |
141 | | /// @param nY Y position of observer |
142 | | /// @param outExtent Extent of output raster (relative to input) |
143 | | /// @param curExtent Extent of active raster. |
144 | | /// @param opts Configuration options. |
145 | | /// @param progress Reference to the progress tracker. |
146 | | /// @param emitWarningIfNoData Whether a warning must be emitted if an input |
147 | | /// pixel is at the nodata value. |
148 | | ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand, |
149 | | GDALRasterBand &sdBand, |
150 | | GDALRasterBand &dstBand, int nX, int nY, |
151 | | const Window &outExtent, |
152 | | const Window &curExtent, const Options &opts, |
153 | | Progress &progress, bool emitWarningIfNoData) |
154 | 0 | : m_pool(4), m_dummyBand(), m_srcBand(srcBand), m_sdBand(sdBand), |
155 | 0 | m_dstBand(dstBand), |
156 | | // If the standard deviation band isn't a dummy band, we're in SD mode. |
157 | 0 | m_hasSdBand(dynamic_cast<DummyBand *>(&m_sdBand) == nullptr), |
158 | 0 | m_emitWarningIfNoData(emitWarningIfNoData), oOutExtent(outExtent), |
159 | 0 | oCurExtent(curExtent), m_nX(nX - oOutExtent.xStart), m_nY(nY), |
160 | 0 | oOpts(opts), oProgress(progress), |
161 | 0 | m_dfMinDistance2(opts.minDistance * opts.minDistance), |
162 | 0 | m_dfMaxDistance2(opts.maxDistance * opts.maxDistance) |
163 | 0 | { |
164 | 0 | if (m_dfMaxDistance2 == 0) |
165 | 0 | m_dfMaxDistance2 = std::numeric_limits<double>::max(); |
166 | 0 | if (opts.lowPitch != -90.0) |
167 | 0 | m_lowTanPitch = std::tan(oOpts.lowPitch * (2 * M_PI / 360.0)); |
168 | 0 | if (opts.highPitch != 90.0) |
169 | 0 | m_highTanPitch = std::tan(oOpts.highPitch * (2 * M_PI / 360.0)); |
170 | 0 | m_srcBand.GetDataset()->GetGeoTransform(m_gt); |
171 | 0 | int hasNoData = false; |
172 | 0 | m_noDataValue = m_srcBand.GetNoDataValue(&hasNoData); |
173 | 0 | m_hasNoData = hasNoData; |
174 | 0 | } |
175 | | |
176 | | /// Constructor - the viewshed algorithm executor |
177 | | /// @param srcBand Source raster band |
178 | | /// @param dstBand Destination raster band |
179 | | /// @param nX X position of observer |
180 | | /// @param nY Y position of observer |
181 | | /// @param outExtent Extent of output raster (relative to input) |
182 | | /// @param curExtent Extent of active raster. |
183 | | /// @param opts Configuration options. |
184 | | /// @param progress Reference to the progress tracker. |
185 | | /// @param emitWarningIfNoData Whether a warning must be emitted if an input |
186 | | /// pixel is at the nodata value. |
187 | | ViewshedExecutor::ViewshedExecutor(GDALRasterBand &srcBand, |
188 | | GDALRasterBand &dstBand, int nX, int nY, |
189 | | const Window &outExtent, |
190 | | const Window &curExtent, const Options &opts, |
191 | | Progress &progress, bool emitWarningIfNoData) |
192 | 0 | : ViewshedExecutor(srcBand, m_dummyBand, dstBand, nX, nY, outExtent, |
193 | 0 | curExtent, opts, progress, emitWarningIfNoData) |
194 | 0 | { |
195 | 0 | } |
196 | | |
197 | | // calculate the height adjustment factor. |
198 | | double ViewshedExecutor::calcHeightAdjFactor() |
199 | 0 | { |
200 | 0 | std::lock_guard g(oMutex); |
201 | |
|
202 | 0 | const OGRSpatialReference *poDstSRS = |
203 | 0 | m_dstBand.GetDataset()->GetSpatialRef(); |
204 | |
|
205 | 0 | if (poDstSRS) |
206 | 0 | { |
207 | 0 | OGRErr eSRSerr; |
208 | | |
209 | | // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR |
210 | 0 | double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr); |
211 | | |
212 | | /* If we fetched the axis from the SRS, use it */ |
213 | 0 | if (eSRSerr != OGRERR_FAILURE) |
214 | 0 | return oOpts.curveCoeff / (dfSemiMajor * 2.0); |
215 | | |
216 | 0 | CPLDebug("GDALViewshedGenerate", |
217 | 0 | "Unable to fetch SemiMajor axis from spatial reference"); |
218 | 0 | } |
219 | 0 | return 0; |
220 | 0 | } |
221 | | |
222 | | /// Set the output Z value depending on the observable height and computation mode |
223 | | /// in normal mode. |
224 | | /// |
225 | | /// dfResult Reference to the result cell |
226 | | /// dfCellVal Reference to the current cell height. Replace with observable height. |
227 | | /// dfZ Minimum observable height at cell. |
228 | | void ViewshedExecutor::setOutputNormal(Lines &lines, int pos, double dfZ) |
229 | 0 | { |
230 | 0 | double &cur = lines.cur[pos]; |
231 | 0 | double &result = lines.result[pos]; |
232 | |
|
233 | 0 | if (oOpts.outputMode != OutputMode::Normal) |
234 | 0 | { |
235 | 0 | double adjustment = dfZ - cur; |
236 | 0 | if (adjustment > 0) |
237 | 0 | result += adjustment; |
238 | 0 | } |
239 | 0 | else |
240 | 0 | { |
241 | 0 | double cellHeight = cur + oOpts.targetHeight; |
242 | 0 | result = (cellHeight < dfZ) ? oOpts.invisibleVal : oOpts.visibleVal; |
243 | 0 | } |
244 | 0 | cur = std::max(cur, dfZ); |
245 | 0 | } |
246 | | |
247 | | /// Set the output Z value depending on the observable height and computation when |
248 | | /// making an standard deviation pass. |
249 | | /// |
250 | | /// dfResult Reference to the result cell |
251 | | /// dfCellVal Reference to the current cell height. Replace with observable height. |
252 | | /// dfZ Minimum observable height at cell. |
253 | | void ViewshedExecutor::setOutputSd(Lines &lines, int pos, double dfZ) |
254 | 0 | { |
255 | 0 | double &cur = lines.cur[pos]; |
256 | 0 | double &result = lines.result[pos]; |
257 | 0 | double &sd = lines.sd[pos]; |
258 | |
|
259 | 0 | assert(oOpts.outputMode == OutputMode::Normal); |
260 | 0 | if (result == oOpts.invisibleVal) |
261 | 0 | { |
262 | 0 | double cellHeight = cur + oOpts.targetHeight; |
263 | 0 | if (cellHeight > dfZ) |
264 | 0 | result = oOpts.maybeVisibleVal; |
265 | 0 | } |
266 | |
|
267 | 0 | if (sd <= 1) |
268 | 0 | cur = std::max(dfZ, cur); |
269 | 0 | else |
270 | 0 | cur = dfZ; |
271 | 0 | } |
272 | | |
273 | | /// Read a line of raster data. |
274 | | /// |
275 | | /// @param nLine Line number to read. |
276 | | /// @param lines Raster line to fill. |
277 | | /// @return Success or failure. |
278 | | bool ViewshedExecutor::readLine(int nLine, Lines &lines) |
279 | 0 | { |
280 | 0 | std::lock_guard g(iMutex); |
281 | |
|
282 | 0 | if (GDALRasterIO(&m_srcBand, GF_Read, oOutExtent.xStart, nLine, |
283 | 0 | oOutExtent.xSize(), 1, lines.cur.data(), |
284 | 0 | oOutExtent.xSize(), 1, GDT_Float64, 0, 0)) |
285 | 0 | { |
286 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
287 | 0 | "RasterIO error when reading DEM at position (%d,%d), " |
288 | 0 | "size (%d,%d)", |
289 | 0 | oOutExtent.xStart, nLine, oOutExtent.xSize(), 1); |
290 | 0 | return false; |
291 | 0 | } |
292 | | |
293 | 0 | if (sdMode()) |
294 | 0 | { |
295 | 0 | double nodata = m_sdBand.GetNoDataValue(); |
296 | 0 | CPLErr sdStatus = m_sdBand.RasterIO( |
297 | 0 | GF_Read, oOutExtent.xStart, nLine, oOutExtent.xSize(), 1, |
298 | 0 | lines.sd.data(), oOutExtent.xSize(), 1, GDT_Float64, 0, 0, nullptr); |
299 | 0 | if (sdStatus != CE_None) |
300 | 0 | { |
301 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
302 | 0 | "RasterIO error when reading standard deviation band at " |
303 | 0 | "position (%d,%d), " |
304 | 0 | "size (%d,%d)", |
305 | 0 | oOutExtent.xStart, nLine, oOutExtent.xSize(), 1); |
306 | 0 | return false; |
307 | 0 | } |
308 | | // Set the standard deviation to 1000 if nodata is found. |
309 | 0 | for (size_t i = 0; i < lines.sd.size(); ++i) |
310 | 0 | if (lines.sd[i] == nodata) |
311 | 0 | lines.sd[i] = 1000.0; |
312 | 0 | } |
313 | | |
314 | | // Initialize the result line. |
315 | | // In DEM mode the base is the pre-adjustment value. In ground mode the base is zero. |
316 | 0 | if (oOpts.outputMode == OutputMode::DEM) |
317 | 0 | lines.result = lines.cur; |
318 | 0 | else if (oOpts.outputMode == OutputMode::Ground) |
319 | 0 | std::fill(lines.result.begin(), lines.result.end(), 0); |
320 | |
|
321 | 0 | return true; |
322 | 0 | } |
323 | | |
324 | | /// Write an output line of either visibility or height data. |
325 | | /// |
326 | | /// @param nLine Line number being written. |
327 | | /// @param vResult Result line to write. |
328 | | /// @return True on success, false otherwise. |
329 | | bool ViewshedExecutor::writeLine(int nLine, std::vector<double> &vResult) |
330 | 0 | { |
331 | | // GDALRasterIO isn't thread-safe. |
332 | 0 | std::lock_guard g(oMutex); |
333 | |
|
334 | 0 | if (GDALRasterIO(&m_dstBand, GF_Write, 0, nLine - oOutExtent.yStart, |
335 | 0 | oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(), |
336 | 0 | 1, GDT_Float64, 0, 0)) |
337 | 0 | { |
338 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
339 | 0 | "RasterIO error when writing target raster at position " |
340 | 0 | "(%d,%d), size (%d,%d)", |
341 | 0 | 0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1); |
342 | 0 | return false; |
343 | 0 | } |
344 | 0 | return true; |
345 | 0 | } |
346 | | |
347 | | /// Adjust the height of the line of data by the observer height and the curvature of the |
348 | | /// earth. |
349 | | /// |
350 | | /// @param nYOffset Y offset of the line being adjusted. |
351 | | /// @param lines Raster lines to adjust. |
352 | | /// @return Processing limits of the line based on min/max distance. |
353 | | LineLimits ViewshedExecutor::adjustHeight(int nYOffset, Lines &lines) |
354 | 0 | { |
355 | 0 | LineLimits ll(0, m_nX + 1, m_nX + 1, oCurExtent.xSize()); |
356 | | |
357 | | // Find the starting point in the raster (m_nX may be outside) |
358 | 0 | int nXStart = oCurExtent.clampX(m_nX); |
359 | |
|
360 | 0 | const auto CheckNoData = [this](double val) |
361 | 0 | { |
362 | 0 | if (!m_hasFoundNoData && |
363 | 0 | ((m_hasNoData && val == m_noDataValue) || std::isnan(val))) |
364 | 0 | { |
365 | 0 | m_hasFoundNoData = true; |
366 | 0 | if (m_emitWarningIfNoData) |
367 | 0 | { |
368 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
369 | 0 | "Nodata value found in input DEM. Output will be " |
370 | 0 | "likely incorrect"); |
371 | 0 | } |
372 | 0 | } |
373 | 0 | }; |
374 | | |
375 | | // If there is a height adjustment factor other than zero or a max distance, |
376 | | // calculate the adjusted height of the cell, stopping if we've exceeded the max |
377 | | // distance. |
378 | 0 | if (static_cast<bool>(m_dfHeightAdjFactor) || oOpts.pitchMasking() || |
379 | 0 | m_dfMaxDistance2 > 0 || m_dfMinDistance2 > 0) |
380 | 0 | { |
381 | | // Hoist invariants from the loops. |
382 | 0 | const double dfLineX = m_gt[2] * nYOffset; |
383 | 0 | const double dfLineY = m_gt[5] * nYOffset; |
384 | | |
385 | | // Go left |
386 | 0 | double *pdfHeight = lines.cur.data() + nXStart; |
387 | 0 | for (int nXOffset = nXStart - m_nX; nXOffset >= -m_nX; |
388 | 0 | nXOffset--, pdfHeight--) |
389 | 0 | { |
390 | 0 | double dfX = m_gt[1] * nXOffset + dfLineX; |
391 | 0 | double dfY = m_gt[4] * nXOffset + dfLineY; |
392 | 0 | double dfR2 = dfX * dfX + dfY * dfY; |
393 | |
|
394 | 0 | if (dfR2 < m_dfMinDistance2) |
395 | 0 | ll.leftMin--; |
396 | 0 | else if (dfR2 > m_dfMaxDistance2) |
397 | 0 | { |
398 | 0 | ll.left = nXOffset + m_nX + 1; |
399 | 0 | break; |
400 | 0 | } |
401 | | |
402 | 0 | CheckNoData(*pdfHeight); |
403 | 0 | *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver; |
404 | 0 | if (oOpts.pitchMasking()) |
405 | 0 | calcPitchMask(*pdfHeight, std::sqrt(dfR2), |
406 | 0 | lines.result[m_nX + nXOffset], |
407 | 0 | lines.pitchMask[m_nX + nXOffset]); |
408 | 0 | } |
409 | | |
410 | | // Go right. |
411 | 0 | pdfHeight = lines.cur.data() + nXStart + 1; |
412 | 0 | for (int nXOffset = nXStart - m_nX + 1; |
413 | 0 | nXOffset < oCurExtent.xSize() - m_nX; nXOffset++, pdfHeight++) |
414 | 0 | { |
415 | 0 | double dfX = m_gt[1] * nXOffset + dfLineX; |
416 | 0 | double dfY = m_gt[4] * nXOffset + dfLineY; |
417 | 0 | double dfR2 = dfX * dfX + dfY * dfY; |
418 | |
|
419 | 0 | if (dfR2 < m_dfMinDistance2) |
420 | 0 | ll.rightMin++; |
421 | 0 | else if (dfR2 > m_dfMaxDistance2) |
422 | 0 | { |
423 | 0 | ll.right = nXOffset + m_nX; |
424 | 0 | break; |
425 | 0 | } |
426 | | |
427 | 0 | CheckNoData(*pdfHeight); |
428 | 0 | *pdfHeight -= m_dfHeightAdjFactor * dfR2 + m_dfZObserver; |
429 | 0 | if (oOpts.pitchMasking()) |
430 | 0 | calcPitchMask(*pdfHeight, std::sqrt(dfR2), |
431 | 0 | lines.result[m_nX + nXOffset], |
432 | 0 | lines.pitchMask[m_nX + nXOffset]); |
433 | 0 | } |
434 | 0 | } |
435 | 0 | else |
436 | 0 | { |
437 | | // No curvature adjustment. Just normalize for the observer height. |
438 | 0 | double *pdfHeight = lines.cur.data(); |
439 | 0 | for (int i = 0; i < oCurExtent.xSize(); ++i) |
440 | 0 | { |
441 | 0 | CheckNoData(*pdfHeight); |
442 | 0 | *pdfHeight -= m_dfZObserver; |
443 | 0 | pdfHeight++; |
444 | 0 | } |
445 | 0 | } |
446 | 0 | return ll; |
447 | 0 | } |
448 | | |
449 | | /// Calculate the pitch masking value to apply after running the viewshed algorithm. |
450 | | /// |
451 | | /// @param dfZ Adjusted input height. |
452 | | /// @param dfDist Distance from observer to cell. |
453 | | /// @param dfResult Result value to which adjustment may be added. |
454 | | /// @param maskVal Output mask value. |
455 | | void ViewshedExecutor::calcPitchMask(double dfZ, double dfDist, double dfResult, |
456 | | double &maskVal) |
457 | 0 | { |
458 | 0 | if (oOpts.lowPitchMasking()) |
459 | 0 | { |
460 | 0 | double dfZMask = dfDist * m_lowTanPitch; |
461 | 0 | double adjustment = dfZMask - dfZ; |
462 | 0 | if (adjustment > 0) |
463 | 0 | { |
464 | 0 | maskVal = (oOpts.outputMode == OutputMode::Normal |
465 | 0 | ? std::numeric_limits<double>::infinity() |
466 | 0 | : adjustment + dfResult); |
467 | 0 | return; |
468 | 0 | } |
469 | 0 | } |
470 | 0 | if (oOpts.highPitchMasking()) |
471 | 0 | { |
472 | 0 | double dfZMask = dfDist * m_highTanPitch; |
473 | 0 | if (dfZ > dfZMask) |
474 | 0 | maskVal = std::numeric_limits<double>::infinity(); |
475 | 0 | } |
476 | 0 | } |
477 | | |
478 | | /// Process the first line (the one with the Y coordinate the same as the observer). |
479 | | /// |
480 | | /// @param lines Raster lines to process. |
481 | | /// @return True on success, false otherwise. |
482 | | bool ViewshedExecutor::processFirstLine(Lines &lines) |
483 | 0 | { |
484 | 0 | int nLine = oOutExtent.clampY(m_nY); |
485 | 0 | int nYOffset = nLine - m_nY; |
486 | |
|
487 | 0 | if (!readLine(nLine, lines)) |
488 | 0 | return false; |
489 | | |
490 | | // If the observer is outside of the raster, take the specified value as the Z height, |
491 | | // otherwise, take it as an offset from the raster height at that location. |
492 | 0 | m_dfZObserver = oOpts.observer.z; |
493 | 0 | if (oCurExtent.containsX(m_nX)) |
494 | 0 | m_dfZObserver += lines.cur[m_nX]; |
495 | |
|
496 | 0 | LineLimits ll = adjustHeight(nYOffset, lines); |
497 | |
|
498 | 0 | std::vector<double> savedInput; |
499 | 0 | if (sdMode()) |
500 | 0 | savedInput = lines.cur; |
501 | |
|
502 | 0 | if (oCurExtent.containsX(m_nX)) |
503 | 0 | { |
504 | 0 | if (ll.leftMin != ll.rightMin) |
505 | 0 | lines.result[m_nX] = oOpts.outOfRangeVal; |
506 | 0 | else if (oOpts.outputMode == OutputMode::Normal) |
507 | 0 | lines.result[m_nX] = oOpts.visibleVal; |
508 | 0 | } |
509 | |
|
510 | 0 | auto process = [this, &ll, &lines](bool sdCalc) |
511 | 0 | { |
512 | 0 | if (!oCurExtent.containsY(m_nY)) |
513 | 0 | processFirstLineTopOrBottom(ll, lines); |
514 | 0 | else |
515 | 0 | { |
516 | 0 | CPLJobQueuePtr pQueue = m_pool.CreateJobQueue(); |
517 | 0 | pQueue->SubmitJob([&]() |
518 | 0 | { processFirstLineLeft(ll, lines, sdCalc); }); |
519 | 0 | pQueue->SubmitJob([&]() |
520 | 0 | { processFirstLineRight(ll, lines, sdCalc); }); |
521 | 0 | pQueue->WaitCompletion(); |
522 | 0 | } |
523 | 0 | }; |
524 | |
|
525 | 0 | process(false); |
526 | 0 | lines.prev = lines.cur; |
527 | 0 | if (sdMode()) |
528 | 0 | { |
529 | 0 | lines.cur = std::move(savedInput); |
530 | 0 | process(true); |
531 | 0 | lines.prevTmp = lines.cur; |
532 | 0 | } |
533 | |
|
534 | 0 | if (oOpts.pitchMasking()) |
535 | 0 | applyPitchMask(lines.result, lines.pitchMask); |
536 | 0 | if (!writeLine(nLine, lines.result)) |
537 | 0 | return false; |
538 | | |
539 | 0 | return oProgress.lineComplete(); |
540 | 0 | } |
541 | | |
542 | | /// Set the pitch masked value into the result vector when applicable. |
543 | | /// |
544 | | /// @param vResult Result vector. |
545 | | /// @param vPitchMaskVal Pitch mask values (nan is no masking, inf is out-of-range, else |
546 | | /// actual value). |
547 | | void ViewshedExecutor::applyPitchMask(std::vector<double> &vResult, |
548 | | const std::vector<double> &vPitchMaskVal) |
549 | 0 | { |
550 | 0 | for (size_t i = 0; i < vResult.size(); ++i) |
551 | 0 | { |
552 | 0 | if (std::isnan(vPitchMaskVal[i])) |
553 | 0 | continue; |
554 | 0 | if (std::isinf(vPitchMaskVal[i])) |
555 | 0 | vResult[i] = oOpts.outOfRangeVal; |
556 | 0 | else |
557 | 0 | vResult[i] = vPitchMaskVal[i]; |
558 | 0 | } |
559 | 0 | } |
560 | | |
561 | | /// If the observer is above or below the raster, set all cells in the first line near the |
562 | | /// observer as observable provided they're in range. Mark cells out of range as such. |
563 | | /// @param ll Line limits for processing. |
564 | | /// @param lines Raster lines to process. |
565 | | void ViewshedExecutor::processFirstLineTopOrBottom(const LineLimits &ll, |
566 | | Lines &lines) |
567 | 0 | { |
568 | 0 | for (int iPixel = ll.left; iPixel < ll.right; ++iPixel) |
569 | 0 | { |
570 | 0 | if (oOpts.outputMode == OutputMode::Normal) |
571 | 0 | lines.result[iPixel] = oOpts.visibleVal; |
572 | 0 | else |
573 | 0 | setOutputNormal(lines, iPixel, lines.cur[iPixel]); |
574 | 0 | } |
575 | |
|
576 | 0 | std::fill(lines.result.begin(), lines.result.begin() + ll.left, |
577 | 0 | oOpts.outOfRangeVal); |
578 | 0 | std::fill(lines.result.begin() + ll.right, |
579 | 0 | lines.result.begin() + oCurExtent.xStop, oOpts.outOfRangeVal); |
580 | 0 | } |
581 | | |
582 | | /// Process the part of the first line to the left of the observer. |
583 | | /// |
584 | | /// @param ll Line limits for masking. |
585 | | /// @param sdCalc True when doing standard deviation calculation. |
586 | | /// @param lines Raster lines to process. |
587 | | void ViewshedExecutor::processFirstLineLeft(const LineLimits &ll, Lines &lines, |
588 | | bool sdCalc) |
589 | 0 | { |
590 | 0 | int iEnd = ll.left - 1; |
591 | 0 | int iStart = m_nX - 1; // One left of the observer. |
592 | | |
593 | | // If end is to the right of start, everything is taken care of by right processing. |
594 | 0 | if (iEnd >= iStart) |
595 | 0 | { |
596 | 0 | maskLineLeft(lines.result, ll, m_nY); |
597 | 0 | return; |
598 | 0 | } |
599 | | |
600 | 0 | iStart = oCurExtent.clampX(iStart); |
601 | | |
602 | | // If the start cell is next to the observer, just mark it visible. |
603 | 0 | if (iStart + 1 == m_nX || iStart + 1 == oCurExtent.xStop) |
604 | 0 | { |
605 | 0 | double dfZ = lines.cur[iStart]; |
606 | 0 | if (oOpts.outputMode == OutputMode::Normal) |
607 | 0 | { |
608 | 0 | lines.result[iStart] = oOpts.visibleVal; |
609 | 0 | if (sdCalc) |
610 | 0 | if (lines.sd[iStart] > 1) |
611 | 0 | lines.cur[iStart] = |
612 | 0 | m_dfZObserver; // Should this be a minimum value? |
613 | 0 | } |
614 | 0 | else |
615 | 0 | setOutputNormal(lines, iStart, dfZ); |
616 | 0 | iStart--; |
617 | 0 | } |
618 | | |
619 | | // Go from the observer to the left, calculating Z as we go. |
620 | 0 | for (int iPixel = iStart; iPixel > iEnd; iPixel--) |
621 | 0 | { |
622 | 0 | int nXOffset = std::abs(iPixel - m_nX); |
623 | 0 | double dfZ = CalcHeightLine(nXOffset, lines.cur[iPixel + 1]); |
624 | 0 | if (!sdCalc) |
625 | 0 | setOutputNormal(lines, iPixel, dfZ); |
626 | 0 | else |
627 | 0 | setOutputSd(lines, iPixel, dfZ); |
628 | 0 | } |
629 | |
|
630 | 0 | maskLineLeft(lines.result, ll, m_nY); |
631 | 0 | } |
632 | | |
633 | | /// Mask cells based on angle intersection to the left of the observer. |
634 | | /// |
635 | | /// @param vResult Result raaster line. |
636 | | /// @param nLine Line number. |
637 | | /// @return True when all cells have been masked. |
638 | | bool ViewshedExecutor::maskAngleLeft(std::vector<double> &vResult, int nLine) |
639 | 0 | { |
640 | 0 | auto clamp = [this](int x) |
641 | 0 | { return (x < 0 || x >= m_nX) ? INVALID_ISECT : x; }; |
642 | |
|
643 | 0 | if (!oOpts.angleMasking()) |
644 | 0 | return false; |
645 | | |
646 | 0 | if (nLine != m_nY) |
647 | 0 | { |
648 | 0 | int startAngleX = |
649 | 0 | clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine)); |
650 | 0 | int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine)); |
651 | | // If neither X intersect is in the quadrant and a ray in the quadrant isn't |
652 | | // between start and stop, fill it all and return true. If it is in between |
653 | | // start and stop, we're done. |
654 | 0 | if (invalid(startAngleX) && invalid(endAngleX)) |
655 | 0 | { |
656 | | // Choose a test angle in quadrant II or III depending on the line. |
657 | 0 | double testAngle = nLine < m_nY ? m_testAngle[2] : m_testAngle[3]; |
658 | 0 | if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle)) |
659 | 0 | { |
660 | 0 | std::fill(vResult.begin(), vResult.begin() + m_nX, |
661 | 0 | oOpts.outOfRangeVal); |
662 | 0 | return true; |
663 | 0 | } |
664 | 0 | return false; |
665 | 0 | } |
666 | 0 | if (nLine > m_nY) |
667 | 0 | std::swap(startAngleX, endAngleX); |
668 | 0 | if (invalid(startAngleX)) |
669 | 0 | startAngleX = 0; |
670 | 0 | if (invalid(endAngleX)) |
671 | 0 | endAngleX = m_nX - 1; |
672 | 0 | if (startAngleX <= endAngleX) |
673 | 0 | { |
674 | 0 | std::fill(vResult.begin(), vResult.begin() + startAngleX, |
675 | 0 | oOpts.outOfRangeVal); |
676 | 0 | std::fill(vResult.begin() + endAngleX + 1, vResult.begin() + m_nX, |
677 | 0 | oOpts.outOfRangeVal); |
678 | 0 | } |
679 | 0 | else |
680 | 0 | { |
681 | 0 | std::fill(vResult.begin() + endAngleX + 1, |
682 | 0 | vResult.begin() + startAngleX, oOpts.outOfRangeVal); |
683 | 0 | } |
684 | 0 | } |
685 | | // nLine == m_nY |
686 | 0 | else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI)) |
687 | 0 | { |
688 | 0 | std::fill(vResult.begin(), vResult.begin() + m_nX, oOpts.outOfRangeVal); |
689 | 0 | return true; |
690 | 0 | } |
691 | 0 | return false; |
692 | 0 | } |
693 | | |
694 | | /// Mask cells based on angle intersection to the right of the observer. |
695 | | /// |
696 | | /// @param vResult Result raaster line. |
697 | | /// @param nLine Line number. |
698 | | /// @return True when all cells have been masked. |
699 | | bool ViewshedExecutor::maskAngleRight(std::vector<double> &vResult, int nLine) |
700 | 0 | { |
701 | 0 | int lineLength = static_cast<int>(vResult.size()); |
702 | |
|
703 | 0 | auto clamp = [this, lineLength](int x) |
704 | 0 | { return (x <= m_nX || x >= lineLength) ? INVALID_ISECT : x; }; |
705 | |
|
706 | 0 | if (oOpts.startAngle == oOpts.endAngle) |
707 | 0 | return false; |
708 | | |
709 | 0 | if (nLine != m_nY) |
710 | 0 | { |
711 | 0 | int startAngleX = |
712 | 0 | clamp(hIntersect(oOpts.startAngle, m_nX, m_nY, nLine)); |
713 | 0 | int endAngleX = clamp(hIntersect(oOpts.endAngle, m_nX, m_nY, nLine)); |
714 | | |
715 | | // If neither X intersect is in the quadrant and a ray in the quadrant isn't |
716 | | // between start and stop, fill it all and return true. If it is in between |
717 | | // start and stop, we're done. |
718 | 0 | if (invalid(startAngleX) && invalid(endAngleX)) |
719 | 0 | { |
720 | | // Choose a test angle in quadrant I or IV depending on the line. |
721 | 0 | double testAngle = nLine < m_nY ? m_testAngle[1] : m_testAngle[4]; |
722 | 0 | if (!rayBetween(oOpts.startAngle, oOpts.endAngle, testAngle)) |
723 | 0 | { |
724 | 0 | std::fill(vResult.begin() + m_nX + 1, vResult.end(), |
725 | 0 | oOpts.outOfRangeVal); |
726 | 0 | return true; |
727 | 0 | } |
728 | 0 | return false; |
729 | 0 | } |
730 | | |
731 | 0 | if (nLine > m_nY) |
732 | 0 | std::swap(startAngleX, endAngleX); |
733 | 0 | if (invalid(endAngleX)) |
734 | 0 | endAngleX = lineLength - 1; |
735 | 0 | if (invalid(startAngleX)) |
736 | 0 | startAngleX = m_nX + 1; |
737 | 0 | if (startAngleX <= endAngleX) |
738 | 0 | { |
739 | 0 | std::fill(vResult.begin() + m_nX + 1, vResult.begin() + startAngleX, |
740 | 0 | oOpts.outOfRangeVal); |
741 | 0 | std::fill(vResult.begin() + endAngleX + 1, vResult.end(), |
742 | 0 | oOpts.outOfRangeVal); |
743 | 0 | } |
744 | 0 | else |
745 | 0 | { |
746 | 0 | std::fill(vResult.begin() + endAngleX + 1, |
747 | 0 | vResult.begin() + startAngleX, oOpts.outOfRangeVal); |
748 | 0 | } |
749 | 0 | } |
750 | | // nLine == m_nY |
751 | 0 | else if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 0)) |
752 | 0 | { |
753 | 0 | std::fill(vResult.begin() + m_nX + 1, vResult.end(), |
754 | 0 | oOpts.outOfRangeVal); |
755 | 0 | return true; |
756 | 0 | } |
757 | 0 | return false; |
758 | 0 | } |
759 | | |
760 | | /// Perform angle and min/max masking to the left of the observer. |
761 | | /// |
762 | | /// @param vResult Raster line to mask. |
763 | | /// @param ll Min/max line limits. |
764 | | /// @param nLine Line number. |
765 | | void ViewshedExecutor::maskLineLeft(std::vector<double> &vResult, |
766 | | const LineLimits &ll, int nLine) |
767 | 0 | { |
768 | | // If we've already masked with angles everything, just return. |
769 | 0 | if (maskAngleLeft(vResult, nLine)) |
770 | 0 | return; |
771 | | |
772 | | // Mask cells from the left edge to the left limit. |
773 | 0 | std::fill(vResult.begin(), vResult.begin() + ll.left, oOpts.outOfRangeVal); |
774 | | // Mask cells from the left min to the observer. |
775 | 0 | if (ll.leftMin < m_nX) |
776 | 0 | std::fill(vResult.begin() + ll.leftMin, vResult.begin() + m_nX, |
777 | 0 | oOpts.outOfRangeVal); |
778 | 0 | } |
779 | | |
780 | | /// Perform angle and min/max masking to the right of the observer. |
781 | | /// |
782 | | /// @param vResult Raster line to mask. |
783 | | /// @param ll Min/max line limits. |
784 | | /// @param nLine Line number. |
785 | | void ViewshedExecutor::maskLineRight(std::vector<double> &vResult, |
786 | | const LineLimits &ll, int nLine) |
787 | 0 | { |
788 | | // If we've already masked with angles everything, just return. |
789 | 0 | if (maskAngleRight(vResult, nLine)) |
790 | 0 | return; |
791 | | |
792 | | // Mask cells from the observer to right min. |
793 | 0 | std::fill(vResult.begin() + m_nX + 1, vResult.begin() + ll.rightMin, |
794 | 0 | oOpts.outOfRangeVal); |
795 | | // Mask cells from the right limit to the right edge. |
796 | | |
797 | | // |
798 | | //ABELL - Changed from ll.right + 1 |
799 | | // |
800 | 0 | if (ll.right <= static_cast<int>(vResult.size())) |
801 | 0 | std::fill(vResult.begin() + ll.right, vResult.end(), |
802 | 0 | oOpts.outOfRangeVal); |
803 | 0 | } |
804 | | |
805 | | /// Process the part of the first line to the right of the observer. |
806 | | /// |
807 | | /// @param ll Line limits |
808 | | /// @param sdCalc True when doing standard deviation calcuation. |
809 | | /// @param lines Raster lines to process. |
810 | | void ViewshedExecutor::processFirstLineRight(const LineLimits &ll, Lines &lines, |
811 | | bool sdCalc) |
812 | 0 | { |
813 | 0 | int iStart = m_nX + 1; |
814 | 0 | int iEnd = ll.right; |
815 | | |
816 | | // If start is to the right of end, everything is taken care of by left processing. |
817 | 0 | if (iStart >= iEnd) |
818 | 0 | { |
819 | 0 | maskLineRight(lines.result, ll, m_nY); |
820 | 0 | return; |
821 | 0 | } |
822 | | |
823 | 0 | iStart = oCurExtent.clampX(iStart); |
824 | | |
825 | | // If the start cell is next to the observer, just mark it visible. |
826 | 0 | if (iStart - 1 == m_nX || iStart == oCurExtent.xStart) |
827 | 0 | { |
828 | 0 | double dfZ = lines.cur[iStart]; |
829 | 0 | if (oOpts.outputMode == OutputMode::Normal) |
830 | 0 | { |
831 | 0 | lines.result[iStart] = oOpts.visibleVal; |
832 | 0 | if (sdCalc) |
833 | 0 | if (lines.sd[iStart] > 1) |
834 | 0 | lines.cur[iStart] = |
835 | 0 | m_dfZObserver; // Use some minimum value instead? |
836 | 0 | } |
837 | 0 | else |
838 | 0 | setOutputNormal(lines, iStart, dfZ); |
839 | 0 | iStart++; |
840 | 0 | } |
841 | | |
842 | | // Go from the observer to the right, calculating Z as we go. |
843 | 0 | for (int iPixel = iStart; iPixel < iEnd; iPixel++) |
844 | 0 | { |
845 | 0 | int nXOffset = std::abs(iPixel - m_nX); |
846 | 0 | double dfZ = CalcHeightLine(nXOffset, lines.cur[iPixel - 1]); |
847 | 0 | if (!sdCalc) |
848 | 0 | setOutputNormal(lines, iPixel, dfZ); |
849 | 0 | else |
850 | 0 | setOutputSd(lines, iPixel, dfZ); |
851 | 0 | } |
852 | |
|
853 | 0 | maskLineRight(lines.result, ll, m_nY); |
854 | 0 | } |
855 | | |
856 | | /// Process a line to the left of the observer. |
857 | | /// |
858 | | /// @param nYOffset Offset of the line being processed from the observer |
859 | | /// @param ll Line limits |
860 | | /// @param lines Raster lines to process. |
861 | | /// @param sdCalc standard deviation calculation indicator. |
862 | | void ViewshedExecutor::processLineLeft(int nYOffset, LineLimits &ll, |
863 | | Lines &lines, bool sdCalc) |
864 | 0 | { |
865 | 0 | int iStart = m_nX - 1; |
866 | 0 | int iEnd = ll.left - 1; |
867 | 0 | int nLine = m_nY + nYOffset; |
868 | | |
869 | | // If start to the left of end, everything is taken care of by processing right. |
870 | 0 | if (iStart <= iEnd) |
871 | 0 | { |
872 | 0 | maskLineLeft(lines.result, ll, nLine); |
873 | 0 | return; |
874 | 0 | } |
875 | 0 | iStart = oCurExtent.clampX(iStart); |
876 | | |
877 | | // If the observer is to the right of the raster, mark the first cell to the left as |
878 | | // visible. This may mark an out-of-range cell with a value, but this will be fixed |
879 | | // with the out of range assignment at the end. |
880 | 0 | if (iStart == oCurExtent.xStop - 1) |
881 | 0 | { |
882 | 0 | if (oOpts.outputMode == OutputMode::Normal) |
883 | 0 | lines.result[iStart] = oOpts.visibleVal; |
884 | 0 | else |
885 | 0 | setOutputNormal(lines, iStart, lines.cur[iStart]); |
886 | 0 | iStart--; |
887 | 0 | } |
888 | | |
889 | | // Go from the observer to the left, calculating Z as we go. |
890 | 0 | nYOffset = std::abs(nYOffset); |
891 | 0 | for (int iPixel = iStart; iPixel > iEnd; iPixel--) |
892 | 0 | { |
893 | 0 | int nXOffset = std::abs(iPixel - m_nX); |
894 | 0 | double dfZ; |
895 | 0 | if (nXOffset == nYOffset) |
896 | 0 | dfZ = CalcHeightLine(nYOffset, lines.cur[iPixel], |
897 | 0 | lines.prev[iPixel + 1]); |
898 | 0 | else |
899 | 0 | dfZ = oZcalc(nXOffset, nYOffset, lines.cur[iPixel + 1], |
900 | 0 | lines.prev[iPixel], lines.prev[iPixel + 1]); |
901 | 0 | if (!sdCalc) |
902 | 0 | setOutputNormal(lines, iPixel, dfZ); |
903 | 0 | else |
904 | 0 | setOutputSd(lines, iPixel, dfZ); |
905 | 0 | } |
906 | |
|
907 | 0 | maskLineLeft(lines.result, ll, nLine); |
908 | 0 | } |
909 | | |
910 | | /// Process a line to the right of the observer. |
911 | | /// |
912 | | /// @param nYOffset Offset of the line being processed from the observer |
913 | | /// @param ll Line limits |
914 | | /// @param lines Raster lines to process. |
915 | | /// @param sdCalc standard deviation calculation indicator. |
916 | | void ViewshedExecutor::processLineRight(int nYOffset, LineLimits &ll, |
917 | | Lines &lines, bool sdCalc) |
918 | 0 | { |
919 | 0 | int iStart = m_nX + 1; |
920 | 0 | int iEnd = ll.right; |
921 | 0 | int nLine = m_nY + nYOffset; |
922 | | |
923 | | // If start is to the right of end, everything is taken care of by processing left. |
924 | 0 | if (iStart >= iEnd) |
925 | 0 | { |
926 | 0 | maskLineRight(lines.result, ll, nLine); |
927 | 0 | return; |
928 | 0 | } |
929 | 0 | iStart = oCurExtent.clampX(iStart); |
930 | | |
931 | | // If the observer is to the left of the raster, mark the first cell to the right as |
932 | | // visible. This may mark an out-of-range cell with a value, but this will be fixed |
933 | | // with the out of range assignment at the end. |
934 | 0 | if (iStart == 0) |
935 | 0 | { |
936 | 0 | if (oOpts.outputMode == OutputMode::Normal) |
937 | 0 | lines.result[iStart] = oOpts.visibleVal; |
938 | 0 | else |
939 | 0 | setOutputNormal(lines, 0, lines.cur[0]); |
940 | 0 | iStart++; |
941 | 0 | } |
942 | | |
943 | | // Go from the observer to the right, calculating Z as we go. |
944 | 0 | nYOffset = std::abs(nYOffset); |
945 | 0 | for (int iPixel = iStart; iPixel < iEnd; iPixel++) |
946 | 0 | { |
947 | 0 | int nXOffset = std::abs(iPixel - m_nX); |
948 | 0 | double dfZ; |
949 | 0 | if (nXOffset == nYOffset) |
950 | 0 | { |
951 | 0 | if (sdCalc && nXOffset == 1) |
952 | 0 | { |
953 | 0 | lines.result[iPixel] = oOpts.visibleVal; |
954 | 0 | if (lines.sd[iPixel] > 1) |
955 | 0 | lines.cur[iPixel] = m_dfZObserver; |
956 | 0 | continue; |
957 | 0 | } |
958 | 0 | dfZ = CalcHeightLine(nYOffset, lines.cur[iPixel], |
959 | 0 | lines.prev[iPixel - 1]); |
960 | 0 | } |
961 | 0 | else |
962 | 0 | dfZ = oZcalc(nXOffset, nYOffset, lines.cur[iPixel - 1], |
963 | 0 | lines.prev[iPixel], lines.prev[iPixel - 1]); |
964 | 0 | if (!sdCalc) |
965 | 0 | setOutputNormal(lines, iPixel, dfZ); |
966 | 0 | else |
967 | 0 | setOutputSd(lines, iPixel, dfZ); |
968 | 0 | } |
969 | |
|
970 | 0 | maskLineRight(lines.result, ll, nLine); |
971 | 0 | } |
972 | | |
973 | | /// Apply angular/distance mask to the initial X position. Assumes m_nX is in the raster. |
974 | | /// @param vResult Raster line on which to apply mask. |
975 | | /// @param ll Line limits. |
976 | | /// @param nLine Line number. |
977 | | /// @return True if the initial X position was masked. |
978 | | bool ViewshedExecutor::maskInitial(std::vector<double> &vResult, |
979 | | const LineLimits &ll, int nLine) |
980 | 0 | { |
981 | | // Mask min/max. |
982 | 0 | if (ll.left >= ll.right || ll.leftMin != ll.rightMin) |
983 | 0 | { |
984 | 0 | vResult[m_nX] = oOpts.outOfRangeVal; |
985 | 0 | return true; |
986 | 0 | } |
987 | | |
988 | 0 | if (!oOpts.angleMasking()) |
989 | 0 | return false; |
990 | | |
991 | 0 | if (nLine < m_nY) |
992 | 0 | { |
993 | 0 | if (!rayBetween(oOpts.startAngle, oOpts.endAngle, M_PI / 2)) |
994 | 0 | { |
995 | 0 | vResult[m_nX] = oOpts.outOfRangeVal; |
996 | 0 | return true; |
997 | 0 | } |
998 | 0 | } |
999 | 0 | else if (nLine > m_nY) |
1000 | 0 | { |
1001 | 0 | if (!rayBetween(oOpts.startAngle, oOpts.endAngle, 3 * M_PI / 2)) |
1002 | 0 | { |
1003 | 0 | vResult[m_nX] = oOpts.outOfRangeVal; |
1004 | 0 | return true; |
1005 | 0 | } |
1006 | 0 | } |
1007 | 0 | return false; |
1008 | 0 | } |
1009 | | |
1010 | | /// Process a line above or below the observer. |
1011 | | /// |
1012 | | /// @param nLine Line number being processed. |
1013 | | /// @param lines Raster lines to process. |
1014 | | /// @return True on success, false otherwise. |
1015 | | bool ViewshedExecutor::processLine(int nLine, Lines &lines) |
1016 | 0 | { |
1017 | 0 | int nYOffset = nLine - m_nY; |
1018 | |
|
1019 | 0 | if (!readLine(nLine, lines)) |
1020 | 0 | return false; |
1021 | | |
1022 | | // Adjust height of the read line. |
1023 | 0 | LineLimits ll = adjustHeight(nYOffset, lines); |
1024 | |
|
1025 | 0 | std::vector<double> savedLine; |
1026 | 0 | if (sdMode()) |
1027 | 0 | savedLine = lines.cur; |
1028 | |
|
1029 | 0 | auto process = [this, nYOffset, &ll, &lines](bool sdCalc) |
1030 | 0 | { |
1031 | 0 | CPLJobQueuePtr pQueue = m_pool.CreateJobQueue(); |
1032 | 0 | pQueue->SubmitJob([&]() |
1033 | 0 | { processLineLeft(nYOffset, ll, lines, sdCalc); }); |
1034 | 0 | pQueue->SubmitJob([&]() |
1035 | 0 | { processLineRight(nYOffset, ll, lines, sdCalc); }); |
1036 | 0 | pQueue->WaitCompletion(); |
1037 | 0 | }; |
1038 | |
|
1039 | 0 | bool masked = false; |
1040 | | // Handle initial position on the line. |
1041 | 0 | if (oCurExtent.containsX(m_nX)) |
1042 | 0 | { |
1043 | 0 | masked = maskInitial(lines.result, ll, nLine); |
1044 | 0 | if (!masked) |
1045 | 0 | { |
1046 | 0 | double dfZ = CalcHeightLine(std::abs(nYOffset), lines.cur[m_nX], |
1047 | 0 | lines.prev[m_nX]); |
1048 | 0 | setOutputNormal(lines, m_nX, dfZ); |
1049 | 0 | } |
1050 | 0 | } |
1051 | |
|
1052 | 0 | process(false); |
1053 | | |
1054 | | // Process standard deviation mode |
1055 | 0 | if (sdMode()) |
1056 | 0 | { |
1057 | 0 | lines.prev = std::move(lines.prevTmp); |
1058 | 0 | lines.prevTmp = std::move(lines.cur); |
1059 | 0 | lines.cur = std::move(savedLine); |
1060 | | // Handle initial position on the line. |
1061 | 0 | if (!masked && oCurExtent.containsX(m_nX)) |
1062 | 0 | { |
1063 | 0 | if (std::abs(nYOffset) == 1) |
1064 | 0 | { |
1065 | 0 | lines.result[m_nX] = oOpts.visibleVal; |
1066 | 0 | if (lines.sd[m_nX] > 1) |
1067 | 0 | lines.cur[m_nX] = m_dfZObserver; |
1068 | 0 | } |
1069 | 0 | else |
1070 | 0 | { |
1071 | |
|
1072 | 0 | double dfZ = CalcHeightLine(std::abs(nYOffset), lines.cur[m_nX], |
1073 | 0 | lines.prev[m_nX]); |
1074 | 0 | setOutputSd(lines, m_nX, dfZ); |
1075 | 0 | } |
1076 | 0 | } |
1077 | 0 | process(true); |
1078 | 0 | lines.prev = std::move(lines.prevTmp); |
1079 | 0 | lines.prevTmp = lines.cur; |
1080 | 0 | } |
1081 | 0 | else |
1082 | 0 | lines.prev = lines.cur; |
1083 | |
|
1084 | 0 | if (oOpts.pitchMasking()) |
1085 | 0 | applyPitchMask(lines.result, lines.pitchMask); |
1086 | 0 | if (!writeLine(nLine, lines.result)) |
1087 | 0 | return false; |
1088 | | |
1089 | 0 | return oProgress.lineComplete(); |
1090 | 0 | } |
1091 | | |
1092 | | // Calculate the ray angle from the origin to middle of the top or bottom |
1093 | | // of each quadrant. |
1094 | | void ViewshedExecutor::calcTestAngles() |
1095 | 0 | { |
1096 | | // Quadrant 1. |
1097 | 0 | { |
1098 | 0 | int ysize = m_nY + 1; |
1099 | 0 | int xsize = oCurExtent.xStop - m_nX; |
1100 | 0 | m_testAngle[1] = atan2(ysize, xsize / 2.0); |
1101 | 0 | } |
1102 | | |
1103 | | // Quadrant 2. |
1104 | 0 | { |
1105 | 0 | int ysize = m_nY + 1; |
1106 | 0 | int xsize = m_nX + 1; |
1107 | 0 | m_testAngle[2] = atan2(ysize, -xsize / 2.0); |
1108 | 0 | } |
1109 | | |
1110 | | // Quadrant 3. |
1111 | 0 | { |
1112 | 0 | int ysize = oCurExtent.yStop - m_nY; |
1113 | 0 | int xsize = m_nX + 1; |
1114 | 0 | m_testAngle[3] = atan2(-ysize, -xsize / 2.0); |
1115 | 0 | } |
1116 | | |
1117 | | // Quadrant 4. |
1118 | 0 | { |
1119 | 0 | int ysize = oCurExtent.yStop - m_nY; |
1120 | 0 | int xsize = oCurExtent.xStop - m_nX; |
1121 | 0 | m_testAngle[4] = atan2(-ysize, xsize / 2.0); |
1122 | 0 | } |
1123 | | |
1124 | | // Adjust range to [0, 2 * M_PI) |
1125 | 0 | for (int i = 1; i <= 4; ++i) |
1126 | 0 | if (m_testAngle[i] < 0) |
1127 | 0 | m_testAngle[i] += (2 * M_PI); |
1128 | 0 | } |
1129 | | |
1130 | | /// Run the viewshed computation |
1131 | | /// @return Success as true or false. |
1132 | | bool ViewshedExecutor::run() |
1133 | 0 | { |
1134 | | // If we're doing angular masking, calculate the test angles used later. |
1135 | 0 | if (oOpts.angleMasking()) |
1136 | 0 | calcTestAngles(); |
1137 | |
|
1138 | 0 | Lines firstLine(oCurExtent.xSize()); |
1139 | 0 | if (oOpts.pitchMasking()) |
1140 | 0 | firstLine.pitchMask.resize(oOutExtent.xSize(), |
1141 | 0 | std::numeric_limits<double>::quiet_NaN()); |
1142 | 0 | if (sdMode()) |
1143 | 0 | firstLine.sd.resize(oOutExtent.xSize()); |
1144 | |
|
1145 | 0 | m_dfHeightAdjFactor = calcHeightAdjFactor(); |
1146 | |
|
1147 | 0 | if (!processFirstLine(firstLine)) |
1148 | 0 | return false; |
1149 | | |
1150 | 0 | if (oOpts.cellMode == CellMode::Edge) |
1151 | 0 | oZcalc = doEdge; |
1152 | 0 | else if (oOpts.cellMode == CellMode::Diagonal) |
1153 | 0 | oZcalc = doDiagonal; |
1154 | 0 | else if (oOpts.cellMode == CellMode::Min) |
1155 | 0 | oZcalc = doMin; |
1156 | 0 | else if (oOpts.cellMode == CellMode::Max) |
1157 | 0 | oZcalc = doMax; |
1158 | | |
1159 | | // scan upwards |
1160 | 0 | int yStart = oCurExtent.clampY(m_nY); |
1161 | 0 | std::atomic<bool> err(false); |
1162 | 0 | CPLJobQueuePtr pQueue = m_pool.CreateJobQueue(); |
1163 | 0 | pQueue->SubmitJob( |
1164 | 0 | [&]() |
1165 | 0 | { |
1166 | 0 | Lines lines(oCurExtent.xSize()); |
1167 | 0 | lines.prev = firstLine.prev; |
1168 | 0 | lines.prevTmp = firstLine.prevTmp; |
1169 | 0 | if (oOpts.pitchMasking()) |
1170 | 0 | lines.pitchMask.resize( |
1171 | 0 | oOutExtent.xSize(), |
1172 | 0 | std::numeric_limits<double>::quiet_NaN()); |
1173 | 0 | if (sdMode()) |
1174 | 0 | lines.sd.resize(oOutExtent.xSize()); |
1175 | |
|
1176 | 0 | for (int nLine = yStart - 1; nLine >= oCurExtent.yStart && !err; |
1177 | 0 | nLine--) |
1178 | 0 | { |
1179 | 0 | if (!processLine(nLine, lines)) |
1180 | 0 | err = true; |
1181 | 0 | if (oOpts.pitchMasking()) |
1182 | 0 | std::fill(lines.pitchMask.begin(), lines.pitchMask.end(), |
1183 | 0 | std::numeric_limits<double>::quiet_NaN()); |
1184 | 0 | } |
1185 | 0 | }); |
1186 | | |
1187 | | // scan downwards |
1188 | 0 | pQueue->SubmitJob( |
1189 | 0 | [&]() |
1190 | 0 | { |
1191 | 0 | Lines lines(oCurExtent.xSize()); |
1192 | 0 | lines.prev = firstLine.prev; |
1193 | 0 | lines.prevTmp = firstLine.prevTmp; |
1194 | 0 | if (oOpts.pitchMasking()) |
1195 | 0 | lines.pitchMask.resize( |
1196 | 0 | oOutExtent.xSize(), |
1197 | 0 | std::numeric_limits<double>::quiet_NaN()); |
1198 | 0 | if (sdMode()) |
1199 | 0 | lines.sd.resize(oOutExtent.xSize()); |
1200 | |
|
1201 | 0 | for (int nLine = yStart + 1; nLine < oCurExtent.yStop && !err; |
1202 | 0 | nLine++) |
1203 | 0 | { |
1204 | 0 | if (!processLine(nLine, lines)) |
1205 | 0 | err = true; |
1206 | 0 | if (oOpts.pitchMasking()) |
1207 | 0 | std::fill(lines.pitchMask.begin(), lines.pitchMask.end(), |
1208 | 0 | std::numeric_limits<double>::quiet_NaN()); |
1209 | 0 | } |
1210 | 0 | }); |
1211 | 0 | return true; |
1212 | 0 | } |
1213 | | |
1214 | | } // namespace viewshed |
1215 | | } // namespace gdal |
1216 | | |
1217 | | // cppcheck-suppress-end knownConditionTrueFalse |