Skip to content

Commit b9334d8

Browse files
alex-soklevAlexander Soklev
andauthored
Improved viewshed rtx. Now result should match the CPU version (#715)
* Improved viewshed rtx. Now result should match the CPU version * Address linter issues * Address linter issues (2) * Address linter issues (3) Co-authored-by: Alexander Soklev <[email protected]>
1 parent 1dc3637 commit b9334d8

File tree

2 files changed

+71
-23
lines changed

2 files changed

+71
-23
lines changed

xrspatial/gpu_rtx/mesh_utils.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ def create_triangulation(raster, optix):
99

1010
# Calculate a scale factor for the height that maintains the ratio
1111
# width/height
12-
x_coords = raster.indexes.get('x').values
13-
x_range = x_coords.max() - x_coords.min()
1412
H, W = raster.shape
15-
# Get the scale factor of the terrain height vs terrain size
16-
scaleFactor = x_range / raster.res[1]
17-
scale = scaleFactor * W / raster.res[1]
13+
14+
# Scale the terrain so that the width is proportional to the height
15+
# Thus the terrain would be neither too flat nor too steep and
16+
# raytracing will give best accuracy
17+
maxH = float(cupy.amax(raster.data))
18+
maxDim = max(H, W)
19+
scale = maxDim / maxH
1820

1921
if optixhash != datahash:
2022
num_tris = (H - 1) * (W - 1) * 2

xrspatial/gpu_rtx/viewshed.py

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,63 @@ def _generate_primary_rays(rays, H, W):
5454
return 0
5555

5656

57+
@nb.cuda.jit(device=True)
58+
def _get_vertical_ang(diff_elev, distance_to_viewpoint):
59+
# Find the vertical angle in degrees between the vp
60+
# and the point represented by the StatusNode
61+
62+
# 0 above, 180 below
63+
if diff_elev == 0.0:
64+
return 90
65+
elif diff_elev > 0:
66+
return math.atan(distance_to_viewpoint / diff_elev) * 180 / math.pi
67+
68+
return math.atan(-diff_elev / distance_to_viewpoint) * 180 / math.pi + 90
69+
70+
5771
@nb.cuda.jit
58-
def _calc_viewshed_kernel(hits, visibility_grid, H, W):
72+
def _calc_viewshed_kernel(hits, visibility_grid, H, W, hmap, v, oe, te, ew_range, ns_range):
5973
i, j = nb.cuda.grid(2)
6074
if i >= 0 and i < H and j >= 0 and j < W:
6175
dist = hits[i, j, 0]
6276
# We traced the viewshed rays and now hits contains the intersection
6377
# data. If dist > 0, then we were able to hit something along the
6478
# length of the ray which means that the pixel we targeted is not
6579
# directly visible from the view point.
80+
t = (i, j) # t for target, v for viewer
6681
if dist >= 0:
67-
visibility_grid[i, j] = INVISIBLE
82+
visibility_grid[t] = INVISIBLE
83+
else:
84+
if t == v:
85+
visibility_grid[t] = 180
86+
else:
87+
diff_elev = (hmap[v]+oe) - (hmap[t]+te)
88+
dy = (v[0]-t[0])*ns_range
89+
dx = (v[1]-t[1])*ew_range
90+
distance_to_viewpoint = math.sqrt(dx*dx + dy*dy)
91+
visibility_grid[t] = _get_vertical_ang(diff_elev, distance_to_viewpoint)
6892

6993

70-
def _calc_viewshed(hits, visibility_grid, H, W):
94+
def _calc_viewshed(hits, visibility_grid, H, W, hmap, vp, oe, te, ew_range, ns_range):
7195
griddim, blockdim = calc_cuda_dims((H, W))
72-
_calc_viewshed_kernel[griddim, blockdim](hits, visibility_grid, H, W)
96+
_calc_viewshed_kernel[griddim, blockdim](
97+
hits,
98+
visibility_grid,
99+
H,
100+
W,
101+
hmap,
102+
vp,
103+
oe,
104+
te,
105+
ew_range,
106+
ns_range
107+
)
73108
return 0
74109

75110

76111
@nb.cuda.jit
77112
def _generate_viewshed_rays_kernel(
78-
camera_rays, hits, vsrays, visibility_grid, H, W, vp
113+
camera_rays, hits, vsrays, H, W, vp
79114
):
80115
i, j = nb.cuda.grid(2)
81116
if i >= 0 and i < H and j >= 0 and j < W:
@@ -124,11 +159,6 @@ def _generate_viewshed_rays_kernel(
124159
# normalize the direction (vector v)
125160
new_dir = mul(new_dir, 1/length)
126161

127-
# cosine of the angle between n and v
128-
cosine = dot(norm, new_dir)
129-
theta = math.acos(cosine) # Cosine angle in radians
130-
theta = (180*theta)/math.pi # Cosine angle in degrees
131-
132162
# prepare a viewshed ray to cast to determine visibility
133163
vsray = vsrays[i, j]
134164
vsray[0] = new_origin[0]
@@ -140,13 +170,11 @@ def _generate_viewshed_rays_kernel(
140170
vsray[6] = new_dir[2]
141171
vsray[7] = length
142172

143-
visibility_grid[i, j] = theta
144-
145173

146-
def _generate_viewshed_rays(rays, hits, vsrays, visibility_grid, H, W, vp):
174+
def _generate_viewshed_rays(rays, hits, vsrays, H, W, vp):
147175
griddim, blockdim = calc_cuda_dims((H, W))
148176
_generate_viewshed_rays_kernel[griddim, blockdim](
149-
rays, hits, vsrays, visibility_grid, H, W, vp)
177+
rays, hits, vsrays, H, W, vp)
150178
return 0
151179

152180

@@ -157,6 +185,7 @@ def _viewshed_rt(
157185
y: Union[int, float],
158186
observer_elev: float,
159187
target_elev: float,
188+
scale: float,
160189
) -> xr.DataArray:
161190

162191
H, W = raster.shape
@@ -183,6 +212,12 @@ def _viewshed_rt(
183212
y_view = np.where(y_coords == y)[0][0]
184213
x_view = np.where(x_coords == x)[0][0]
185214

215+
y_range = (y_coords[0], y_coords[-1])
216+
x_range = (x_coords[0], x_coords[-1])
217+
218+
ew_res = (x_range[1] - x_range[0]) / (W - 1)
219+
ns_res = (y_range[1] - y_range[0]) / (H - 1)
220+
186221
# Device buffers
187222
d_rays = cupy.empty((H, W, 8), np.float32)
188223
d_hits = cupy.empty((H, W, 4), np.float32)
@@ -196,14 +231,25 @@ def _viewshed_rt(
196231
if res:
197232
raise RuntimeError(f"Failed trace 1, error code: {res}")
198233

199-
_generate_viewshed_rays(d_rays, d_hits, d_vsrays, d_visgrid, H, W,
200-
(x_view, y_view, observer_elev, target_elev))
234+
_generate_viewshed_rays(d_rays, d_hits, d_vsrays, H, W,
235+
(x_view, y_view, observer_elev*scale, target_elev*scale))
201236
device.synchronize()
202237
res = optix.trace(d_vsrays, d_hits, W*H)
203238
if res:
204239
raise RuntimeError(f"Failed trace 2, error code: {res}")
205240

206-
_calc_viewshed(d_hits, d_visgrid, H, W)
241+
_calc_viewshed(
242+
d_hits,
243+
d_visgrid,
244+
H,
245+
W,
246+
raster.data,
247+
(y_view, x_view),
248+
observer_elev,
249+
target_elev,
250+
ew_res,
251+
ns_res
252+
)
207253

208254
if isinstance(raster.data, np.ndarray):
209255
visgrid = cupy.asnumpy(d_visgrid)
@@ -233,4 +279,4 @@ def viewshed_gpu(
233279
optix = RTX()
234280
scale = create_triangulation(raster, optix)
235281

236-
return _viewshed_rt(raster, optix, x, y, observer_elev*scale, target_elev*scale)
282+
return _viewshed_rt(raster, optix, x, y, observer_elev, target_elev, scale)

0 commit comments

Comments
 (0)