Skip to content

Commit b965e5c

Browse files
authored
Fix DInf and FD8 methods (#53)
* Fix DInf and FD8 methods. * Remove DEMON draft. * Duplicate dep. * Fix cherry pick. * Fix docs.
1 parent f9fe497 commit b965e5c

File tree

8 files changed

+125
-38
lines changed

8 files changed

+125
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ All notable changes to this project will be documented in this file.
6060
- Fixed bug in TPI
6161

6262
## [0.3.1] - 2022-06-15
63-
###
63+
### Added
6464
- Added `skewness` filter
6565

6666
## [0.3.0] - 2022-02-01

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Geomorphometry"
22
uuid = "714e3e49-7933-471a-9334-4a6a65a92f36"
33
authors = ["Maarten Pronk <[email protected]>", "Deltares"]
4-
version = "0.7.0"
4+
version = "0.7.1"
55

66
[deps]
77
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
@@ -20,8 +20,8 @@ Stencils = "264155e8-78a8-466a-aa59-c9b28c34d21a"
2020
[weakdeps]
2121
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
2222
Eikonal = "a6aab1ba-8f88-4217-b671-4d0788596809"
23-
GeoArrays = "2fb1d81b-e6a0-5fc5-82e6-8e06903437ab"
2423
Rasters = "a3a2b9e3-a471-40c9-b274-f788e487c689"
24+
GeoArrays = "2fb1d81b-e6a0-5fc5-82e6-8e06903437ab"
2525

2626
[extensions]
2727
GeomorphometryEikonalExt = "Eikonal"

docs/Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[deps]
22
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
3+
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
34
ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
45
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
56
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
@@ -12,4 +13,5 @@ Rasters = "a3a2b9e3-a471-40c9-b274-f788e487c689"
1213
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
1314

1415
[compat]
15-
ColorSchemes = "3.29"
16+
Makie = "0.22"
17+

docs/src/concepts.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dtm = coalesce(r, NaN)
2020
== Rasters (projected)
2121
```@example plots
2222
r = Raster("saba.tif")
23+
r = coalesce.(r, NaN) # hide
2324
Geomorphometry.cellsize(r)
2425
```
2526
```@example plots
@@ -29,7 +30,7 @@ heatmap(multihillshade(r))
2930
== GeoArrays (projected)
3031
```@example plots
3132
r = GeoArrays.read("saba.tif")
32-
r = coalesce(r, NaN)
33+
r = coalesce(r, NaN) # hide
3334
Geomorphometry.cellsize(r)
3435
```
3536
```@example plots
@@ -39,6 +40,7 @@ heatmap(multihillshade(r))
3940
== Rasters (geographic)
4041
```@example plots
4142
r = Raster("Copernicus_DSM_10_N52_00_E004_00_DEM.tif")
43+
r = coalesce.(r, NaN) # hide
4244
Geomorphometry.cellsize(r)
4345
```
4446
```@example plots
@@ -48,7 +50,7 @@ heatmap(multihillshade(r))
4850
== GeoArrays (geographic)
4951
```@example plots
5052
r = GeoArrays.read("Copernicus_DSM_10_N52_00_E004_00_DEM.tif")
51-
r = coalesce(r, NaN)
53+
r = coalesce(r, NaN) # hide
5254
Geomorphometry.cellsize(r)
5355
```
5456
```@example plots

docs/src/guide.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
# Guides
2-
3-
##

docs/src/refs.bib

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,19 @@ @article{bartelsThresholdfreeObjectGround2010
254254
langid = {english},
255255
keywords = {_tablet,DSM,DTM,GIS,LIDAR,Remote sensing,Skewness Balancing}
256256
}
257+
@article{costa-cabralDigitalElevationModel1994,
258+
title = {Digital {{Elevation Model Networks}} ({{DEMON}}): {{A}} Model of Flow over Hillslopes for Computation of Contributing and Dispersal Areas},
259+
shorttitle = {Digital {{Elevation Model Networks}} ({{DEMON}})},
260+
author = {{Costa-Cabral}, Mariza C. and Burges, Stephen J.},
261+
year = {1994},
262+
journal = {Water Resources Research},
263+
volume = {30},
264+
number = {6},
265+
pages = {1681--1692},
266+
issn = {1944-7973},
267+
doi = {10.1029/93WR03512},
268+
urldate = {2025-06-11},
269+
abstract = {Current algorithms for computing contributing areas from a rectangular grid digital elevation model (DEM) use the flow-routing model of O'Callaghan and Mark (1984), which has two major restrictions: (1) flow which originates over a two-dimensional pixel is treated as a point source (nondimensional) and is projected downslope by a line (one dimensional) (Moore and Grayson, 1991), and (2) the flow direction in each pixel is restricted to eight possibilities. We show that large errors in the computed contributing areas result for any terrain topography: divergent, convergent, or planar. We present a new model, called digital elevation model networks (DEMON), which avoids the above problems by representing flow in two dimensions and directed by aspect. DEMON allows computation of both contributing and dispersal areas. DEMON offers the main advantage of contour-based models (e.g., Moore et al., 1988), the representation of varying flow width over nonplanar topography, while having the convenience of using rectangular grid DEMs.},
270+
copyright = {Copyright 1994 by the American Geophysical Union.},
271+
langid = {english},
272+
}

src/hydrology.jl

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ function filldepressions!(dem::AbstractMatrix, queued = falses(size(dem)))
6868
return dem
6969
end
7070

71+
nbs = CartesianIndex.([
72+
(-1, -1),
73+
(-1, 1),
74+
(1, -1),
75+
(1, 1),
76+
(-1, 0),
77+
(0, -1),
78+
(0, 1),
79+
(1, 0),
80+
])
81+
7182
function watersheds(dem::AbstractMatrix, queued = falses(size(dem)))
7283
open = PriorityQueue{CartesianIndex{2}, eltype(dem)}()
7384
pit = DataStructures.Queue{CartesianIndex{2}}()
@@ -91,7 +102,10 @@ function watersheds(dem::AbstractMatrix, queued = falses(size(dem)))
91102
labels[cell] = label
92103
label += 1
93104
end
94-
for ncell in max(I_first, cell - Δ):min(I_last, cell + Δ)
105+
# for ncell in max(I_first, cell - Δ):min(I_last, cell + Δ)
106+
for nb in nbs
107+
ncell = cell + nb
108+
ncell in R || continue
95109
(queued[ncell] || ncell == cell) && continue
96110
queued[ncell] = true
97111
if dem[ncell] <= dem[cell]
@@ -116,31 +130,54 @@ function watersheds(dem::AbstractMatrix, queued = falses(size(dem)))
116130
return dem, labels
117131
end
118132

119-
const nb =
133+
# 1 2 3
134+
# 4 5 6
135+
# 7 8 9
136+
137+
nbb2 =
120138
CartesianIndex.([
121-
(0, -1),
122-
(-1, -1),
123-
(-1, 0),
124-
(-1, 1),
125-
(0, 1),
126-
(1, 1),
127-
(1, 0),
128-
(1, -1),
129-
(0, -1),
130-
(-1, -1),
139+
(-1, 0), # N - 270°
140+
(-1, 1), # NE - 315°
141+
(0, 1), # E - 0°
142+
(1, 1), # SE - 45°
143+
(1, 0), # S - 90°
144+
(1, -1), # SW - 135°
145+
(0, -1), # W - 180°
146+
(-1, -1), # NW - 225°
147+
(-1, 0), # N - 270°
148+
(-1, 1), # NE - 315°
149+
(0, 1), # E - 0°
150+
(1, 1), # SE - 45°
131151
])
132152

133153
function infc(aspect)
134-
aspect = (aspect + 360) % 360
135-
i = round(Int, aspect / 45) + 1
136-
return nb[i], nb[i + 1]
154+
# Normalize aspect to 0-360 range
155+
aspect = mod(aspect+90, 360)
156+
157+
# Convert aspect to index (0° = North, clockwise)
158+
# Each direction covers 45° sector
159+
sector = floor(Int, aspect / 45)
160+
161+
# Get the two neighboring directions
162+
dir1_idx = sector + 1
163+
dir2_idx = (sector + 1) % 8 + 1
164+
165+
return nbb2[dir1_idx], nbb2[dir2_idx]
137166
end
138167

139168
function infa(aspect)
140-
aspect = (aspect + 360) % 360
141-
a1 = aspect % 45
142-
a2 = 45 - a1
143-
return a2 / 45, a1 / 45
169+
# Normalize aspect to 0-360 range
170+
aspect = mod(aspect, 360)
171+
172+
# Find position within 45° sector
173+
sector_angle = mod(aspect, 45)
174+
175+
# Calculate weights for the two directions
176+
# Weight decreases linearly from 1 to 0 as we move away from the direction
177+
weight2 = sector_angle / 45
178+
weight1 = 1 - weight2
179+
180+
return weight1, weight2
144181
end
145182

146183
const directions = centered([1 2 3; 4 5 6; 7 8 9])
@@ -157,20 +194,21 @@ function flowaccumulation(
157194
method = D8(),
158195
cellsize = cellsize(dem),
159196
)
160-
flowaccumulation!(dem, copy(closed); method, cellsize)
197+
acc = similar(dem, Float32)
198+
acc .= abs(cellsize[1] * cellsize[2])
199+
flowaccumulation!(dem, acc, copy(closed); method, cellsize)
161200
end
162201

163202
function flowaccumulation!(
164203
dem::AbstractMatrix,
204+
acc::AbstractMatrix{<:Real},
165205
closed = falses(size(dem));
166206
method = D8(),
167207
cellsize = cellsize(dem),
168208
)
169209
dir = fill(CartesianIndex{2}(0, 0), size(dem))
170210
order = ones(Int64, length(closed) - sum(closed))
171211

172-
acc = similar(dem, Float32)
173-
acc .= abs(cellsize[1] * cellsize[2])
174212
output = similar(dem, eltype(directions))
175213

176214
open = PriorityQueue{CartesianIndex{2}, eltype(dem)}()
@@ -188,7 +226,10 @@ function flowaccumulation!(
188226
cell = dequeue!(open)
189227
order[i] = L[cell]
190228
i += 1
191-
for ncell in max(I_first, cell - Δ):min(I_last, cell + Δ)
229+
# for ncell in max(I_first, cell - Δ):min(I_last, cell + Δ)
230+
for nb in nbs
231+
ncell = cell + nb
232+
ncell in R || continue
192233
# skip visited and center cells
193234
(closed[ncell] || ncell == cell) && continue
194235

@@ -210,30 +251,52 @@ function _accumulate!(::D8, acc, order, dir, R, dem, cellsize)
210251
end
211252
end
212253
function _accumulate!(::DInf, acc, order, dir, R, dem, cellsize)
213-
asp = aspect(dem; method = Horn())
254+
asp = aspect(dem; method = Horn(), cellsize=abs.(cellsize))
255+
visited = falses(size(acc))
256+
214257
for i in reverse(order)
215258
aspect = asp[i]
216259

217260
if !isfinite(aspect)
218261
acc[R[i] + dir[i]] += acc[i]
262+
visited[i] = true
219263
continue
220264
end
221265

222266
a, b = infc(aspect)
223-
aa, ab = infa(aspect)
267+
aa, bb = infa(aspect)
268+
269+
# a, b = lookup[dir[i] + CartesianIndex(2, 2)]
224270

225271
# Depression
226-
if a != dir[i] && b != dir[i]
272+
if (a != dir[i] && b != dir[i])
227273
acc[R[i] + dir[i]] += acc[i]
274+
# acc[R[i] + dir[i]] = 5000
275+
visited[i] = true
228276
continue
229277
end
230278

279+
# Scale flows correctly at the edges
280+
if !(R[i] + a in R) || visited[R[i] + a]
281+
aa = 0
282+
bb = 1
283+
end
284+
if !(R[i] + b in R) || visited[R[i] + b]
285+
aa = 1
286+
bb = 0
287+
end
288+
231289
if R[i] + a in R
232290
acc[R[i] + a] += acc[i] * aa
233291
end
234292
if R[i] + b in R
235-
acc[R[i] + b] += acc[i] * ab
293+
acc[R[i] + b] += acc[i] * bb
294+
end
295+
if visited[R[i] + a] && visited[R[i] + b]
296+
error()
297+
acc[R[i] + dir[i]] += acc[i]
236298
end
299+
visited[i] = true
237300
end
238301
end
239302

@@ -256,6 +319,9 @@ function _accumulate!(fd8::FD8, acc, order, dir, R, dem, cellsize)
256319
δxy δx δxy
257320
]
258321

322+
visited = falses(size(acc))
323+
nb = vec(collect(CartesianIndices(dists)).-CartesianIndex(2,2))
324+
259325
weights = zeros(size(contour_lengths))
260326
Σw = 0.0
261327
for i in reverse(order)
@@ -265,18 +331,19 @@ function _accumulate!(fd8::FD8, acc, order, dir, R, dem, cellsize)
265331
ri == 5 && continue
266332
I = R[i] + nb[ri]
267333
I in R || continue
334+
visited[I] && continue
268335
diff = dem[R[i]] - dem[I]
269336
if diff < 0 || isnan(diff) # neighbor is higher
270-
weights[ri] = 0.0
271337
continue
272338
end
273339
# TODO Check whether this diff/dist is good enough
274340
weight = (diff / dist * contour_lengths[ri])^fd8.p
275341
weights[ri] = weight
276342
Σw += weight
277343
end
278-
if iszero(Σw) # depression
344+
if iszero(Σw) || iszero(weights[CartesianIndex(2,2)+dir[i]])
279345
acc[R[i] + dir[i]] += acc[i]
346+
visited[i] = true
280347
continue
281348
end
282349
for (ri, weight) in enumerate(weights)
@@ -285,6 +352,8 @@ function _accumulate!(fd8::FD8, acc, order, dir, R, dem, cellsize)
285352
I in R || continue
286353
acc[I] += acc[i] * (weight / Σw)
287354
end
355+
visited[i] = true
356+
fill!(weights, 0)
288357
end
289358
end
290359

src/relative.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ end
4444
TRI(dem::AbstractMatrix{<:Real})
4545
4646
TRI stands for Terrain Ruggedness Index, which measures the difference between a central pixel and its surrounding cells.
47-
This algorithm uses the square root of the sum of the square of the difference between a central pixel and its surrounding cells.
47+
This algorithm uses the square root of the sum of the square of the absolute difference between a central pixel and its surrounding cells.
4848
This is recommended for terrestrial use cases.
4949
"""
5050
function TRI(dem::AbstractMatrix{<:Real}; normalize = false, squared = true)

0 commit comments

Comments
 (0)