dea-tools 0.3.6.dev31__tar.gz → 0.3.6.dev38__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.dev31/dea_tools.egg-info → dea_tools-0.3.6.dev38}/PKG-INFO +1 -1
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/landcover.py +406 -440
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38/dea_tools.egg-info}/PKG-INFO +1 -1
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/.gitignore +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/LICENSE +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/MANIFEST.in +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/README.rst +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/__init__.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/__main__.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/__init__.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/animations.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/changefilmstrips.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/crophealth.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/deacoastlines.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/geomedian.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/imageexport.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/miningrehab.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/app/widgetconstructors.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/bandindices.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/bom.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/classification.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/climate.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/coastal.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/dask.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/datahandling.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/maps.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/plotting.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/pyfes_model.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/spatial.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/temporal.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/validation.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/waterbodies.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools/wetlands.py +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/SOURCES.txt +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/dependency_links.txt +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/requires.txt +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/top_level.txt +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/index.rst +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/mock_imports.txt +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/pyproject.toml +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/setup.cfg +0 -0
- {dea_tools-0.3.6.dev31 → dea_tools-0.3.6.dev38}/setup.py +0 -0
|
@@ -17,13 +17,13 @@ here: https://gis.stackexchange.com/questions/tagged/open-data-cube).
|
|
|
17
17
|
If you would like to report an issue with this script, you can file one
|
|
18
18
|
on GitHub (https://github.com/GeoscienceAustralia/dea-notebooks/issues/new).
|
|
19
19
|
|
|
20
|
-
Last modified:
|
|
20
|
+
Last modified: March 2025
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
import numpy as np
|
|
24
24
|
import pandas as pd
|
|
25
|
-
import ast
|
|
26
|
-
import sys
|
|
25
|
+
# import ast
|
|
26
|
+
# import sys
|
|
27
27
|
|
|
28
28
|
from IPython.display import Image
|
|
29
29
|
|
|
@@ -32,13 +32,15 @@ from matplotlib import colors as mcolours
|
|
|
32
32
|
from matplotlib import patheffects
|
|
33
33
|
from matplotlib.animation import FuncAnimation
|
|
34
34
|
|
|
35
|
+
|
|
36
|
+
|
|
35
37
|
# Define colour schemes for each land cover measurement
|
|
36
38
|
lc_colours = {
|
|
37
|
-
'level3': {111: (172, 188, 45, 255, "Cultivated
|
|
38
|
-
112: (14, 121, 18, 255, "Natural
|
|
39
|
-
124: (30, 191, 121, 255, "Natural
|
|
40
|
-
215: (218, 92, 105, 255, "Artificial
|
|
41
|
-
216: (243, 171, 105, 255, "Natural
|
|
39
|
+
'level3': {111: (172, 188, 45, 255, "Cultivated\nTerrestrial\nVegetation"),
|
|
40
|
+
112: (14, 121, 18, 255, "Natural\nTerrestrial\nVegetation"),
|
|
41
|
+
124: (30, 191, 121, 255, "Natural\nAquatic\nVegetation"),
|
|
42
|
+
215: (218, 92, 105, 255, "Artificial\nSurface"),
|
|
43
|
+
216: (243, 171, 105, 255, "Natural\nBare\nSurface"),
|
|
42
44
|
220: (77, 159, 220, 255, "Water"),
|
|
43
45
|
255: (255, 255, 255, 255, "No Data")},
|
|
44
46
|
|
|
@@ -72,39 +74,6 @@ lc_colours = {
|
|
|
72
74
|
220: (77, 159, 220, 255, "Changed to Water"),
|
|
73
75
|
0: (255, 255, 255, 255, "No Change")},
|
|
74
76
|
|
|
75
|
-
'lifeform_veg_cat_l4a': {1: (14, 121, 18, 255, "Woody Vegetation"),
|
|
76
|
-
2: (172, 188, 45, 255, "Herbaceous\n Vegetation"),
|
|
77
|
-
255: (255, 255, 255, 255, "No Data /\n Not vegetated")},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
'canopyco_veg_cat_l4d': {10: (14, 121, 18, 255, "> 65 % cover"),
|
|
81
|
-
12: (45, 141, 47, 255, "40 to 65 % cover"),
|
|
82
|
-
13: (80, 160, 82, 255, "15 to 40 % cover"),
|
|
83
|
-
15: (117, 180, 118, 255, "4 to 15 % cover"),
|
|
84
|
-
16: (154, 199, 156, 255, "1 to 4 % cover"),
|
|
85
|
-
255: (255, 255, 255, 255, "No Data /\n Not vegetated")},
|
|
86
|
-
|
|
87
|
-
'waterstt_wat_cat_l4a': {1: (77, 159, 220, 255, "Water"),
|
|
88
|
-
255: (255, 255, 255, 255, "No Data /\n Not water")},
|
|
89
|
-
|
|
90
|
-
'watersea_veg_cat_l4a_au': {1: (25, 173, 109, 255, "> 3 months"),
|
|
91
|
-
2: (176, 218, 201, 255, "< 3 months"),
|
|
92
|
-
255: (255, 255, 255, 255, "No data /\n Not aquatic vegetation")},
|
|
93
|
-
|
|
94
|
-
'inttidal_wat_cat_l4a': {3: (77, 159, 220, 255, "Intertidal"),
|
|
95
|
-
255: (255, 255, 255, 255, "No data /\n Not intertidal")},
|
|
96
|
-
|
|
97
|
-
'waterper_wat_cat_l4d_au': {1: (27, 85, 186, 255, "> 9 months"),
|
|
98
|
-
7: (52, 121, 201, 255, "7 to 9 months"),
|
|
99
|
-
8: (79, 157, 217, 255, "4 to 6 months"),
|
|
100
|
-
9: (113, 202, 253, 255, "1 to 3 months"),
|
|
101
|
-
255: (255, 255, 255, 255, "No data /\n Not water")},
|
|
102
|
-
|
|
103
|
-
'baregrad_phy_cat_l4d_au': {10: (255, 230, 140, 255, "Sparsely vegetated\n (< 20% bare)"),
|
|
104
|
-
12: (250, 210, 110, 255, "Very sparsely\n vegetated (20 to 60% bare)"),
|
|
105
|
-
15: (243, 171, 105, 255, "Bare areas,\n unvegetated (> 60% bare)"),
|
|
106
|
-
255: (255, 255, 255, 255, "No data /\n Not bare")},
|
|
107
|
-
|
|
108
77
|
'level4': {
|
|
109
78
|
1: (151, 187, 26, 255, 'Cultivated Terrestrial\n Vegetated:'),
|
|
110
79
|
2: (151, 187, 26, 255, 'Cultivated Terrestrial\n Vegetated: Woody'),
|
|
@@ -257,38 +226,35 @@ lc_colours = {
|
|
|
257
226
|
},
|
|
258
227
|
}
|
|
259
228
|
|
|
229
|
+
|
|
230
|
+
|
|
260
231
|
# dictionary needed to generate colour schemes of descriptors from the level 4 colour scheme. The structure is as follow:
|
|
261
232
|
# long_descriptor_name[string]: {keyword_for_finding_classes_in_level4_colourscheme[string] : (RGB_colourscheme[4 integers], label_of_descriptor[string])}
|
|
262
233
|
lc_colours_mapping = {
|
|
263
|
-
'lifeform_veg_cat_l4a': {'Woody': (14, 121,18, 255, 'Woody
|
|
264
|
-
'Herbaceous': (172, 188, 45, 255, 'Herbaceous\
|
|
265
|
-
'canopyco_veg_cat_l4d': {'> 65 %': (14, 121, 18, 255, '> 65
|
|
266
|
-
'40 to 65 %': (45, 141, 47, 255, '40 to 65
|
|
267
|
-
'15 to 40 %': (80, 160, 82, 255, '15 to 40
|
|
268
|
-
'4 to 15 %': (117, 180, 118, 255, '4 to 15
|
|
269
|
-
'1 to 4 %': (154, 199, 156, 255, '1 to 4
|
|
234
|
+
'lifeform_veg_cat_l4a': {'Woody': (14, 121,18, 255, 'Woody\nVegetation'),
|
|
235
|
+
'Herbaceous': (172, 188, 45, 255, 'Herbaceous\nVegetation')},
|
|
236
|
+
'canopyco_veg_cat_l4d': {'> 65 %': (14, 121, 18, 255, '> 65 %\ncover'),
|
|
237
|
+
'40 to 65 %': (45, 141, 47, 255, '40 to 65 %\ncover'),
|
|
238
|
+
'15 to 40 %': (80, 160, 82, 255, '15 to 40 %\ncover'),
|
|
239
|
+
'4 to 15 %': (117, 180, 118, 255, '4 to 15 %\ncover'),
|
|
240
|
+
'1 to 4 %': (154, 199, 156, 255, '1 to 4 %\ncover')},
|
|
270
241
|
'watersea_veg_cat_l4a_au': {'(semi-) permenant': (25, 173, 109, 255, '> 3 months'),
|
|
271
242
|
'(temporary or seasonal)': (176, 218, 201, 255, '< 3 months')},
|
|
272
243
|
'waterstt_wat_cat_l4a': {'Water: (Water)': (77, 159, 220, 255, 'Water')},
|
|
273
|
-
'inttidal_wat_cat_l4a': {'Tidal area': (77, 159, 220, 255, '
|
|
274
|
-
'waterper_wat_cat_l4d_au': {'> 9 months': (27, 85, 186, 255, '> 9
|
|
275
|
-
'7 to 9 months': (52, 121, 201, 255, '7 to 9
|
|
276
|
-
'4 to 6 months': (79, 157, 217, 255, '4 to 6
|
|
277
|
-
'1 to 3 months': (113, 202, 253, 255, '1 to 3
|
|
278
|
-
'baregrad_phy_cat_l4d_au': {'Sparsely vegetated': (255, 230, 140, 255, 'Sparsely
|
|
279
|
-
'Very sparesely': (250, 210, 110, 255, 'Very sparsely\n
|
|
280
|
-
'Bare areas': (243, 171, 105, 255, 'Bare areas,\n
|
|
244
|
+
'inttidal_wat_cat_l4a': {'Tidal area': (77, 159, 220, 255, 'Tidal area')},
|
|
245
|
+
'waterper_wat_cat_l4d_au': {'> 9 months': (27, 85, 186, 255, '> 9\nmonths'),
|
|
246
|
+
'7 to 9 months': (52, 121, 201, 255, '7 to 9\nmonths'),
|
|
247
|
+
'4 to 6 months': (79, 157, 217, 255, '4 to 6\nmonths'),
|
|
248
|
+
'1 to 3 months': (113, 202, 253, 255, '1 to 3\nmonths')},
|
|
249
|
+
'baregrad_phy_cat_l4d_au': {'Sparsely vegetated': (255, 230, 140, 255, 'Sparsely\nvegetated\n(< 20% bare)'),
|
|
250
|
+
'Very sparesely': (250, 210, 110, 255, 'Very sparsely\nvegetated\n(20 to 60% bare)'),
|
|
251
|
+
'Bare areas': (243, 171, 105, 255, 'Bare areas,\nunvegetated\n(> 60% bare)')},
|
|
281
252
|
|
|
282
253
|
}
|
|
283
254
|
|
|
284
255
|
|
|
285
256
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
def get_layer_name(measurement, da):
|
|
291
|
-
aliases = {
|
|
257
|
+
aliases = {
|
|
292
258
|
'lifeform': 'lifeform_veg_cat_l4a',
|
|
293
259
|
'vegetation_cover': 'canopyco_veg_cat_l4d',
|
|
294
260
|
'water_seasonality': 'watersea_veg_cat_l4a_au',
|
|
@@ -300,97 +266,35 @@ def get_layer_name(measurement, da):
|
|
|
300
266
|
'level_4': 'level4'
|
|
301
267
|
}
|
|
302
268
|
|
|
303
|
-
# Use provided measurement if able
|
|
304
|
-
measurement = measurement.lower() if measurement else da.name
|
|
305
|
-
measurement = aliases[measurement] if measurement in aliases.keys(
|
|
306
|
-
) else measurement
|
|
307
|
-
return measurement
|
|
308
269
|
|
|
309
270
|
|
|
310
|
-
def
|
|
271
|
+
def _get_layer_name(measurement, da, aliases=aliases):
|
|
311
272
|
"""
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
For DEA Land Cover Level 4 data, this function must be used with a double plot.
|
|
315
|
-
The 'ax' should be on the left side of the figure, and the colour bar will added
|
|
316
|
-
on the right hand side.
|
|
317
|
-
|
|
318
|
-
Parameters
|
|
319
|
-
----------
|
|
320
|
-
fig : matplotlib figure
|
|
321
|
-
Figure to add colourbar to
|
|
322
|
-
ax : matplotlib ax
|
|
323
|
-
Matplotlib figure ax to add colorbar to.
|
|
324
|
-
measurement : str
|
|
325
|
-
Land cover measurement to use for colour map and labels.
|
|
326
|
-
|
|
273
|
+
Returns detailed name of descriptor given the short alias
|
|
327
274
|
"""
|
|
328
275
|
|
|
329
|
-
#
|
|
330
|
-
|
|
331
|
-
|
|
276
|
+
# Use provided measurement if able
|
|
277
|
+
measurement = measurement.lower() if measurement else da.name
|
|
278
|
+
measurement = aliases[measurement] if measurement in aliases.keys(
|
|
279
|
+
) else measurement
|
|
332
280
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
# special spacing settings for level 4
|
|
336
|
-
cax = fig.add_axes([0.62, 0.10, 0.02, 0.80])
|
|
337
|
-
orient = 'vertical'
|
|
338
|
-
|
|
339
|
-
# get level 4 colour bar colour map ect
|
|
340
|
-
cb_cmap, cb_norm, cb_labels, cb_ticks = lc_colourmap_colourbar('level4_colourbar_labels',
|
|
341
|
-
colour_bar=True)
|
|
342
|
-
elif measurement == 'level4' and animation == False:
|
|
343
|
-
|
|
344
|
-
# get level 4 colour bar colour map ect
|
|
345
|
-
cb_cmap, cb_norm, cb_labels, cb_ticks = lc_colourmap_colourbar('level4_colourbar_labels',
|
|
346
|
-
colour_bar=True)
|
|
347
|
-
#move plot over to make room for colourbar
|
|
348
|
-
fig.subplots_adjust(right=0.825)
|
|
349
|
-
|
|
350
|
-
# Settings for axis positions
|
|
351
|
-
cax = fig.add_axes([0.84, 0.15, 0.02, 0.70])
|
|
352
|
-
orient = 'vertical'
|
|
353
|
-
|
|
354
|
-
else:
|
|
355
|
-
#for all other measurements
|
|
356
|
-
|
|
357
|
-
#move plot over to make room for colourbar
|
|
358
|
-
fig.subplots_adjust(right=0.825)
|
|
359
|
-
|
|
360
|
-
# Settings for different axis positions
|
|
361
|
-
if horizontal:
|
|
362
|
-
cax = fig.add_axes([0.02, 0.05, 0.90, 0.03])
|
|
363
|
-
orient = 'horizontal'
|
|
364
|
-
else:
|
|
365
|
-
cax = fig.add_axes([0.84, 0.15, 0.02, 0.70])
|
|
366
|
-
orient = 'vertical'
|
|
367
|
-
|
|
368
|
-
# get measurement colour bar colour map ect
|
|
369
|
-
cb_cmap, cb_norm, cb_labels, cb_ticks = lc_colourmap_colourbar(measurement,
|
|
370
|
-
colour_bar=True)
|
|
371
|
-
|
|
372
|
-
img = ax.imshow([cb_ticks], cmap=cb_cmap, norm=cb_norm)
|
|
373
|
-
cb = fig.colorbar(img, cax=cax, orientation=orient)
|
|
374
|
-
|
|
375
|
-
cb.ax.tick_params(labelsize=12)
|
|
376
|
-
cb.set_ticks(cb_ticks + np.diff(cb_ticks, append=cb_ticks[-1]+1) / 2)
|
|
377
|
-
cb.set_ticklabels(cb_labels)
|
|
378
|
-
|
|
281
|
+
return measurement
|
|
379
282
|
|
|
380
283
|
|
|
381
284
|
|
|
382
|
-
def
|
|
285
|
+
def _descriptors_colours(lc_colours, lc_colours_mapping, descriptor):
|
|
383
286
|
"""
|
|
384
287
|
Generates a sorted dictionary of colours based on a given descriptor.
|
|
385
288
|
|
|
386
289
|
This function takes in a dictionary of Land Cover classes, a mapping of descriptors to colours,
|
|
387
|
-
and a specific descriptor. It returns a dictionary where the keys
|
|
388
|
-
the corresponding colours and labels from the descriptor mapping.
|
|
290
|
+
and a specific descriptor. It returns a dictionary where the keys (i.e., classes values) are sorted
|
|
291
|
+
and the values are the corresponding colours and labels from the descriptor mapping.
|
|
389
292
|
|
|
390
293
|
Parameters
|
|
391
294
|
----------
|
|
392
295
|
lc_colours : dict
|
|
393
|
-
Dictionary containing colour schemes for all Land Cover classes
|
|
296
|
+
Dictionary containing colour schemes for all Land Cover classes,
|
|
297
|
+
including Level 4 scheme (needed in this function).
|
|
394
298
|
|
|
395
299
|
lc_colours_mapping : dict
|
|
396
300
|
Dictionary mapping descriptors (e.g., lifeform) to their corresponding colours and labels.
|
|
@@ -411,43 +315,87 @@ def descriptors_colours(lc_colours, lc_colours_mapping, descriptor):
|
|
|
411
315
|
descriptor_dict = lc_colours_mapping[descriptor]
|
|
412
316
|
|
|
413
317
|
# create a new colours dictionary with all level 4 values set to white colour
|
|
414
|
-
#
|
|
318
|
+
# this dictionary is the foundation of the output returned at the end
|
|
415
319
|
colours_dict = level4_colours.copy()
|
|
416
320
|
for key in colours_dict:
|
|
417
|
-
colours_dict[key] = (255, 255, 255, 255, "No Data")
|
|
321
|
+
colours_dict[key] = (255, 255, 255, 255, "No Data/\nOther\nClasses")
|
|
418
322
|
|
|
419
|
-
# update the colours dictionary with the descriptor-specific colours
|
|
420
|
-
|
|
323
|
+
# based on the descriptor, update the colours dictionary with the descriptor-specific colours
|
|
324
|
+
# (all the rest will stay white)
|
|
325
|
+
for class_keyword, colour_n_label in descriptor_dict.items(): # iterate over descriptors mapping keys
|
|
421
326
|
|
|
422
|
-
for class_value, lvl4_scheme in level4_colours.items():
|
|
423
|
-
|
|
327
|
+
for class_value, lvl4_scheme in level4_colours.items(): # iterate over Level 4 colour scheme
|
|
328
|
+
# get the label of current Level 4 class colour
|
|
329
|
+
label_lvl4 = lvl4_scheme[4]
|
|
424
330
|
|
|
425
|
-
if class_keyword in label_lvl4:
|
|
426
|
-
|
|
331
|
+
if class_keyword in label_lvl4: # check if the current Level 4 class colour contains the current descriptor mapping key
|
|
332
|
+
# replace white colour with RGB indicated by the descriptor mapping dictionary
|
|
333
|
+
colours_dict[class_value] = colour_n_label
|
|
427
334
|
|
|
428
|
-
# sort the colours dictionary by keys
|
|
335
|
+
# sort the colours dictionary by keys (i.e., the values of classes)
|
|
429
336
|
sorted_colours_dict = {key: colours_dict[key] for key in sorted(colours_dict.keys())}
|
|
430
337
|
|
|
431
338
|
return sorted_colours_dict
|
|
432
|
-
|
|
433
339
|
|
|
434
|
-
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def get_colour_scheme(measurement):
|
|
343
|
+
"""
|
|
344
|
+
Gets colour scheme dictionary given the measurement of interest
|
|
435
345
|
"""
|
|
436
|
-
Returns colour map and normalisation for the provided DEA Land Cover
|
|
437
|
-
measurement, for use in plotting with Matplotlib library
|
|
438
346
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
plotting a colour bar. Default: False.
|
|
347
|
+
# ensure a valid colour scheme was requested
|
|
348
|
+
assert (
|
|
349
|
+
(measurement in lc_colours.keys()) # either in main colour scheme dictionary
|
|
350
|
+
or (measurement in lc_colours_mapping.keys()) # or in mapping dictionary for descriptors
|
|
351
|
+
or (measurement in aliases.keys()) # or short aliases of descriptors
|
|
352
|
+
), f'colour scheme must be one of {lc_colours.keys()} {lc_colours_mapping.keys()} {aliases.keys()} (got "{measurement}")'
|
|
353
|
+
|
|
354
|
+
# if a descriptor colour scheme is required, use the _descriptors_colours function
|
|
355
|
+
if measurement in lc_colours_mapping:
|
|
356
|
+
colour_scheme=_descriptors_colours(lc_colours,lc_colours_mapping, measurement)
|
|
450
357
|
|
|
358
|
+
else: # else, use standard colours scheme
|
|
359
|
+
colour_scheme = lc_colours[measurement]
|
|
360
|
+
|
|
361
|
+
return colour_scheme
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _reduce_colour_scheme(colour_scheme):
|
|
366
|
+
"""
|
|
367
|
+
Takes a colour scheme dictionary and returns the dictionary without duplicate values.
|
|
368
|
+
This also replaces classes values with subsequent integers, useful for placing ticks of colourbar on the side
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
# foundation of the output dictionary
|
|
372
|
+
reduced_scheme = {}
|
|
373
|
+
|
|
374
|
+
# empty list to be filled with names of classes added to the output dictionary
|
|
375
|
+
classes_added = []
|
|
376
|
+
|
|
377
|
+
new_key = 2 # key 1 was added earlier and corresponds with "no data"
|
|
378
|
+
|
|
379
|
+
for key, value in colour_scheme.items():
|
|
380
|
+
# get string with class name
|
|
381
|
+
class_name = value[4]
|
|
382
|
+
|
|
383
|
+
if (class_name not in classes_added): # check if already added in list
|
|
384
|
+
classes_added.append(class_name)
|
|
385
|
+
|
|
386
|
+
# assign the colour scheme and label to a new key in reduced_scheme
|
|
387
|
+
reduced_scheme[new_key] = value
|
|
388
|
+
# increase value of new_key for next iteration
|
|
389
|
+
new_key += 1
|
|
390
|
+
|
|
391
|
+
return reduced_scheme
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def lc_colourmap(colour_scheme):
|
|
396
|
+
"""
|
|
397
|
+
Takes a colour scheme dictionary and returns colormap for matplotlib
|
|
398
|
+
|
|
451
399
|
Returns
|
|
452
400
|
---------
|
|
453
401
|
cmap : matplotlib colormap
|
|
@@ -457,135 +405,144 @@ def lc_colourmap(colour_scheme, colour_bar=False):
|
|
|
457
405
|
Matplotlib colormap index based on the discrete intervals of the
|
|
458
406
|
classes in the specified DEA Land Cover measurement. Ensures the
|
|
459
407
|
colormap maps the colours to the class numbers correctly.
|
|
460
|
-
cblables : array
|
|
461
|
-
A two dimentional array containing the numerical class values
|
|
462
|
-
(first dim) and string labels (second dim) of the classes found
|
|
463
|
-
in the chosen DEA Land Cover measurement.
|
|
464
408
|
"""
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
colour_scheme = colour_scheme.lower()
|
|
468
|
-
|
|
469
|
-
# if a descriptor colour scheme is required, use the descriptors_colours function
|
|
470
|
-
if colour_scheme in lc_colours_mapping:
|
|
471
|
-
lc_colour_scheme=descriptors_colours(lc_colours,lc_colours_mapping, colour_scheme)
|
|
472
|
-
|
|
473
|
-
else: # standard colours scheme
|
|
474
|
-
lc_colour_scheme = lc_colours[colour_scheme]
|
|
475
|
-
# Ensure a valid colour scheme was requested
|
|
476
|
-
assert (colour_scheme in lc_colours.keys(
|
|
477
|
-
)), f'colour scheme must be one of [{lc_colours.keys()}] (got "{colour_scheme}")'
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
# Create colour map
|
|
481
|
-
colour_arr = []
|
|
482
|
-
for key, value in lc_colour_scheme.items():
|
|
483
|
-
colour_arr.append(np.array(value[:-2]) / 255)
|
|
484
|
-
|
|
485
|
-
cmap = mcolours.ListedColormap(colour_arr)
|
|
486
|
-
bounds = list(lc_colour_scheme)
|
|
487
409
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
cb_labels = []
|
|
495
|
-
for x in cb_ticks:
|
|
496
|
-
cb_labels.append(lc_colour_scheme[x][4])
|
|
410
|
+
colour_arr = [] # empty list to be populated with colours
|
|
411
|
+
for key, value in colour_scheme.items():
|
|
412
|
+
colour_arr.append(np.array(value[:-2]) / 255) # add colour to list
|
|
413
|
+
|
|
414
|
+
# create a colour map from the list of colours
|
|
415
|
+
cmap = mcolours.ListedColormap(colour_arr)
|
|
497
416
|
|
|
417
|
+
# create boundaries of colours by using the exact class values and adding a larger value at the end
|
|
418
|
+
bounds = list(colour_scheme)
|
|
498
419
|
bounds.append(bounds[-1]+1)
|
|
499
420
|
|
|
500
|
-
# shift all back by 0.5 to make sure level4 values are within bounds
|
|
421
|
+
# shift all boundaries back by 0.5 to make sure level4 values are within bounds
|
|
422
|
+
# this is a robust method to make sure each value is within a colour bin
|
|
501
423
|
bounds = [i-0.5 for i in bounds]
|
|
502
|
-
|
|
503
|
-
norm = mcolours.BoundaryNorm(np.array(bounds), cmap.N)
|
|
504
|
-
|
|
505
|
-
if colour_bar == False:
|
|
506
|
-
return (cmap, norm)
|
|
507
|
-
else:
|
|
508
|
-
return (cmap, norm, cb_labels, cb_ticks)
|
|
509
424
|
|
|
425
|
+
# normalisation for colourmap
|
|
426
|
+
norm = mcolours.BoundaryNorm(np.array(bounds), cmap.N)
|
|
427
|
+
|
|
428
|
+
return (cmap, norm)
|
|
510
429
|
|
|
430
|
+
|
|
511
431
|
|
|
512
|
-
def
|
|
432
|
+
def _legend_colourmap(colour_scheme):
|
|
513
433
|
"""
|
|
514
|
-
Returns colour map and normalisation for the
|
|
515
|
-
measurement, for use in plotting with Matplotlib library
|
|
516
|
-
|
|
434
|
+
Returns colour map and normalisation specifcially for the colourbar
|
|
435
|
+
of the provided DEA Land Cover measurement, for use in plotting with Matplotlib library
|
|
436
|
+
|
|
517
437
|
Parameters
|
|
518
438
|
----------
|
|
519
|
-
colour_scheme :
|
|
520
|
-
|
|
521
|
-
Valid options: 'level3', 'level4', 'lifeform_veg_cat_l4a',
|
|
522
|
-
'canopyco_veg_cat_l4d', 'watersea_veg_cat_l4a_au',
|
|
523
|
-
'waterstt_wat_cat_l4a', 'inttidal_wat_cat_l4a',
|
|
524
|
-
'waterper_wat_cat_l4d_au', 'baregrad_phy_cat_l4d_au'.
|
|
525
|
-
colour_bar : bool, optional
|
|
526
|
-
Controls if colour bar labels are returned as a list for
|
|
527
|
-
plotting a colour bar. Default: False.
|
|
528
|
-
|
|
439
|
+
colour_scheme : dictionary with colour scheme
|
|
440
|
+
|
|
529
441
|
Returns
|
|
530
442
|
---------
|
|
531
|
-
|
|
443
|
+
cb_cmap : matplotlib colormap
|
|
532
444
|
Matplotlib colormap containing the colour scheme for the
|
|
533
445
|
specified DEA Land Cover measurement.
|
|
534
|
-
|
|
446
|
+
cb_norm : matplotlib colormap index
|
|
535
447
|
Matplotlib colormap index based on the discrete intervals of the
|
|
536
448
|
classes in the specified DEA Land Cover measurement. Ensures the
|
|
537
|
-
colormap maps the colours to the class numbers correctly.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
(first dim) and string labels (second dim) of the classes found
|
|
449
|
+
colormap maps the colours to the class numbers correctly.
|
|
450
|
+
cb_labels : list
|
|
451
|
+
string labels of the classes found
|
|
541
452
|
in the chosen DEA Land Cover measurement.
|
|
453
|
+
cb_ticks : list
|
|
454
|
+
position of ticks in colour bar
|
|
455
|
+
|
|
542
456
|
"""
|
|
457
|
+
|
|
458
|
+
# delete duplicates to create colour bar (this effectively applies only with descriptors),
|
|
459
|
+
# and fix values for correct colourbar label positioning
|
|
460
|
+
colour_scheme = _reduce_colour_scheme(colour_scheme)
|
|
461
|
+
|
|
462
|
+
cb_cmap, cb_norm = lc_colourmap(colour_scheme)
|
|
463
|
+
|
|
464
|
+
cb_ticks = list(colour_scheme)
|
|
465
|
+
cb_labels = []
|
|
466
|
+
for x in cb_ticks:
|
|
467
|
+
cb_labels.append(colour_scheme[x][4])
|
|
468
|
+
|
|
469
|
+
return (cb_cmap, cb_norm, cb_labels, cb_ticks)
|
|
470
|
+
|
|
543
471
|
|
|
544
472
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
assert (colour_scheme in lc_colours.keys(
|
|
549
|
-
)), f'colour scheme must be one of [{lc_colours.keys()}] (got "{colour_scheme}")'
|
|
473
|
+
def make_colourbar(fig, ax, measurement, labelsize=10, horizontal=False, animation=False): # in practice, horizontal arg in never used, currently
|
|
474
|
+
"""
|
|
475
|
+
Adds a new colorbar with appropriate land cover colours and labels.
|
|
550
476
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
477
|
+
For DEA Land Cover Level 4 data, this function must be used with a double plot.
|
|
478
|
+
The 'ax' should be on the left side of the figure, and the colour bar will added
|
|
479
|
+
on the right hand side.
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
fig : matplotlib figure
|
|
484
|
+
Figure to add colourbar to
|
|
485
|
+
ax : matplotlib ax
|
|
486
|
+
Matplotlib figure ax to add colorbar to.
|
|
487
|
+
measurement : string
|
|
488
|
+
name of layer or descriptor of interest
|
|
489
|
+
labelsize : int
|
|
490
|
+
size of labels of colourbar
|
|
491
|
+
|
|
492
|
+
Returns
|
|
493
|
+
----------
|
|
494
|
+
matplotlib colorbar in its own colour axis
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
if measurement == 'level4':
|
|
498
|
+
colour_scheme = lc_colours['level4_colourbar_labels'] # use shorten labels dictionary
|
|
554
499
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
#
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
500
|
+
if animation == True:
|
|
501
|
+
# special spacing settings for level 4
|
|
502
|
+
cax = fig.add_axes([0.62, 0.05, 0.02, 0.90]) # parameters for add_axes are [left, bottom, width, height], in fractions of total plot
|
|
503
|
+
orient = 'vertical'
|
|
504
|
+
# get level 4 colour bar colour map ect
|
|
505
|
+
cb_cmap, cb_norm, cb_labels, cb_ticks = _legend_colourmap(colour_scheme)
|
|
506
|
+
|
|
507
|
+
elif animation == False:
|
|
508
|
+
#move plot over to make room for colourbar
|
|
509
|
+
fig.subplots_adjust(right=0.825)
|
|
510
|
+
# Settings for axis positions
|
|
511
|
+
cax = fig.add_axes([0.84, 0.145, 0.02, 0.70])
|
|
512
|
+
orient = 'vertical'
|
|
513
|
+
# get level 4 colour bar colour map ect
|
|
514
|
+
cb_cmap, cb_norm, cb_labels, cb_ticks = _legend_colourmap(colour_scheme)
|
|
515
|
+
|
|
516
|
+
else: #for all other measurements
|
|
517
|
+
colour_scheme = get_colour_scheme(measurement) # use standard colour scheme
|
|
518
|
+
|
|
519
|
+
#move plot over to make room for colourbar
|
|
520
|
+
fig.subplots_adjust(right=0.825)
|
|
575
521
|
|
|
576
|
-
|
|
577
|
-
|
|
522
|
+
# settings for different axis positions
|
|
523
|
+
if horizontal:
|
|
524
|
+
cax = fig.add_axes([0.02, 0.05, 0.90, 0.03])
|
|
525
|
+
orient = 'horizontal'
|
|
526
|
+
else:
|
|
527
|
+
cax = fig.add_axes([0.84, 0.145, 0.02, 0.70])
|
|
528
|
+
orient = 'vertical'
|
|
529
|
+
|
|
530
|
+
# get measurement colour bar colour map ect
|
|
531
|
+
cb_cmap, cb_norm, cb_labels, cb_ticks = _legend_colourmap(colour_scheme)
|
|
578
532
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
533
|
+
img = ax.imshow([cb_ticks], cmap=cb_cmap, norm=cb_norm)
|
|
534
|
+
cb = fig.colorbar(img, cax=cax, orientation=orient)
|
|
535
|
+
|
|
536
|
+
cb.ax.tick_params(labelsize=labelsize)
|
|
537
|
+
cb.set_ticks(cb_ticks)
|
|
538
|
+
cb.set_ticklabels(cb_labels)
|
|
583
539
|
|
|
584
540
|
|
|
585
541
|
|
|
586
|
-
def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
542
|
+
def plot_land_cover(data, labelsize=10, year=None, measurement=None, out_width=15, cols=4,):
|
|
587
543
|
"""
|
|
588
544
|
Plot a single land cover measurement with appropriate colour scheme.
|
|
545
|
+
|
|
589
546
|
Parameters
|
|
590
547
|
----------
|
|
591
548
|
data : xarray.DataArray
|
|
@@ -595,22 +552,25 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
595
552
|
all time slices are plotted.
|
|
596
553
|
measurement : string, optional
|
|
597
554
|
Name of the DEA land cover classification to be plotted. Passed to
|
|
598
|
-
|
|
555
|
+
_legend_colourmap to specify which colour scheme will be used. If non
|
|
599
556
|
provided, reads data array name from `da` to determine.
|
|
557
|
+
out_width : integer, optional
|
|
558
|
+
It controls the size of the output
|
|
559
|
+
cols: integer, optional
|
|
560
|
+
Sets number of columns if multiple time steps are visualised
|
|
561
|
+
|
|
562
|
+
Returns
|
|
563
|
+
---------
|
|
564
|
+
Matplotlib image
|
|
565
|
+
|
|
600
566
|
"""
|
|
567
|
+
|
|
601
568
|
# get measurement name
|
|
602
|
-
measurement =
|
|
569
|
+
measurement = _get_layer_name(measurement, data)
|
|
603
570
|
|
|
604
|
-
|
|
605
|
-
try:
|
|
606
|
-
cmap, norm = lc_colourmap(measurement)
|
|
607
|
-
except AssertionError:
|
|
571
|
+
colour_scheme = get_colour_scheme(measurement)
|
|
608
572
|
|
|
609
|
-
|
|
610
|
-
f'DataArray name {measurement}. Please specify which '
|
|
611
|
-
'DEA Landcover measurement is being plotted by providing'
|
|
612
|
-
'the name using the "measurement" variable For example'
|
|
613
|
-
'(measurement = "level4")')
|
|
573
|
+
cmap, norm = lc_colourmap(colour_scheme)
|
|
614
574
|
|
|
615
575
|
height, width = data.geobox.shape
|
|
616
576
|
scale = out_width / width
|
|
@@ -625,16 +585,16 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
625
585
|
|
|
626
586
|
fig, ax = plt.subplots()
|
|
627
587
|
fig.set_size_inches(width * scale, height * scale)
|
|
628
|
-
|
|
588
|
+
make_colourbar(fig, ax, measurement, labelsize)
|
|
629
589
|
im = ax.imshow(data.values, cmap=cmap, norm=norm, interpolation="nearest")
|
|
630
590
|
|
|
631
|
-
|
|
632
591
|
elif len(data.time) == 1:
|
|
633
592
|
#plotting protocol if only one timestep is passed and not a year variable
|
|
634
593
|
fig, ax = plt.subplots()
|
|
635
594
|
fig.set_size_inches(width * scale, height * scale)
|
|
636
|
-
|
|
595
|
+
make_colourbar(fig, ax, measurement, labelsize)
|
|
637
596
|
im = ax.imshow(data.isel(time=0), cmap=cmap, norm=norm, interpolation="nearest")
|
|
597
|
+
|
|
638
598
|
else:
|
|
639
599
|
#plotting protocol if multible time steps are passed to plot
|
|
640
600
|
if cols > len(data.time):
|
|
@@ -645,7 +605,7 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
645
605
|
fig.set_size_inches(
|
|
646
606
|
width * scale, (height * scale / cols) * (len(data.time) / cols))
|
|
647
607
|
|
|
648
|
-
|
|
608
|
+
make_colourbar(fig, ax.flat[0], measurement, labelsize)
|
|
649
609
|
|
|
650
610
|
for a, b in enumerate(ax.flat):
|
|
651
611
|
if a < data.shape[0]:
|
|
@@ -655,6 +615,60 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
655
615
|
return im
|
|
656
616
|
|
|
657
617
|
|
|
618
|
+
|
|
619
|
+
def _calc_class_ratio(da, measurement):
|
|
620
|
+
"""
|
|
621
|
+
Creates a table listing year by year what percentage of the
|
|
622
|
+
total area is taken up by each class.
|
|
623
|
+
Parameters
|
|
624
|
+
----------
|
|
625
|
+
da : xarray.DataArray with time dimension
|
|
626
|
+
measurement: string with name of descriptor/measurement
|
|
627
|
+
|
|
628
|
+
Returns
|
|
629
|
+
-------
|
|
630
|
+
Pandas Dataframe : containing class percentages per year
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
# list all class codes in dataset
|
|
634
|
+
list_classes = (np.unique(da, return_counts=False)).tolist()
|
|
635
|
+
|
|
636
|
+
# if a descriptor colour scheme is required, list_classes need to be chnaged to contain only classes of that descriptor
|
|
637
|
+
# the following code uses the _descriptors_colours function to get the colours scheme and then the values of the descriptor of interest
|
|
638
|
+
if measurement in lc_colours_mapping:
|
|
639
|
+
lc_colour_scheme=_descriptors_colours(lc_colours,lc_colours_mapping, measurement)
|
|
640
|
+
# sort based on RGB colour, so stack plot will show same colours next to each other
|
|
641
|
+
lc_colour_scheme= dict(sorted(lc_colour_scheme.items(), key=lambda item: item[1][0:3]))
|
|
642
|
+
# create list of values
|
|
643
|
+
all_classes_descriptor = list(lc_colour_scheme.keys())
|
|
644
|
+
# out of all possible classes of that descriptor, keep only the ones actually in the data array
|
|
645
|
+
list_classes = [i for i in all_classes_descriptor if i in list_classes] # the order of all_classes_descriptor and list_classes is important: the correct sorting order is the one of all_classes_descriptor
|
|
646
|
+
|
|
647
|
+
# create empty dataframe & dictionary
|
|
648
|
+
ratio_table = pd.DataFrame(data=None, columns=list_classes)
|
|
649
|
+
date_line = {}
|
|
650
|
+
|
|
651
|
+
# count all pixels, should be consistent
|
|
652
|
+
total_pix = da.isel(time=1).size
|
|
653
|
+
|
|
654
|
+
# iterate through each year in dataset
|
|
655
|
+
for i in range(0, len(da.time)):
|
|
656
|
+
date = str(da.time[i].data)[0:10]
|
|
657
|
+
|
|
658
|
+
# for each year iterate though each present class number
|
|
659
|
+
# and count pixels
|
|
660
|
+
for n in list_classes:
|
|
661
|
+
number_of_pixles = int(np.sum(da.isel(time=i) == n))
|
|
662
|
+
percentage = number_of_pixles / total_pix * 100
|
|
663
|
+
date_line[n] = percentage
|
|
664
|
+
|
|
665
|
+
# add each year's counts to dataframe
|
|
666
|
+
ratio_table.loc[date] = date_line
|
|
667
|
+
|
|
668
|
+
return ratio_table
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
|
|
658
672
|
def lc_animation(
|
|
659
673
|
da,
|
|
660
674
|
file_name="default_animation",
|
|
@@ -665,6 +679,7 @@ def lc_animation(
|
|
|
665
679
|
width_pixels=10,
|
|
666
680
|
dpi=150,
|
|
667
681
|
font_size=15,
|
|
682
|
+
label_size = 15,
|
|
668
683
|
label_ax=True,
|
|
669
684
|
):
|
|
670
685
|
"""
|
|
@@ -682,7 +697,7 @@ def lc_animation(
|
|
|
682
697
|
Default: "default_animation" code adds .gif suffix.
|
|
683
698
|
measurement : string, optional
|
|
684
699
|
Name of the DEA land cover classification to be plotted. Passed to
|
|
685
|
-
|
|
700
|
+
_legend_colourmap to specify which colour scheme will be used. If non
|
|
686
701
|
provided, reads data array name from `da` to determine.
|
|
687
702
|
stacked_plot: boolean, optional
|
|
688
703
|
Determines if a stacked plot showing the percentage of area
|
|
@@ -703,6 +718,8 @@ def lc_animation(
|
|
|
703
718
|
produce a higher resolution image but a larger file size and
|
|
704
719
|
slower processing. Default: 150.
|
|
705
720
|
font_size : int, optional.
|
|
721
|
+
Controls the size of the text on the axes and colour bar. Default: 15.
|
|
722
|
+
label_size : int, optional.
|
|
706
723
|
Controls the size of the text which indicates the year
|
|
707
724
|
displayed. Default: 15.
|
|
708
725
|
label_ax : boolean, optional
|
|
@@ -714,87 +731,25 @@ def lc_animation(
|
|
|
714
731
|
-------
|
|
715
732
|
A GIF (.gif) animation file.
|
|
716
733
|
"""
|
|
717
|
-
|
|
718
|
-
def calc_class_ratio(da, measurement):
|
|
719
|
-
"""
|
|
720
|
-
Creates a table listing year by year what percentage of the
|
|
721
|
-
total area is taken up by each class.
|
|
722
|
-
Parameters
|
|
723
|
-
----------
|
|
724
|
-
da : xarray.DataArray with time dimension
|
|
725
|
-
measurement: string with name of descriptor/measurement
|
|
726
|
-
Returns
|
|
727
|
-
-------
|
|
728
|
-
Pandas Dataframe : containing class percentages per year
|
|
729
|
-
"""
|
|
730
|
-
|
|
731
|
-
# list all class codes in dataset
|
|
732
|
-
list_classes = (np.unique(da, return_counts=False)).tolist()
|
|
733
|
-
|
|
734
|
-
# if a descriptor colour scheme is required, list_classes need to be chnaged to contain only classes of that descriptor
|
|
735
|
-
# the following code uses the descriptors_colours function to get the colours scheme and then the values of the descriptor of interest
|
|
736
|
-
if measurement in lc_colours_mapping:
|
|
737
|
-
lc_colour_scheme=descriptors_colours(lc_colours,lc_colours_mapping, measurement)
|
|
738
|
-
# sort based on first RGB colour, so stack plot will show same colours next to each other
|
|
739
|
-
lc_colour_scheme= dict(sorted(lc_colour_scheme.items(), key=lambda item: item[1][0]))
|
|
740
|
-
# create list of values
|
|
741
|
-
all_classes_descriptor = list(lc_colour_scheme.keys())
|
|
742
|
-
# out of all possible classes of that descriptor, keep only the ones actually in the data array
|
|
743
|
-
list_classes = [i for i in all_classes_descriptor if i in list_classes] # the order of all_classes_descriptor and list_classes is important: the correct sorting order is the one of all_classes_descriptor
|
|
744
|
-
|
|
745
|
-
# create empty dataframe & dictionary
|
|
746
|
-
ratio_table = pd.DataFrame(data=None, columns=list_classes)
|
|
747
|
-
date_line = {}
|
|
748
|
-
|
|
749
|
-
# count all pixels, should be consistent
|
|
750
|
-
total_pix = da.isel(time=1).size
|
|
751
|
-
|
|
752
|
-
# iterate through each year in dataset
|
|
753
|
-
for i in range(0, len(da.time)):
|
|
754
|
-
date = str(da.time[i].data)[0:10]
|
|
755
|
-
|
|
756
|
-
# for each year iterate though each present class number
|
|
757
|
-
# and count pixels
|
|
758
|
-
for n in list_classes:
|
|
759
|
-
number_of_pixles = int(np.sum(da.isel(time=i) == n))
|
|
760
|
-
percentage = number_of_pixles / total_pix * 100
|
|
761
|
-
date_line[n] = percentage
|
|
762
|
-
|
|
763
|
-
# add each year's counts to dataframe
|
|
764
|
-
ratio_table.loc[date] = date_line
|
|
765
|
-
return ratio_table
|
|
766
|
-
|
|
767
|
-
def rgb_to_hex(r, g, b):
|
|
768
|
-
hex = "#%x%x%x" % (r, g, b)
|
|
769
|
-
if len(hex) < 7:
|
|
770
|
-
hex = "#0" + hex[1:]
|
|
771
|
-
return hex
|
|
772
|
-
|
|
773
|
-
measurement = get_layer_name(measurement, da)
|
|
774
|
-
|
|
775
734
|
# Add gif to end of filename
|
|
776
735
|
file_name = file_name + ".gif"
|
|
777
|
-
|
|
778
|
-
# Create colour map and normalisation for specified lc measurement
|
|
779
|
-
try:
|
|
780
|
-
layer_cmap, layer_norm, cb_labels, cb_ticks = lc_colourmap(
|
|
781
|
-
measurement, colour_bar=True)
|
|
782
|
-
except AssertionError:
|
|
783
|
-
|
|
784
|
-
raise KeyError(f'Could not automatically determine colour scheme from '
|
|
785
|
-
f'DataArray name {measurement}. Please specify which '
|
|
786
|
-
'DEA Landcover measurement is being plotted by providing '
|
|
787
|
-
'the name using the "measurement" variable For example '
|
|
788
|
-
'(measurement = "level4")')
|
|
789
736
|
|
|
790
|
-
#
|
|
791
|
-
|
|
737
|
+
# get long name of measurement/variable
|
|
738
|
+
measurement = _get_layer_name(measurement, da)
|
|
739
|
+
|
|
740
|
+
# get colour scheme
|
|
741
|
+
colour_scheme = get_colour_scheme(measurement)
|
|
742
|
+
|
|
743
|
+
# Create colour map and normalisation for specified lc measurement
|
|
744
|
+
layer_cmap, layer_norm = lc_colourmap(colour_scheme)
|
|
745
|
+
|
|
746
|
+
# Get info on dataset dimensions and define size of output
|
|
792
747
|
height, width = da.geobox.shape
|
|
793
748
|
scale = width_pixels / width
|
|
794
749
|
left, bottom, right, top = da.geobox.extent.boundingbox
|
|
795
750
|
extent = [left, right, bottom, top]
|
|
796
751
|
|
|
797
|
-
|
|
752
|
+
# settings for the label showed on top of the images
|
|
798
753
|
annotation_defaults = {
|
|
799
754
|
"xy": (1, 1),
|
|
800
755
|
"xycoords": "axes fraction",
|
|
@@ -802,90 +757,54 @@ def lc_animation(
|
|
|
802
757
|
"textcoords": "offset points",
|
|
803
758
|
"horizontalalignment": "right",
|
|
804
759
|
"verticalalignment": "top",
|
|
805
|
-
"fontsize":
|
|
760
|
+
"fontsize": label_size,
|
|
806
761
|
"color": "white",
|
|
807
|
-
"path_effects":
|
|
762
|
+
"path_effects": [patheffects.withStroke(linewidth=1,
|
|
763
|
+
foreground="black")],
|
|
808
764
|
}
|
|
809
765
|
|
|
766
|
+
|
|
810
767
|
# Get information needed to display the year in the top corner
|
|
811
768
|
times_list = da.time.dt.strftime("%Y").values
|
|
812
769
|
text_list = [False] * len(times_list)
|
|
813
770
|
annotation_list = ["\n".join([str(i) for i in (a, b) if i])
|
|
814
771
|
for a, b in zip(times_list, text_list)]
|
|
815
772
|
|
|
816
|
-
|
|
773
|
+
|
|
774
|
+
if stacked_plot == True: # if need to add stacked line plot on the right
|
|
817
775
|
|
|
818
776
|
# Create table for stacked plot
|
|
819
|
-
stacked_plot_table =
|
|
777
|
+
stacked_plot_table = _calc_class_ratio(da, measurement)
|
|
820
778
|
|
|
821
779
|
# Build colour list of hex vals for stacked plot
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
colour_def = lc_colours[measurement]
|
|
780
|
+
def _rgb_to_hex(r, g, b):
|
|
781
|
+
hex = "#%x%x%x" % (r, g, b)
|
|
782
|
+
if len(hex) < 7:
|
|
783
|
+
hex = "#0" + hex[1:]
|
|
784
|
+
return hex
|
|
828
785
|
|
|
829
|
-
|
|
786
|
+
hex_colour_list = []
|
|
830
787
|
for val in list(stacked_plot_table):
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
except KeyError:
|
|
834
|
-
raise KeyError(
|
|
835
|
-
"class number not found in colour definition. "
|
|
836
|
-
"Ensure measurement name provided matches the dataset being used")
|
|
837
|
-
hex_val = rgb_to_hex(r, g, b)
|
|
788
|
+
r, g, b = colour_scheme[val][0:3]
|
|
789
|
+
hex_val = _rgb_to_hex(r, g, b)
|
|
838
790
|
hex_colour_list.append(hex_val)
|
|
839
791
|
|
|
840
|
-
# Define & set up figure
|
|
792
|
+
# Define & set up figure (two axes: the LC array and the stacked line plot)
|
|
841
793
|
fig, (ax1, ax2) = plt.subplots(1, 2, dpi=dpi, constrained_layout=True)
|
|
842
794
|
fig.set_size_inches(width * scale * 2, height * scale, forward=True)
|
|
843
|
-
fig.set_constrained_layout_pads(
|
|
844
|
-
w_pad=0.2, h_pad=0.2, hspace=0, wspace=0)
|
|
845
|
-
|
|
846
|
-
# This function is called at regular intervals with changing i
|
|
847
|
-
# values for each frame
|
|
848
|
-
def _update_frames(i, ax1, ax2, extent, annotation_text,
|
|
849
|
-
annotation_defaults, cmap, norm):
|
|
850
|
-
# Clear previous frame to optimise render speed and plot imagery
|
|
851
|
-
ax1.clear()
|
|
852
|
-
ax2.clear()
|
|
853
|
-
|
|
854
|
-
ax1.imshow(da[i, ...], cmap=cmap, norm=norm,
|
|
855
|
-
extent=extent, interpolation="nearest")
|
|
856
|
-
if(not label_ax):
|
|
857
|
-
ax1.set_axis_off()
|
|
858
|
-
|
|
859
|
-
clipped_table = stacked_plot_table.iloc[: int(i + 1)]
|
|
860
|
-
data = clipped_table.to_dict(orient="list")
|
|
861
|
-
date = clipped_table.index
|
|
795
|
+
fig.set_constrained_layout_pads(w_pad=0.2, h_pad=0.2, hspace=0, wspace=0)
|
|
862
796
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
# Add annotation text
|
|
868
|
-
ax1.annotate(annotation_text[i], **annotation_defaults)
|
|
869
|
-
ax2.annotate(annotation_text[i], **annotation_defaults)
|
|
797
|
+
# set the size of the ticks labels using font_size
|
|
798
|
+
ax1.tick_params(axis='both', which='major', labelsize=font_size)
|
|
799
|
+
ax2.tick_params(axis='both', which='major', labelsize=font_size)
|
|
870
800
|
|
|
871
|
-
#
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
# earlier being passed through
|
|
875
|
-
anim_fargs = (
|
|
876
|
-
ax1,
|
|
877
|
-
ax2, # axis to plot into
|
|
878
|
-
[left, right, bottom, top], # imshow extent
|
|
879
|
-
annotation_list,
|
|
880
|
-
annotation_defaults,
|
|
881
|
-
layer_cmap,
|
|
882
|
-
layer_norm,
|
|
883
|
-
)
|
|
801
|
+
# define list of axes to use in anim_fargs and, in turn, in _update_frames
|
|
802
|
+
axes = [ax1,ax2]
|
|
803
|
+
|
|
884
804
|
|
|
885
|
-
else: # stacked_plot
|
|
805
|
+
else: # i.e., stacked_plot == False
|
|
886
806
|
|
|
887
807
|
# if plotting level 4 with colourbar
|
|
888
|
-
|
|
889
808
|
if measurement == 'level4' and colour_bar == True:
|
|
890
809
|
|
|
891
810
|
# specific setting to fit level 4 colour bar beside the plot
|
|
@@ -895,62 +814,106 @@ def lc_animation(
|
|
|
895
814
|
# Define & set up figure, two subplots so colour bar fits :)
|
|
896
815
|
fig, (ax1, ax2) = plt.subplots(1, 2, dpi=dpi,
|
|
897
816
|
constrained_layout=True, gridspec_kw={'width_ratios': [3, 1]})
|
|
898
|
-
fig.set_size_inches(width * scale * 2,
|
|
899
|
-
|
|
900
|
-
fig.set_constrained_layout_pads(
|
|
901
|
-
w_pad=0.2, h_pad=0.2, hspace=0, wspace=0)
|
|
817
|
+
fig.set_size_inches(width * scale * 2, height * scale, forward=True)
|
|
818
|
+
fig.set_constrained_layout_pads(w_pad=0.2, h_pad=0.2, hspace=0, wspace=0)
|
|
902
819
|
|
|
903
820
|
# make colour bar
|
|
904
821
|
# provide left hand canvas to colour bar fuction which is where the image will go
|
|
905
822
|
# colourbar will plot on right side beside it
|
|
906
|
-
|
|
907
|
-
make_colorbar(fig, ax1, measurement, animation=True)
|
|
823
|
+
make_colourbar(fig, ax1, measurement, labelsize=font_size, animation=True)
|
|
908
824
|
|
|
909
825
|
# turn off lines for second plot so it's not ontop of colourbar
|
|
910
826
|
ax2.set_axis_off()
|
|
911
827
|
|
|
912
828
|
# plotting any other measurement with or with-out colour bar or level 4 without
|
|
913
829
|
else:
|
|
914
|
-
|
|
915
830
|
# Define & set up figure
|
|
916
831
|
fig, ax1 = plt.subplots(1, 1, dpi=dpi)
|
|
917
832
|
fig.set_size_inches(width * scale, height * scale, forward=True)
|
|
833
|
+
|
|
918
834
|
if(not label_ax):
|
|
919
835
|
fig.subplots_adjust(left=0, bottom=0, right=1,
|
|
920
836
|
top=1, wspace=None, hspace=None)
|
|
921
|
-
#
|
|
837
|
+
# make colourbar if required
|
|
922
838
|
if colour_bar:
|
|
923
|
-
|
|
839
|
+
make_colourbar(fig, ax1, measurement, labelsize=font_size)
|
|
924
840
|
|
|
841
|
+
# set the size of the ticks labels using font_size
|
|
842
|
+
ax1.tick_params(axis='both', which='major', labelsize=font_size)
|
|
843
|
+
|
|
844
|
+
# define list of axes to use in anim_fargs and, in turn, in _update_frames
|
|
845
|
+
axes = [ax1]
|
|
925
846
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if(not label_ax):
|
|
935
|
-
ax1.set_axis_off()
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
#################################################################
|
|
850
|
+
#### This function is called at the end at regular intervals ####
|
|
851
|
+
#### with changing i values for each frame ####
|
|
852
|
+
#################################################################
|
|
853
|
+
def _update_frames(i, axes, extent, annotation_text,
|
|
854
|
+
annotation_defaults, cmap, norm):
|
|
936
855
|
|
|
856
|
+
ax1 = axes[0] # at least one axis is always present
|
|
857
|
+
|
|
858
|
+
# Clear previous frame to optimise render speed and plot imagery
|
|
859
|
+
ax1.clear()
|
|
860
|
+
|
|
861
|
+
# Add annotation text
|
|
862
|
+
ax1.annotate(annotation_text[i], **annotation_defaults)
|
|
863
|
+
|
|
864
|
+
# Generate image
|
|
865
|
+
ax1.imshow(da[i, ...], cmap=cmap, norm=norm,
|
|
866
|
+
extent=extent, interpolation="nearest")
|
|
867
|
+
|
|
868
|
+
# set size of 1e6 using font_size
|
|
869
|
+
ax1.yaxis.get_offset_text().set_fontsize(font_size)
|
|
870
|
+
ax1.xaxis.get_offset_text().set_fontsize(font_size)
|
|
871
|
+
|
|
872
|
+
# if asked that axes have no labels, remove them
|
|
873
|
+
if(not label_ax):
|
|
874
|
+
ax1.set_axis_off()
|
|
875
|
+
|
|
876
|
+
try: # this will fail and be skipped if a second axes (i.e. stacked line plot) does not exist
|
|
877
|
+
ax2 = axes[1]
|
|
878
|
+
ax2.clear()
|
|
879
|
+
|
|
880
|
+
# get the classes ratio up to the current time step i
|
|
881
|
+
clipped_table = stacked_plot_table.iloc[: int(i + 1)]
|
|
882
|
+
data = clipped_table.to_dict(orient="list")
|
|
883
|
+
date = clipped_table.index
|
|
884
|
+
|
|
885
|
+
# add stacked line plot to axes 2
|
|
886
|
+
ax2.stackplot(date, data.values(), colors=hex_colour_list)
|
|
887
|
+
ax2.tick_params(axis="x", labelrotation=-90)
|
|
888
|
+
ax2.margins(x=0, y=0)
|
|
889
|
+
|
|
937
890
|
# Add annotation text
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
891
|
+
ax2.annotate(annotation_text[i], **annotation_defaults)
|
|
892
|
+
|
|
893
|
+
# set size of 1e6 using font_size
|
|
894
|
+
ax2.yaxis.get_offset_text().set_fontsize(font_size)
|
|
895
|
+
ax2.xaxis.get_offset_text().set_fontsize(font_size)
|
|
896
|
+
|
|
897
|
+
except:
|
|
898
|
+
pass
|
|
899
|
+
#################################################################
|
|
900
|
+
#################################################################
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
# anim_fargs contains all the values we send to our
|
|
905
|
+
# _update_frames function.
|
|
906
|
+
anim_fargs = (
|
|
907
|
+
axes,
|
|
908
|
+
[left, right, bottom, top], # imshow extent
|
|
909
|
+
annotation_list,
|
|
910
|
+
annotation_defaults,
|
|
911
|
+
layer_cmap,
|
|
912
|
+
layer_norm,
|
|
951
913
|
)
|
|
952
914
|
|
|
953
|
-
|
|
915
|
+
|
|
916
|
+
# create animation
|
|
954
917
|
anim = FuncAnimation(
|
|
955
918
|
fig=fig,
|
|
956
919
|
func=_update_frames,
|
|
@@ -960,6 +923,9 @@ def lc_animation(
|
|
|
960
923
|
repeat=False,
|
|
961
924
|
)
|
|
962
925
|
|
|
926
|
+
# save animation
|
|
963
927
|
anim.save(file_name, writer="pillow", dpi=dpi)
|
|
928
|
+
|
|
964
929
|
plt.close()
|
|
930
|
+
|
|
965
931
|
return Image(filename=file_name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|