dea-tools 0.3.6.dev34__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.dev34/dea_tools.egg-info → dea_tools-0.3.6.dev38}/PKG-INFO +1 -1
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/landcover.py +397 -445
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38/dea_tools.egg-info}/PKG-INFO +1 -1
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/.gitignore +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/LICENSE +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/MANIFEST.in +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/README.rst +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/__init__.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/__main__.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/__init__.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/animations.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/changefilmstrips.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/crophealth.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/deacoastlines.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/geomedian.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/imageexport.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/miningrehab.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/app/widgetconstructors.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/bandindices.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/bom.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/classification.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/climate.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/coastal.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/dask.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/datahandling.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/maps.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/plotting.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/pyfes_model.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/spatial.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/temporal.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/validation.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/waterbodies.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools/wetlands.py +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/SOURCES.txt +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/dependency_links.txt +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/requires.txt +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/dea_tools.egg-info/top_level.txt +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/index.rst +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/mock_imports.txt +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/pyproject.toml +0 -0
- {dea_tools-0.3.6.dev34 → dea_tools-0.3.6.dev38}/setup.cfg +0 -0
- {dea_tools-0.3.6.dev34 → 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,6 +32,8 @@ 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
39
|
'level3': {111: (172, 188, 45, 255, "Cultivated\nTerrestrial\nVegetation"),
|
|
@@ -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\nVegetation"),
|
|
76
|
-
2: (172, 188, 45, 255, "Herbaceous\nVegetation"),
|
|
77
|
-
255: (255, 255, 255, 255, "No Data/\nNot vegetated")},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
'canopyco_veg_cat_l4d': {10: (14, 121, 18, 255, "> 65 % \ncover"),
|
|
81
|
-
12: (45, 141, 47, 255, "40 to 65 % \ncover"),
|
|
82
|
-
13: (80, 160, 82, 255, "15 to 40 % \ncover"),
|
|
83
|
-
15: (117, 180, 118, 255, "4 to 15 % \ncover"),
|
|
84
|
-
16: (154, 199, 156, 255, "1 to 4 % \ncover"),
|
|
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 /\nNot 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/\nNot aquatic\nvegetation")},
|
|
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\nmonths"),
|
|
98
|
-
7: (52, 121, 201, 255, "7 to 9\nmonths"),
|
|
99
|
-
8: (79, 157, 217, 255, "4 to 6\nmonths"),
|
|
100
|
-
9: (113, 202, 253, 255, "1 to 3\nmonths"),
|
|
101
|
-
255: (255, 255, 255, 255, "No data/\nNot water")},
|
|
102
|
-
|
|
103
|
-
'baregrad_phy_cat_l4d_au': {10: (255, 230, 140, 255, "Sparsely\nvegetated\n(<20% bare)"),
|
|
104
|
-
12: (250, 210, 110, 255, "Very sparsely\nvegetated\n(20 to 60% bare)"),
|
|
105
|
-
15: (243, 171, 105, 255, "Bare areas,\nunvegetated\n(>60% bare)"),
|
|
106
|
-
255: (255, 255, 255, 255, "No data/\nNot 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,98 +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
|
-
labelsize : int
|
|
327
|
-
size of labels of colourbar
|
|
328
|
-
|
|
273
|
+
Returns detailed name of descriptor given the short alias
|
|
329
274
|
"""
|
|
330
275
|
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
334
280
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
# special spacing settings for level 4
|
|
338
|
-
cax = fig.add_axes([0.62, 0.10, 0.02, 0.80])
|
|
339
|
-
orient = 'vertical'
|
|
340
|
-
|
|
341
|
-
# get level 4 colour bar colour map ect
|
|
342
|
-
cb_cmap, cb_norm, cb_labels, cb_ticks = lc_colourmap_colourbar('level4_colourbar_labels',
|
|
343
|
-
colour_bar=True)
|
|
344
|
-
elif measurement == 'level4' and animation == False:
|
|
345
|
-
|
|
346
|
-
# get level 4 colour bar colour map ect
|
|
347
|
-
cb_cmap, cb_norm, cb_labels, cb_ticks = lc_colourmap_colourbar('level4_colourbar_labels',
|
|
348
|
-
colour_bar=True)
|
|
349
|
-
#move plot over to make room for colourbar
|
|
350
|
-
fig.subplots_adjust(right=0.825)
|
|
351
|
-
|
|
352
|
-
# Settings for axis positions
|
|
353
|
-
cax = fig.add_axes([0.84, 0.15, 0.02, 0.70])
|
|
354
|
-
orient = 'vertical'
|
|
355
|
-
|
|
356
|
-
else:
|
|
357
|
-
#for all other measurements
|
|
358
|
-
|
|
359
|
-
#move plot over to make room for colourbar
|
|
360
|
-
fig.subplots_adjust(right=0.825)
|
|
361
|
-
|
|
362
|
-
# Settings for different axis positions
|
|
363
|
-
if horizontal:
|
|
364
|
-
cax = fig.add_axes([0.02, 0.05, 0.90, 0.03])
|
|
365
|
-
orient = 'horizontal'
|
|
366
|
-
else:
|
|
367
|
-
cax = fig.add_axes([0.84, 0.15, 0.02, 0.70])
|
|
368
|
-
orient = 'vertical'
|
|
369
|
-
|
|
370
|
-
# get measurement colour bar colour map ect
|
|
371
|
-
cb_cmap, cb_norm, cb_labels, cb_ticks = lc_colourmap_colourbar(measurement,
|
|
372
|
-
colour_bar=True)
|
|
373
|
-
|
|
374
|
-
img = ax.imshow([cb_ticks], cmap=cb_cmap, norm=cb_norm)
|
|
375
|
-
cb = fig.colorbar(img, cax=cax, orientation=orient)
|
|
376
|
-
|
|
377
|
-
cb.ax.tick_params(labelsize=labelsize)
|
|
378
|
-
cb.set_ticks(cb_ticks + np.diff(cb_ticks, append=cb_ticks[-1]+1) / 2)
|
|
379
|
-
cb.set_ticklabels(cb_labels)
|
|
281
|
+
return measurement
|
|
380
282
|
|
|
381
283
|
|
|
382
284
|
|
|
383
|
-
def
|
|
285
|
+
def _descriptors_colours(lc_colours, lc_colours_mapping, descriptor):
|
|
384
286
|
"""
|
|
385
287
|
Generates a sorted dictionary of colours based on a given descriptor.
|
|
386
288
|
|
|
387
289
|
This function takes in a dictionary of Land Cover classes, a mapping of descriptors to colours,
|
|
388
|
-
and a specific descriptor. It returns a dictionary where the keys
|
|
389
|
-
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.
|
|
390
292
|
|
|
391
293
|
Parameters
|
|
392
294
|
----------
|
|
393
295
|
lc_colours : dict
|
|
394
|
-
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).
|
|
395
298
|
|
|
396
299
|
lc_colours_mapping : dict
|
|
397
300
|
Dictionary mapping descriptors (e.g., lifeform) to their corresponding colours and labels.
|
|
@@ -412,43 +315,87 @@ def descriptors_colours(lc_colours, lc_colours_mapping, descriptor):
|
|
|
412
315
|
descriptor_dict = lc_colours_mapping[descriptor]
|
|
413
316
|
|
|
414
317
|
# create a new colours dictionary with all level 4 values set to white colour
|
|
415
|
-
#
|
|
318
|
+
# this dictionary is the foundation of the output returned at the end
|
|
416
319
|
colours_dict = level4_colours.copy()
|
|
417
320
|
for key in colours_dict:
|
|
418
|
-
colours_dict[key] = (255, 255, 255, 255, "No Data")
|
|
321
|
+
colours_dict[key] = (255, 255, 255, 255, "No Data/\nOther\nClasses")
|
|
419
322
|
|
|
420
|
-
# update the colours dictionary with the descriptor-specific colours
|
|
421
|
-
|
|
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
|
|
422
326
|
|
|
423
|
-
for class_value, lvl4_scheme in level4_colours.items():
|
|
424
|
-
|
|
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]
|
|
425
330
|
|
|
426
|
-
if class_keyword in label_lvl4:
|
|
427
|
-
|
|
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
|
|
428
334
|
|
|
429
|
-
# sort the colours dictionary by keys
|
|
335
|
+
# sort the colours dictionary by keys (i.e., the values of classes)
|
|
430
336
|
sorted_colours_dict = {key: colours_dict[key] for key in sorted(colours_dict.keys())}
|
|
431
337
|
|
|
432
338
|
return sorted_colours_dict
|
|
433
339
|
|
|
434
340
|
|
|
435
|
-
|
|
341
|
+
|
|
342
|
+
def get_colour_scheme(measurement):
|
|
343
|
+
"""
|
|
344
|
+
Gets colour scheme dictionary given the measurement of interest
|
|
436
345
|
"""
|
|
437
|
-
Returns colour map and normalisation for the provided DEA Land Cover
|
|
438
|
-
measurement, for use in plotting with Matplotlib library
|
|
439
346
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
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)
|
|
451
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
|
+
|
|
452
399
|
Returns
|
|
453
400
|
---------
|
|
454
401
|
cmap : matplotlib colormap
|
|
@@ -458,135 +405,144 @@ def lc_colourmap(colour_scheme, colour_bar=False):
|
|
|
458
405
|
Matplotlib colormap index based on the discrete intervals of the
|
|
459
406
|
classes in the specified DEA Land Cover measurement. Ensures the
|
|
460
407
|
colormap maps the colours to the class numbers correctly.
|
|
461
|
-
cblables : array
|
|
462
|
-
A two dimentional array containing the numerical class values
|
|
463
|
-
(first dim) and string labels (second dim) of the classes found
|
|
464
|
-
in the chosen DEA Land Cover measurement.
|
|
465
408
|
"""
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
colour_scheme = colour_scheme.lower()
|
|
469
|
-
|
|
470
|
-
# if a descriptor colour scheme is required, use the descriptors_colours function
|
|
471
|
-
if colour_scheme in lc_colours_mapping:
|
|
472
|
-
lc_colour_scheme=descriptors_colours(lc_colours,lc_colours_mapping, colour_scheme)
|
|
473
|
-
|
|
474
|
-
else: # standard colours scheme
|
|
475
|
-
lc_colour_scheme = lc_colours[colour_scheme]
|
|
476
|
-
# Ensure a valid colour scheme was requested
|
|
477
|
-
assert (colour_scheme in lc_colours.keys(
|
|
478
|
-
)), f'colour scheme must be one of [{lc_colours.keys()}] (got "{colour_scheme}")'
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
# Create colour map
|
|
482
|
-
colour_arr = []
|
|
483
|
-
for key, value in lc_colour_scheme.items():
|
|
484
|
-
colour_arr.append(np.array(value[:-2]) / 255)
|
|
485
|
-
|
|
486
|
-
cmap = mcolours.ListedColormap(colour_arr)
|
|
487
|
-
bounds = list(lc_colour_scheme)
|
|
488
409
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
cb_labels = []
|
|
496
|
-
for x in cb_ticks:
|
|
497
|
-
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)
|
|
498
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)
|
|
499
419
|
bounds.append(bounds[-1]+1)
|
|
500
420
|
|
|
501
|
-
# 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
|
|
502
423
|
bounds = [i-0.5 for i in bounds]
|
|
503
|
-
|
|
504
|
-
norm = mcolours.BoundaryNorm(np.array(bounds), cmap.N)
|
|
505
|
-
|
|
506
|
-
if colour_bar == False:
|
|
507
|
-
return (cmap, norm)
|
|
508
|
-
else:
|
|
509
|
-
return (cmap, norm, cb_labels, cb_ticks)
|
|
510
424
|
|
|
425
|
+
# normalisation for colourmap
|
|
426
|
+
norm = mcolours.BoundaryNorm(np.array(bounds), cmap.N)
|
|
427
|
+
|
|
428
|
+
return (cmap, norm)
|
|
511
429
|
|
|
430
|
+
|
|
512
431
|
|
|
513
|
-
def
|
|
432
|
+
def _legend_colourmap(colour_scheme):
|
|
514
433
|
"""
|
|
515
|
-
Returns colour map and normalisation for the
|
|
516
|
-
measurement, for use in plotting with Matplotlib library
|
|
517
|
-
|
|
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
|
+
|
|
518
437
|
Parameters
|
|
519
438
|
----------
|
|
520
|
-
colour_scheme :
|
|
521
|
-
|
|
522
|
-
Valid options: 'level3', 'level4', 'lifeform_veg_cat_l4a',
|
|
523
|
-
'canopyco_veg_cat_l4d', 'watersea_veg_cat_l4a_au',
|
|
524
|
-
'waterstt_wat_cat_l4a', 'inttidal_wat_cat_l4a',
|
|
525
|
-
'waterper_wat_cat_l4d_au', 'baregrad_phy_cat_l4d_au'.
|
|
526
|
-
colour_bar : bool, optional
|
|
527
|
-
Controls if colour bar labels are returned as a list for
|
|
528
|
-
plotting a colour bar. Default: False.
|
|
529
|
-
|
|
439
|
+
colour_scheme : dictionary with colour scheme
|
|
440
|
+
|
|
530
441
|
Returns
|
|
531
442
|
---------
|
|
532
|
-
|
|
443
|
+
cb_cmap : matplotlib colormap
|
|
533
444
|
Matplotlib colormap containing the colour scheme for the
|
|
534
445
|
specified DEA Land Cover measurement.
|
|
535
|
-
|
|
446
|
+
cb_norm : matplotlib colormap index
|
|
536
447
|
Matplotlib colormap index based on the discrete intervals of the
|
|
537
448
|
classes in the specified DEA Land Cover measurement. Ensures the
|
|
538
|
-
colormap maps the colours to the class numbers correctly.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
(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
|
|
542
452
|
in the chosen DEA Land Cover measurement.
|
|
453
|
+
cb_ticks : list
|
|
454
|
+
position of ticks in colour bar
|
|
455
|
+
|
|
543
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)
|
|
544
461
|
|
|
462
|
+
cb_cmap, cb_norm = lc_colourmap(colour_scheme)
|
|
545
463
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
+
|
|
551
471
|
|
|
552
|
-
# ('The dataset provided does not have a valid '
|
|
553
|
-
# 'name. Please specify which DEA Landcover measurement is being plotted '
|
|
554
|
-
# 'by providing the name using the "measurement" variable. For example (measurement = "full_classification")')
|
|
555
472
|
|
|
556
|
-
|
|
557
|
-
|
|
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.
|
|
558
476
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
+
"""
|
|
563
496
|
|
|
564
|
-
|
|
565
|
-
|
|
497
|
+
if measurement == 'level4':
|
|
498
|
+
colour_scheme = lc_colours['level4_colourbar_labels'] # use shorten labels dictionary
|
|
499
|
+
|
|
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)
|
|
566
521
|
|
|
567
|
-
|
|
568
|
-
if
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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)
|
|
576
532
|
|
|
577
|
-
|
|
578
|
-
|
|
533
|
+
img = ax.imshow([cb_ticks], cmap=cb_cmap, norm=cb_norm)
|
|
534
|
+
cb = fig.colorbar(img, cax=cax, orientation=orient)
|
|
579
535
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return (cmap, norm, cb_labels, cb_ticks)
|
|
536
|
+
cb.ax.tick_params(labelsize=labelsize)
|
|
537
|
+
cb.set_ticks(cb_ticks)
|
|
538
|
+
cb.set_ticklabels(cb_labels)
|
|
584
539
|
|
|
585
540
|
|
|
586
541
|
|
|
587
|
-
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,):
|
|
588
543
|
"""
|
|
589
544
|
Plot a single land cover measurement with appropriate colour scheme.
|
|
545
|
+
|
|
590
546
|
Parameters
|
|
591
547
|
----------
|
|
592
548
|
data : xarray.DataArray
|
|
@@ -596,22 +552,25 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
596
552
|
all time slices are plotted.
|
|
597
553
|
measurement : string, optional
|
|
598
554
|
Name of the DEA land cover classification to be plotted. Passed to
|
|
599
|
-
|
|
555
|
+
_legend_colourmap to specify which colour scheme will be used. If non
|
|
600
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
|
+
|
|
601
566
|
"""
|
|
567
|
+
|
|
602
568
|
# get measurement name
|
|
603
|
-
measurement =
|
|
569
|
+
measurement = _get_layer_name(measurement, data)
|
|
604
570
|
|
|
605
|
-
|
|
606
|
-
try:
|
|
607
|
-
cmap, norm = lc_colourmap(measurement)
|
|
608
|
-
except AssertionError:
|
|
571
|
+
colour_scheme = get_colour_scheme(measurement)
|
|
609
572
|
|
|
610
|
-
|
|
611
|
-
f'DataArray name {measurement}. Please specify which '
|
|
612
|
-
'DEA Landcover measurement is being plotted by providing'
|
|
613
|
-
'the name using the "measurement" variable For example'
|
|
614
|
-
'(measurement = "level4")')
|
|
573
|
+
cmap, norm = lc_colourmap(colour_scheme)
|
|
615
574
|
|
|
616
575
|
height, width = data.geobox.shape
|
|
617
576
|
scale = out_width / width
|
|
@@ -626,16 +585,16 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
626
585
|
|
|
627
586
|
fig, ax = plt.subplots()
|
|
628
587
|
fig.set_size_inches(width * scale, height * scale)
|
|
629
|
-
|
|
588
|
+
make_colourbar(fig, ax, measurement, labelsize)
|
|
630
589
|
im = ax.imshow(data.values, cmap=cmap, norm=norm, interpolation="nearest")
|
|
631
590
|
|
|
632
|
-
|
|
633
591
|
elif len(data.time) == 1:
|
|
634
592
|
#plotting protocol if only one timestep is passed and not a year variable
|
|
635
593
|
fig, ax = plt.subplots()
|
|
636
594
|
fig.set_size_inches(width * scale, height * scale)
|
|
637
|
-
|
|
595
|
+
make_colourbar(fig, ax, measurement, labelsize)
|
|
638
596
|
im = ax.imshow(data.isel(time=0), cmap=cmap, norm=norm, interpolation="nearest")
|
|
597
|
+
|
|
639
598
|
else:
|
|
640
599
|
#plotting protocol if multible time steps are passed to plot
|
|
641
600
|
if cols > len(data.time):
|
|
@@ -646,7 +605,7 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
646
605
|
fig.set_size_inches(
|
|
647
606
|
width * scale, (height * scale / cols) * (len(data.time) / cols))
|
|
648
607
|
|
|
649
|
-
|
|
608
|
+
make_colourbar(fig, ax.flat[0], measurement, labelsize)
|
|
650
609
|
|
|
651
610
|
for a, b in enumerate(ax.flat):
|
|
652
611
|
if a < data.shape[0]:
|
|
@@ -656,6 +615,60 @@ def plot_land_cover(data, year=None, measurement=None, out_width=15, cols=4,):
|
|
|
656
615
|
return im
|
|
657
616
|
|
|
658
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
|
+
|
|
659
672
|
def lc_animation(
|
|
660
673
|
da,
|
|
661
674
|
file_name="default_animation",
|
|
@@ -666,6 +679,7 @@ def lc_animation(
|
|
|
666
679
|
width_pixels=10,
|
|
667
680
|
dpi=150,
|
|
668
681
|
font_size=15,
|
|
682
|
+
label_size = 15,
|
|
669
683
|
label_ax=True,
|
|
670
684
|
):
|
|
671
685
|
"""
|
|
@@ -683,7 +697,7 @@ def lc_animation(
|
|
|
683
697
|
Default: "default_animation" code adds .gif suffix.
|
|
684
698
|
measurement : string, optional
|
|
685
699
|
Name of the DEA land cover classification to be plotted. Passed to
|
|
686
|
-
|
|
700
|
+
_legend_colourmap to specify which colour scheme will be used. If non
|
|
687
701
|
provided, reads data array name from `da` to determine.
|
|
688
702
|
stacked_plot: boolean, optional
|
|
689
703
|
Determines if a stacked plot showing the percentage of area
|
|
@@ -704,6 +718,8 @@ def lc_animation(
|
|
|
704
718
|
produce a higher resolution image but a larger file size and
|
|
705
719
|
slower processing. Default: 150.
|
|
706
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.
|
|
707
723
|
Controls the size of the text which indicates the year
|
|
708
724
|
displayed. Default: 15.
|
|
709
725
|
label_ax : boolean, optional
|
|
@@ -715,87 +731,25 @@ def lc_animation(
|
|
|
715
731
|
-------
|
|
716
732
|
A GIF (.gif) animation file.
|
|
717
733
|
"""
|
|
718
|
-
|
|
719
|
-
def calc_class_ratio(da, measurement):
|
|
720
|
-
"""
|
|
721
|
-
Creates a table listing year by year what percentage of the
|
|
722
|
-
total area is taken up by each class.
|
|
723
|
-
Parameters
|
|
724
|
-
----------
|
|
725
|
-
da : xarray.DataArray with time dimension
|
|
726
|
-
measurement: string with name of descriptor/measurement
|
|
727
|
-
Returns
|
|
728
|
-
-------
|
|
729
|
-
Pandas Dataframe : containing class percentages per year
|
|
730
|
-
"""
|
|
731
|
-
|
|
732
|
-
# list all class codes in dataset
|
|
733
|
-
list_classes = (np.unique(da, return_counts=False)).tolist()
|
|
734
|
-
|
|
735
|
-
# if a descriptor colour scheme is required, list_classes need to be chnaged to contain only classes of that descriptor
|
|
736
|
-
# the following code uses the descriptors_colours function to get the colours scheme and then the values of the descriptor of interest
|
|
737
|
-
if measurement in lc_colours_mapping:
|
|
738
|
-
lc_colour_scheme=descriptors_colours(lc_colours,lc_colours_mapping, measurement)
|
|
739
|
-
# sort based on first RGB colour, so stack plot will show same colours next to each other
|
|
740
|
-
lc_colour_scheme= dict(sorted(lc_colour_scheme.items(), key=lambda item: item[1][0]))
|
|
741
|
-
# create list of values
|
|
742
|
-
all_classes_descriptor = list(lc_colour_scheme.keys())
|
|
743
|
-
# out of all possible classes of that descriptor, keep only the ones actually in the data array
|
|
744
|
-
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
|
|
745
|
-
|
|
746
|
-
# create empty dataframe & dictionary
|
|
747
|
-
ratio_table = pd.DataFrame(data=None, columns=list_classes)
|
|
748
|
-
date_line = {}
|
|
749
|
-
|
|
750
|
-
# count all pixels, should be consistent
|
|
751
|
-
total_pix = da.isel(time=1).size
|
|
752
|
-
|
|
753
|
-
# iterate through each year in dataset
|
|
754
|
-
for i in range(0, len(da.time)):
|
|
755
|
-
date = str(da.time[i].data)[0:10]
|
|
756
|
-
|
|
757
|
-
# for each year iterate though each present class number
|
|
758
|
-
# and count pixels
|
|
759
|
-
for n in list_classes:
|
|
760
|
-
number_of_pixles = int(np.sum(da.isel(time=i) == n))
|
|
761
|
-
percentage = number_of_pixles / total_pix * 100
|
|
762
|
-
date_line[n] = percentage
|
|
763
|
-
|
|
764
|
-
# add each year's counts to dataframe
|
|
765
|
-
ratio_table.loc[date] = date_line
|
|
766
|
-
return ratio_table
|
|
767
|
-
|
|
768
|
-
def rgb_to_hex(r, g, b):
|
|
769
|
-
hex = "#%x%x%x" % (r, g, b)
|
|
770
|
-
if len(hex) < 7:
|
|
771
|
-
hex = "#0" + hex[1:]
|
|
772
|
-
return hex
|
|
773
|
-
|
|
774
|
-
measurement = get_layer_name(measurement, da)
|
|
775
|
-
|
|
776
734
|
# Add gif to end of filename
|
|
777
735
|
file_name = file_name + ".gif"
|
|
778
|
-
|
|
779
|
-
# Create colour map and normalisation for specified lc measurement
|
|
780
|
-
try:
|
|
781
|
-
layer_cmap, layer_norm, cb_labels, cb_ticks = lc_colourmap(
|
|
782
|
-
measurement, colour_bar=True)
|
|
783
|
-
except AssertionError:
|
|
784
|
-
|
|
785
|
-
raise KeyError(f'Could not automatically determine colour scheme from '
|
|
786
|
-
f'DataArray name {measurement}. Please specify which '
|
|
787
|
-
'DEA Landcover measurement is being plotted by providing '
|
|
788
|
-
'the name using the "measurement" variable For example '
|
|
789
|
-
'(measurement = "level4")')
|
|
790
736
|
|
|
791
|
-
#
|
|
792
|
-
|
|
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
|
|
793
747
|
height, width = da.geobox.shape
|
|
794
748
|
scale = width_pixels / width
|
|
795
749
|
left, bottom, right, top = da.geobox.extent.boundingbox
|
|
796
750
|
extent = [left, right, bottom, top]
|
|
797
751
|
|
|
798
|
-
|
|
752
|
+
# settings for the label showed on top of the images
|
|
799
753
|
annotation_defaults = {
|
|
800
754
|
"xy": (1, 1),
|
|
801
755
|
"xycoords": "axes fraction",
|
|
@@ -803,96 +757,54 @@ def lc_animation(
|
|
|
803
757
|
"textcoords": "offset points",
|
|
804
758
|
"horizontalalignment": "right",
|
|
805
759
|
"verticalalignment": "top",
|
|
806
|
-
"fontsize":
|
|
760
|
+
"fontsize": label_size,
|
|
807
761
|
"color": "white",
|
|
808
|
-
"path_effects":
|
|
762
|
+
"path_effects": [patheffects.withStroke(linewidth=1,
|
|
763
|
+
foreground="black")],
|
|
809
764
|
}
|
|
810
765
|
|
|
766
|
+
|
|
811
767
|
# Get information needed to display the year in the top corner
|
|
812
768
|
times_list = da.time.dt.strftime("%Y").values
|
|
813
769
|
text_list = [False] * len(times_list)
|
|
814
770
|
annotation_list = ["\n".join([str(i) for i in (a, b) if i])
|
|
815
771
|
for a, b in zip(times_list, text_list)]
|
|
816
772
|
|
|
817
|
-
|
|
773
|
+
|
|
774
|
+
if stacked_plot == True: # if need to add stacked line plot on the right
|
|
818
775
|
|
|
819
776
|
# Create table for stacked plot
|
|
820
|
-
stacked_plot_table =
|
|
777
|
+
stacked_plot_table = _calc_class_ratio(da, measurement)
|
|
821
778
|
|
|
822
779
|
# Build colour list of hex vals for stacked plot
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
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
|
|
829
785
|
|
|
830
|
-
|
|
786
|
+
hex_colour_list = []
|
|
831
787
|
for val in list(stacked_plot_table):
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
except KeyError:
|
|
835
|
-
raise KeyError(
|
|
836
|
-
"class number not found in colour definition. "
|
|
837
|
-
"Ensure measurement name provided matches the dataset being used")
|
|
838
|
-
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)
|
|
839
790
|
hex_colour_list.append(hex_val)
|
|
840
791
|
|
|
841
|
-
# Define & set up figure
|
|
792
|
+
# Define & set up figure (two axes: the LC array and the stacked line plot)
|
|
842
793
|
fig, (ax1, ax2) = plt.subplots(1, 2, dpi=dpi, constrained_layout=True)
|
|
843
794
|
fig.set_size_inches(width * scale * 2, height * scale, forward=True)
|
|
844
|
-
fig.set_constrained_layout_pads(
|
|
845
|
-
|
|
795
|
+
fig.set_constrained_layout_pads(w_pad=0.2, h_pad=0.2, hspace=0, wspace=0)
|
|
796
|
+
|
|
797
|
+
# set the size of the ticks labels using font_size
|
|
846
798
|
ax1.tick_params(axis='both', which='major', labelsize=font_size)
|
|
847
799
|
ax2.tick_params(axis='both', which='major', labelsize=font_size)
|
|
848
|
-
ax1.yaxis.get_offset_text().set_fontsize(font_size)
|
|
849
|
-
|
|
850
|
-
# This function is called at regular intervals with changing i
|
|
851
|
-
# values for each frame
|
|
852
|
-
def _update_frames(i, ax1, ax2, extent, annotation_text,
|
|
853
|
-
annotation_defaults, cmap, norm):
|
|
854
|
-
# Clear previous frame to optimise render speed and plot imagery
|
|
855
|
-
ax1.clear()
|
|
856
|
-
ax2.clear()
|
|
857
800
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
ax1.set_axis_off()
|
|
862
|
-
|
|
863
|
-
clipped_table = stacked_plot_table.iloc[: int(i + 1)]
|
|
864
|
-
data = clipped_table.to_dict(orient="list")
|
|
865
|
-
date = clipped_table.index
|
|
801
|
+
# define list of axes to use in anim_fargs and, in turn, in _update_frames
|
|
802
|
+
axes = [ax1,ax2]
|
|
803
|
+
|
|
866
804
|
|
|
867
|
-
|
|
868
|
-
ax2.tick_params(axis="x", labelrotation=-90)
|
|
869
|
-
ax2.margins(x=0, y=0)
|
|
870
|
-
|
|
871
|
-
# Add annotation text
|
|
872
|
-
ax1.annotate(annotation_text[i], **annotation_defaults)
|
|
873
|
-
ax2.annotate(annotation_text[i], **annotation_defaults)
|
|
874
|
-
|
|
875
|
-
ax1.yaxis.get_offset_text().set_fontsize(font_size)
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
# anim_fargs contains all the values we send to our
|
|
879
|
-
# _update_frames function.
|
|
880
|
-
# Note the layer_cmap and layer_norm which were calculated
|
|
881
|
-
# earlier being passed through
|
|
882
|
-
anim_fargs = (
|
|
883
|
-
ax1,
|
|
884
|
-
ax2, # axis to plot into
|
|
885
|
-
[left, right, bottom, top], # imshow extent
|
|
886
|
-
annotation_list,
|
|
887
|
-
annotation_defaults,
|
|
888
|
-
layer_cmap,
|
|
889
|
-
layer_norm,
|
|
890
|
-
)
|
|
891
|
-
|
|
892
|
-
else: # stacked_plot = False
|
|
805
|
+
else: # i.e., stacked_plot == False
|
|
893
806
|
|
|
894
807
|
# if plotting level 4 with colourbar
|
|
895
|
-
|
|
896
808
|
if measurement == 'level4' and colour_bar == True:
|
|
897
809
|
|
|
898
810
|
# specific setting to fit level 4 colour bar beside the plot
|
|
@@ -902,69 +814,106 @@ def lc_animation(
|
|
|
902
814
|
# Define & set up figure, two subplots so colour bar fits :)
|
|
903
815
|
fig, (ax1, ax2) = plt.subplots(1, 2, dpi=dpi,
|
|
904
816
|
constrained_layout=True, gridspec_kw={'width_ratios': [3, 1]})
|
|
905
|
-
fig.set_size_inches(width * scale * 2,
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
w_pad=0.2, h_pad=0.2, hspace=0, wspace=0)
|
|
909
|
-
ax1.tick_params(axis='both', which='major', labelsize=font_size)
|
|
910
|
-
ax2.tick_params(axis='both', which='major', labelsize=font_size)
|
|
911
|
-
ax1.yaxis.get_offset_text().set_fontsize(font_size)
|
|
912
|
-
|
|
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)
|
|
819
|
+
|
|
913
820
|
# make colour bar
|
|
914
821
|
# provide left hand canvas to colour bar fuction which is where the image will go
|
|
915
822
|
# colourbar will plot on right side beside it
|
|
916
|
-
|
|
917
|
-
make_colorbar(fig, ax1, measurement, labelsize=font_size, animation=True)
|
|
823
|
+
make_colourbar(fig, ax1, measurement, labelsize=font_size, animation=True)
|
|
918
824
|
|
|
919
825
|
# turn off lines for second plot so it's not ontop of colourbar
|
|
920
826
|
ax2.set_axis_off()
|
|
921
827
|
|
|
922
828
|
# plotting any other measurement with or with-out colour bar or level 4 without
|
|
923
829
|
else:
|
|
924
|
-
|
|
925
830
|
# Define & set up figure
|
|
926
831
|
fig, ax1 = plt.subplots(1, 1, dpi=dpi)
|
|
927
|
-
fig.set_size_inches(width * scale
|
|
928
|
-
ax1.tick_params(axis='both', which='major', labelsize=font_size)
|
|
929
|
-
ax1.yaxis.get_offset_text().set_fontsize(font_size)
|
|
832
|
+
fig.set_size_inches(width * scale, height * scale, forward=True)
|
|
930
833
|
|
|
931
834
|
if(not label_ax):
|
|
932
835
|
fig.subplots_adjust(left=0, bottom=0, right=1,
|
|
933
836
|
top=1, wspace=None, hspace=None)
|
|
934
|
-
#
|
|
837
|
+
# make colourbar if required
|
|
935
838
|
if colour_bar:
|
|
936
|
-
|
|
839
|
+
make_colourbar(fig, ax1, measurement, labelsize=font_size)
|
|
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]
|
|
937
846
|
|
|
847
|
+
|
|
938
848
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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):
|
|
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()
|
|
949
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
|
+
|
|
950
890
|
# Add annotation text
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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,
|
|
965
913
|
)
|
|
966
914
|
|
|
967
|
-
|
|
915
|
+
|
|
916
|
+
# create animation
|
|
968
917
|
anim = FuncAnimation(
|
|
969
918
|
fig=fig,
|
|
970
919
|
func=_update_frames,
|
|
@@ -974,6 +923,9 @@ def lc_animation(
|
|
|
974
923
|
repeat=False,
|
|
975
924
|
)
|
|
976
925
|
|
|
926
|
+
# save animation
|
|
977
927
|
anim.save(file_name, writer="pillow", dpi=dpi)
|
|
928
|
+
|
|
978
929
|
plt.close()
|
|
930
|
+
|
|
979
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
|