Skip to content

Commit 2878771

Browse files
authored
Merge pull request #131 from JuliaGeodynamics/bk-waterflow
Waterflow routing algorithm
2 parents 7bde28e + 087a273 commit 2878771

File tree

7 files changed

+245
-5
lines changed

7 files changed

+245
-5
lines changed

.typos.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
MOR = "MOR"
33
dum = "dum"
44
Shepard = "Shepard"
5+
nin = "nin"
56

67
[files]
78
extend-exclude = ["tutorials/*.pvsm"]

Project.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
name = "GeophysicalModelGenerator"
22
uuid = "3700c31b-fa53-48a6-808a-ef22d5a84742"
33
authors = ["Boris Kaus", "Marcel Thielmann"]
4-
version = "0.7.4"
4+
version = "0.7.5"
55

66
[deps]
77
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
88
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
99
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
1010
FFMPEG = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
1111
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
12+
GDAL_jll = "a7073274-a066-55f0-b90d-d619367d196c"
1213
GeoParams = "e018b62d-d9de-4a26-8697-af89c310ae38"
1314
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
1415
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
@@ -26,6 +27,7 @@ SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
2627
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2728
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2829
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
30+
WhereTheWaterFlows = "ea906314-1493-4d22-a0af-f886a20c9fba"
2931
WriteVTK = "64499a7a-5c06-52f2-abe2-ccb03c286192"
3032

3133
[weakdeps]
@@ -43,8 +45,9 @@ Colors = "0.9 - 0.12"
4345
Downloads = "1"
4446
FFMPEG = "0.4"
4547
FileIO = "1"
48+
GDAL_jll = "300.900.0 - 301.900.0"
4649
GLMakie = "0.8, 0.9, 0.10"
47-
GMT = "1"
50+
GMT = "1.0 - 1.14"
4851
GeoParams = "0.2 - 0.6"
4952
Geodesy = "1"
5053
GeometryBasics = "0.1 - 0.4"
@@ -69,4 +72,4 @@ StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
6972
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
7073

7174
[targets]
72-
test = ["Test", "GMT", "StableRNGs","GridapGmsh"]
75+
test = ["Test", "GMT", "StableRNGs", "GridapGmsh"]

ext/GLMakie_Visualisation.jl

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module GLMakie_Visualisation
33

44
using Statistics
55
using GeophysicalModelGenerator: lonlatdepth_grid, GeoData, CartData, km, AbstractGeneralGrid
6-
import GeophysicalModelGenerator: visualise
6+
import GeophysicalModelGenerator: visualise, ustrip
77

88
# We do not check `isdefined(Base, :get_extension)` as recommended since
99
# Julia v1.9.0 does not load package extensions when their dependency is
@@ -14,7 +14,9 @@ else
1414
using ..GLMakie
1515
end
1616

17-
export visualise
17+
import GLMakie: heatmap!, heatmap
18+
19+
export visualise, heatmap, heatmap!
1820

1921
println("Loading GLMakie extensions for GMG")
2022

@@ -271,5 +273,112 @@ function visualise(Data::AbstractGeneralGrid; Topography=nothing, Topo_range=not
271273
return nothing
272274
end
273275

276+
"""
277+
heatmap(x::GeoData, args...; field=:Topography, kwargs...)
278+
heatmap for a 2D GeoData object (surface)
279+
"""
280+
function heatmap(x::GeoData, args...; field=:Topography, kwargs...)
281+
@assert size(x.depth.val,3)==1
282+
@assert hasfield(typeof(x.fields), field)
283+
284+
heatmap(x.lon.val[:,1], x.lat.val[1,:], ustrip.(x.fields[field][:,:,1]), args...; kwargs...)
285+
286+
end
287+
288+
"""
289+
heatmap(x::GeoData, a::Array{_T,N}, args...; kwargs...)
290+
in-place heatmap for a 2D GeoData object (surface) with an array `a`.
291+
"""
292+
function heatmap(x::GeoData, a::Array{_T,N}, args...; kwargs...) where{_T,N}
293+
@assert size(x.depth.val,3)==1
294+
295+
if N==3
296+
heatmap(x.lon.val[:,1], x.lat.val[1,:], ustrip.(a[:,:,1]), args...; kwargs...)
297+
elseif N==2
298+
heatmap(x.lon.val[:,1], x.lat.val[1,:], ustrip.(a), args...; kwargs...)
299+
end
300+
301+
end
302+
303+
"""
304+
heatmap(x::CartData, args...; field=:Topography, kwargs...)
305+
heatmap for a 2D CartData object (surface)
306+
"""
307+
function heatmap(x::CartData, args...; field=:Topography, kwargs...)
308+
@assert size(x.z.val,3)==1
309+
@assert hasfield(typeof(x.fields), field)
310+
311+
heatmap(x.x.val[:,1], x.y.val[1,:], ustrip.(x.fields[field][:,:,1]), args...; kwargs...)
312+
313+
end
314+
315+
"""
316+
heatmap(x::CartData, a::Array{_T,N}, args...; kwargs...)
317+
in-place heatmap for a 2D CartData object (surface) with an array `a`.
318+
"""
319+
function heatmap(x::CartData, a::Array{_T,N}, args...; kwargs...) where{_T,N}
320+
@assert size(x.z.val,3)==1
321+
322+
if N==3
323+
heatmap(x.x.val[:,1], x.y.val[1,:], ustrip.(a[:,:,1]), args...; kwargs...)
324+
elseif N==2
325+
heatmap(x.x.val[:,1], x.y.val[1,:], ustrip.(a), args...; kwargs...)
326+
end
327+
328+
end
329+
330+
331+
"""
332+
heatmap!(x::GeoData, args...; field=:Topography, kwargs...)
333+
in-place heatmap for a 2D GeoData object (surface),
334+
"""
335+
function heatmap!(x::GeoData, args...; field=:Topography, kwargs...)
336+
@assert size(x.z.val,3)==1
337+
@assert hasfield(typeof(x.fields), field)
338+
339+
heatmap(x.lon.val[:,1], x.lat.val[1,:], ustrip.(x.fields[field][:,:,1]), args...; kwargs...)
340+
341+
end
342+
343+
"""
344+
heatmap!(x::GeoData, a::Array{_T,N}, args...; kwargs...)
345+
in-place heatmap for a 2D GeoData object (surface) with an array `a`.
346+
"""
347+
function heatmap!(x::GeoData, a::Array{_T,N}, args...; kwargs...) where{_T,N}
348+
@assert size(x.z.val,3)==1
349+
350+
if N==3
351+
heatmap!(x.lon.val[:,1], x.lat.val[1,:], ustrip.(a[:,:,1]), args...; kwargs...)
352+
elseif N==2
353+
heatmap!(x.lon.val[:,1], x.lat.val[1,:], ustrip.(a), args...; kwargs...)
354+
end
355+
356+
end
357+
358+
"""
359+
heatmap!(x::CartData, args...; field=:Topography, kwargs...)
360+
in-place heatmap for a 2D CartData object (surface)
361+
"""
362+
function heatmap!(x::CartData, args...; field=:Topography, kwargs...)
363+
@assert size(x.z.val,3)==1
364+
@assert hasfield(typeof(x.fields), field)
365+
366+
heatmap!(x.x.val[:,1], x.y.val[1,:], ustrip.(x.fields[field][:,:,1]), args...; kwargs...)
367+
end
368+
369+
"""
370+
heatmap!(x::CartData, a::Array{_T,N}, args...; kwargs...)
371+
in-place heatmap for a 2D CartData object (surface) with an array `a`.
372+
"""
373+
function heatmap!(x::CartData, a::Array{_T,N}, args...; kwargs...) where{_T,N}
374+
@assert size(x.z.val,3)==1
375+
376+
if N==3
377+
heatmap!(x.x.val[:,1], x.y.val[1,:], ustrip.(a[:,:,1]), args...; kwargs...)
378+
elseif N==2
379+
heatmap!(x.x.val[:,1], x.y.val[1,:], ustrip.(a[:,:]), args...; kwargs...)
380+
end
381+
382+
end
274383

275384
end

src/GeophysicalModelGenerator.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ include("event_counts.jl")
4848
include("surface_functions.jl")
4949
include("movies_from_pics.jl")
5050
include("sea_lvl.jl")
51+
include("WaterFlow.jl")
5152

5253
# Add optional routines (only activated when the packages are loaded)
5354

src/WaterFlow.jl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# These are routes to perform waterflow calculations (upstream area etc.) on a DEM
2+
3+
using WhereTheWaterFlows
4+
import WhereTheWaterFlows: waterflows
5+
6+
export waterflows
7+
8+
"""
9+
dlon, dlat = spacing(lon,lat)
10+
11+
Computes the spacing with central differences
12+
"""
13+
function spacing(lon,lat)
14+
dlon = zeros(size(lon.val)[1:2])
15+
dlat = zeros(size(lat.val)[1:2])
16+
@views dlon[2:end-1,:] = (lon.val[3:end,:,1] - lon.val[1:end-2,:,1])/2
17+
dlon[1,:] = dlon[2,:]
18+
dlon[end,:] = dlon[end-1,:]
19+
dlat[:,2:end-1] = (lat.val[:,3:end,1] - lat.val[:,1:end-2,1])/2
20+
dlat[:,1] = dlat[:,2]
21+
dlat[:,end] = dlat[:,end-1]
22+
23+
return dlon, dlat
24+
end
25+
26+
"""
27+
area_m2 = cell_area(Topo::GeoData)
28+
Returns the cell area for a Topographic dataset in m² (required for upstream area calculation)
29+
"""
30+
function cell_area(Topo::GeoData)
31+
32+
proj = ProjectionPoint(Lon=mean(Topo.lon.val[:]), Lat=mean(Topo.lat.val[:]))
33+
Topo_cart = convert2CartData(Topo, proj)
34+
dx, dy = spacing(Topo_cart.x, Topo_cart.y)
35+
36+
area_m2 = dx.*dy*1e6
37+
return area_m2
38+
end
39+
40+
41+
"""
42+
Topo_water, sinks, pits, bnds = waterflows(Topo::GeoData;
43+
flowdir_fn=WhereTheWaterFlows.d8dir_feature, feedback_fn=nothing, drain_pits=true, bnd_as_sink=true,
44+
rainfall = nothing,
45+
minsize=300)
46+
47+
Takes a GMG GeoData object of a topographic map and routes water through the grid. Optionally,
48+
you can specify `rainfall` in which case we accumulate the rain as specified in this 2D array instead of the cellarea.
49+
This allows you to, for example, sum, up water if you have variable rainfall in the area.
50+
The other options are as in the `waterflows` function of the package `WhereTheWaterFlows`.
51+
52+
Example
53+
===
54+
```julia
55+
# Download some topographic data
56+
julia> Topo = import_topo([6.5,7.3,50.2,50.6], file="@earth_relief_03s");
57+
58+
# Flow the water through the area:
59+
julia> Topo_water, sinks, pits, bnds = waterflows(Topo)
60+
julia> Topo_water
61+
GeoData
62+
size : (961, 481, 1)
63+
lon ϵ [ 6.5 : 7.3]
64+
lat ϵ [ 50.2 : 50.59999999999999]
65+
depth ϵ [ 0.045 : 0.724]
66+
fields : (:Topography, :area, :slen, :dir, :nout, :nin, :c)
67+
68+
```
69+
70+
"""
71+
function waterflows(Topo::GeoData, flowdir_fn= WhereTheWaterFlows.d8dir_feature; feedback_fn=nothing, drain_pits=true, bnd_as_sink=true, rainfall=nothing, minsize=300)
72+
73+
cellarea = cell_area(Topo)
74+
cellarea_m2 = cellarea
75+
if !isnothing(rainfall)
76+
@assert typeof(rainfall) == Array{Float64,2}
77+
cellarea = rainfall
78+
end
79+
80+
dem = Topo.depth.val[:,:,1]
81+
82+
ni = size(Topo.depth.val)
83+
area = zeros(ni)
84+
slen = zeros(Int64, ni)
85+
dir = zeros(Int8, ni)
86+
nout = zeros(Int8, ni)
87+
nin = zeros(Int8, ni)
88+
c = zeros(Int64, ni)
89+
90+
area[:,:,1], slen[:,:,1], dir[:,:,1], nout[:,:,1], nin[:,:,1], sinks, pits, c[:,:,1], bnds = waterflows(dem, cellarea, flowdir_fn;
91+
feedback_fn=feedback_fn, drain_pits=drain_pits, bnd_as_sink=bnd_as_sink)
92+
93+
catchment_large = prune_catchments(c, minsize; val=0)
94+
largest_catchment = catchment_large .== maximum(catchment_large)
95+
largest_area = copy(area)
96+
largest_area[.!largest_catchment] .= NaN
97+
98+
log10_area = log10.(area)
99+
100+
Topo_water = addfield(Topo,(;area, slen, dir, nout, nin, c, cellarea_m2, catchment_large, log10_area, largest_catchment, largest_area))
101+
return Topo_water, sinks, pits, bnds
102+
end

test/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ end
7373
end
7474

7575

76+
@testset "Waterflow" begin
77+
include("test_WaterFlow.jl")
78+
end
79+
7680
# Cleanup
7781
foreach(rm, filter(endswith(".vts"), readdir()))
7882
foreach(rm, filter(endswith(".vtu"), readdir()))

test/test_WaterFlow.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Test, GMT
2+
3+
4+
# Download some topographic data
5+
Topo = import_topo([6.5,7.3,50.2,50.6], file="@earth_relief_03s");
6+
7+
# Flow the water through the area:
8+
Topo_water, sinks, pits, bnds = waterflows(Topo)
9+
10+
@test maximum(Topo_water.fields.area) 9.309204547276944e8
11+
@test sum(Topo_water.fields.c) == 834501044
12+
@test sum(Topo_water.fields.nin) == 459361
13+
@test sum(Topo_water.fields.dir) == 2412566
14+
15+
# With rain in m3/s per cell
16+
rainfall = ones(size(Topo.lon.val[:,:,1]))*1e-3 # 2D array with rainfall per cell area
17+
Topo_water1, sinks, pits, bnds = waterflows(Topo, rainfall=rainfall)
18+
19+
@test maximum(Topo_water1.fields.area) 169.79800000000208
20+

0 commit comments

Comments
 (0)