dea-tools 0.3.6.dev41__tar.gz → 0.3.6.dev45__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dea_tools-0.3.6.dev41/dea_tools.egg-info → dea_tools-0.3.6.dev45}/PKG-INFO +1 -1
- dea_tools-0.3.6.dev45/dea_tools/app/wetlandsinsighttool.py +635 -0
- dea_tools-0.3.6.dev45/dea_tools/landcover.py +1194 -0
- dea_tools-0.3.6.dev45/dea_tools/wit_app.py +502 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45/dea_tools.egg-info}/PKG-INFO +1 -1
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools.egg-info/SOURCES.txt +2 -0
- dea_tools-0.3.6.dev41/dea_tools/landcover.py +0 -935
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/.gitignore +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/LICENSE +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/MANIFEST.in +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/README.rst +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/__init__.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/__main__.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/__init__.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/animations.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/changefilmstrips.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/crophealth.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/deacoastlines.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/geomedian.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/imageexport.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/miningrehab.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/app/widgetconstructors.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/bandindices.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/bom.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/classification.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/climate.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/coastal.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/dask.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/datahandling.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/maps.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/plotting.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/pyfes_model.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/spatial.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/temporal.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/validation.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/waterbodies.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools/wetlands.py +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools.egg-info/dependency_links.txt +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools.egg-info/requires.txt +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/dea_tools.egg-info/top_level.txt +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/index.rst +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/mock_imports.txt +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/pyproject.toml +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/setup.cfg +0 -0
- {dea_tools-0.3.6.dev41 → dea_tools-0.3.6.dev45}/setup.py +0 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Digital Earth Australia Wetlands Insight Tool widget, which can be used to interactively
|
|
3
|
+
extract a stacked line plot using the wetlands insight tool on a wetland polygon.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Import required packages
|
|
7
|
+
import fiona
|
|
8
|
+
import sys
|
|
9
|
+
import datacube
|
|
10
|
+
import warnings
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
from datacube.utils.geometry import CRS
|
|
13
|
+
from ipyleaflet import (
|
|
14
|
+
WMSLayer,
|
|
15
|
+
basemaps,
|
|
16
|
+
basemap_to_tiles,
|
|
17
|
+
Map,
|
|
18
|
+
DrawControl,
|
|
19
|
+
WidgetControl,
|
|
20
|
+
SearchControl,
|
|
21
|
+
Marker,
|
|
22
|
+
LayerGroup,
|
|
23
|
+
LayersControl,
|
|
24
|
+
GeoData,
|
|
25
|
+
)
|
|
26
|
+
from traitlets import Unicode
|
|
27
|
+
from ipywidgets import (
|
|
28
|
+
GridspecLayout,
|
|
29
|
+
Button,
|
|
30
|
+
Layout,
|
|
31
|
+
HBox,
|
|
32
|
+
VBox,
|
|
33
|
+
HTML,
|
|
34
|
+
Output,
|
|
35
|
+
)
|
|
36
|
+
import json
|
|
37
|
+
import geopandas as gpd
|
|
38
|
+
from io import BytesIO
|
|
39
|
+
import ipywidgets as widgets
|
|
40
|
+
import datetime
|
|
41
|
+
import seaborn as sns
|
|
42
|
+
|
|
43
|
+
# from shapely.geometry import box, shape
|
|
44
|
+
import matplotlib.dates as mdates
|
|
45
|
+
|
|
46
|
+
sys.path.insert(1, "../Tools/")
|
|
47
|
+
import dea_tools.app.widgetconstructors as deawidgets
|
|
48
|
+
from dea_tools.dask import create_local_dask_cluster
|
|
49
|
+
from dea_tools.wetlands import generate_low_quality_data_periods
|
|
50
|
+
from dea_tools.wit_app import WIT_drill, spatial_wit
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def make_box_layout():
|
|
54
|
+
return Layout(
|
|
55
|
+
# border='solid 1px black',
|
|
56
|
+
margin="0px 10px 10px 0px",
|
|
57
|
+
padding="5px 5px 5px 5px",
|
|
58
|
+
width="100%",
|
|
59
|
+
height="100%",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def create_expanded_button(description, button_style):
|
|
64
|
+
return Button(
|
|
65
|
+
description=description,
|
|
66
|
+
button_style=button_style,
|
|
67
|
+
layout=Layout(width="auto", height="auto"),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class wit_app(HBox):
|
|
72
|
+
|
|
73
|
+
def __init__(self):
|
|
74
|
+
super().__init__()
|
|
75
|
+
|
|
76
|
+
######################
|
|
77
|
+
# INITIAL ATTRIBUTES #
|
|
78
|
+
######################
|
|
79
|
+
|
|
80
|
+
enddate = datetime.datetime.today()
|
|
81
|
+
startdate = datetime.datetime(
|
|
82
|
+
year=enddate.year - 1, month=enddate.month, day=enddate.day
|
|
83
|
+
)
|
|
84
|
+
self.startdate = startdate.strftime("%Y-%m-%d")
|
|
85
|
+
self.enddate = enddate.strftime("%Y-%m-%d")
|
|
86
|
+
self.wetland_name = "example WIT"
|
|
87
|
+
# self.out_csv = "example_WIT.csv"
|
|
88
|
+
self.out_plot = False
|
|
89
|
+
self.product_list = [
|
|
90
|
+
("ESRI World Imagery", "none"),
|
|
91
|
+
("Open Street Map", "open_street_map"),
|
|
92
|
+
]
|
|
93
|
+
self.product = self.product_list[0][1]
|
|
94
|
+
self.target = None
|
|
95
|
+
self.action = None
|
|
96
|
+
self.gdf_drawn = None
|
|
97
|
+
self.gdf_uploaded = None
|
|
98
|
+
self.max_size = False
|
|
99
|
+
self.spatial_wit = False
|
|
100
|
+
|
|
101
|
+
##################
|
|
102
|
+
# HEADER FOR APP #
|
|
103
|
+
##################
|
|
104
|
+
|
|
105
|
+
# Create the Header widget
|
|
106
|
+
header_title_text = "<h3>Digital Earth Australia Wetlands Insight Tool </h3>"
|
|
107
|
+
instruction_text = "Select parameters and draw a polygon on the map to extract a stacked line plot for a given area. Alternatively, <b>upload a shapefile or a GeoJSON</b> to extract a stacked line plot for your wetland of interest polygon."
|
|
108
|
+
self.header = deawidgets.create_html(
|
|
109
|
+
f"{header_title_text}<p>{instruction_text}</p>"
|
|
110
|
+
)
|
|
111
|
+
self.header.layout = make_box_layout()
|
|
112
|
+
|
|
113
|
+
#####################################
|
|
114
|
+
# HANDLER FUNCTION FOR DRAW CONTROL #
|
|
115
|
+
#####################################
|
|
116
|
+
|
|
117
|
+
# Define the action to take once something is drawn on the map
|
|
118
|
+
def update_geojson(target, action, geo_json):
|
|
119
|
+
|
|
120
|
+
# Remove previously uploaded data if present
|
|
121
|
+
self.gdf_uploaded = None
|
|
122
|
+
fileupload_wetlands._counter = 0
|
|
123
|
+
|
|
124
|
+
# Get data from action
|
|
125
|
+
self.action = action
|
|
126
|
+
|
|
127
|
+
# Convert data to geopandas
|
|
128
|
+
json_data = json.dumps(geo_json)
|
|
129
|
+
binary_data = json_data.encode()
|
|
130
|
+
io = BytesIO(binary_data)
|
|
131
|
+
io.seek(0)
|
|
132
|
+
gdf = gpd.read_file(io)
|
|
133
|
+
gdf.crs = "EPSG:4326"
|
|
134
|
+
|
|
135
|
+
# Convert to Albers and compute area
|
|
136
|
+
gdf_drawn_albers = gdf.copy().to_crs("EPSG:3577")
|
|
137
|
+
m2_per_km2 = 10**6
|
|
138
|
+
area = gdf_drawn_albers.area.values[0] / m2_per_km2
|
|
139
|
+
polyarea_label = "Total polygon area"
|
|
140
|
+
polyarea_text = f"<b>{polyarea_label}</b>: {area:.2f} km<sup>2</sup>"
|
|
141
|
+
|
|
142
|
+
# Test area size
|
|
143
|
+
if self.max_size:
|
|
144
|
+
confirmation_text = '<span style="color: #33cc33"> <b>(Overriding maximum size limit; use with caution as may lead to memory issues)</b></span>'
|
|
145
|
+
self.header.value = (
|
|
146
|
+
header_title_text + polyarea_text + confirmation_text
|
|
147
|
+
)
|
|
148
|
+
self.gdf_drawn = gdf
|
|
149
|
+
elif area <= 2000:
|
|
150
|
+
confirmation_text = '<span style="color: #33cc33"> <b>(Area to extract falls within recommended limit)</b></span>'
|
|
151
|
+
self.header.value = (
|
|
152
|
+
header_title_text + polyarea_text + confirmation_text
|
|
153
|
+
)
|
|
154
|
+
self.gdf_drawn = gdf
|
|
155
|
+
else:
|
|
156
|
+
warning_text = '<span style="color: #ff5050"> <b>(Area to extract is too large, please update your polygon)</b></span>'
|
|
157
|
+
self.header.value = header_title_text + polyarea_text + warning_text
|
|
158
|
+
self.gdf_drawn = None
|
|
159
|
+
|
|
160
|
+
###########################
|
|
161
|
+
# WIDGETS FOR APP OUTPUTS #
|
|
162
|
+
###########################
|
|
163
|
+
|
|
164
|
+
self.dask_client = Output(layout=make_box_layout())
|
|
165
|
+
self.progress_bar = Output(layout=make_box_layout())
|
|
166
|
+
self.wit_plot = Output(layout=make_box_layout())
|
|
167
|
+
self.progress_header = deawidgets.create_html("")
|
|
168
|
+
|
|
169
|
+
#########################################
|
|
170
|
+
# MAP WIDGET, DRAWING TOOLS, WMS LAYERS #
|
|
171
|
+
#########################################
|
|
172
|
+
|
|
173
|
+
# Create drawing tools
|
|
174
|
+
desired_drawtools = ["rectangle", "polygon"]
|
|
175
|
+
draw_control = deawidgets.create_drawcontrol(desired_drawtools)
|
|
176
|
+
|
|
177
|
+
# Begin by displaying an empty layer group, and update the group with desired WMS on interaction.
|
|
178
|
+
self.map_layers = LayerGroup(layers=())
|
|
179
|
+
self.map_layers.name = "Map Overlays"
|
|
180
|
+
|
|
181
|
+
# Create map widget
|
|
182
|
+
self.m = deawidgets.create_map(
|
|
183
|
+
map_center=(-28, 135), zoom_level=4, basemap=basemaps.Esri.WorldImagery
|
|
184
|
+
)
|
|
185
|
+
self.m.layout = make_box_layout()
|
|
186
|
+
|
|
187
|
+
# Add tools to map widget
|
|
188
|
+
self.m.add_control(draw_control)
|
|
189
|
+
self.m.add_control(
|
|
190
|
+
SearchControl(
|
|
191
|
+
position="topleft",
|
|
192
|
+
url="https://nominatim.openstreetmap.org/search?format=json&q={s}",
|
|
193
|
+
zoom=13, # 'Village / Suburb' level zoom
|
|
194
|
+
marker=Marker(draggable=False),
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
self.m.add_layer(self.map_layers)
|
|
198
|
+
|
|
199
|
+
# Store current basemap for future use
|
|
200
|
+
self.basemap = self.m.basemap
|
|
201
|
+
|
|
202
|
+
############################
|
|
203
|
+
# WIDGETS FOR APP CONTROLS #
|
|
204
|
+
############################
|
|
205
|
+
|
|
206
|
+
# Create parameter widgets
|
|
207
|
+
startdate_picker = deawidgets.create_datepicker(
|
|
208
|
+
value=startdate,
|
|
209
|
+
)
|
|
210
|
+
enddate_picker = deawidgets.create_datepicker(
|
|
211
|
+
value=enddate,
|
|
212
|
+
)
|
|
213
|
+
wetland_name = deawidgets.create_inputtext(self.wetland_name, self.wetland_name)
|
|
214
|
+
# output_csv = deawidgets.create_inputtext(self.out_csv, self.out_csv)
|
|
215
|
+
output_plot = deawidgets.create_checkbox(self.out_plot, "Figure (.png)")
|
|
216
|
+
deaoverlay_dropdown = deawidgets.create_dropdown(
|
|
217
|
+
self.product_list, self.product_list[0][1]
|
|
218
|
+
)
|
|
219
|
+
run_button = create_expanded_button("Run", "info")
|
|
220
|
+
fileupload_wetlands = widgets.FileUpload(accept="", multiple=True)
|
|
221
|
+
|
|
222
|
+
# Expandable advanced section
|
|
223
|
+
max_size = deawidgets.create_checkbox(
|
|
224
|
+
self.max_size, "Enable", layout={"width": "95%"}
|
|
225
|
+
)
|
|
226
|
+
output_spatial_wit = deawidgets.create_checkbox(
|
|
227
|
+
self.spatial_wit, "Animation (.gif)"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
####################################
|
|
231
|
+
# UPDATE FUNCTIONS FOR EACH WIDGET #
|
|
232
|
+
####################################
|
|
233
|
+
|
|
234
|
+
# Run update functions whenever various widgets are changed.
|
|
235
|
+
startdate_picker.observe(self.update_startdate, "value")
|
|
236
|
+
enddate_picker.observe(self.update_enddate, "value")
|
|
237
|
+
wetland_name.observe(self.update_wetlandname, "value")
|
|
238
|
+
# output_csv.observe(self.update_outputcsv, "value")
|
|
239
|
+
output_plot.observe(self.update_outputplot, "value")
|
|
240
|
+
deaoverlay_dropdown.observe(self.update_deaoverlay, "value")
|
|
241
|
+
run_button.on_click(self.run_app)
|
|
242
|
+
draw_control.on_draw(update_geojson)
|
|
243
|
+
fileupload_wetlands.observe(self.update_fileupload_wetlands, "value")
|
|
244
|
+
max_size.observe(self.update_maxsize, "value")
|
|
245
|
+
output_spatial_wit.observe(self.update_outputspatialwit, "value")
|
|
246
|
+
|
|
247
|
+
##################################
|
|
248
|
+
# COLLECTION OF ALL APP CONTROLS #
|
|
249
|
+
##################################
|
|
250
|
+
expand_box = VBox(
|
|
251
|
+
[
|
|
252
|
+
HTML(
|
|
253
|
+
"<b>Override maximum size limit:</b></br> (use with caution; may cause memory issues/crashes)"
|
|
254
|
+
),
|
|
255
|
+
max_size,
|
|
256
|
+
HTML("<b>Spatial WIT animation:<b/>"),
|
|
257
|
+
output_spatial_wit,
|
|
258
|
+
]
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
expand = widgets.Accordion(
|
|
262
|
+
children=[expand_box],
|
|
263
|
+
selected_index=None,
|
|
264
|
+
)
|
|
265
|
+
expand.set_title(0, "Advanced")
|
|
266
|
+
|
|
267
|
+
parameter_selection = VBox(
|
|
268
|
+
[
|
|
269
|
+
HTML("<b>Start Date:</b>"),
|
|
270
|
+
startdate_picker,
|
|
271
|
+
HTML("<b>End Date:</b>"),
|
|
272
|
+
enddate_picker,
|
|
273
|
+
HTML("<b>Wetland Name:</b>"),
|
|
274
|
+
wetland_name,
|
|
275
|
+
# HTML("<b>Output CSV:</b>"),
|
|
276
|
+
# output_csv,
|
|
277
|
+
HTML("<b>Output Plot:</b>"),
|
|
278
|
+
output_plot,
|
|
279
|
+
HTML(
|
|
280
|
+
"</br><i><b>Upload Polygon:</b></br>Upload a GeoJSON or"
|
|
281
|
+
" Shapefile (<5 mb) containing a wetland polygon.</i>"
|
|
282
|
+
),
|
|
283
|
+
fileupload_wetlands,
|
|
284
|
+
HTML("</br>"),
|
|
285
|
+
expand,
|
|
286
|
+
]
|
|
287
|
+
)
|
|
288
|
+
map_selection = VBox(
|
|
289
|
+
[
|
|
290
|
+
HTML("</br><b>Map overlay:</b>"),
|
|
291
|
+
deaoverlay_dropdown,
|
|
292
|
+
]
|
|
293
|
+
)
|
|
294
|
+
parameter_selection.layout = make_box_layout()
|
|
295
|
+
map_selection.layout = make_box_layout()
|
|
296
|
+
|
|
297
|
+
###############################
|
|
298
|
+
# SPECIFICATION OF APP LAYOUT #
|
|
299
|
+
###############################
|
|
300
|
+
|
|
301
|
+
# 0 1 2 3 4 5 6 7 8 9
|
|
302
|
+
# ---------------------------------------------
|
|
303
|
+
# 0 | Header | Map sel. |
|
|
304
|
+
# ---------------------------------------------
|
|
305
|
+
# 1 | Params | |
|
|
306
|
+
# 2 | | |
|
|
307
|
+
# 3 | | |
|
|
308
|
+
# 4 | | Map |
|
|
309
|
+
# 5 | | |
|
|
310
|
+
# 6 | | |
|
|
311
|
+
# ---------- |
|
|
312
|
+
# 7 | Run | |
|
|
313
|
+
# ---------------------------------------------
|
|
314
|
+
# 8 | Status info |
|
|
315
|
+
# ---------------------------------------------
|
|
316
|
+
# 9 | |
|
|
317
|
+
# 10 | Output/figure |
|
|
318
|
+
# 11 | |
|
|
319
|
+
# 12 | ------------------------------------------|
|
|
320
|
+
|
|
321
|
+
# Create the layout #[rowspan, colspan]
|
|
322
|
+
grid = GridspecLayout(13, 10, height="1350px", width="auto")
|
|
323
|
+
|
|
324
|
+
# Header and controls
|
|
325
|
+
grid[0, :8] = self.header
|
|
326
|
+
grid[0, 8:] = map_selection
|
|
327
|
+
grid[1:7, 0:2] = parameter_selection
|
|
328
|
+
grid[7, 0:2] = run_button
|
|
329
|
+
|
|
330
|
+
# Status info, map and plot
|
|
331
|
+
grid[1:8, 2:] = self.m # map
|
|
332
|
+
grid[8:9, :] = self.progress_bar
|
|
333
|
+
# grid[7:8, :] = self.dask_client
|
|
334
|
+
|
|
335
|
+
# Plot
|
|
336
|
+
grid[9:, :] = self.wit_plot
|
|
337
|
+
|
|
338
|
+
# Display using HBox children attribute
|
|
339
|
+
self.children = [grid]
|
|
340
|
+
|
|
341
|
+
######################################
|
|
342
|
+
# DEFINITION OF ALL UPDATE FUNCTIONS #
|
|
343
|
+
######################################
|
|
344
|
+
|
|
345
|
+
# Set the output csv
|
|
346
|
+
def update_fileupload_wetlands(self, change):
|
|
347
|
+
|
|
348
|
+
# Clear any drawn data if present
|
|
349
|
+
self.gdf_drawn = None
|
|
350
|
+
|
|
351
|
+
# Temporary compatibility fix for ipywidget > 8.0
|
|
352
|
+
# TODO: Update code to use new fileupload API documented here:
|
|
353
|
+
# https://ipywidgets.readthedocs.io/en/latest/user_migration_guides.html#fileupload
|
|
354
|
+
uploaded_data = {
|
|
355
|
+
f["name"]: {"content": f.content.tobytes()} for f in change.new
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
# Save to file
|
|
359
|
+
for uploaded_filename in uploaded_data.keys():
|
|
360
|
+
with open(uploaded_filename, "wb") as output_file:
|
|
361
|
+
content = uploaded_data[uploaded_filename]["content"]
|
|
362
|
+
output_file.write(content)
|
|
363
|
+
|
|
364
|
+
with self.progress_bar:
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
|
|
368
|
+
print("Loading vector data...", end="\r")
|
|
369
|
+
valid_files = [
|
|
370
|
+
file
|
|
371
|
+
for file in uploaded_data.keys()
|
|
372
|
+
if file.lower().endswith((".shp", ".geojson"))
|
|
373
|
+
]
|
|
374
|
+
valid_file = valid_files[0]
|
|
375
|
+
wetlands_gdf = (
|
|
376
|
+
gpd.read_file(valid_file)
|
|
377
|
+
.to_crs("EPSG:4326")
|
|
378
|
+
.explode(index_parts=True)
|
|
379
|
+
.reset_index(drop=True)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Use ID column if it exists
|
|
383
|
+
if "id" in wetlands_gdf:
|
|
384
|
+
wetlands_gdf = wetlands_gdf.set_index("id")
|
|
385
|
+
print(f"Uploaded '{valid_file}'; automatically labelling ")
|
|
386
|
+
else:
|
|
387
|
+
print(f"Uploaded '{valid_file}'; no 'id' column detected.")
|
|
388
|
+
|
|
389
|
+
# Create a geodata
|
|
390
|
+
geodata = GeoData(
|
|
391
|
+
geo_dataframe=wetlands_gdf, style={"color": "black", "weight": 3}
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Add to map
|
|
395
|
+
xmin, ymin, xmax, ymax = wetlands_gdf.total_bounds
|
|
396
|
+
self.m.fit_bounds([[ymin, xmin], [ymax, xmax]])
|
|
397
|
+
self.m.add_layer(geodata)
|
|
398
|
+
|
|
399
|
+
# If completed, add to attribute
|
|
400
|
+
self.gdf_uploaded = wetlands_gdf
|
|
401
|
+
|
|
402
|
+
except IndexError:
|
|
403
|
+
print(
|
|
404
|
+
"Cannot read uploaded files. Please ensure that data is "
|
|
405
|
+
"in either GeoJSON or Shapefile format.",
|
|
406
|
+
end="\r",
|
|
407
|
+
)
|
|
408
|
+
self.gdf_uploaded = None
|
|
409
|
+
|
|
410
|
+
except fiona.errors.DriverError:
|
|
411
|
+
print(
|
|
412
|
+
"Shapefile is invalid. Please ensure that all shapefile "
|
|
413
|
+
"components (e.g. .shp, .shx, .dbf, .prj) are uploaded.",
|
|
414
|
+
end="\r",
|
|
415
|
+
)
|
|
416
|
+
self.gdf_uploaded = None
|
|
417
|
+
|
|
418
|
+
# Set the start date to the new edited date
|
|
419
|
+
def update_startdate(self, change):
|
|
420
|
+
self.startdate = change.new
|
|
421
|
+
|
|
422
|
+
# Set the end date to the new edited date
|
|
423
|
+
def update_enddate(self, change):
|
|
424
|
+
self.enddate = change.new
|
|
425
|
+
|
|
426
|
+
# Set the wetland name
|
|
427
|
+
def update_wetlandname(self, change):
|
|
428
|
+
self.wetland_name = change.new
|
|
429
|
+
|
|
430
|
+
# Set the output csv
|
|
431
|
+
# def update_outputcsv(self, change):
|
|
432
|
+
# self.out_csv = change.new
|
|
433
|
+
|
|
434
|
+
# Set the output plot
|
|
435
|
+
def update_outputplot(self, change):
|
|
436
|
+
self.out_plot = change.new
|
|
437
|
+
|
|
438
|
+
# Override max size limit
|
|
439
|
+
def update_maxsize(self, change):
|
|
440
|
+
self.max_size = change.new
|
|
441
|
+
|
|
442
|
+
# Select to output spatial WIT
|
|
443
|
+
def update_outputspatialwit(self, change):
|
|
444
|
+
self.spatial_wit = change.new
|
|
445
|
+
|
|
446
|
+
# Update product
|
|
447
|
+
def update_deaoverlay(self, change):
|
|
448
|
+
|
|
449
|
+
self.product = change.new
|
|
450
|
+
|
|
451
|
+
if self.product == "none":
|
|
452
|
+
self.map_layers.clear_layers()
|
|
453
|
+
|
|
454
|
+
elif self.product == "open_street_map":
|
|
455
|
+
self.map_layers.clear_layers()
|
|
456
|
+
layer = basemap_to_tiles(basemaps.OpenStreetMap.Mapnik)
|
|
457
|
+
self.map_layers.add_layer(layer)
|
|
458
|
+
|
|
459
|
+
def run_app(self, change):
|
|
460
|
+
|
|
461
|
+
# Clear progress bar and output areas before running
|
|
462
|
+
self.progress_bar.clear_output()
|
|
463
|
+
self.wit_plot.clear_output()
|
|
464
|
+
self.dask_client.clear_output()
|
|
465
|
+
|
|
466
|
+
# Configure local dask cluster
|
|
467
|
+
with self.dask_client:
|
|
468
|
+
client = create_local_dask_cluster(return_client=True, display_client=True)
|
|
469
|
+
|
|
470
|
+
# Set any defaults
|
|
471
|
+
dask_chunks = dict(x=1000, y=1000, time=1)
|
|
472
|
+
|
|
473
|
+
self.progress_header.value = "<h3>" + ("Progress") + "</h3>"
|
|
474
|
+
|
|
475
|
+
# Run DEA WIT analysis
|
|
476
|
+
with self.progress_bar:
|
|
477
|
+
warnings.filterwarnings("ignore")
|
|
478
|
+
|
|
479
|
+
# Load polygons from either map or uploaded files
|
|
480
|
+
if self.gdf_uploaded is not None:
|
|
481
|
+
wetlands_gdf = self.gdf_uploaded
|
|
482
|
+
run_text = "uploaded file"
|
|
483
|
+
elif self.gdf_drawn is not None:
|
|
484
|
+
wetlands_gdf = self.gdf_drawn
|
|
485
|
+
|
|
486
|
+
# save the drawn polygon as a geojson in the current directory
|
|
487
|
+
try:
|
|
488
|
+
output_geojson_path = f"{self.wetland_name}_drawn_polygon.geojson"
|
|
489
|
+
wetlands_gdf.to_file(output_geojson_path, driver="GeoJSON")
|
|
490
|
+
print(f"Drawn polygon saved to: {output_geojson_path}")
|
|
491
|
+
except Exception as e:
|
|
492
|
+
print(f"Error saving drawn polygon: {e}")
|
|
493
|
+
run_text = "selected polygon"
|
|
494
|
+
else:
|
|
495
|
+
print(
|
|
496
|
+
f"No polygon drawn or uploaded. Please select a polygon on the map, or upload a GeoJSON or Shapefile.",
|
|
497
|
+
end="\r",
|
|
498
|
+
)
|
|
499
|
+
wetlands_gdf = None
|
|
500
|
+
|
|
501
|
+
# Run wetlands polygon drill
|
|
502
|
+
df = None
|
|
503
|
+
|
|
504
|
+
if not self.wetland_name.endswith(".csv"):
|
|
505
|
+
output_csv = self.wetland_name + ".csv"
|
|
506
|
+
else:
|
|
507
|
+
output_csv = self.wetland_name
|
|
508
|
+
|
|
509
|
+
if wetlands_gdf is not None:
|
|
510
|
+
try:
|
|
511
|
+
ds_wit, df = WIT_drill(
|
|
512
|
+
gdf=wetlands_gdf,
|
|
513
|
+
time=(self.startdate, self.enddate),
|
|
514
|
+
export_csv=output_csv,
|
|
515
|
+
dask_chunks=dask_chunks,
|
|
516
|
+
verbose=False,
|
|
517
|
+
verbose_progress=True,
|
|
518
|
+
)
|
|
519
|
+
print("WIT complete")
|
|
520
|
+
except AttributeError:
|
|
521
|
+
print("No polygon selected")
|
|
522
|
+
|
|
523
|
+
else:
|
|
524
|
+
print(
|
|
525
|
+
"No valid polygon to process. Please select or draw a new polygon."
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# close down the dask client
|
|
529
|
+
client.shutdown()
|
|
530
|
+
|
|
531
|
+
# save the csv
|
|
532
|
+
if df is not None and self.wetland_name:
|
|
533
|
+
df.to_csv(output_csv, index=False)
|
|
534
|
+
|
|
535
|
+
else:
|
|
536
|
+
print("No valid polygon to process. Please select or draw a new polygon.")
|
|
537
|
+
df = None
|
|
538
|
+
|
|
539
|
+
# ---Plotting------------------------------
|
|
540
|
+
if df is not None:
|
|
541
|
+
with self.wit_plot:
|
|
542
|
+
|
|
543
|
+
fontsize = 17
|
|
544
|
+
plt.rcParams.update({"font.size": fontsize})
|
|
545
|
+
# set up color palette
|
|
546
|
+
pal = [
|
|
547
|
+
sns.xkcd_rgb["cobalt blue"],
|
|
548
|
+
sns.xkcd_rgb["neon blue"],
|
|
549
|
+
sns.xkcd_rgb["grass"],
|
|
550
|
+
sns.xkcd_rgb["beige"],
|
|
551
|
+
sns.xkcd_rgb["brown"],
|
|
552
|
+
]
|
|
553
|
+
|
|
554
|
+
# make a stacked area plot
|
|
555
|
+
plt.close("all")
|
|
556
|
+
|
|
557
|
+
fig, ax = plt.subplots(constrained_layout=True, figsize=(20, 6))
|
|
558
|
+
|
|
559
|
+
ax.stackplot(
|
|
560
|
+
df["date"],
|
|
561
|
+
df["water"] * 100,
|
|
562
|
+
df["wet"] * 100,
|
|
563
|
+
df["norm_pv"] * 100,
|
|
564
|
+
df["norm_npv"] * 100,
|
|
565
|
+
df["norm_bs"] * 100,
|
|
566
|
+
colors=pal,
|
|
567
|
+
alpha=0.7,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# manually change the legend display order
|
|
571
|
+
legend = ax.legend(
|
|
572
|
+
["open water", "wet", "green veg", "dry veg", "bare soil"][::-1],
|
|
573
|
+
loc="lower left",
|
|
574
|
+
)
|
|
575
|
+
handles = legend.legend_handles
|
|
576
|
+
|
|
577
|
+
for i, handle in enumerate(handles):
|
|
578
|
+
handle.set_facecolor(pal[::-1][i])
|
|
579
|
+
handle.set_alpha(0.7)
|
|
580
|
+
|
|
581
|
+
# setup the display ranges
|
|
582
|
+
ax.set_ylim(0, 100)
|
|
583
|
+
ax.set_xlim(df["date"].min(), df["date"].max())
|
|
584
|
+
|
|
585
|
+
# add a new column: 'off_value' based on low quality data setting
|
|
586
|
+
df = generate_low_quality_data_periods(df)
|
|
587
|
+
|
|
588
|
+
ax.fill_between(
|
|
589
|
+
df["date"],
|
|
590
|
+
0,
|
|
591
|
+
100,
|
|
592
|
+
where=df["off_value"] == 100,
|
|
593
|
+
color="white",
|
|
594
|
+
alpha=0.5,
|
|
595
|
+
hatch="//",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
ax.xaxis.set_major_locator(mdates.MonthLocator())
|
|
599
|
+
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b-%Y"))
|
|
600
|
+
|
|
601
|
+
# Rotates and right-aligns the x labels so they don't crowd each other.
|
|
602
|
+
for label in ax.get_xticklabels(which="major"):
|
|
603
|
+
label.set(rotation=30, horizontalalignment="right")
|
|
604
|
+
|
|
605
|
+
x_label_text = "The Fractional Cover algorithm developed by the Joint Remote Sensing Research Program and\n the Water Observations from Space algorithm developed by Geoscience Australia are used in the production of this data"
|
|
606
|
+
|
|
607
|
+
ax.set_xlabel(x_label_text, style="italic")
|
|
608
|
+
|
|
609
|
+
ax.set_ylabel("Percentage of wetland (%)")
|
|
610
|
+
|
|
611
|
+
# add a title
|
|
612
|
+
plt.title(
|
|
613
|
+
f"Percentage of area dominated by WOs, Wetness, Fractional Cover for\n {self.wetland_name}",
|
|
614
|
+
fontsize=16,
|
|
615
|
+
)
|
|
616
|
+
plt.show()
|
|
617
|
+
|
|
618
|
+
if self.out_plot:
|
|
619
|
+
# save the figure
|
|
620
|
+
fig.savefig(f"{self.wetland_name}")
|
|
621
|
+
|
|
622
|
+
else:
|
|
623
|
+
print("No valid polygon to process. Please select or draw a new polygon.")
|
|
624
|
+
|
|
625
|
+
# Export spatial WIT animation if checkbox is selected
|
|
626
|
+
if self.spatial_wit and ds_wit is not None:
|
|
627
|
+
|
|
628
|
+
try:
|
|
629
|
+
ds = spatial_wit(ds=ds_wit, wetland_name=self.wetland_name)
|
|
630
|
+
print("Animation complete")
|
|
631
|
+
except AttributeError:
|
|
632
|
+
print("No polygon selected")
|
|
633
|
+
|
|
634
|
+
else:
|
|
635
|
+
print("No valid polygon to process. Please select or draw a new polygon.")
|