Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/alg/viewshed/util.cpp
Line
Count
Source
1
/******************************************************************************
2
 * (c) 2024 info@hobu.co
3
 *
4
 * SPDX-License-Identifier: MIT
5
 ****************************************************************************/
6
7
#include <array>
8
#include <cmath>
9
#include <iostream>
10
#include <limits>
11
12
#include "gdal_priv.h"
13
#include "util.h"
14
#include "viewshed_types.h"
15
16
namespace gdal
17
{
18
namespace viewshed
19
{
20
21
/// Normalize a masking angle. Change from clockwise with 0 north (up) to counterclockwise
22
/// with 0 to the east (right) and change to radians.
23
///
24
// @param maskAngle  Masking angle in degrees.
25
double normalizeAngle(double maskAngle)
26
0
{
27
0
    maskAngle = 90 - maskAngle;
28
0
    if (maskAngle < 0)
29
0
        maskAngle += 360;
30
0
    return maskAngle * (M_PI / 180);
31
0
}
32
33
/// Compute the X intersect position on the line Y = y with a ray extending
34
/// from (nX, nY) along `angle`.
35
///
36
/// @param angle  Angle in radians, standard arrangement.
37
/// @param nX  X coordinate of ray endpoint.
38
/// @param nY  Y coordinate of ray endpoint.
39
/// @param y  Horizontal line where Y = y.
40
/// @return  X intersect or NaN
41
double horizontalIntersect(double angle, int nX, int nY, int y)
42
0
{
43
0
    double x = std::numeric_limits<double>::quiet_NaN();
44
45
0
    if (nY == y)
46
0
        x = nX;
47
0
    else if (nY > y)
48
0
    {
49
0
        if (ARE_REAL_EQUAL(angle, M_PI / 2))
50
0
            x = nX;
51
0
        else if (angle > 0 && angle < M_PI)
52
0
            x = nX + (nY - y) / std::tan(angle);
53
0
    }
54
0
    else  // nY < y
55
0
    {
56
0
        if (ARE_REAL_EQUAL(angle, 3 * M_PI / 2))
57
0
            x = nX;
58
0
        else if (angle > M_PI)
59
0
            x = nX - (y - nY) / std::tan(angle);
60
0
    }
61
0
    return x;
62
0
}
63
64
/// Compute the X intersect position on the line Y = y with a ray extending
65
/// from (nX, nY) along `angle`.
66
///
67
/// @param angle  Angle in radians, standard arrangement.
68
/// @param nX  X coordinate of ray endpoint.
69
/// @param nY  Y coordinate of ray endpoint.
70
/// @param y  Horizontal line where Y = y.
71
/// @return  Rounded X intersection of the sentinel INVALID_ISECT
72
int hIntersect(double angle, int nX, int nY, int y)
73
0
{
74
0
    double x = horizontalIntersect(angle, nX, nY, y);
75
0
    if (std::isnan(x))
76
0
        return INVALID_ISECT;
77
0
    return static_cast<int>(std::round(x));
78
0
}
79
80
/// Compute the X intersect on one of the horizontal edges of a window
81
/// with a ray extending from (nX, nY) along `angle`, clamped the the extent of a window.
82
///
83
/// @param angle  Angle in radians, standard arrangement.
84
/// @param nX  X coordinate of ray endpoint.
85
/// @param nY  Y coordinate of ray endpoint.
86
/// @param win  Window to intersect.
87
/// @return  X intersect, clamped to the window extent.
88
int hIntersect(double angle, int nX, int nY, const Window &win)
89
0
{
90
0
    if (ARE_REAL_EQUAL(angle, M_PI))
91
0
        return win.xStart;
92
0
    if (ARE_REAL_EQUAL(angle, 0))
93
0
        return win.xStop;
94
0
    double x = horizontalIntersect(angle, nX, nY, win.yStart);
95
0
    if (std::isnan(x))
96
0
        x = horizontalIntersect(angle, nX, nY, win.yStop);
97
0
    return std::clamp(static_cast<int>(std::round(x)), win.xStart, win.xStop);
98
0
}
99
100
/// Compute the X intersect position on the line Y = y with a ray extending
101
/// from (nX, nY) along `angle`.
102
///
103
/// @param angle  Angle in radians, standard arrangement.
104
/// @param nX  X coordinate of ray endpoint.
105
/// @param nY  Y coordinate of ray endpoint.
106
/// @param x  Vertical  line where X = x.
107
/// @return  Y intersect or NaN
108
double verticalIntersect(double angle, int nX, int nY, int x)
109
0
{
110
0
    double y = std::numeric_limits<double>::quiet_NaN();
111
112
0
    if (nX == x)
113
0
        y = nY;
114
0
    else if (nX < x)
115
0
    {
116
0
        if (ARE_REAL_EQUAL(angle, 0))
117
0
            y = nY;
118
0
        else if (angle < M_PI / 2 || angle > M_PI * 3 / 2)
119
0
            y = nY + (nX - x) * std::tan(angle);
120
0
    }
121
0
    else  // nX > x
122
0
    {
123
0
        if (ARE_REAL_EQUAL(angle, M_PI))
124
0
            y = nY;
125
0
        else if (angle > M_PI / 2 && angle < M_PI * 3 / 2)
126
0
            y = nY - (x - nX) * std::tan(angle);
127
0
    }
128
0
    return y;
129
0
}
130
131
/// Compute the X intersect position on the line Y = y with a ray extending
132
/// from (nX, nY) along `angle`.
133
///
134
/// @param angle  Angle in radians, standard arrangement.
135
/// @param nX  X coordinate of ray endpoint.
136
/// @param nY  Y coordinate of ray endpoint.
137
/// @param x  Horizontal line where X = x.
138
/// @return  Rounded Y intersection of the sentinel INVALID_ISECT
139
int vIntersect(double angle, int nX, int nY, int x)
140
0
{
141
0
    double y = verticalIntersect(angle, nX, nY, x);
142
0
    if (std::isnan(y))
143
0
        return INVALID_ISECT;
144
0
    return static_cast<int>(std::round(y));
145
0
}
146
147
/// Compute the Y intersect on one of the vertical edges of a window
148
/// with a ray extending from (nX, nY) along `angle`, clamped the the extent
149
/// of the window.
150
///
151
/// @param angle  Angle in radians, standard arrangement.
152
/// @param nX  X coordinate of ray endpoint.
153
/// @param nY  Y coordinate of ray endpoint.
154
/// @param win  Window to intersect.
155
/// @return  y intersect, clamped to the window extent.
156
int vIntersect(double angle, int nX, int nY, const Window &win)
157
0
{
158
0
    if (ARE_REAL_EQUAL(angle, M_PI / 2))
159
0
        return win.yStart;
160
0
    if (ARE_REAL_EQUAL(angle, 3 * M_PI / 2))
161
0
        return win.yStop;
162
0
    double y = verticalIntersect(angle, nX, nY, win.xStart);
163
0
    if (std::isnan(y))
164
0
        y = verticalIntersect(angle, nX, nY, win.xStop);
165
0
    return std::clamp(static_cast<int>(std::round(y)), win.yStart, win.yStop);
166
0
}
167
168
/// Determine if ray is in the slice between two rays starting at `start` and
169
/// going clockwise to `end` (inclusive). [start, end]
170
/// @param  start  Start angle.
171
/// @param  end  End angle.
172
/// @param  test  Test angle.
173
/// @return  Whether `test` lies in the slice [start, end]
174
bool rayBetween(double start, double end, double test)
175
0
{
176
    // Our angles go counterclockwise, so swap start and end
177
0
    std::swap(start, end);
178
0
    if (start < end)
179
0
        return (test >= start && test <= end);
180
0
    else if (start > end)
181
0
        return (test >= start || test <= end);
182
0
    return false;
183
0
}
184
185
/// Get the band size
186
///
187
/// @param  band Raster band
188
/// @return  The raster band size.
189
size_t bandSize(GDALRasterBand &band)
190
0
{
191
0
    return static_cast<size_t>(band.GetXSize()) * band.GetYSize();
192
0
}
193
194
/// Create the output dataset.
195
///
196
/// @param  srcBand  Source raster band.
197
/// @param  opts  Options.
198
/// @param  extent  Output dataset extent.
199
/// @return  The output dataset to be filled with data.
200
DatasetPtr createOutputDataset(GDALRasterBand &srcBand, const Options &opts,
201
                               const Window &extent)
202
0
{
203
0
    GDALDriverManager *hMgr = GetGDALDriverManager();
204
0
    GDALDriver *hDriver = hMgr->GetDriverByName(opts.outputFormat.c_str());
205
0
    if (!hDriver)
206
0
    {
207
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot get driver");
208
0
        return nullptr;
209
0
    }
210
211
    /* create output raster */
212
0
    DatasetPtr dataset(hDriver->Create(
213
0
        opts.outputFilename.c_str(), extent.xSize(), extent.ySize(), 1,
214
0
        opts.outputMode == OutputMode::Normal ? GDT_UInt8 : GDT_Float64,
215
0
        const_cast<char **>(opts.creationOpts.List())));
216
0
    if (!dataset)
217
0
    {
218
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create dataset for %s",
219
0
                 opts.outputFilename.c_str());
220
0
        return nullptr;
221
0
    }
222
223
    /* copy srs */
224
0
    dataset->SetSpatialRef(srcBand.GetDataset()->GetSpatialRef());
225
226
0
    GDALGeoTransform srcGT, dstGT;
227
0
    srcBand.GetDataset()->GetGeoTransform(srcGT);
228
0
    dstGT[0] = srcGT[0] + srcGT[1] * extent.xStart + srcGT[2] * extent.yStart;
229
0
    dstGT[1] = srcGT[1];
230
0
    dstGT[2] = srcGT[2];
231
0
    dstGT[3] = srcGT[3] + srcGT[4] * extent.xStart + srcGT[5] * extent.yStart;
232
0
    dstGT[4] = srcGT[4];
233
0
    dstGT[5] = srcGT[5];
234
0
    dataset->SetGeoTransform(dstGT);
235
236
0
    GDALRasterBand *pBand = dataset->GetRasterBand(1);
237
0
    if (!pBand)
238
0
    {
239
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot get band for %s",
240
0
                 opts.outputFilename.c_str());
241
0
        return nullptr;
242
0
    }
243
244
0
    if (opts.nodataVal >= 0)
245
0
        GDALSetRasterNoDataValue(pBand, opts.nodataVal);
246
0
    return dataset;
247
0
}
248
249
}  // namespace viewshed
250
}  // namespace gdal