-
Notifications
You must be signed in to change notification settings - Fork 0
/
criteria.jl
181 lines (148 loc) · 5.26 KB
/
criteria.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
using Base.Threads
using Dates
using StructTypes
import Rasters: Between
using Oxygen: json
include("query_parser.jl")
include("tiles.jl")
const REEF_TYPE = [:slopes, :flats]
# HTTP response headers for COG files
const COG_HEADERS = [
"Cache-Control" => "max-age=86400, no-transform"
]
function criteria_data_map()
# TODO: Load from config?
return OrderedDict(
:Depth => "_bathy",
:Benthic => "_benthic",
:Geomorphic => "_geomorphic",
:Slope => "_slope",
:Turbidity => "_turbid",
:WavesHs => "_waves_Hs",
:WavesTp => "_waves_Tp",
:Rugosity => "_rugosity",
:ValidSlopes => "_valid_slopes",
:ValidFlats => "_valid_flats"
)
end
struct RegionalCriteria{T}
stack::RasterStack
valid_slopes::T
valid_flats::T
end
function valid_slope_lon_inds(reg::RegionalCriteria)
return reg.valid_slopes.lon_idx
end
function valid_slope_lat_inds(reg::RegionalCriteria)
return reg.valid_slopes.lat_idx
end
function valid_flat_lon_inds(reg::RegionalCriteria)
return reg.valid_flats.lon_idx
end
function valid_flat_lat_inds(reg::RegionalCriteria)
return reg.valid_flats.lat_idx
end
function Base.show(io::IO, ::MIME"text/plain", z::RegionalCriteria)
# TODO: Include the extent
println("""
Criteria: $(names(z.stack))
Number of valid slope locations: $(nrow(z.valid_slopes))
Number of valid flat locations: $(nrow(z.valid_flats))
""")
return nothing
end
struct CriteriaBounds{F<:Function}
name::Symbol
lower_bound::Float32
upper_bound::Float32
rule::F
function CriteriaBounds(name::String, lb::S, ub::S)::CriteriaBounds where {S<:String}
lower_bound::Float32 = parse(Float32, lb)
upper_bound::Float32 = parse(Float32, ub)
func = (x) -> lower_bound .<= x .<= upper_bound
return new{Function}(Symbol(name), lower_bound, upper_bound, func)
end
end
# Define struct type definition to auto-serialize/deserialize to JSON
StructTypes.StructType(::Type{CriteriaBounds}) = StructTypes.Struct()
"""
criteria_middleware(handle)
Creates middleware that parses a criteria query before reaching an endpoint
# Example
`https://somewhere:8000/suitability/assess/region-name/reeftype?criteria_names=Depth,Slope&lb=-9.0,0.0&ub=-2.0,40`
"""
function criteria_middleware(handle)
function (req)
fd = queryparams(req)
criteria_names = string.(split(fd["criteria_names"], ","))
lbs = string.(split(fd["lb"], ","))
ubs = string.(split(fd["ub"], ","))
return handle(CriteriaBounds.(criteria_names, lbs, ubs))
end
end
# Not sure why, but this ain't working
# reeftype_router = router("/suitability", middleware=[criteria_middleware], tags=["suitability"])
function setup_region_routes(config, auth)
reg_assess_data = setup_regional_data(config)
@get auth("/assess/{reg}/{rtype}") function (req::Request, reg::String, rtype::String)
qp = queryparams(req)
file_id = string(hash(qp))
mask_temp_path = _cache_location(config)
mask_path = joinpath(mask_temp_path, file_id * ".tiff")
if isfile(mask_path)
return file(mask_path; headers=COG_HEADERS)
end
criteria_names, lbs, ubs = remove_rugosity(reg, parse_criteria_query(qp)...)
# Otherwise, create the file
@debug "$(now()) : Assessing criteria"
assess = reg_assess_data[reg]
mask_data = make_threshold_mask(
assess,
Symbol(rtype),
CriteriaBounds.(criteria_names, lbs, ubs)
)
@debug "$(now()) : Running on thread $(threadid())"
@debug "Writing to $(mask_path)"
# Writing time: ~10-25 seconds
Rasters.write(
mask_path,
UInt8.(mask_data);
ext=".tiff",
source="gdal",
driver="COG",
options=Dict{String,String}(
"COMPRESS" => "DEFLATE",
"SPARSE_OK" => "TRUE",
"OVERVIEW_COUNT" => "5",
"BLOCKSIZE" => "256",
"NUM_THREADS" => n_gdal_threads(config)
),
force=true
)
return file(mask_path; headers=COG_HEADERS)
end
@get auth("/bounds/{reg}") function (req::Request, reg::String)
rst_stack = reg_assess_data[reg].stack
return json(Rasters.bounds(rst_stack))
end
# Form for testing/dev
# https:://somewhere:8000/suitability/assess/region-name/reeftype?criteria_names=Depth,Slope&lb=-9.0,0.0&ub=-2.0,40
@get "/" function ()
return html("""
<form action="/assess/Cairns-Cooktown/slopes" method="post">
<label for="criteria_names">Criteria Names:</label><br>
<input type="text" id="criteria_names" name="criteria"><br>
<label for="lb">Lower Bound:</label><br>
<input type="text" id="lb" name="lower_bound"><br><br>
<label for="ub">Upper Bound:</label><br>
<input type="text" id="ub" name="upper_bound"><br><br>
<input type="submit" value="Submit">
</form>
""")
end
# Parse the form data and return it
@post auth("/form") function (req)
data = formdata(req)
return data
end
end