dea-tools 0.3.7.dev17__tar.gz → 0.3.7.dev19__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.7.dev17 → dea_tools-0.3.7.dev19}/PKG-INFO +12 -7
- {dea_tools-0.3.7.dev17 → dea_tools-0.3.7.dev19}/README_tools.md +3 -3
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/__init__.py +1 -1
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/animations.py +101 -176
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/changefilmstrips.py +133 -161
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/crophealth.py +51 -84
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/deacoastlines.py +101 -127
- dea_tools-0.3.7.dev19/app/geomedian.py +246 -0
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/imageexport.py +88 -162
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/miningrehab.py +24 -35
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/wetlandsinsighttool.py +85 -137
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/widgetconstructors.py +104 -145
- dea_tools-0.3.7.dev19/bandindices.py +393 -0
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/bom.py +74 -91
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/classification.py +92 -224
- dea_tools-0.3.7.dev19/coastal.py +304 -0
- dea_tools-0.3.7.dev19/dask.py +143 -0
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/datahandling.py +85 -136
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/landcover.py +396 -524
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/maps.py +101 -67
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/plotting.py +329 -392
- {dea_tools-0.3.7.dev17 → dea_tools-0.3.7.dev19}/pyproject.toml +23 -27
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/spatial.py +139 -359
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/temporal.py +50 -83
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/validation.py +8 -11
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/waterbodies.py +24 -28
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/wetlands.py +27 -35
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/wit_app.py +55 -61
- dea_tools-0.3.7.dev17/dea_tools/app/geomedian.py +0 -126
- dea_tools-0.3.7.dev17/dea_tools/bandindices.py +0 -432
- dea_tools-0.3.7.dev17/dea_tools/coastal.py +0 -2381
- dea_tools-0.3.7.dev17/dea_tools/dask.py +0 -133
- dea_tools-0.3.7.dev17/dea_tools/pyfes_model.py +0 -70
- {dea_tools-0.3.7.dev17 → dea_tools-0.3.7.dev19}/.gitignore +0 -0
- {dea_tools-0.3.7.dev17 → dea_tools-0.3.7.dev19}/LICENSE +0 -0
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/__main__.py +0 -0
- {dea_tools-0.3.7.dev17/dea_tools → dea_tools-0.3.7.dev19}/app/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dea-tools
|
|
3
|
-
Version: 0.3.7.
|
|
3
|
+
Version: 0.3.7.dev19
|
|
4
4
|
Summary: Functions and algorithms for analysing Digital Earth Australia data.
|
|
5
5
|
Project-URL: Homepage, https://github.com/GeoscienceAustralia/dea-notebooks
|
|
6
6
|
Project-URL: Repository, https://github.com/GeoscienceAustralia/dea-notebooks
|
|
@@ -22,6 +22,7 @@ Requires-Python: <4.0,>=3.10
|
|
|
22
22
|
Requires-Dist: aiohttp>=3.5.0
|
|
23
23
|
Requires-Dist: dask-ml>=2023.3.24
|
|
24
24
|
Requires-Dist: dask>=2023.1.0
|
|
25
|
+
Requires-Dist: eo-tides>=0.8.0
|
|
25
26
|
Requires-Dist: fiona>=1.10.0
|
|
26
27
|
Requires-Dist: folium>=0.16.0
|
|
27
28
|
Requires-Dist: geopandas>=0.10.0
|
|
@@ -38,7 +39,7 @@ Requires-Dist: owslib>=0.26.0
|
|
|
38
39
|
Requires-Dist: packaging>=22.0
|
|
39
40
|
Requires-Dist: pandas>=2.2.0
|
|
40
41
|
Requires-Dist: pyproj>=3.7.0
|
|
41
|
-
Requires-Dist:
|
|
42
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
42
43
|
Requires-Dist: pytz>=2022.1
|
|
43
44
|
Requires-Dist: rasterio>=1.3.11
|
|
44
45
|
Requires-Dist: rasterstats>=0.16.0
|
|
@@ -54,10 +55,11 @@ Requires-Dist: traitlets>=5.0.0
|
|
|
54
55
|
Requires-Dist: xarray>=2022.3.0
|
|
55
56
|
Provides-Extra: all
|
|
56
57
|
Requires-Dist: affine>=2.3.1; extra == 'all'
|
|
57
|
-
Requires-Dist:
|
|
58
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == 'all'
|
|
58
59
|
Requires-Dist: cmocean>=3.0.0; extra == 'all'
|
|
59
60
|
Requires-Dist: contextily>=1.3.0; extra == 'all'
|
|
60
61
|
Requires-Dist: dask-gateway>=2023.1.0; extra == 'all'
|
|
62
|
+
Requires-Dist: datacube-ows>=1.9.3; extra == 'all'
|
|
61
63
|
Requires-Dist: datacube>=1.8.5; extra == 'all'
|
|
62
64
|
Requires-Dist: gcsfs>=2022.1.0; extra == 'all'
|
|
63
65
|
Requires-Dist: hdstats>=0.1.8.post1; extra == 'all'
|
|
@@ -73,6 +75,7 @@ Requires-Dist: statsmodels>=0.14.0; extra == 'all'
|
|
|
73
75
|
Provides-Extra: dask-gateway
|
|
74
76
|
Requires-Dist: dask-gateway>=2023.1.0; extra == 'dask-gateway'
|
|
75
77
|
Provides-Extra: datacube
|
|
78
|
+
Requires-Dist: datacube-ows>=1.9.3; extra == 'datacube'
|
|
76
79
|
Requires-Dist: datacube>=1.8.5; extra == 'datacube'
|
|
77
80
|
Requires-Dist: odc-ui>=0.2.1; extra == 'datacube'
|
|
78
81
|
Provides-Extra: hdstats
|
|
@@ -82,10 +85,12 @@ Requires-Dist: ipyleaflet>=0.17.0; extra == 'jupyter'
|
|
|
82
85
|
Requires-Dist: jupyter>=1.0.0; extra == 'jupyter'
|
|
83
86
|
Provides-Extra: notebooks
|
|
84
87
|
Requires-Dist: affine>=2.3.1; extra == 'notebooks'
|
|
85
|
-
Requires-Dist:
|
|
88
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == 'notebooks'
|
|
86
89
|
Requires-Dist: cmocean>=3.0.0; extra == 'notebooks'
|
|
87
90
|
Requires-Dist: contextily>=1.3.0; extra == 'notebooks'
|
|
88
91
|
Requires-Dist: gcsfs>=2022.1.0; extra == 'notebooks'
|
|
92
|
+
Requires-Dist: ipyleaflet>=0.17.0; extra == 'notebooks'
|
|
93
|
+
Requires-Dist: jupyter>=1.0.0; extra == 'notebooks'
|
|
89
94
|
Requires-Dist: opencv-python>=4.6.0.66; extra == 'notebooks'
|
|
90
95
|
Requires-Dist: pydotplus>=2.0.0; extra == 'notebooks'
|
|
91
96
|
Requires-Dist: statsmodels>=0.14.0; extra == 'notebooks'
|
|
@@ -95,9 +100,9 @@ Requires-Dist: planetary-computer>=1.0.0; extra == 'stac'
|
|
|
95
100
|
Requires-Dist: pystac-client>=0.8.3; extra == 'stac'
|
|
96
101
|
Description-Content-Type: text/markdown
|
|
97
102
|
|
|
98
|
-
#
|
|
103
|
+
# DEA Tools Python package
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
`dea_tools`` is an open-source Python package providing functions and algorithms for geospatial analysis using Open Data Cube, Xarray, and Digital Earth Australia.
|
|
101
106
|
It includes utilities for loading data, plotting, spatial and temporal analysis, and applying machine learning to satellite data.
|
|
102
107
|
|
|
103
108
|
The package is organised into the following modules:
|
|
@@ -110,7 +115,7 @@ The package is organised into the following modules:
|
|
|
110
115
|
* `dea_tools.classification`: Machine learning utilities (e.g. training and applying models on satellite data)
|
|
111
116
|
* `dea_tools.dask`: Utilities for parallel processing with Dask (e.g. creating scalable Dask clusters)
|
|
112
117
|
* `dea_tools.landcover`: Tools for accessing and visualising DEA Land Cover data
|
|
113
|
-
* `dea_tools.coastal`: Coastal and intertidal analysis tools (e.g.
|
|
118
|
+
* `dea_tools.coastal`: Coastal and intertidal analysis tools (e.g. coastal change time series, sunglint mapping)
|
|
114
119
|
* `dea_tools.bom`: Accessing Bureau of Meteorology water data (e.g. gauge and discharge data)
|
|
115
120
|
* `dea_tools.waterbodies`: Accessing and analysing DEA Waterbodies data (e.g. loading waterbody time series)
|
|
116
121
|
* `dea_tools.maps`: Tools for interactive mapping (e.g. folium and ipyleaflet maps)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DEA Tools Python package
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`dea_tools`` is an open-source Python package providing functions and algorithms for geospatial analysis using Open Data Cube, Xarray, and Digital Earth Australia.
|
|
4
4
|
It includes utilities for loading data, plotting, spatial and temporal analysis, and applying machine learning to satellite data.
|
|
5
5
|
|
|
6
6
|
The package is organised into the following modules:
|
|
@@ -13,7 +13,7 @@ The package is organised into the following modules:
|
|
|
13
13
|
* `dea_tools.classification`: Machine learning utilities (e.g. training and applying models on satellite data)
|
|
14
14
|
* `dea_tools.dask`: Utilities for parallel processing with Dask (e.g. creating scalable Dask clusters)
|
|
15
15
|
* `dea_tools.landcover`: Tools for accessing and visualising DEA Land Cover data
|
|
16
|
-
* `dea_tools.coastal`: Coastal and intertidal analysis tools (e.g.
|
|
16
|
+
* `dea_tools.coastal`: Coastal and intertidal analysis tools (e.g. coastal change time series, sunglint mapping)
|
|
17
17
|
* `dea_tools.bom`: Accessing Bureau of Meteorology water data (e.g. gauge and discharge data)
|
|
18
18
|
* `dea_tools.waterbodies`: Accessing and analysing DEA Waterbodies data (e.g. loading waterbody time series)
|
|
19
19
|
* `dea_tools.maps`: Tools for interactive mapping (e.g. folium and ipyleaflet maps)
|
|
@@ -1,57 +1,55 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
Satellite imagery animation widget, which can be used to interactively
|
|
3
|
+
Satellite imagery animation widget, which can be used to interactively
|
|
4
4
|
produce animations for multiple DEA products.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
# Import required packages
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
8
|
+
import datetime
|
|
9
|
+
import itertools
|
|
10
|
+
import json
|
|
11
11
|
import warnings
|
|
12
|
+
from io import BytesIO
|
|
13
|
+
|
|
14
|
+
import datacube
|
|
15
|
+
import geopandas as gpd
|
|
16
|
+
import ipywidgets as widgets
|
|
17
|
+
import matplotlib as mpl
|
|
12
18
|
import matplotlib.pyplot as plt
|
|
13
|
-
|
|
19
|
+
import numpy as np
|
|
20
|
+
import pandas as pd
|
|
21
|
+
from datacube.utils.geometry import Geometry
|
|
22
|
+
from datacube.utils.masking import mask_invalid_data
|
|
14
23
|
from ipyleaflet import (
|
|
15
|
-
WMSLayer,
|
|
16
|
-
basemaps,
|
|
17
|
-
basemap_to_tiles,
|
|
18
|
-
Map,
|
|
19
|
-
DrawControl,
|
|
20
|
-
WidgetControl,
|
|
21
|
-
SearchControl,
|
|
22
|
-
Marker,
|
|
23
24
|
LayerGroup,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
Marker,
|
|
26
|
+
SearchControl,
|
|
27
|
+
basemap_to_tiles,
|
|
28
|
+
basemaps,
|
|
26
29
|
)
|
|
27
|
-
from traitlets import Unicode
|
|
28
30
|
from ipywidgets import (
|
|
29
|
-
|
|
31
|
+
HTML,
|
|
30
32
|
Button,
|
|
31
|
-
|
|
33
|
+
GridspecLayout,
|
|
32
34
|
HBox,
|
|
33
|
-
|
|
34
|
-
HTML,
|
|
35
|
+
Layout,
|
|
35
36
|
Output,
|
|
37
|
+
VBox,
|
|
36
38
|
)
|
|
37
|
-
import json
|
|
38
|
-
import itertools
|
|
39
|
-
import numpy as np
|
|
40
|
-
import geopandas as gpd
|
|
41
|
-
from io import BytesIO
|
|
42
|
-
import ipywidgets as widgets
|
|
43
|
-
import datetime
|
|
44
|
-
from skimage import exposure
|
|
45
39
|
from skimage.filters import unsharp_mask
|
|
46
40
|
|
|
47
|
-
from
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
from dea_tools.app.widgetconstructors import (
|
|
42
|
+
create_checkbox,
|
|
43
|
+
create_datepicker,
|
|
44
|
+
create_drawcontrol,
|
|
45
|
+
create_dropdown,
|
|
46
|
+
create_html,
|
|
47
|
+
create_map,
|
|
48
|
+
)
|
|
49
|
+
from dea_tools.coastal import get_coastlines
|
|
51
50
|
from dea_tools.dask import create_local_dask_cluster
|
|
52
51
|
from dea_tools.spatial import reverse_geocode
|
|
53
52
|
|
|
54
|
-
import warnings
|
|
55
53
|
warnings.filterwarnings("ignore")
|
|
56
54
|
|
|
57
55
|
# WMS params and satellite style bands
|
|
@@ -137,14 +135,12 @@ def update_map_layers(self, update_basemap=False):
|
|
|
137
135
|
self.query_params = None
|
|
138
136
|
|
|
139
137
|
if update_basemap:
|
|
140
|
-
|
|
141
138
|
# Clear all layers and add basemap
|
|
142
139
|
self.map_layers.clear_layers()
|
|
143
140
|
self.map_layers.add_layer(self.basemap)
|
|
144
141
|
|
|
145
142
|
|
|
146
143
|
def extract_data(self):
|
|
147
|
-
|
|
148
144
|
# Connect to datacube database
|
|
149
145
|
dc = datacube.Datacube(app="Exporting satellite images")
|
|
150
146
|
|
|
@@ -166,15 +162,11 @@ def extract_data(self):
|
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
# Find matching datasets
|
|
169
|
-
dss = [
|
|
170
|
-
dc.find_datasets(product=i, **self.query_params)
|
|
171
|
-
for i in sat_params[self.dealayer]["products"]
|
|
172
|
-
]
|
|
165
|
+
dss = [dc.find_datasets(product=i, **self.query_params) for i in sat_params[self.dealayer]["products"]]
|
|
173
166
|
dss = list(itertools.chain.from_iterable(dss))
|
|
174
167
|
|
|
175
168
|
# If data is found
|
|
176
169
|
if len(dss) > 0:
|
|
177
|
-
|
|
178
170
|
# Get CRS
|
|
179
171
|
crs = str(dss[0].crs)
|
|
180
172
|
|
|
@@ -224,26 +216,21 @@ def extract_data(self):
|
|
|
224
216
|
|
|
225
217
|
|
|
226
218
|
def plot_data(self, fname):
|
|
227
|
-
|
|
228
219
|
# Data to plot
|
|
229
220
|
to_plot = self.timeseries_ds
|
|
230
221
|
|
|
231
222
|
# If rolling median specified
|
|
232
223
|
if self.rolling_median:
|
|
233
224
|
with self.status_info:
|
|
234
|
-
print(
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
to_plot = to_plot.rolling(
|
|
238
|
-
time=int(self.rolling_median_window), center=True, min_periods=1
|
|
239
|
-
).median()
|
|
225
|
+
print(f"\nApplying rolling median ({self.rolling_median_window} timesteps window)")
|
|
226
|
+
to_plot = to_plot.rolling(time=int(self.rolling_median_window), center=True, min_periods=1).median()
|
|
240
227
|
|
|
241
228
|
# Raise by power to dampen bright features and enhance dark.
|
|
242
229
|
# Raise vmin and vmax by same amount to ensure proper stretch
|
|
243
230
|
if self.power < 1.0:
|
|
244
231
|
with self.status_info:
|
|
245
232
|
print(f"\nApplying power transformation ({self.power})")
|
|
246
|
-
to_plot = to_plot
|
|
233
|
+
to_plot = to_plot**self.power
|
|
247
234
|
|
|
248
235
|
# Apply unsharp masking to enhance overall dynamic range,
|
|
249
236
|
# and improve fine scale detail
|
|
@@ -257,9 +244,7 @@ def plot_data(self, fname):
|
|
|
257
244
|
|
|
258
245
|
funcs_list = [
|
|
259
246
|
rescale_intensity,
|
|
260
|
-
lambda x: unsharp_mask(
|
|
261
|
-
x, radius=self.unsharp_mask_radius, amount=self.unsharp_mask_amount
|
|
262
|
-
),
|
|
247
|
+
lambda x: unsharp_mask(x, radius=self.unsharp_mask_radius, amount=self.unsharp_mask_amount),
|
|
263
248
|
]
|
|
264
249
|
else:
|
|
265
250
|
funcs_list = None
|
|
@@ -288,13 +273,6 @@ def plot_data(self, fname):
|
|
|
288
273
|
|
|
289
274
|
|
|
290
275
|
def deacoastlines_overlay(ds):
|
|
291
|
-
|
|
292
|
-
import geopandas as gpd
|
|
293
|
-
import pandas as pd
|
|
294
|
-
import matplotlib
|
|
295
|
-
from shapely.geometry import box, Point
|
|
296
|
-
from dea_tools.coastal import get_coastlines
|
|
297
|
-
|
|
298
276
|
# Get bounding box of data
|
|
299
277
|
xmin, ymin, xmax, ymax = ds.geobox.geographic_extent.boundingbox
|
|
300
278
|
bounds = [xmin, ymin, xmax, ymax]
|
|
@@ -308,8 +286,8 @@ def deacoastlines_overlay(ds):
|
|
|
308
286
|
deacl_gdf = deacl_gdf.dissolve("year") # values("year", ascending=True)
|
|
309
287
|
|
|
310
288
|
# Apply colours
|
|
311
|
-
norm =
|
|
312
|
-
cmap =
|
|
289
|
+
norm = mpl.colors.Normalize(vmin=0, vmax=len(deacl_gdf.index))
|
|
290
|
+
cmap = mpl.cm.get_cmap("inferno")
|
|
313
291
|
rgba = cmap(norm(deacl_gdf.reset_index().index))
|
|
314
292
|
deacl_gdf["color"] = list(rgba)
|
|
315
293
|
deacl_gdf["start_time"] = pd.to_datetime(deacl_gdf.index) + pd.DateOffset(months=0)
|
|
@@ -317,8 +295,7 @@ def deacoastlines_overlay(ds):
|
|
|
317
295
|
|
|
318
296
|
if len(deacl_gdf.index) > 0:
|
|
319
297
|
return deacl_gdf
|
|
320
|
-
|
|
321
|
-
return None
|
|
298
|
+
return None
|
|
322
299
|
|
|
323
300
|
|
|
324
301
|
class animation_app(HBox):
|
|
@@ -338,15 +315,13 @@ class animation_app(HBox):
|
|
|
338
315
|
|
|
339
316
|
# Satellite data
|
|
340
317
|
end_date = datetime.datetime.today()
|
|
341
|
-
start_date = datetime.datetime(
|
|
342
|
-
year=end_date.year - 3, month=end_date.month, day=end_date.day
|
|
343
|
-
)
|
|
318
|
+
start_date = datetime.datetime(year=end_date.year - 3, month=end_date.month, day=end_date.day)
|
|
344
319
|
self.start_date = start_date.strftime("%Y-%m-%d")
|
|
345
320
|
self.end_date = end_date.strftime("%Y-%m-%d")
|
|
346
321
|
self.dealayer_list = [
|
|
347
322
|
("Landsat", "Landsat"),
|
|
348
323
|
("Sentinel-2", "Sentinel-2"),
|
|
349
|
-
("Sentinel-2 and Landsat", "Sentinel-2 and Landsat")
|
|
324
|
+
("Sentinel-2 and Landsat", "Sentinel-2 and Landsat"),
|
|
350
325
|
]
|
|
351
326
|
self.dealayer = self.dealayer_list[0][1]
|
|
352
327
|
|
|
@@ -395,16 +370,14 @@ class animation_app(HBox):
|
|
|
395
370
|
##################
|
|
396
371
|
|
|
397
372
|
# Create the Header widget
|
|
398
|
-
header_title_text =
|
|
399
|
-
"<h3>Digital Earth Australia satellite imagery animations</h3>"
|
|
400
|
-
)
|
|
373
|
+
header_title_text = "<h3>Digital Earth Australia satellite imagery animations</h3>"
|
|
401
374
|
instruction_text = (
|
|
402
375
|
"<p>Select the desired satellite data, imagery date range "
|
|
403
376
|
"and image style, then zoom in and draw a rectangle to "
|
|
404
377
|
"select an area export as a satellite imagery time-series "
|
|
405
378
|
"animation.</p>"
|
|
406
379
|
)
|
|
407
|
-
self.header =
|
|
380
|
+
self.header = create_html(f"{header_title_text}{instruction_text}")
|
|
408
381
|
self.header.layout = make_box_layout()
|
|
409
382
|
|
|
410
383
|
#####################################
|
|
@@ -413,7 +386,6 @@ class animation_app(HBox):
|
|
|
413
386
|
|
|
414
387
|
# Define the action to take once something is drawn on the map
|
|
415
388
|
def update_geojson(target, action, geo_json):
|
|
416
|
-
|
|
417
389
|
# Get data from action
|
|
418
390
|
self.action = action
|
|
419
391
|
|
|
@@ -443,12 +415,7 @@ class animation_app(HBox):
|
|
|
443
415
|
'<span style="color: #33cc33"> '
|
|
444
416
|
"<b>(Overriding maximum size limit; use with caution as may lead to memory issues)</b></span>"
|
|
445
417
|
)
|
|
446
|
-
self.header.value =
|
|
447
|
-
header_title_text
|
|
448
|
-
+ instruction_text
|
|
449
|
-
+ polyarea_text
|
|
450
|
-
+ confirmation_text
|
|
451
|
-
)
|
|
418
|
+
self.header.value = header_title_text + instruction_text + polyarea_text + confirmation_text
|
|
452
419
|
self.gdf_drawn = gdf
|
|
453
420
|
elif area <= 50000:
|
|
454
421
|
confirmation_text = (
|
|
@@ -456,12 +423,7 @@ class animation_app(HBox):
|
|
|
456
423
|
"<b>(Area to extract falls within "
|
|
457
424
|
"recommended 50000 ha limit)</b></span>"
|
|
458
425
|
)
|
|
459
|
-
self.header.value =
|
|
460
|
-
header_title_text
|
|
461
|
-
+ instruction_text
|
|
462
|
-
+ polyarea_text
|
|
463
|
-
+ confirmation_text
|
|
464
|
-
)
|
|
426
|
+
self.header.value = header_title_text + instruction_text + polyarea_text + confirmation_text
|
|
465
427
|
self.gdf_drawn = gdf
|
|
466
428
|
else:
|
|
467
429
|
warning_text = (
|
|
@@ -470,9 +432,7 @@ class animation_app(HBox):
|
|
|
470
432
|
"please select an area less than 50000 "
|
|
471
433
|
"ha)</b></span>"
|
|
472
434
|
)
|
|
473
|
-
self.header.value =
|
|
474
|
-
header_title_text + instruction_text + polyarea_text + warning_text
|
|
475
|
-
)
|
|
435
|
+
self.header.value = header_title_text + instruction_text + polyarea_text + warning_text
|
|
476
436
|
self.gdf_drawn = None
|
|
477
437
|
|
|
478
438
|
###########################
|
|
@@ -488,24 +448,26 @@ class animation_app(HBox):
|
|
|
488
448
|
|
|
489
449
|
# Create drawing tools
|
|
490
450
|
desired_drawtools = ["rectangle"]
|
|
491
|
-
draw_control =
|
|
451
|
+
draw_control = create_drawcontrol(desired_drawtools)
|
|
492
452
|
|
|
493
453
|
# Begin by displaying an empty layer group, and update the group with desired WMS on interaction.
|
|
494
454
|
self.map_layers = LayerGroup(layers=())
|
|
495
455
|
self.map_layers.name = "Map Overlays"
|
|
496
456
|
|
|
497
457
|
# Create map widget
|
|
498
|
-
self.m =
|
|
458
|
+
self.m = create_map(map_center=(-33.96, 151.20), zoom_level=13)
|
|
499
459
|
self.m.layout = make_box_layout()
|
|
500
460
|
|
|
501
461
|
# Add tools to map widget
|
|
502
462
|
self.m.add_control(draw_control)
|
|
503
|
-
self.m.add_control(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
463
|
+
self.m.add_control(
|
|
464
|
+
SearchControl(
|
|
465
|
+
position="topleft",
|
|
466
|
+
url="https://nominatim.openstreetmap.org/search?format=json&q={s}",
|
|
467
|
+
zoom=13, # 'Village / Suburb' level zoom
|
|
468
|
+
marker=Marker(draggable=False),
|
|
469
|
+
)
|
|
470
|
+
)
|
|
509
471
|
self.m.add_layer(self.map_layers)
|
|
510
472
|
|
|
511
473
|
# Update all maps to starting defaults
|
|
@@ -516,24 +478,16 @@ class animation_app(HBox):
|
|
|
516
478
|
############################
|
|
517
479
|
|
|
518
480
|
# Create parameter widgets
|
|
519
|
-
dropdown_basemap =
|
|
520
|
-
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
self.dealayer_list, self.dealayer_list[0][1]
|
|
524
|
-
)
|
|
525
|
-
dropdown_output = deawidgets.create_dropdown(
|
|
526
|
-
self.output_list, self.output_list[0][1]
|
|
527
|
-
)
|
|
528
|
-
date_picker_start = deawidgets.create_datepicker(
|
|
481
|
+
dropdown_basemap = create_dropdown(self.basemap_list, self.basemap_list[0][1])
|
|
482
|
+
dropdown_dealayer = create_dropdown(self.dealayer_list, self.dealayer_list[0][1])
|
|
483
|
+
dropdown_output = create_dropdown(self.output_list, self.output_list[0][1])
|
|
484
|
+
date_picker_start = create_datepicker(
|
|
529
485
|
value=start_date,
|
|
530
486
|
)
|
|
531
|
-
date_picker_end =
|
|
487
|
+
date_picker_end = create_datepicker(
|
|
532
488
|
value=end_date,
|
|
533
489
|
)
|
|
534
|
-
dropdown_styles =
|
|
535
|
-
self.styles_list, self.styles_list[0]
|
|
536
|
-
)
|
|
490
|
+
dropdown_styles = create_dropdown(self.styles_list, self.styles_list[0])
|
|
537
491
|
slider_percentile = widgets.FloatRangeSlider(
|
|
538
492
|
value=[0.01, 0.99],
|
|
539
493
|
min=0,
|
|
@@ -553,7 +507,7 @@ class animation_app(HBox):
|
|
|
553
507
|
layout={"width": "85%"},
|
|
554
508
|
)
|
|
555
509
|
|
|
556
|
-
checkbox_rolling_median =
|
|
510
|
+
checkbox_rolling_median = create_checkbox(
|
|
557
511
|
self.rolling_median,
|
|
558
512
|
"Apply rolling median to produce smooth, cloud-free animations",
|
|
559
513
|
layout={"width": "85%"},
|
|
@@ -571,26 +525,20 @@ class animation_app(HBox):
|
|
|
571
525
|
)
|
|
572
526
|
|
|
573
527
|
# Expandable advanced section
|
|
574
|
-
text_interval = widgets.IntText(
|
|
575
|
-
value=100, description="", step=50, layout={"width": "95%"}
|
|
576
|
-
)
|
|
528
|
+
text_interval = widgets.IntText(value=100, description="", step=50, layout={"width": "95%"})
|
|
577
529
|
text_resolution = widgets.FloatText(
|
|
578
530
|
value=30,
|
|
579
531
|
description="",
|
|
580
532
|
layout={"width": "95%", "margin": "0px", "padding": "0px"},
|
|
581
533
|
)
|
|
582
|
-
text_width = widgets.IntText(
|
|
583
|
-
|
|
584
|
-
)
|
|
585
|
-
dropdown_resampling = deawidgets.create_dropdown(
|
|
534
|
+
text_width = widgets.IntText(value=900, description="", step=50, layout={"width": "95%"})
|
|
535
|
+
dropdown_resampling = create_dropdown(
|
|
586
536
|
self.resample_list,
|
|
587
537
|
self.resample_freq,
|
|
588
538
|
description="",
|
|
589
539
|
layout={"width": "95%"},
|
|
590
540
|
)
|
|
591
|
-
checkbox_cloud_mask =
|
|
592
|
-
self.cloud_mask, "Mask out cloudy pixels", layout={"width": "95%"}
|
|
593
|
-
)
|
|
541
|
+
checkbox_cloud_mask = create_checkbox(self.cloud_mask, "Mask out cloudy pixels", layout={"width": "95%"})
|
|
594
542
|
slider_power = widgets.FloatSlider(
|
|
595
543
|
value=1.0,
|
|
596
544
|
min=0.01,
|
|
@@ -599,9 +547,7 @@ class animation_app(HBox):
|
|
|
599
547
|
description="",
|
|
600
548
|
layout={"width": "95%"},
|
|
601
549
|
)
|
|
602
|
-
checkbox_unsharp_mask =
|
|
603
|
-
self.unsharp_mask, "Enable", layout={"width": "95%"}
|
|
604
|
-
)
|
|
550
|
+
checkbox_unsharp_mask = create_checkbox(self.unsharp_mask, "Enable", layout={"width": "95%"})
|
|
605
551
|
text_unsharp_mask_radius = widgets.FloatText(
|
|
606
552
|
value=20,
|
|
607
553
|
step=1,
|
|
@@ -624,12 +570,10 @@ class animation_app(HBox):
|
|
|
624
570
|
"display": "none",
|
|
625
571
|
},
|
|
626
572
|
)
|
|
627
|
-
checkbox_deacoastlines =
|
|
573
|
+
checkbox_deacoastlines = create_checkbox(
|
|
628
574
|
self.deacoastlines, "Add DEA Coastlines overlay", layout={"width": "95%"}
|
|
629
575
|
)
|
|
630
|
-
checkbox_max_size =
|
|
631
|
-
self.max_size, "Enable", layout={"width": "95%"}
|
|
632
|
-
)
|
|
576
|
+
checkbox_max_size = create_checkbox(self.max_size, "Enable", layout={"width": "95%"})
|
|
633
577
|
expand_box = widgets.VBox(
|
|
634
578
|
[
|
|
635
579
|
HTML("Frame interval (milliseconds):"),
|
|
@@ -649,9 +593,7 @@ class animation_app(HBox):
|
|
|
649
593
|
checkbox_unsharp_mask,
|
|
650
594
|
text_unsharp_mask_radius,
|
|
651
595
|
text_unsharp_mask_amount,
|
|
652
|
-
HTML(
|
|
653
|
-
"</br>Override maximum size limit: (use with caution; may cause memory issues/crashes)"
|
|
654
|
-
),
|
|
596
|
+
HTML("</br>Override maximum size limit: (use with caution; may cause memory issues/crashes)"),
|
|
655
597
|
checkbox_max_size,
|
|
656
598
|
],
|
|
657
599
|
)
|
|
@@ -680,13 +622,9 @@ class animation_app(HBox):
|
|
|
680
622
|
dropdown_styles.observe(self.update_styles, "value")
|
|
681
623
|
|
|
682
624
|
slider_percentile.observe(self.update_slider_percentile, "value")
|
|
683
|
-
floatslider_max_cloud_cover.observe(
|
|
684
|
-
self.update_floatslider_max_cloud_cover, "value"
|
|
685
|
-
)
|
|
625
|
+
floatslider_max_cloud_cover.observe(self.update_floatslider_max_cloud_cover, "value")
|
|
686
626
|
checkbox_rolling_median.observe(self.update_checkbox_rolling_median, "value")
|
|
687
|
-
text_rolling_median_window.observe(
|
|
688
|
-
self.update_text_rolling_median_window, "value"
|
|
689
|
-
)
|
|
627
|
+
text_rolling_median_window.observe(self.update_text_rolling_median_window, "value")
|
|
690
628
|
dropdown_output.observe(self.update_output, "value")
|
|
691
629
|
run_button.on_click(self.run_app)
|
|
692
630
|
draw_control.on_draw(update_geojson)
|
|
@@ -708,34 +646,30 @@ class animation_app(HBox):
|
|
|
708
646
|
# COLLECTION OF ALL APP CONTROLS #
|
|
709
647
|
##################################
|
|
710
648
|
|
|
711
|
-
parameter_selection = VBox(
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
HTML("</br><b>Map overlay:</b>"),
|
|
736
|
-
dropdown_basemap,
|
|
737
|
-
]
|
|
738
|
-
)
|
|
649
|
+
parameter_selection = VBox([
|
|
650
|
+
HTML("<b>Start date:</b>"),
|
|
651
|
+
date_picker_start,
|
|
652
|
+
HTML("<b>End date:</b>"),
|
|
653
|
+
date_picker_end,
|
|
654
|
+
HTML("<b>Satellite imagery:</b>"),
|
|
655
|
+
dropdown_dealayer,
|
|
656
|
+
HTML("<b>Style:</b>"),
|
|
657
|
+
dropdown_styles,
|
|
658
|
+
HTML("<b>Colour percentile stretch:</b>"),
|
|
659
|
+
slider_percentile,
|
|
660
|
+
HTML("<b>Maximum cloud cover (%):</b>"),
|
|
661
|
+
floatslider_max_cloud_cover,
|
|
662
|
+
checkbox_rolling_median,
|
|
663
|
+
text_rolling_median_window,
|
|
664
|
+
HTML("</br><b>Output file format:</b>"),
|
|
665
|
+
dropdown_output,
|
|
666
|
+
HTML("</br>"),
|
|
667
|
+
expand,
|
|
668
|
+
])
|
|
669
|
+
map_selection = VBox([
|
|
670
|
+
HTML("</br><b>Map overlay:</b>"),
|
|
671
|
+
dropdown_basemap,
|
|
672
|
+
])
|
|
739
673
|
parameter_selection.layout = make_box_layout()
|
|
740
674
|
map_selection.layout = make_box_layout()
|
|
741
675
|
|
|
@@ -810,10 +744,10 @@ class animation_app(HBox):
|
|
|
810
744
|
|
|
811
745
|
elif change.new == "Sentinel-2":
|
|
812
746
|
self.text_resolution.value = 10
|
|
813
|
-
|
|
747
|
+
|
|
814
748
|
elif change.new == "Sentinel-2 and Landsat":
|
|
815
749
|
self.text_resolution.value = 30
|
|
816
|
-
|
|
750
|
+
|
|
817
751
|
# Clear data load params to trigger data re-load
|
|
818
752
|
update_map_layers(self)
|
|
819
753
|
|
|
@@ -915,16 +849,13 @@ class animation_app(HBox):
|
|
|
915
849
|
update_map_layers(self)
|
|
916
850
|
|
|
917
851
|
def run_app(self, change):
|
|
918
|
-
|
|
919
852
|
# Clear progress bar and output areas before running
|
|
920
853
|
self.status_info.clear_output()
|
|
921
854
|
self.output_plot.clear_output()
|
|
922
855
|
|
|
923
856
|
# Verify that polygon was drawn
|
|
924
857
|
if self.gdf_drawn is not None:
|
|
925
|
-
|
|
926
858
|
with self.status_info:
|
|
927
|
-
|
|
928
859
|
# Load data and add to attribute
|
|
929
860
|
if self.timeseries_ds is None:
|
|
930
861
|
self.timeseries_ds = extract_data(self)
|
|
@@ -933,9 +864,7 @@ class animation_app(HBox):
|
|
|
933
864
|
print("Using previously loaded data")
|
|
934
865
|
|
|
935
866
|
if self.timeseries_ds is not None:
|
|
936
|
-
|
|
937
867
|
with self.status_info:
|
|
938
|
-
|
|
939
868
|
# Create unique file name
|
|
940
869
|
centre_coords = self.gdf_drawn.geometry[0].centroid.coords[0][::-1]
|
|
941
870
|
site = reverse_geocode(coords=centre_coords)
|
|
@@ -947,9 +876,7 @@ class animation_app(HBox):
|
|
|
947
876
|
.lower()
|
|
948
877
|
)
|
|
949
878
|
|
|
950
|
-
print(
|
|
951
|
-
f"\nExporting animation for {site}.\nThis may take several minutes..."
|
|
952
|
-
)
|
|
879
|
+
print(f"\nExporting animation for {site}.\nThis may take several minutes...")
|
|
953
880
|
|
|
954
881
|
############
|
|
955
882
|
# Plotting #
|
|
@@ -968,6 +895,4 @@ class animation_app(HBox):
|
|
|
968
895
|
|
|
969
896
|
else:
|
|
970
897
|
with self.status_info:
|
|
971
|
-
print(
|
|
972
|
-
'Please draw a valid rectangle on the map, then press "Generate animation".'
|
|
973
|
-
)
|
|
898
|
+
print('Please draw a valid rectangle on the map, then press "Generate animation".')
|