openforis-whisp 2.0.0a6__py3-none-any.whl → 2.0.0b2__py3-none-any.whl

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.
@@ -1,1384 +1,1352 @@
1
- # This file contains python code for the Google Earth Engine datasets used in the Whisp pacakge.
2
-
3
- # If you are running a bespoke analysis including your own datasets see also the main README.md file.
4
-
5
- # Key aspects to include in the code for each function are:
6
- # a) a suffix of ' _prep' and
7
- # b) a prefix of "nXX_" if it is national/sub-national dataset (where XX is replaced by that country code), or a prefix of 'g_' if it covers more than one country.
8
- # c) a name for your image, defined by ".rename('add_your_image_name_here')". This becomes the column header in the output table.
9
-
10
- # NB for all the above you will need to be running the package in editable mode for these local changes to take effect.
11
- # Editable mode runs the package locally and thus changes to any files are reflected immediately.
12
-
13
- import ee
14
-
15
- # ee.Authenticate()
16
- # ee.Initialize()
17
-
18
- from datetime import datetime
19
-
20
- # from openforis_whisp.parameters.config_runtime import (
21
- # geometry_area_column,
22
- # ) # ideally make relative import statement
23
-
24
- # defining here instead of importing from config_runtime, to allow functioning as more of a standalone script
25
- geometry_area_column = "Area"
26
-
27
- import inspect
28
-
29
- import logging
30
-
31
- # Configure logging
32
- logging.basicConfig(
33
- level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
34
- )
35
-
36
-
37
- def get_logger(name):
38
- return logging.getLogger(name)
39
-
40
-
41
- # Add datasets below
42
-
43
- # tree cover datasets
44
-
45
-
46
- # ESA_TC_2020
47
- def g_esa_worldcover_trees_prep():
48
- esa_worldcover_2020_raw = ee.Image("ESA/WorldCover/v100/2020")
49
- esa_worldcover_trees_2020 = esa_worldcover_2020_raw.eq(95).Or(
50
- esa_worldcover_2020_raw.eq(10)
51
- ) # get trees and mnangroves
52
- return esa_worldcover_trees_2020.rename("ESA_TC_2020")
53
-
54
-
55
- # EUFO_2020
56
- def g_jrc_gfc_2020_prep():
57
- jrc_gfc2020_raw = ee.ImageCollection("JRC/GFC2020/V2")
58
- return jrc_gfc2020_raw.mosaic().rename("EUFO_2020")
59
-
60
-
61
- # GFC_TC_2020
62
- def g_glad_gfc_10pc_prep():
63
- gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
64
- gfc_treecover2000 = gfc.select(["treecover2000"])
65
- gfc_loss2001_2020 = gfc.select(["lossyear"]).lte(20)
66
- gfc_treecover2020 = gfc_treecover2000.where(gfc_loss2001_2020.eq(1), 0)
67
- return gfc_treecover2020.gt(10).rename("GFC_TC_2020")
68
-
69
-
70
- # GLAD_Primary
71
- def g_glad_pht_prep():
72
- primary_ht_forests2001_raw = ee.ImageCollection(
73
- "UMD/GLAD/PRIMARY_HUMID_TROPICAL_FORESTS/v1"
74
- )
75
- primary_ht_forests2001 = (
76
- primary_ht_forests2001_raw.select("Primary_HT_forests").mosaic().selfMask()
77
- )
78
- gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
79
- gfc_loss2001_2020 = gfc.select(["lossyear"]).lte(20)
80
- return primary_ht_forests2001.where(gfc_loss2001_2020.eq(1), 0).rename(
81
- "GLAD_Primary"
82
- )
83
-
84
-
85
- # TMF_undist (undistrubed forest in 2020)
86
- def g_jrc_tmf_undisturbed_prep():
87
- TMF_undist_2020 = (
88
- ee.ImageCollection("projects/JRC/TMF/v1_2024/AnnualChanges")
89
- .select("Dec2020")
90
- .mosaic()
91
- .eq(1)
92
- ) # update from https://github.com/forestdatapartnership/whisp/issues/42
93
- return TMF_undist_2020.rename("TMF_undist")
94
-
95
-
96
- # Forest Persistence FDaP
97
- def g_fdap_forest_prep():
98
- fdap_forest_raw = ee.Image(
99
- "projects/forestdatapartnership/assets/community_forests/ForestPersistence_2020"
100
- )
101
- fdap_forest = fdap_forest_raw.gt(0.75)
102
- return fdap_forest.rename("Forest_FDaP")
103
-
104
-
105
- #########################primary forest
106
- # EUFO JRC Global forest type - primary
107
- def g_gft_primary_prep():
108
- gft_raw = ee.ImageCollection("JRC/GFC2020_subtypes/V0").mosaic()
109
- gft_primary = gft_raw.eq(10)
110
- return gft_primary.rename("GFT_primary")
111
-
112
-
113
- # Intact Forest Landscape 2020
114
- def g_ifl_2020_prep():
115
- IFL_2020 = ee.Image("users/potapovpeter/IFL_2020")
116
- return IFL_2020.rename("IFL_2020")
117
-
118
-
119
- # European Primary Forest Dataset
120
- def g_epfd_prep():
121
- EPFD = ee.FeatureCollection("HU_BERLIN/EPFD/V2/polygons")
122
- EPFD_binary = ee.Image().paint(EPFD, 1)
123
- return EPFD_binary.rename("European_Primary_Forest")
124
-
125
-
126
- # EUFO JRC Global forest type - naturally regenerating planted/plantation forests
127
- def g_gft_nat_reg_prep():
128
- gft_raw = ee.ImageCollection("JRC/GFC2020_subtypes/V0").mosaic()
129
- gft_nat_reg = gft_raw.eq(1)
130
- return gft_nat_reg.rename("GFT_naturally_regenerating")
131
-
132
-
133
- #########################planted and plantation forests
134
-
135
- # EUFO JRC Global forest type - planted/plantation forests
136
- def g_gft_plantation_prep():
137
- gft_raw = ee.ImageCollection("JRC/GFC2020_subtypes/V0").mosaic()
138
- gft_plantation = gft_raw.eq(20)
139
- return gft_plantation.rename("GFT_planted_plantation")
140
-
141
-
142
- def g_iiasa_planted_prep():
143
- iiasa = ee.Image("projects/sat-io/open-datasets/GFM/FML_v3-2")
144
- iiasa_PL = iiasa.eq(31).Or(iiasa.eq(32))
145
- return iiasa_PL.rename("IIASA_planted_plantation")
146
-
147
-
148
- #########################TMF regrowth in 2023
149
- def g_tmf_regrowth_prep():
150
- # Load the TMF Degradation annual product
151
- TMF_AC = ee.ImageCollection("projects/JRC/TMF/v1_2024/AnnualChanges").mosaic()
152
- TMF_AC_2023 = TMF_AC.select("Dec2023")
153
- Regrowth_TMF = TMF_AC_2023.eq(4)
154
- return Regrowth_TMF.rename("TMF_regrowth_2023")
155
-
156
-
157
- ############tree crops
158
-
159
- # TMF_plant (plantations in 2020)
160
- def g_jrc_tmf_plantation_prep():
161
- transition = ee.ImageCollection(
162
- "projects/JRC/TMF/v1_2024/TransitionMap_Subtypes"
163
- ).mosaic()
164
- deforestation_year = ee.ImageCollection(
165
- "projects/JRC/TMF/v1_2024/DeforestationYear"
166
- ).mosaic()
167
- plantation = (transition.gte(81)).And(transition.lte(86))
168
- plantation_2020 = plantation.where(
169
- deforestation_year.gte(2021), 0
170
- ) # update from https://github.com/forestdatapartnership/whisp/issues/42
171
- return plantation_2020.rename("TMF_plant")
172
-
173
-
174
- # # Oil_palm_Descals
175
- # NB updated to Descals et al 2024 paper (as opposed to Descals et al 2021 paper)
176
- def g_creaf_descals_palm_prep():
177
- # Load the Global Oil Palm Year of Plantation image and mosaic it
178
- img = (
179
- ee.ImageCollection(
180
- "projects/ee-globaloilpalm/assets/shared/GlobalOilPalm_YoP_2021"
181
- )
182
- .mosaic()
183
- .select("minNBR_date")
184
- )
185
-
186
- # Calculate the year of plantation and select all below and including 2020
187
- oil_palm_plantation_year = img.divide(365).add(1970).floor().lte(2020)
188
-
189
- # Create a mask for plantations in the year 2020 or earlier
190
- plantation_2020 = oil_palm_plantation_year.lte(2020).selfMask()
191
- return plantation_2020.rename("Oil_palm_Descals")
192
-
193
-
194
- # Cocoa_ETH
195
- def g_eth_kalischek_cocoa_prep():
196
- return ee.Image("projects/ee-nk-cocoa/assets/cocoa_map_threshold_065").rename(
197
- "Cocoa_ETH"
198
- )
199
-
200
-
201
- # fdap datasets
202
-
203
- # Thresholds and model info here https://github.com/google/forest-data-partnership/blob/main/models/README.md
204
-
205
- # Oil Palm FDaP
206
- def g_fdap_palm_prep():
207
- fdap_palm2020_model_raw = ee.ImageCollection(
208
- "projects/forestdatapartnership/assets/palm/model_2025a"
209
- )
210
- fdap_palm = (
211
- fdap_palm2020_model_raw.filterDate("2020-01-01", "2020-12-31")
212
- .mosaic()
213
- .gt(0.88) # Precision and recall ~78% at 0.88 threshold.
214
- )
215
- return fdap_palm.rename("Oil_palm_FDaP")
216
-
217
-
218
- def g_fdap_palm_2023_prep():
219
- fdap_palm2020_model_raw = ee.ImageCollection(
220
- "projects/forestdatapartnership/assets/palm/model_2025a"
221
- )
222
- fdap_palm = (
223
- fdap_palm2020_model_raw.filterDate("2023-01-01", "2023-12-31")
224
- .mosaic()
225
- .gt(0.88) # Precision and recall ~78% at 0.88 threshold.
226
- )
227
- return fdap_palm.rename("Oil_palm_2023_FDaP")
228
-
229
-
230
- # Cocoa FDaP
231
- def g_fdap_cocoa_prep():
232
- fdap_cocoa2020_model_raw = ee.ImageCollection(
233
- "projects/forestdatapartnership/assets/cocoa/model_2025a"
234
- )
235
- fdap_cocoa = (
236
- fdap_cocoa2020_model_raw.filterDate("2020-01-01", "2020-12-31")
237
- .mosaic()
238
- .gt(0.96) # Precision and recall ~87% 0.96 threshold.
239
- )
240
- return fdap_cocoa.rename("Cocoa_FDaP")
241
-
242
-
243
- def g_fdap_cocoa_2023_prep():
244
- fdap_cocoa2020_model_raw = ee.ImageCollection(
245
- "projects/forestdatapartnership/assets/cocoa/model_2025a"
246
- )
247
- fdap_cocoa = (
248
- fdap_cocoa2020_model_raw.filterDate("2023-01-01", "2023-12-31")
249
- .mosaic()
250
- .gt(0.96) # Precision and recall ~87% 0.96 threshold.
251
- )
252
- return fdap_cocoa.rename("Cocoa_2023_FDaP")
253
-
254
-
255
- # Rubber FDaP
256
- def g_fdap_rubber_prep():
257
- fdap_rubber2020_model_raw = ee.ImageCollection(
258
- "projects/forestdatapartnership/assets/rubber/model_2025a"
259
- )
260
- fdap_rubber = (
261
- fdap_rubber2020_model_raw.filterDate("2020-01-01", "2020-12-31")
262
- .mosaic()
263
- .gt(0.59) # Precision and recall ~80% 0.59 threshold.
264
- )
265
- return fdap_rubber.rename("Rubber_FDaP")
266
-
267
-
268
- def g_fdap_rubber_2023_prep():
269
- fdap_rubber2020_model_raw = ee.ImageCollection(
270
- "projects/forestdatapartnership/assets/rubber/model_2025a"
271
- )
272
- fdap_rubber = (
273
- fdap_rubber2020_model_raw.filterDate("2023-01-01", "2023-12-31")
274
- .mosaic()
275
- .gt(0.59) # Threshold for Rubber
276
- )
277
- return fdap_rubber.rename("Rubber_2023_FDaP")
278
-
279
-
280
- # # Coffee FDaP
281
- def g_fdap_coffee_2020_prep():
282
- # Load the coffee model for 2020
283
- collection = ee.ImageCollection(
284
- "projects/forestdatapartnership/assets/coffee/model_2025a"
285
- )
286
-
287
- # Filter the collection for the year 2020 and create a binary mask
288
- coffee_2020 = (
289
- collection.filterDate("2020-01-01", "2020-12-31")
290
- .mosaic()
291
- .gt(0.99) # Precision and recall ~54% 0.99 threshold.
292
- )
293
-
294
- return coffee_2020.rename("Coffee_FDaP")
295
-
296
-
297
- def g_fdap_coffee_2023_prep():
298
- # Load the coffee model for 2020
299
- collection = ee.ImageCollection(
300
- "projects/forestdatapartnership/assets/coffee/model_2025a"
301
- )
302
-
303
- # Filter the collection for the year 2023 and create a binary mask
304
- coffee_2023 = (
305
- collection.filterDate("2023-01-01", "2023-12-31")
306
- .mosaic()
307
- .gt(0.99) # Precision and recall ~54% 0.99 threshold.
308
- )
309
- return coffee_2023.rename("Coffee_FDaP_2023")
310
-
311
-
312
- # Rubber_RBGE - from Royal Botanical Gardens of Edinburgh (RBGE) NB for 2021
313
- def g_rbge_rubber_prep():
314
- return (
315
- ee.Image(
316
- "users/wangyxtina/MapRubberPaper/rRubber10m202122_perc1585DifESAdist5pxPF"
317
- )
318
- .unmask()
319
- .rename("Rubber_RBGE")
320
- )
321
-
322
-
323
- # soy 2020 South America
324
- def g_soy_song_2020_prep():
325
- return ee.Image("projects/glad/soy_annual_SA/2020").unmask().rename("Soy_Song_2020")
326
-
327
-
328
- ##############
329
- # ESRI 2023
330
-
331
- # ESRI 2023 - Tree Cover
332
- def g_esri_2023_tc_prep():
333
- esri_lulc10_raw = ee.ImageCollection(
334
- "projects/sat-io/open-datasets/landcover/ESRI_Global-LULC_10m_TS"
335
- )
336
- esri_lulc10_TC = (
337
- esri_lulc10_raw.filterDate("2023-01-01", "2023-12-31").mosaic().eq(2)
338
- )
339
- return esri_lulc10_TC.rename("ESRI_2023_TC")
340
-
341
-
342
- # ESRI 2023 - Crop
343
- def g_esri_2023_crop_prep():
344
- esri_lulc10_raw = ee.ImageCollection(
345
- "projects/sat-io/open-datasets/landcover/ESRI_Global-LULC_10m_TS"
346
- )
347
- esri_lulc10_crop = (
348
- esri_lulc10_raw.filterDate("2023-01-01", "2023-12-31").mosaic().eq(5)
349
- )
350
- return esri_lulc10_crop.rename("ESRI_2023_crop")
351
-
352
-
353
- # GLC_FCS30D 2022
354
-
355
- # GLC_FCS30D Tree Cover
356
- # forest classes + swamp + mangrove / what to do with shrubland?
357
- def g_glc_fcs30d_tc_2022_prep():
358
- GLC_FCS30D = (
359
- ee.ImageCollection("projects/sat-io/open-datasets/GLC-FCS30D/annual")
360
- .mosaic()
361
- .select(22)
362
- )
363
- GLC_FCS30D_TC = (
364
- (GLC_FCS30D.gte(51))
365
- .And(GLC_FCS30D.lte(92))
366
- .Or(GLC_FCS30D.eq(181))
367
- .Or(GLC_FCS30D.eq(185))
368
- )
369
- return GLC_FCS30D_TC.rename("GLC_FCS30D_TC_2022")
370
-
371
-
372
- # GLC_FCS30D crop
373
- # 10 Rainfed cropland; 11 Herbaceous cover; 12 Tree or shrub cover (Orchard); 20 Irrigated cropland
374
- def g_glc_fcs30d_crop_2022_prep():
375
- GLC_FCS30D = (
376
- ee.ImageCollection("projects/sat-io/open-datasets/GLC-FCS30D/annual")
377
- .mosaic()
378
- .select(22)
379
- )
380
- GLC_FCS30D_crop = GLC_FCS30D.gte(10).And(GLC_FCS30D.lte(20))
381
- return GLC_FCS30D_crop.rename("GLC_FCS30D_crop_2022")
382
-
383
-
384
- #### disturbances by year
385
-
386
- # RADD_year_2019 to RADD_year_< current year >
387
- def g_radd_year_prep():
388
- from datetime import datetime
389
-
390
- radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
391
-
392
- radd_date = (
393
- radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
394
- )
395
- # date of avaialbility
396
- start_year = 19 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about
397
-
398
- current_year = (
399
- datetime.now().year
400
- % 100
401
- # NB the % 100 part gets last two digits needed
402
- )
403
-
404
- img_stack = None
405
- # Generate an image based on GFC with one band of forest tree loss per year from 2001 to <current year>
406
- for year in range(start_year, current_year + 1):
407
- # gfc_loss_year = gfc.select(['lossyear']).eq(i).And(gfc.select(['treecover2000']).gt(10)) # use any definition of loss
408
- start = year * 1000
409
- end = year * 1000 + 365
410
- radd_year = (
411
- radd_date.updateMask(radd_date.gte(start))
412
- .updateMask(radd_date.lte(end))
413
- .gt(0)
414
- .rename("RADD_year_" + "20" + str(year))
415
- )
416
-
417
- if img_stack is None:
418
- img_stack = radd_year
419
- else:
420
- img_stack = img_stack.addBands(radd_year)
421
- return img_stack
422
-
423
-
424
- # TMF_def_2000 to TMF_def_2023
425
- def g_tmf_def_per_year_prep():
426
- # Load the TMF Deforestation annual product
427
- tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DeforestationYear").mosaic()
428
- img_stack = None
429
- # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
430
- for i in range(0, 24 + 1):
431
- tmf_def_year = tmf_def.eq(2000 + i).rename("TMF_def_" + str(2000 + i))
432
- if img_stack is None:
433
- img_stack = tmf_def_year
434
- else:
435
- img_stack = img_stack.addBands(tmf_def_year)
436
- return img_stack
437
-
438
-
439
- # TMF_deg_2000 to TMF_deg_2023
440
- def g_tmf_deg_per_year_prep():
441
- # Load the TMF Degradation annual product
442
- tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DegradationYear").mosaic()
443
- img_stack = None
444
- # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
445
- for i in range(0, 24 + 1):
446
- tmf_def_year = tmf_def.eq(2000 + i).rename("TMF_deg_" + str(2000 + i))
447
- if img_stack is None:
448
- img_stack = tmf_def_year
449
- else:
450
- img_stack = img_stack.addBands(tmf_def_year)
451
- return img_stack
452
-
453
-
454
- # GFC_loss_year_2001 to GFC_loss_year_2023 (correct for version 11)
455
- def g_glad_gfc_loss_per_year_prep():
456
- # Load the Global Forest Change dataset
457
- gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
458
- img_stack = None
459
- # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
460
- for i in range(1, 24 + 1):
461
- gfc_loss_year = (
462
- gfc.select(["lossyear"]).eq(i).And(gfc.select(["treecover2000"]).gt(10))
463
- )
464
- gfc_loss_year = gfc_loss_year.rename("GFC_loss_year_" + str(2000 + i))
465
- if img_stack is None:
466
- img_stack = gfc_loss_year
467
- else:
468
- img_stack = img_stack.addBands(gfc_loss_year)
469
- return img_stack
470
-
471
-
472
- # MODIS_fire_2000 to MODIS_fire_< current year >
473
- def g_modis_fire_prep():
474
- modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
475
- start_year = 2000
476
-
477
- # Determine the last available year by checking the latest image in the collection
478
- last_image = modis_fire.sort("system:time_start", False).first()
479
- last_date = ee.Date(last_image.get("system:time_start"))
480
- end_year = last_date.get("year").getInfo()
481
-
482
- img_stack = None
483
-
484
- for year in range(start_year, end_year + 1):
485
- date_st = f"{year}-01-01"
486
- date_ed = f"{year}-12-31"
487
- modis_year = (
488
- modis_fire.filterDate(date_st, date_ed)
489
- .mosaic()
490
- .select(["BurnDate"])
491
- .gte(0)
492
- .rename(f"MODIS_fire_{year}")
493
- )
494
- img_stack = modis_year if img_stack is None else img_stack.addBands(modis_year)
495
-
496
- return img_stack
497
-
498
-
499
- # ESA_fire_2000 to ESA_fire_2020
500
- def g_esa_fire_prep():
501
- esa_fire = ee.ImageCollection("ESA/CCI/FireCCI/5_1")
502
- start_year = 2001
503
-
504
- # Determine the last available year by checking the latest image in the collection
505
- last_image = esa_fire.sort("system:time_start", False).first()
506
- last_date = ee.Date(last_image.get("system:time_start"))
507
- end_year = last_date.get("year").getInfo()
508
-
509
- img_stack = None
510
-
511
- for year in range(start_year, end_year + 1):
512
- date_st = f"{year}-01-01"
513
- date_ed = f"{year}-12-31"
514
- esa_year = (
515
- esa_fire.filterDate(date_st, date_ed)
516
- .mosaic()
517
- .select(["BurnDate"])
518
- .gte(0)
519
- .rename(f"ESA_fire_{year}")
520
- )
521
- img_stack = esa_year if img_stack is None else img_stack.addBands(esa_year)
522
-
523
- return img_stack
524
-
525
-
526
- # # DIST_alert_2024 to DIST_alert_< current year >
527
- # # Notes:
528
- # # 1) so far only available for 2024 onwards in GEE
529
- # # TO DO - see if gee asset for pre 2020-2024 is available from GLAD team, else download from nasa and put in Whisp assets
530
- # # 2) masked alerts (as dist alerts are for all vegetation) to JRC EUFO 2020 layer, as close to EUDR definition
531
- # # TO DO - ask opinions on if others (such as treecover data from GLAD team) should be used instead
532
-
533
-
534
- # def glad_dist_year_prep():
535
-
536
- # # Load the vegetation disturbance collections
537
-
538
- # # Vegetation disturbance status (0-8, class flag, 8-bit)
539
- # VEGDISTSTATUS = ee.ImageCollection(
540
- # "projects/glad/HLSDIST/current/VEG-DIST-STATUS"
541
- # ).mosaic()
542
- # # Initial vegetation disturbance date (>0: days since 2020-12-31, 16-bit)
543
- # VEGDISTDATE = ee.ImageCollection(
544
- # "projects/glad/HLSDIST/current/VEG-DIST-DATE"
545
- # ).mosaic()
546
-
547
- # # NB relies on initial date of disturbance - consider if last date needed? : VEGLASTDATE = ee.ImageCollection("projects/glad/HLSDIST/current/VEG-LAST-DATE").mosaic(); # Last assessed observation date (≥1, days, 16-bit)
548
-
549
- # # Key for high-confidence alerts (values 3, 6, 7, 8)
550
- # high_conf_values = [3, 6, 7, 8]
551
- # # where:
552
- # # 3 = <50% loss, high confidence, ongoing
553
- # # 6 = ≥50% loss, high confidence, ongoing
554
- # # 7 = <50% loss, high confidence, finished
555
- # # 8 = ≥50% loss, high confidence, finished
556
- # # Note could use <50% loss (i.e. only 6 and 7) for if want to be more strict
557
-
558
- # # Create high-confidence mask
559
- # dist_high_conf = VEGDISTSTATUS.remap(
560
- # high_conf_values, [1] * len(high_conf_values), 0
561
- # )
562
-
563
- # # Determine start year and current year dynamically
564
- # start_year = 2024 # Set the first year of interest
565
- # current_year = datetime.now().year
566
-
567
- # # Calculate days since December 31, 2020 for start and end dates (server-side)
568
- # start_of_2020 = ee.Date("2020-12-31").millis().divide(86400000).int()
569
-
570
- # # Create a list to hold the yearly images
571
- # yearly_images = []
572
-
573
- # for year in range(start_year, current_year + 1):
574
- # start_of_year = (
575
- # ee.Date(f"{year}-01-01")
576
- # .millis()
577
- # .divide(86400000)
578
- # .int()
579
- # .subtract(start_of_2020)
580
- # )
581
- # start_of_next_year = (
582
- # ee.Date(f"{year + 1}-01-01")
583
- # .millis()
584
- # .divide(86400000)
585
- # .int()
586
- # .subtract(start_of_2020)
587
- # )
588
-
589
- # # Filter VEG-DIST-DATE for the selected year
590
- # dist_year = VEGDISTDATE.gte(start_of_year).And(
591
- # VEGDISTDATE.lt(start_of_next_year)
592
- # )
593
-
594
- # # Apply high-confidence mask and rename the band
595
- # high_conf_year = dist_year.updateMask(dist_high_conf).rename(
596
- # f"DIST_year_{year}"
597
- # )
598
-
599
- # # Append the year's data to the list
600
- # yearly_images.append(high_conf_year)
601
-
602
- # # Combine all yearly images into a single image
603
- # img_stack = ee.Image.cat(yearly_images)
604
-
605
- # # Rename the bands correctly
606
- # band_names = [f"DIST_year_{year}" for year in range(start_year, current_year + 1)]
607
- # img_stack = img_stack.select(img_stack.bandNames(), band_names)
608
-
609
- # return img_stack.updateMask(
610
- # jrc_gfc_2020_prep()
611
- # ) # mask yearly dist alerts to forest cover in 2020
612
-
613
-
614
- #### disturbances combined (split into before and after 2020)
615
-
616
- # RADD_after_2020
617
- def g_radd_after_2020_prep():
618
- from datetime import datetime
619
-
620
- radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
621
-
622
- radd_date = (
623
- radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
624
- )
625
- # date of avaialbility
626
- start_year = 21 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about)
627
-
628
- current_year = (
629
- datetime.now().year % 100
630
- ) # NB the % 100 part gets last two digits needed
631
- start = start_year * 1000
632
- end = current_year * 1000 + 365
633
- return (
634
- radd_date.updateMask(radd_date.gte(start))
635
- .updateMask(radd_date.lte(end))
636
- .gt(0)
637
- .rename("RADD_after_2020")
638
- )
639
-
640
-
641
- # RADD_before_2020
642
- def g_radd_before_2020_prep():
643
- from datetime import datetime
644
-
645
- radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
646
-
647
- radd_date = (
648
- radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
649
- )
650
- # date of avaialbility
651
- start_year = 19 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about)
652
-
653
- # current_year = datetime.now().year % 100 # NB the % 100 part gets last two digits needed
654
-
655
- start = start_year * 1000
656
- end = 20 * 1000 + 365
657
- return (
658
- radd_date.updateMask(radd_date.gte(start))
659
- .updateMask(radd_date.lte(end))
660
- .gt(0)
661
- .rename("RADD_before_2020")
662
- )
663
-
664
-
665
- # # DIST_after_2020
666
- # # alerts only for after 2020 currently so need to use date
667
- # def glad_dist_after_2020_prep():
668
-
669
- # # Load the vegetation disturbance collections
670
- # VEGDISTSTATUS = ee.ImageCollection(
671
- # "projects/glad/HLSDIST/current/VEG-DIST-STATUS"
672
- # ).mosaic()
673
-
674
- # # Key for high-confidence alerts (values 3, 6, 7, 8)
675
- # high_conf_values = [3, 6, 7, 8]
676
-
677
- # # Create high-confidence mask
678
- # dist_high_conf = VEGDISTSTATUS.remap(
679
- # high_conf_values, [1] * len(high_conf_values), 0
680
- # )
681
-
682
- # return dist_high_conf.updateMask(jrc_gfc_2020_prep()).rename(
683
- # "DIST_after_2020"
684
- # ) # Mask alerts to forest and rename band
685
-
686
-
687
- # TMF_deg_before_2020
688
- def g_tmf_deg_before_2020_prep():
689
- tmf_deg = ee.ImageCollection("projects/JRC/TMF/v1_2024/DegradationYear").mosaic()
690
- return (tmf_deg.lte(2020)).And(tmf_deg.gte(2000)).rename("TMF_deg_before_2020")
691
-
692
-
693
- # TMF_deg_after_2020
694
- def g_tmf_deg_after_2020_prep():
695
- tmf_deg = ee.ImageCollection("projects/JRC/TMF/v1_2024/DegradationYear").mosaic()
696
- return tmf_deg.gt(2020).rename("TMF_deg_after_2020")
697
-
698
-
699
- # tmf_def_before_2020
700
- def g_tmf_def_before_2020_prep():
701
- tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DeforestationYear").mosaic()
702
- return (tmf_def.lte(2020)).And(tmf_def.gte(2000)).rename("TMF_def_before_2020")
703
-
704
-
705
- # tmf_def_after_2020
706
- def g_tmf_def_after_2020_prep():
707
- tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DeforestationYear").mosaic()
708
- return tmf_def.gt(2020).rename("TMF_def_after_2020")
709
-
710
-
711
- # GFC_loss_before_2020 (loss within 10 percent cover; includes 2020; correct for version 11)
712
- def g_glad_gfc_loss_before_2020_prep():
713
- # Load the Global Forest Change dataset
714
- gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
715
- gfc_loss = (
716
- gfc.select(["lossyear"]).lte(20).And(gfc.select(["treecover2000"]).gt(10))
717
- )
718
- return gfc_loss.rename("GFC_loss_before_2020")
719
-
720
-
721
- # GFC_loss_after_2020 (loss within 10 percent cover; correct for version 11)
722
- def g_glad_gfc_loss_after_2020_prep():
723
- # Load the Global Forest Change dataset
724
- gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
725
- gfc_loss = gfc.select(["lossyear"]).gt(20).And(gfc.select(["treecover2000"]).gt(10))
726
- return gfc_loss.rename("GFC_loss_after_2020")
727
-
728
-
729
- # MODIS_fire_before_2020
730
- def g_modis_fire_before_2020_prep():
731
- modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
732
- start_year = 2000
733
- end_year = 2020
734
- date_st = str(start_year) + "-01-01"
735
- date_ed = str(end_year) + "-12-31"
736
- return (
737
- modis_fire.filterDate(date_st, date_ed)
738
- .mosaic()
739
- .select(["BurnDate"])
740
- .gte(0)
741
- .rename("MODIS_fire_before_2020")
742
- )
743
-
744
-
745
- # MODIS_fire_after_2020
746
- def g_modis_fire_after_2020_prep():
747
- modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
748
- start_year = 2021
749
- end_year = datetime.now().year
750
- date_st = str(start_year) + "-01-01"
751
- date_ed = str(end_year) + "-12-31"
752
- return (
753
- modis_fire.filterDate(date_st, date_ed)
754
- .mosaic()
755
- .select(["BurnDate"])
756
- .gte(0)
757
- .rename("MODIS_fire_after_2020")
758
- )
759
-
760
-
761
- # ESA_fire_before_2020
762
- def g_esa_fire_before_2020_prep():
763
- esa_fire = ee.ImageCollection("ESA/CCI/FireCCI/5_1")
764
- start_year = 2000
765
- end_year = 2020
766
- date_st = str(start_year) + "-01-01"
767
- date_ed = str(end_year) + "-12-31"
768
- return (
769
- esa_fire.filterDate(date_st, date_ed)
770
- .mosaic()
771
- .select(["BurnDate"])
772
- .gte(0)
773
- .rename("ESA_fire_before_2020")
774
- )
775
-
776
-
777
- #########################logging concessions
778
- # http://data.globalforestwatch.org/datasets?q=logging&sort_by=relevance
779
- def g_logging_concessions_before_2020_prep():
780
- RCA = ee.FeatureCollection(
781
- "projects/ee-whisp/assets/logging/RCA_Permis_dExploitation_et_dAmenagement"
782
- )
783
- RCA_binary = ee.Image().paint(RCA, 1)
784
- CMR = ee.FeatureCollection(
785
- "projects/ee-whisp/assets/logging/Cameroon_Forest_Management_Units"
786
- )
787
- CMR_binary = ee.Image().paint(CMR, 1)
788
- Eq_G = ee.FeatureCollection(
789
- "projects/ee-whisp/assets/logging/Equatorial_Guinea_logging_concessions"
790
- )
791
- Eq_G_binary = ee.Image().paint(Eq_G, 1)
792
- DRC = ee.FeatureCollection(
793
- "projects/ee-whisp/assets/logging/DRC_Forest_concession_agreements"
794
- )
795
- DRC_binary = ee.Image().paint(DRC, 1)
796
- Liberia = ee.FeatureCollection(
797
- "projects/ee-whisp/assets/logging/Liberia_Forest_Management_Contracts"
798
- )
799
- Liberia_binary = ee.Image().paint(Liberia, 1)
800
- RoC = ee.FeatureCollection(
801
- "projects/ee-whisp/assets/logging/Republic_of_the_Congo_logging_concessions"
802
- )
803
- Roc_binary = ee.Image().paint(RoC, 1)
804
- Sarawak = ee.FeatureCollection(
805
- "projects/ee-whisp/assets/logging/Sarawak_logging_concessions"
806
- )
807
- Sarawak_binary = ee.Image().paint(Sarawak, 1)
808
- logging_concessions_binary = ee.ImageCollection(
809
- [
810
- RCA_binary,
811
- CMR_binary,
812
- Eq_G_binary,
813
- DRC_binary,
814
- Liberia_binary,
815
- Roc_binary,
816
- Sarawak_binary,
817
- ]
818
- ).mosaic()
819
-
820
- return logging_concessions_binary.rename("GFW_logging_before_2020")
821
-
822
-
823
- #########################national datasets
824
-
825
- # nBR Brazil
826
-
827
- # ### nBR Natural forests in 2020:
828
-
829
- # %%
830
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
831
- # Subsetting criteria: primary forests (DN=1) and secondary forests (DN=2) // secondary forests are those recovering from deforestation
832
- # the resulting dataset shows primary and secondary forest cover in 2020 (mostly by August 2020)
833
-
834
- ##########################primary forests###############################################
835
- def nbr_terraclass_amz20_primary_prep():
836
- tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
837
- tcamz20_f = tcamz20.eq(1)
838
- return tcamz20_f.rename("nBR_INPE_TC_primary_forest_Amazon_2020")
839
-
840
-
841
- # [Official NFMS dataset] Brazilian Forest Service dataset on natural forest cover from PRODES and TerraClass data, base year 2022
842
- # Subsetting criteria: ano_desmat > 2020 and nom_class = 'Floresta'
843
- # the resulting datasets show primary forest cover in 2020 for the Pantanal, Caatinga, Atlantic Forest and Pampa biomes.
844
- # the resulting dataset shows primary and secondary forest cover in 2020 for the Cerrado biome (TerraClass 2020)
845
- # For the Amazon, best to use Terraclass 2020 directly, because the BFS used TerraClass 2014.
846
-
847
- # Pantanal
848
- def nbr_bfs_ptn_f20_prep():
849
- bfs_fptn20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_ptn_2020")
850
-
851
- bfs_fptn20_binary = ee.Image().paint(bfs_fptn20, 1)
852
- return bfs_fptn20_binary.rename("nBR_BFS_primary_forest_Pantanal_2020")
853
-
854
-
855
- # Caatinga - filtered with QGIS because the original geodatabase is too large to export as a shapefile (GEE accepted format)
856
- ## couldn't convert it to asset, working on it (Error: Primary geometry of feature '306862' has 2454627 vertices, above the limit of 1000000 vertices. (Error code: 3)
857
- def nbr_bfs_caat_f20_prep():
858
- bfs_fcaat20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_caat_2020")
859
- bfs_fcaat20_binary = ee.Image().paint(bfs_fcaat20, 1)
860
- return bfs_fcaat20_binary.rename("nBR_BFS_primary_forest_Caatinga_2020")
861
-
862
-
863
- # Atlantic Forest - filtered with QGIS because the original geodatabase is too large to export as a shapefile (GEE accepted format)
864
- def nbr_bfs_atlf_f20_prep():
865
- bfs_fatlf20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_atlf_2020")
866
- bfs_fatlf20_binary = ee.Image().paint(bfs_fatlf20, 1)
867
- return bfs_fatlf20_binary.rename("nBR_BFS_primary_forest_AtlanticForest_2020")
868
-
869
-
870
- # Pampa - filtered in QGIS to save some storage space
871
- def nbr_bfs_pmp_f20_prep():
872
- bfs_fpmp20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_pmp_2020")
873
- bfs_fpmp20_binary = ee.Image().paint(bfs_fpmp20, 1)
874
- return bfs_fpmp20_binary.rename("nBR_BFS_primary_forest_Pampa_2020")
875
-
876
-
877
- ##########################secondary forests###############################################
878
- def nbr_terraclass_amz20_secondary_prep():
879
- tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
880
- tcamz20_f = tcamz20.eq(2)
881
- return tcamz20_f.rename("nBR_INPE_TC_secondary_forest_Amazon_2020")
882
-
883
-
884
- # Cerrado - filtered with QGIS because the original geodatabase is too large to export as a shapefile (GEE accepted format)
885
- def nbr_bfs_cer_f20_prep():
886
- bfs_fcer20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_cerr_2020")
887
- bfs_fcer20_binary = ee.Image().paint(bfs_fcer20, 1)
888
- return bfs_fcer20_binary.rename("nBR_BFS_primary_and_secondary_forest_Cerrado_2020")
889
-
890
-
891
- # %%
892
- # [non-official dataset by MapBiomas multisector initiative]
893
- # land use/cover from 1985 up to 2023, collection 9
894
- # Subsetting criteria: classification_2020 = Forest formation (DN=3), Savanna Formation (DN=4, forest according to BR definition), Mangrove (DN=5), Floodable Forest (DN=6), Wooded Sandbank veg (DN=49)
895
- # the resulting dataset shows forest cover in 2020, without distinguishing between primary and secondary forests
896
- def nbr_mapbiomasc9_f20_prep():
897
- mapbiomasc9_20 = ee.Image(
898
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
899
- ).select("classification_2020")
900
- mapbiomasc9_20_forest = (
901
- mapbiomasc9_20.eq(3)
902
- .Or(mapbiomasc9_20.eq(4))
903
- .Or(mapbiomasc9_20.eq(5))
904
- .Or(mapbiomasc9_20.eq(6))
905
- .Or(mapbiomasc9_20.eq(49))
906
- )
907
- return mapbiomasc9_20_forest.rename("nBR_MapBiomas_col9_forest_Brazil_2020")
908
-
909
-
910
- # ### ########################NBR plantation forest in 2020:#######################################
911
-
912
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
913
- # Subsetting criteria: silviculture (DN=9)
914
- # the resulting dataset shows monospecific commercial plantations, mostly eucalyptus and pinus.
915
- def nbr_terraclass_amz20_silv_prep():
916
- tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
917
- tcamz20_silviculture = tcamz20.eq(9)
918
- return tcamz20_silviculture.rename("nBR_INPE_TCsilviculture_Amazon_2020")
919
-
920
-
921
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
922
- # Subsetting criteria: silviculture (DN=9)
923
- # the resulting dataset shows monospecific commercial plantations, mostly eucalyptus and pinus.
924
- def nbr_terraclass_silv_cer20_prep():
925
- tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
926
- tccer20_silviculture = tccer20.eq(9)
927
- return tccer20_silviculture.rename("nBR_INPE_TCsilviculture_Cerrado_2020")
928
-
929
-
930
- # [non-official dataset by MapBiomas multisector initiative]
931
- # land use/cover from 1985 up to 2023, collection 9
932
- # Subsetting criteria: 'classification_2020' = Forest plantation (DN=9)
933
- # the resulting dataset shows forest plantation in 2020
934
- def nbr_mapbiomasc9_silv20_prep():
935
- mapbiomasc9_20 = ee.Image(
936
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
937
- ).select("classification_2020")
938
- mapbiomasc9_20_silviculture = mapbiomasc9_20.eq(9)
939
- return mapbiomasc9_20_silviculture.rename(
940
- "nBR_MapBiomas_col9_silviculture_Brazil_2020"
941
- )
942
-
943
-
944
- ################ ### NBR Disturbances before 2020:########################################
945
-
946
- # [Official NFMS dataset] INPE PRODES data up to 2023
947
- # Subsetting criteria: DN = [0, 2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60];
948
-
949
- # the resulting dataset shows deforestation and conversion of OWL and OL up to 2020 (mostly August 2020), including residues (omission errors corrections)
950
- def nbr_prodes_before_2020_prep():
951
- prodes = ee.Image("projects/ee-whisp/assets/NBR/prodes_brasil_2023")
952
- prodes_before_20_dn = [
953
- 0,
954
- 2,
955
- 4,
956
- 6,
957
- 7,
958
- 8,
959
- 9,
960
- 10,
961
- 11,
962
- 12,
963
- 13,
964
- 14,
965
- 15,
966
- 16,
967
- 17,
968
- 18,
969
- 19,
970
- 20,
971
- 50,
972
- 51,
973
- 52,
974
- 53,
975
- 54,
976
- 55,
977
- 56,
978
- 57,
979
- 58,
980
- 59,
981
- 60,
982
- ]
983
- prodes_before_20_mask = prodes.remap(
984
- prodes_before_20_dn, [1] * len(prodes_before_20_dn)
985
- ) # .eq(1)
986
- prodes_before_20 = prodes_before_20_mask.selfMask()
987
- return prodes_before_20.rename("nBR_PRODES_deforestation_Brazil_before_2020")
988
-
989
-
990
- ## Caution: 1) includes deforestation and conversion of other wooded land and grassland
991
-
992
- # [Official NFMS dataset] INPE.DETER data from 2nd August 2016 up to the 04th of April 2025
993
- # Subsetting criteria: forest degradation classes ['CICATRIZ_DE_QUEIMADA', 'CS_DESORDENADO', 'DEGRADACAO'] and view_date until 2020-12-31
994
- # 'CS_GEOMETRICO' excluded to align with FREL
995
-
996
-
997
- def nbr_deter_amazon_before_2020_prep():
998
- deteramz = ee.FeatureCollection("projects/ee-whisp/assets/NBR/deter_amz_16apr2025")
999
- degradation_classes = ["CICATRIZ_DE_QUEIMADA", "CS_DESORDENADO", "DEGRADACAO"]
1000
-
1001
- # Add a formatted date field based on VIEW_DATE
1002
- def add_formatted_date(feature):
1003
- return feature.set("formatted_date", ee.Date(feature.get("VIEW_DATE")))
1004
-
1005
- deteramz = deteramz.map(add_formatted_date)
1006
-
1007
- deter_deg = deteramz.filter(
1008
- ee.Filter.inList("CLASSNAME", degradation_classes)
1009
- ).filter(ee.Filter.lt("formatted_date", ee.Date("2020-12-31")))
1010
-
1011
- deter_deg_binary = ee.Image().paint(deter_deg, 1)
1012
- return deter_deg_binary.rename("nBR_DETER_forestdegradation_Amazon_before_2020")
1013
-
1014
-
1015
- ################ ### NBR Disturbances after 2020:########################################
1016
- # [Official NFMS dataset] INPE PRODES data up to 2023
1017
- # Subsetting criteria: DN = [21, 22, 23, 61, 62, 63];
1018
-
1019
- # the resulting dataset shows deforestation and conversion of OWL and OL up to 2020 (mostly August 2020), including residues (omission errors corrections)
1020
-
1021
-
1022
- def nbr_prodes_after_2020_prep():
1023
- prodes = ee.Image("projects/ee-whisp/assets/NBR/prodes_brasil_2023")
1024
- prodes_after_20_dn = [21, 22, 23, 61, 62, 63]
1025
- prodes_after_20_mask = prodes.remap(
1026
- prodes_after_20_dn, [1] * len(prodes_after_20_dn)
1027
- ) # .eq(1)
1028
- prodes_after_20 = prodes_after_20_mask.selfMask()
1029
- return prodes_after_20.rename("nBR_PRODES_deforestation_Brazil_after_2020")
1030
-
1031
-
1032
- # %%
1033
- # [Official NFMS dataset] INPE.DETER data from 2nd August 2016 up to the 04th of April 2025
1034
- # Subsetting criteria: forest degradation classes ['CICATRIZ_DE_QUEIMADA', 'CS_DESORDENADO', 'DEGRADACAO'] and view_date from 2021-01-01 onward
1035
- # 'CS_GEOMETRICO' excluded to align with FREL
1036
- def nbr_deter_amazon_after_2020_prep():
1037
- deteramz = ee.FeatureCollection("projects/ee-whisp/assets/NBR/deter_amz_16apr2025")
1038
- degradation_classes = ["CICATRIZ_DE_QUEIMADA", "CS_DESORDENADO", "DEGRADACAO"]
1039
-
1040
- # Add a formatted date field based on VIEW_DATE
1041
- def add_formatted_date(feature):
1042
- return feature.set("formatted_date", ee.Date(feature.get("VIEW_DATE")))
1043
-
1044
- deteramz = deteramz.map(add_formatted_date)
1045
-
1046
- deter_deg = deteramz.filter(
1047
- ee.Filter.inList("CLASSNAME", degradation_classes)
1048
- ).filter(ee.Filter.gt("formatted_date", ee.Date("2021-01-01")))
1049
-
1050
- deter_deg_binary = ee.Image().paint(deter_deg, 1)
1051
- return deter_deg_binary.rename("nBR_DETER_forestdegradation_Amazon_after_2020")
1052
-
1053
-
1054
- # ########################## NBR commodities - permanent/perennial crops in 2020:###############################
1055
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
1056
- # OR [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
1057
- # Subsetting criteria: perennial (DN=12) and semi-perennial (DN=13) crops
1058
- # the resulting dataset shows perennial and semi-perennial crops in 2020
1059
- def nbr_terraclass_amz_cer20_pc_prep():
1060
- tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
1061
- tcamz20_pc = tcamz20.eq(12).Or(tcamz20.eq(13))
1062
- tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
1063
- tccer20_pc = tccer20.eq(12).Or(tccer20.eq(13))
1064
- tc_pc = ee.ImageCollection([tcamz20_pc, tccer20_pc]).mosaic()
1065
- return tc_pc.rename("nBR_INPE_TCamz_cer_perennial_2020")
1066
-
1067
-
1068
- # [non-official dataset by MapBiomas multisector initiative]
1069
- # land use/cover from 1985 up to 2023, collection 9
1070
- # Subsetting criteria: 'classification_2020' = coffee (DN=46) <================== COFFEE
1071
- # the resulting dataset shows coffee area in 2020
1072
- def nbr_mapbiomasc9_cof_prep():
1073
- mapbiomasc9_20 = ee.Image(
1074
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1075
- ).select("classification_2020")
1076
- mapbiomasc9_20_coffee = mapbiomasc9_20.eq(46)
1077
- return mapbiomasc9_20_coffee.rename("nBR_MapBiomas_col9_coffee_2020")
1078
-
1079
-
1080
- # [non-official dataset by MapBiomas multisector initiative]
1081
- # land use/cover from 1985 up to 2023, collection 9
1082
- # Subsetting criteria: 'classification_2020' = palm oil (DN=35) <================= PALM OIL
1083
- # the resulting dataset shows palm oil area in 2020
1084
- def nbr_mapbiomasc9_po_prep():
1085
- mapbiomasc9_20 = ee.Image(
1086
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1087
- ).select("classification_2020")
1088
- mapbiomasc9_20_palm = mapbiomasc9_20.eq(35)
1089
- return mapbiomasc9_20_palm.rename("nBR_MapBiomas_col9_palmoil_2020")
1090
-
1091
-
1092
- # [non-official dataset by MapBiomas multisector initiative]
1093
- # land use/cover from 1985 up to 2023, collection 9
1094
- # Subsetting criteria: 'classification_2020' = other perennial crops (DN=48)
1095
- # the resulting dataset shows citrus and perennial crops other than coffee and palm oil in 2020
1096
- def nbr_mapbiomasc9_pc_prep():
1097
- mapbiomasc9_20 = ee.Image(
1098
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1099
- ).select("classification_2020")
1100
- mapbiomasc9_20_pc = mapbiomasc9_20.eq(35).Or(mapbiomasc9_20.eq(46))
1101
- return mapbiomasc9_20_pc.rename("nBR_MapBiomas_col9_pc_2020")
1102
-
1103
-
1104
- # ######################## NBR commodities - annual crops in 2020:##############################
1105
-
1106
- # %%
1107
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
1108
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
1109
- # Subsetting criteria: annual/temporary 1 cycle (DN=14) or more than 1 cycle (DN=15)
1110
- # the resulting dataset shows temporary crop in 2020
1111
- def nbr_terraclass_amz_cer20_ac_prep():
1112
- tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
1113
- tcamz20_ac = tcamz20.eq(14).Or(tcamz20.eq(15))
1114
- tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
1115
- tccer20_ac = tccer20.eq(14).Or(tccer20.eq(15))
1116
- tc_ac = ee.ImageCollection([tcamz20_ac, tccer20_ac]).mosaic()
1117
- return tc_ac.rename("nBR_INPE_TCamz_cer_annual_2020")
1118
-
1119
-
1120
- # [non-official dataset by MapBiomas multisector initiative]
1121
- # land use/cover from 1985 up to 2023, collection 9
1122
- # Subsetting criteria: 'classification_2020' = soybean (DN=39) <================== SOY
1123
- # the resulting dataset shows soybean plantation area in 2020
1124
- def nbr_mapbiomasc9_soy_prep():
1125
- mapbiomasc9_20 = ee.Image(
1126
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1127
- ).select("classification_2020")
1128
- mapbiomasc9_20_soy = mapbiomasc9_20.eq(39)
1129
- return mapbiomasc9_20_soy.rename("nBR_MapBiomas_col9_soy_2020")
1130
-
1131
-
1132
- # [non-official dataset by MapBiomas multisector initiative]
1133
- # land use/cover from 1985 up to 2023, collection 9
1134
- # Subsetting criteria: 'classification_2020' = other temporary crops (DN=41)
1135
- # Subsetting criteria: 'classification_2020' = sugar cane (DN=20)
1136
- # Subsetting criteria: 'classification_2020' = rice (DN=40)
1137
- # Subsetting criteria: 'classification_2020' = cotton (beta version, DN=62)
1138
- # the resulting dataset shows temporary crop area other than soy, includes sugar cane, rice, and cotton
1139
- def nbr_mapbiomasc9_ac_prep():
1140
- mapbiomasc9_20 = ee.Image(
1141
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1142
- ).select("classification_2020")
1143
- mapbiomasc9_20_ac = (
1144
- mapbiomasc9_20.eq(41)
1145
- .Or(mapbiomasc9_20.eq(20))
1146
- .Or(mapbiomasc9_20.eq(40))
1147
- .Or(mapbiomasc9_20.eq(62))
1148
- )
1149
- return mapbiomasc9_20_ac.rename("nBR_MapBiomas_col9_annual_crops_2020")
1150
-
1151
-
1152
- # ################################### NBR commodities - pasture/livestock in 2020:##############################
1153
-
1154
- # %%
1155
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
1156
- # Subsetting criteria: BUSH/SHRUB PASTURE (DN=10) or HERBACEOUS PASTURE (DN=11)
1157
-
1158
- # the resulting dataset shows 2020 pasture area in the Amazon
1159
- def nbr_terraclass_amz20_pasture_prep():
1160
- tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
1161
- tcamz20_pasture = tcamz20.eq(10).Or(tcamz20.eq(11))
1162
- return tcamz20_pasture.rename("nBR_INPE_TCamz_pasture_2020")
1163
-
1164
-
1165
- # %%
1166
- # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
1167
- # Subsetting criteria: PASTURE (DN=11)
1168
- # the resulting dataset shows 2020 pasture area in the Cerrado
1169
-
1170
-
1171
- def nbr_terraclass_cer20_ac_prep():
1172
- tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
1173
- tccer20_pasture = tccer20.eq(11)
1174
- return tccer20_pasture.rename("nBR_INPE_TCcer_pasture_2020")
1175
-
1176
-
1177
- # %%
1178
- # [non-official dataset by MapBiomas multisector initiative]
1179
- # land use/cover from 1985 up to 2023, collection 9
1180
- # Subsetting criteria: 'classification_2020' = pasture (DN=15)
1181
- # the resulting dataset shows pasture area in 2020 in Brazil
1182
- def nbr_mapbiomasc9_pasture_prep():
1183
- mapbiomasc9_20 = ee.Image(
1184
- "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1185
- ).select("classification_2020")
1186
- mapbiomasc9_20_pasture = mapbiomasc9_20.eq(15)
1187
- return mapbiomasc9_20_pasture.rename("nBR_MapBiomas_col9_pasture_2020")
1188
-
1189
-
1190
- ###################################################################
1191
- # nCO - Colombia
1192
-
1193
-
1194
- def nco_ideam_forest_2020_prep():
1195
- ideam_forest_raw = ee.Image("projects/ee-whisp/assets/nCO/ideam_2020_geo")
1196
- ideam_forest = ideam_forest_raw.eq(1) # get forest class
1197
- return ideam_forest.rename("nCO_ideam_forest_2020")
1198
-
1199
-
1200
- def nco_ideam_eufo_commission_2020_prep():
1201
- ideam_agroforest_raw = ee.Image("projects/ee-whisp/assets/nCO/ideam_2020_geo_EUFO")
1202
- ideam_agroforest = ideam_agroforest_raw.eq(4) # get forest class
1203
- return ideam_agroforest.rename("nCO_ideam_eufo_commission_2020")
1204
-
1205
-
1206
- # Cocoa_bnetd
1207
- def nci_ocs2020_prep():
1208
- return (
1209
- ee.Image("BNETD/land_cover/v1/2020")
1210
- .select("classification")
1211
- .eq(9)
1212
- .rename("nCI_Cocoa_bnetd")
1213
- ) # cocoa from national land cover map for Côte d'Ivoire
1214
-
1215
-
1216
- ###Combining datasets
1217
-
1218
- ###Combining datasets
1219
-
1220
- # def combine_datasets():
1221
- # """Combines datasets into a single multiband image, with fallback if assets are missing."""
1222
- # img_combined = ee.Image(1).rename(geometry_area_column)
1223
-
1224
- # # Combine images directly
1225
- # for img in [func() for func in list_functions()]:
1226
- # try:
1227
- # img_combined = img_combined.addBands(img)
1228
- # except ee.EEException as e:
1229
- # # logger.error(f"Error adding image: {e}")
1230
- # print(f"Error adding image: {e}")
1231
-
1232
- # try:
1233
- # # Attempt to print band names to check for errors
1234
- # print(img_combined.bandNames().getInfo())
1235
- # except ee.EEException as e:
1236
- # # logger.error(f"Error printing band names: {e}")
1237
- # # logger.info("Running code for filtering to only valid datasets due to error in input")
1238
- # print("using valid datasets filter due to error in input")
1239
- # # Validate images
1240
- # images_to_test = [func() for func in list_functions()]
1241
- # valid_imgs = keep_valid_images(images_to_test) # Validate images
1242
-
1243
- # # Retry combining images after validation
1244
- # img_combined = ee.Image(1).rename(geometry_area_column)
1245
- # for img in valid_imgs:
1246
- # img_combined = img_combined.addBands(img)
1247
-
1248
- # img_combined = img_combined.multiply(ee.Image.pixelArea())
1249
-
1250
- # return img_combined
1251
-
1252
-
1253
- def combine_datasets(national_codes=None):
1254
- """Combines datasets into a single multiband image, with fallback if assets are missing."""
1255
- img_combined = ee.Image(1).rename(geometry_area_column)
1256
-
1257
- # Combine images directly
1258
- for img in [func() for func in list_functions(national_codes=national_codes)]:
1259
- try:
1260
- img_combined = img_combined.addBands(img)
1261
- except ee.EEException as e:
1262
- # logger.error(f"Error adding image: {e}")
1263
- print(f"Error adding image: {e}")
1264
-
1265
- try:
1266
- # Attempt to print band names to check for errors
1267
- # print(img_combined.bandNames().getInfo())
1268
- img_combined.bandNames().getInfo()
1269
-
1270
- except ee.EEException as e:
1271
- # logger.error(f"Error printing band names: {e}")
1272
- # logger.info("Running code for filtering to only valid datasets due to error in input")
1273
- print("using valid datasets filter due to error in input")
1274
- # Validate images
1275
- images_to_test = [
1276
- func() for func in list_functions(national_codes=national_codes)
1277
- ]
1278
- valid_imgs = keep_valid_images(images_to_test) # Validate images
1279
-
1280
- # Retry combining images after validation
1281
- img_combined = ee.Image(1).rename(geometry_area_column)
1282
- for img in valid_imgs:
1283
- img_combined = img_combined.addBands(img)
1284
-
1285
- img_combined = img_combined.multiply(ee.Image.pixelArea())
1286
- print("Whisp multiband image compiled")
1287
-
1288
- return img_combined
1289
-
1290
-
1291
- ######helper functions to check images
1292
- # list all functions ending with "_prep" (in the current script)
1293
- # def list_functions():
1294
- # # Use the module's globals to get all defined functions
1295
- # current_module = inspect.getmodule(inspect.currentframe())
1296
- # functions = [
1297
- # func
1298
- # for name, func in inspect.getmembers(current_module, inspect.isfunction)
1299
- # if name.endswith("_prep")
1300
- # ]
1301
- # return functions
1302
-
1303
-
1304
- def list_functions(national_codes=None):
1305
- """
1306
- Returns a list of functions that end with "_prep" and either:
1307
- - Start with "g_" (global/regional products)
1308
- - Start with any provided national code prefix (nXX_)
1309
-
1310
- Args:
1311
- national_codes: List of ISO2 country codes (without the 'n' prefix)
1312
- """
1313
- # Use the module's globals to get all defined functions
1314
- current_module = inspect.getmodule(inspect.currentframe())
1315
-
1316
- # If national_codes is None, default to an empty list
1317
- if national_codes is None:
1318
- national_codes = []
1319
-
1320
- # Create prefixes list with proper formatting ('n' + code + '_')
1321
- allowed_prefixes = ["g_"] + [f"n{code.lower()}_" for code in national_codes]
1322
-
1323
- # Filter functions in a single pass
1324
- functions = [
1325
- func
1326
- for name, func in inspect.getmembers(current_module, inspect.isfunction)
1327
- if name.endswith("_prep")
1328
- and any(name.startswith(prefix) for prefix in allowed_prefixes)
1329
- ]
1330
-
1331
- return functions
1332
-
1333
-
1334
- # # IN PROGRESS - expected behaviour
1335
- # def filter_by_prefix_list(input_list=None,prefix_list=None):
1336
-
1337
- # if input_list is None:
1338
- # print ("No function in list")
1339
- # if prefix_list is None:
1340
- # print ("No prefixes listed by which to filter")
1341
- # if input_list is not None:
1342
- # for prefix in prefix_list:
1343
- # if element.startsWith(prefix):
1344
- # list.
1345
-
1346
-
1347
- def keep_valid_images(images):
1348
- """Keeps only valid images."""
1349
- valid_images = []
1350
- for img in images:
1351
- try:
1352
- img.getInfo() # This will raise an exception if the image is invalid
1353
- valid_images.append(img)
1354
- except ee.EEException as e:
1355
- # logger.error(f"Invalid image: {e}")
1356
- print(f"Invalid image: {e}")
1357
- return valid_images
1358
-
1359
-
1360
- # function to check if an image is valid
1361
- def ee_image_checker(image):
1362
- """
1363
- Tests if the input is a valid ee.Image.
1364
-
1365
- Args:
1366
- image: An ee.Image object.
1367
-
1368
- Returns:
1369
- bool: True if the input is a valid ee.Image, False otherwise.
1370
- """
1371
- try:
1372
- if ee.Algorithms.ObjectType(image).getInfo() == "Image":
1373
- # Trigger some action on the image to ensure it's a valid image
1374
- image.getInfo() # This will raise an exception if the image is invalid
1375
- return True
1376
- except ee.EEException as e:
1377
- print(f"Image validation failed with EEException: {e}")
1378
- except Exception as e:
1379
- print(f"Image validation failed with exception: {e}")
1380
- return False
1381
-
1382
-
1383
- # print(combine_valid_datasets().bandNames().getInfo())
1384
- # print(combine_datasets().bandNames().getInfo())
1
+ # This file contains python code for the Google Earth Engine datasets used in the Whisp pacakge.
2
+
3
+ # If you are running a bespoke analysis including your own datasets see also the main README.md file.
4
+
5
+ # Key aspects to include in the code for each function are:
6
+ # a) a suffix of ' _prep' and
7
+ # b) a prefix of "nXX_" if it is national/sub-national dataset (where XX is replaced by that country code), or a prefix of 'g_' if it covers more than one country.
8
+ # c) a name for your image, defined by ".rename('add_your_image_name_here')". This becomes the column header in the output table.
9
+
10
+ # NB for all the above you will need to be running the package in editable mode for these local changes to take effect.
11
+ # Editable mode runs the package locally and thus changes to any files are reflected immediately.
12
+
13
+ import ee
14
+
15
+ # ee.Authenticate()
16
+ # ee.Initialize()
17
+
18
+ from datetime import datetime
19
+
20
+ # from openforis_whisp.parameters.config_runtime import (
21
+ # geometry_area_column,
22
+ # ) # ideally make relative import statement
23
+
24
+ # defining here instead of importing from config_runtime, to allow functioning as more of a standalone script
25
+ geometry_area_column = "Area"
26
+
27
+ import inspect
28
+
29
+ import logging
30
+
31
+ # Configure logging
32
+ logging.basicConfig(
33
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
34
+ )
35
+
36
+
37
+ def get_logger(name):
38
+ return logging.getLogger(name)
39
+
40
+
41
+ # Add datasets below
42
+
43
+ # tree cover datasets
44
+
45
+
46
+ # ESA_TC_2020
47
+ def g_esa_worldcover_trees_prep():
48
+ esa_worldcover_2020_raw = ee.Image("ESA/WorldCover/v100/2020")
49
+ esa_worldcover_trees_2020 = esa_worldcover_2020_raw.eq(95).Or(
50
+ esa_worldcover_2020_raw.eq(10)
51
+ ) # get trees and mnangroves
52
+ return esa_worldcover_trees_2020.rename("ESA_TC_2020")
53
+
54
+
55
+ # EUFO_2020
56
+ def g_jrc_gfc_2020_prep():
57
+ jrc_gfc2020_raw = ee.ImageCollection("JRC/GFC2020/V2")
58
+ return jrc_gfc2020_raw.mosaic().rename("EUFO_2020")
59
+
60
+
61
+ # GFC_TC_2020
62
+ def g_glad_gfc_10pc_prep():
63
+ gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
64
+ gfc_treecover2000 = gfc.select(["treecover2000"])
65
+ gfc_loss2001_2020 = gfc.select(["lossyear"]).lte(20)
66
+ gfc_treecover2020 = gfc_treecover2000.where(gfc_loss2001_2020.eq(1), 0)
67
+ return gfc_treecover2020.gt(10).rename("GFC_TC_2020")
68
+
69
+
70
+ # GLAD_Primary
71
+ def g_glad_pht_prep():
72
+ primary_ht_forests2001_raw = ee.ImageCollection(
73
+ "UMD/GLAD/PRIMARY_HUMID_TROPICAL_FORESTS/v1"
74
+ )
75
+ primary_ht_forests2001 = (
76
+ primary_ht_forests2001_raw.select("Primary_HT_forests").mosaic().selfMask()
77
+ )
78
+ gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
79
+ gfc_loss2001_2020 = gfc.select(["lossyear"]).lte(20)
80
+ return primary_ht_forests2001.where(gfc_loss2001_2020.eq(1), 0).rename(
81
+ "GLAD_Primary"
82
+ )
83
+
84
+
85
+ # TMF_undist (undistrubed forest in 2020)
86
+ def g_jrc_tmf_undisturbed_prep():
87
+ TMF_undist_2020 = (
88
+ ee.ImageCollection("projects/JRC/TMF/v1_2024/AnnualChanges")
89
+ .select("Dec2020")
90
+ .mosaic()
91
+ .eq(1)
92
+ ) # update from https://github.com/forestdatapartnership/whisp/issues/42
93
+ return TMF_undist_2020.rename("TMF_undist")
94
+
95
+
96
+ # Forest Persistence FDaP
97
+ def g_fdap_forest_prep():
98
+ fdap_forest_raw = ee.Image(
99
+ "projects/forestdatapartnership/assets/community_forests/ForestPersistence_2020"
100
+ )
101
+ fdap_forest = fdap_forest_raw.gt(0.75)
102
+ return fdap_forest.rename("Forest_FDaP")
103
+
104
+
105
+ #########################primary forest
106
+ # EUFO JRC Global forest type - primary
107
+ def g_gft_primary_prep():
108
+ gft_raw = ee.ImageCollection("JRC/GFC2020_subtypes/V0").mosaic()
109
+ gft_primary = gft_raw.eq(10)
110
+ return gft_primary.rename("GFT_primary")
111
+
112
+
113
+ # Intact Forest Landscape 2020
114
+ def g_ifl_2020_prep():
115
+ IFL_2020 = ee.Image("users/potapovpeter/IFL_2020")
116
+ return IFL_2020.rename("IFL_2020")
117
+
118
+
119
+ # European Primary Forest Dataset
120
+ def g_epfd_prep():
121
+ EPFD = ee.FeatureCollection("HU_BERLIN/EPFD/V2/polygons")
122
+ EPFD_binary = ee.Image().paint(EPFD, 1)
123
+ return EPFD_binary.rename("European_Primary_Forest")
124
+
125
+
126
+ # EUFO JRC Global forest type - naturally regenerating planted/plantation forests
127
+ def g_gft_nat_reg_prep():
128
+ gft_raw = ee.ImageCollection("JRC/GFC2020_subtypes/V0").mosaic()
129
+ gft_nat_reg = gft_raw.eq(1)
130
+ return gft_nat_reg.rename("GFT_naturally_regenerating")
131
+
132
+
133
+ #########################planted and plantation forests
134
+
135
+ # EUFO JRC Global forest type - planted/plantation forests
136
+ def g_gft_plantation_prep():
137
+ gft_raw = ee.ImageCollection("JRC/GFC2020_subtypes/V0").mosaic()
138
+ gft_plantation = gft_raw.eq(20)
139
+ return gft_plantation.rename("GFT_planted_plantation")
140
+
141
+
142
+ def g_iiasa_planted_prep():
143
+ iiasa = ee.Image("projects/sat-io/open-datasets/GFM/FML_v3-2")
144
+ iiasa_PL = iiasa.eq(31).Or(iiasa.eq(32))
145
+ return iiasa_PL.rename("IIASA_planted_plantation")
146
+
147
+
148
+ #########################TMF regrowth in 2023
149
+ def g_tmf_regrowth_prep():
150
+ # Load the TMF Degradation annual product
151
+ TMF_AC = ee.ImageCollection("projects/JRC/TMF/v1_2024/AnnualChanges").mosaic()
152
+ TMF_AC_2023 = TMF_AC.select("Dec2023")
153
+ Regrowth_TMF = TMF_AC_2023.eq(4)
154
+ return Regrowth_TMF.rename("TMF_regrowth_2023")
155
+
156
+
157
+ ############tree crops
158
+
159
+ # TMF_plant (plantations in 2020)
160
+ def g_jrc_tmf_plantation_prep():
161
+ transition = ee.ImageCollection(
162
+ "projects/JRC/TMF/v1_2024/TransitionMap_Subtypes"
163
+ ).mosaic()
164
+ deforestation_year = ee.ImageCollection(
165
+ "projects/JRC/TMF/v1_2024/DeforestationYear"
166
+ ).mosaic()
167
+ plantation = (transition.gte(81)).And(transition.lte(86))
168
+ plantation_2020 = plantation.where(
169
+ deforestation_year.gte(2021), 0
170
+ ) # update from https://github.com/forestdatapartnership/whisp/issues/42
171
+ return plantation_2020.rename("TMF_plant")
172
+
173
+
174
+ # # Oil_palm_Descals
175
+ # NB updated to Descals et al 2024 paper (as opposed to Descals et al 2021 paper)
176
+ def g_creaf_descals_palm_prep():
177
+ # Load the Global Oil Palm Year of Plantation image and mosaic it
178
+ img = (
179
+ ee.ImageCollection(
180
+ "projects/ee-globaloilpalm/assets/shared/GlobalOilPalm_YoP_2021"
181
+ )
182
+ .mosaic()
183
+ .select("minNBR_date")
184
+ )
185
+
186
+ # Calculate the year of plantation and select all below and including 2020
187
+ oil_palm_plantation_year = img.divide(365).add(1970).floor().lte(2020)
188
+
189
+ # Create a mask for plantations in the year 2020 or earlier
190
+ plantation_2020 = oil_palm_plantation_year.lte(2020).selfMask()
191
+ return plantation_2020.rename("Oil_palm_Descals")
192
+
193
+
194
+ # Cocoa_ETH
195
+ def g_eth_kalischek_cocoa_prep():
196
+ return ee.Image("projects/ee-nk-cocoa/assets/cocoa_map_threshold_065").rename(
197
+ "Cocoa_ETH"
198
+ )
199
+
200
+
201
+ # fdap datasets
202
+
203
+ # Thresholds and model info here https://github.com/google/forest-data-partnership/blob/main/models/README.md
204
+
205
+ # Oil Palm FDaP
206
+ def g_fdap_palm_prep():
207
+ fdap_palm2020_model_raw = ee.ImageCollection(
208
+ "projects/forestdatapartnership/assets/palm/model_2025a"
209
+ )
210
+ fdap_palm = (
211
+ fdap_palm2020_model_raw.filterDate("2020-01-01", "2020-12-31")
212
+ .mosaic()
213
+ .gt(0.88) # Precision and recall ~78% at 0.88 threshold.
214
+ )
215
+ return fdap_palm.rename("Oil_palm_FDaP")
216
+
217
+
218
+ def g_fdap_palm_2023_prep():
219
+ fdap_palm2020_model_raw = ee.ImageCollection(
220
+ "projects/forestdatapartnership/assets/palm/model_2025a"
221
+ )
222
+ fdap_palm = (
223
+ fdap_palm2020_model_raw.filterDate("2023-01-01", "2023-12-31")
224
+ .mosaic()
225
+ .gt(0.88) # Precision and recall ~78% at 0.88 threshold.
226
+ )
227
+ return fdap_palm.rename("Oil_palm_2023_FDaP")
228
+
229
+
230
+ # Cocoa FDaP
231
+ def g_fdap_cocoa_prep():
232
+ fdap_cocoa2020_model_raw = ee.ImageCollection(
233
+ "projects/forestdatapartnership/assets/cocoa/model_2025a"
234
+ )
235
+ fdap_cocoa = (
236
+ fdap_cocoa2020_model_raw.filterDate("2020-01-01", "2020-12-31")
237
+ .mosaic()
238
+ .gt(0.96) # Precision and recall ~87% 0.96 threshold.
239
+ )
240
+ return fdap_cocoa.rename("Cocoa_FDaP")
241
+
242
+
243
+ def g_fdap_cocoa_2023_prep():
244
+ fdap_cocoa2020_model_raw = ee.ImageCollection(
245
+ "projects/forestdatapartnership/assets/cocoa/model_2025a"
246
+ )
247
+ fdap_cocoa = (
248
+ fdap_cocoa2020_model_raw.filterDate("2023-01-01", "2023-12-31")
249
+ .mosaic()
250
+ .gt(0.96) # Precision and recall ~87% 0.96 threshold.
251
+ )
252
+ return fdap_cocoa.rename("Cocoa_2023_FDaP")
253
+
254
+
255
+ # Rubber FDaP
256
+ def g_fdap_rubber_prep():
257
+ fdap_rubber2020_model_raw = ee.ImageCollection(
258
+ "projects/forestdatapartnership/assets/rubber/model_2025a"
259
+ )
260
+ fdap_rubber = (
261
+ fdap_rubber2020_model_raw.filterDate("2020-01-01", "2020-12-31")
262
+ .mosaic()
263
+ .gt(0.59) # Precision and recall ~80% 0.59 threshold.
264
+ )
265
+ return fdap_rubber.rename("Rubber_FDaP")
266
+
267
+
268
+ def g_fdap_rubber_2023_prep():
269
+ fdap_rubber2020_model_raw = ee.ImageCollection(
270
+ "projects/forestdatapartnership/assets/rubber/model_2025a"
271
+ )
272
+ fdap_rubber = (
273
+ fdap_rubber2020_model_raw.filterDate("2023-01-01", "2023-12-31")
274
+ .mosaic()
275
+ .gt(0.59) # Threshold for Rubber
276
+ )
277
+ return fdap_rubber.rename("Rubber_2023_FDaP")
278
+
279
+
280
+ # # Coffee FDaP
281
+ def g_fdap_coffee_2020_prep():
282
+ # Load the coffee model for 2020
283
+ collection = ee.ImageCollection(
284
+ "projects/forestdatapartnership/assets/coffee/model_2025a"
285
+ )
286
+
287
+ # Filter the collection for the year 2020 and create a binary mask
288
+ coffee_2020 = (
289
+ collection.filterDate("2020-01-01", "2020-12-31")
290
+ .mosaic()
291
+ .gt(0.99) # Precision and recall ~54% 0.99 threshold.
292
+ )
293
+
294
+ return coffee_2020.rename("Coffee_FDaP")
295
+
296
+
297
+ def g_fdap_coffee_2023_prep():
298
+ # Load the coffee model for 2020
299
+ collection = ee.ImageCollection(
300
+ "projects/forestdatapartnership/assets/coffee/model_2025a"
301
+ )
302
+
303
+ # Filter the collection for the year 2023 and create a binary mask
304
+ coffee_2023 = (
305
+ collection.filterDate("2023-01-01", "2023-12-31")
306
+ .mosaic()
307
+ .gt(0.99) # Precision and recall ~54% 0.99 threshold.
308
+ )
309
+ return coffee_2023.rename("Coffee_FDaP_2023")
310
+
311
+
312
+ # Rubber_RBGE - from Royal Botanical Gardens of Edinburgh (RBGE) NB for 2021
313
+ def g_rbge_rubber_prep():
314
+ return (
315
+ ee.Image(
316
+ "users/wangyxtina/MapRubberPaper/rRubber10m202122_perc1585DifESAdist5pxPF"
317
+ )
318
+ .unmask()
319
+ .rename("Rubber_RBGE")
320
+ )
321
+
322
+
323
+ # soy 2020 South America
324
+ def g_soy_song_2020_prep():
325
+ return ee.Image("projects/glad/soy_annual_SA/2020").unmask().rename("Soy_Song_2020")
326
+
327
+
328
+ ##############
329
+ # ESRI 2023
330
+
331
+ # ESRI 2023 - Tree Cover
332
+ def g_esri_2023_tc_prep():
333
+ esri_lulc10_raw = ee.ImageCollection(
334
+ "projects/sat-io/open-datasets/landcover/ESRI_Global-LULC_10m_TS"
335
+ )
336
+ esri_lulc10_TC = (
337
+ esri_lulc10_raw.filterDate("2023-01-01", "2023-12-31").mosaic().eq(2)
338
+ )
339
+ return esri_lulc10_TC.rename("ESRI_2023_TC")
340
+
341
+
342
+ # ESRI 2023 - Crop
343
+ def g_esri_2020_2023_crop_prep():
344
+ esri_lulc10_raw = ee.ImageCollection(
345
+ "projects/sat-io/open-datasets/landcover/ESRI_Global-LULC_10m_TS"
346
+ )
347
+ esri_lulc10_crop_2020 = (
348
+ esri_lulc10_raw.filterDate("2020-01-01", "2020-12-31").mosaic().eq(5)
349
+ )
350
+ esri_lulc10_crop_2023 = (
351
+ esri_lulc10_raw.filterDate("2023-01-01", "2023-12-31").mosaic().eq(5)
352
+ )
353
+
354
+ newCrop = esri_lulc10_crop_2023.And(esri_lulc10_crop_2020.Not())
355
+
356
+ return newCrop.rename("ESRI_crop_gain_2020_2023")
357
+
358
+
359
+ #### disturbances by year
360
+
361
+ # RADD_year_2019 to RADD_year_< current year >
362
+ def g_radd_year_prep():
363
+ from datetime import datetime
364
+
365
+ radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
366
+
367
+ radd_date = (
368
+ radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
369
+ )
370
+ # date of avaialbility
371
+ start_year = 19 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about
372
+
373
+ current_year = (
374
+ datetime.now().year
375
+ % 100
376
+ # NB the % 100 part gets last two digits needed
377
+ )
378
+
379
+ img_stack = None
380
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to <current year>
381
+ for year in range(start_year, current_year + 1):
382
+ # gfc_loss_year = gfc.select(['lossyear']).eq(i).And(gfc.select(['treecover2000']).gt(10)) # use any definition of loss
383
+ start = year * 1000
384
+ end = year * 1000 + 365
385
+ radd_year = (
386
+ radd_date.updateMask(radd_date.gte(start))
387
+ .updateMask(radd_date.lte(end))
388
+ .gt(0)
389
+ .rename("RADD_year_" + "20" + str(year))
390
+ )
391
+
392
+ if img_stack is None:
393
+ img_stack = radd_year
394
+ else:
395
+ img_stack = img_stack.addBands(radd_year)
396
+ return img_stack
397
+
398
+
399
+ # TMF_def_2000 to TMF_def_2023
400
+ def g_tmf_def_per_year_prep():
401
+ # Load the TMF Deforestation annual product
402
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DeforestationYear").mosaic()
403
+ img_stack = None
404
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
405
+ for i in range(0, 24 + 1):
406
+ tmf_def_year = tmf_def.eq(2000 + i).rename("TMF_def_" + str(2000 + i))
407
+ if img_stack is None:
408
+ img_stack = tmf_def_year
409
+ else:
410
+ img_stack = img_stack.addBands(tmf_def_year)
411
+ return img_stack
412
+
413
+
414
+ # TMF_deg_2000 to TMF_deg_2023
415
+ def g_tmf_deg_per_year_prep():
416
+ # Load the TMF Degradation annual product
417
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DegradationYear").mosaic()
418
+ img_stack = None
419
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
420
+ for i in range(0, 24 + 1):
421
+ tmf_def_year = tmf_def.eq(2000 + i).rename("TMF_deg_" + str(2000 + i))
422
+ if img_stack is None:
423
+ img_stack = tmf_def_year
424
+ else:
425
+ img_stack = img_stack.addBands(tmf_def_year)
426
+ return img_stack
427
+
428
+
429
+ # GFC_loss_year_2001 to GFC_loss_year_2023 (correct for version 11)
430
+ def g_glad_gfc_loss_per_year_prep():
431
+ # Load the Global Forest Change dataset
432
+ gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
433
+ img_stack = None
434
+ # Generate an image based on GFC with one band of forest tree loss per year from 2001 to 2022
435
+ for i in range(1, 24 + 1):
436
+ gfc_loss_year = (
437
+ gfc.select(["lossyear"]).eq(i).And(gfc.select(["treecover2000"]).gt(10))
438
+ )
439
+ gfc_loss_year = gfc_loss_year.rename("GFC_loss_year_" + str(2000 + i))
440
+ if img_stack is None:
441
+ img_stack = gfc_loss_year
442
+ else:
443
+ img_stack = img_stack.addBands(gfc_loss_year)
444
+ return img_stack
445
+
446
+
447
+ # MODIS_fire_2000 to MODIS_fire_< current year >
448
+ def g_modis_fire_prep():
449
+ modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
450
+ start_year = 2000
451
+
452
+ # Determine the last available year by checking the latest image in the collection
453
+ last_image = modis_fire.sort("system:time_start", False).first()
454
+ last_date = ee.Date(last_image.get("system:time_start"))
455
+ end_year = last_date.get("year").getInfo()
456
+
457
+ img_stack = None
458
+
459
+ for year in range(start_year, end_year + 1):
460
+ date_st = f"{year}-01-01"
461
+ date_ed = f"{year}-12-31"
462
+ modis_year = (
463
+ modis_fire.filterDate(date_st, date_ed)
464
+ .mosaic()
465
+ .select(["BurnDate"])
466
+ .gte(0)
467
+ .rename(f"MODIS_fire_{year}")
468
+ )
469
+ img_stack = modis_year if img_stack is None else img_stack.addBands(modis_year)
470
+
471
+ return img_stack
472
+
473
+
474
+ # ESA_fire_2000 to ESA_fire_2020
475
+ def g_esa_fire_prep():
476
+ esa_fire = ee.ImageCollection("ESA/CCI/FireCCI/5_1")
477
+ start_year = 2001
478
+
479
+ # Determine the last available year by checking the latest image in the collection
480
+ last_image = esa_fire.sort("system:time_start", False).first()
481
+ last_date = ee.Date(last_image.get("system:time_start"))
482
+ end_year = last_date.get("year").getInfo()
483
+
484
+ img_stack = None
485
+
486
+ for year in range(start_year, end_year + 1):
487
+ date_st = f"{year}-01-01"
488
+ date_ed = f"{year}-12-31"
489
+ esa_year = (
490
+ esa_fire.filterDate(date_st, date_ed)
491
+ .mosaic()
492
+ .select(["BurnDate"])
493
+ .gte(0)
494
+ .rename(f"ESA_fire_{year}")
495
+ )
496
+ img_stack = esa_year if img_stack is None else img_stack.addBands(esa_year)
497
+
498
+ return img_stack
499
+
500
+
501
+ # # DIST_alert_2024 to DIST_alert_< current year >
502
+ # # Notes:
503
+ # # 1) so far only available for 2024 onwards in GEE
504
+ # # TO DO - see if gee asset for pre 2020-2024 is available from GLAD team, else download from nasa and put in Whisp assets
505
+ # # 2) masked alerts (as dist alerts are for all vegetation) to JRC EUFO 2020 layer, as close to EUDR definition
506
+ # # TO DO - ask opinions on if others (such as treecover data from GLAD team) should be used instead
507
+
508
+
509
+ # def glad_dist_year_prep():
510
+
511
+ # # Load the vegetation disturbance collections
512
+
513
+ # # Vegetation disturbance status (0-8, class flag, 8-bit)
514
+ # VEGDISTSTATUS = ee.ImageCollection(
515
+ # "projects/glad/HLSDIST/current/VEG-DIST-STATUS"
516
+ # ).mosaic()
517
+ # # Initial vegetation disturbance date (>0: days since 2020-12-31, 16-bit)
518
+ # VEGDISTDATE = ee.ImageCollection(
519
+ # "projects/glad/HLSDIST/current/VEG-DIST-DATE"
520
+ # ).mosaic()
521
+
522
+ # # NB relies on initial date of disturbance - consider if last date needed? : VEGLASTDATE = ee.ImageCollection("projects/glad/HLSDIST/current/VEG-LAST-DATE").mosaic(); # Last assessed observation date (≥1, days, 16-bit)
523
+
524
+ # # Key for high-confidence alerts (values 3, 6, 7, 8)
525
+ # high_conf_values = [3, 6, 7, 8]
526
+ # # where:
527
+ # # 3 = <50% loss, high confidence, ongoing
528
+ # # 6 = ≥50% loss, high confidence, ongoing
529
+ # # 7 = <50% loss, high confidence, finished
530
+ # # 8 = ≥50% loss, high confidence, finished
531
+ # # Note could use <50% loss (i.e. only 6 and 7) for if want to be more strict
532
+
533
+ # # Create high-confidence mask
534
+ # dist_high_conf = VEGDISTSTATUS.remap(
535
+ # high_conf_values, [1] * len(high_conf_values), 0
536
+ # )
537
+
538
+ # # Determine start year and current year dynamically
539
+ # start_year = 2024 # Set the first year of interest
540
+ # current_year = datetime.now().year
541
+
542
+ # # Calculate days since December 31, 2020 for start and end dates (server-side)
543
+ # start_of_2020 = ee.Date("2020-12-31").millis().divide(86400000).int()
544
+
545
+ # # Create a list to hold the yearly images
546
+ # yearly_images = []
547
+
548
+ # for year in range(start_year, current_year + 1):
549
+ # start_of_year = (
550
+ # ee.Date(f"{year}-01-01")
551
+ # .millis()
552
+ # .divide(86400000)
553
+ # .int()
554
+ # .subtract(start_of_2020)
555
+ # )
556
+ # start_of_next_year = (
557
+ # ee.Date(f"{year + 1}-01-01")
558
+ # .millis()
559
+ # .divide(86400000)
560
+ # .int()
561
+ # .subtract(start_of_2020)
562
+ # )
563
+
564
+ # # Filter VEG-DIST-DATE for the selected year
565
+ # dist_year = VEGDISTDATE.gte(start_of_year).And(
566
+ # VEGDISTDATE.lt(start_of_next_year)
567
+ # )
568
+
569
+ # # Apply high-confidence mask and rename the band
570
+ # high_conf_year = dist_year.updateMask(dist_high_conf).rename(
571
+ # f"DIST_year_{year}"
572
+ # )
573
+
574
+ # # Append the year's data to the list
575
+ # yearly_images.append(high_conf_year)
576
+
577
+ # # Combine all yearly images into a single image
578
+ # img_stack = ee.Image.cat(yearly_images)
579
+
580
+ # # Rename the bands correctly
581
+ # band_names = [f"DIST_year_{year}" for year in range(start_year, current_year + 1)]
582
+ # img_stack = img_stack.select(img_stack.bandNames(), band_names)
583
+
584
+ # return img_stack.updateMask(
585
+ # jrc_gfc_2020_prep()
586
+ # ) # mask yearly dist alerts to forest cover in 2020
587
+
588
+
589
+ #### disturbances combined (split into before and after 2020)
590
+
591
+ # RADD_after_2020
592
+ def g_radd_after_2020_prep():
593
+ from datetime import datetime
594
+
595
+ radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
596
+
597
+ radd_date = (
598
+ radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
599
+ )
600
+ # date of avaialbility
601
+ start_year = 21 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about)
602
+
603
+ current_year = (
604
+ datetime.now().year % 100
605
+ ) # NB the % 100 part gets last two digits needed
606
+ start = start_year * 1000
607
+ end = current_year * 1000 + 365
608
+ return (
609
+ radd_date.updateMask(radd_date.gte(start))
610
+ .updateMask(radd_date.lte(end))
611
+ .gt(0)
612
+ .rename("RADD_after_2020")
613
+ )
614
+
615
+
616
+ # RADD_before_2020
617
+ def g_radd_before_2020_prep():
618
+ from datetime import datetime
619
+
620
+ radd = ee.ImageCollection("projects/radar-wur/raddalert/v1")
621
+
622
+ radd_date = (
623
+ radd.filterMetadata("layer", "contains", "alert").select("Date").mosaic()
624
+ )
625
+ # date of avaialbility
626
+ start_year = 19 ## (starts 2019 in Africa, then 2020 for S America and Asia: https://data.globalforestwatch.org/datasets/gfw::deforestation-alerts-radd/about)
627
+
628
+ # current_year = datetime.now().year % 100 # NB the % 100 part gets last two digits needed
629
+
630
+ start = start_year * 1000
631
+ end = 20 * 1000 + 365
632
+ return (
633
+ radd_date.updateMask(radd_date.gte(start))
634
+ .updateMask(radd_date.lte(end))
635
+ .gt(0)
636
+ .rename("RADD_before_2020")
637
+ )
638
+
639
+
640
+ # # DIST_after_2020
641
+ # # alerts only for after 2020 currently so need to use date
642
+ # def glad_dist_after_2020_prep():
643
+
644
+ # # Load the vegetation disturbance collections
645
+ # VEGDISTSTATUS = ee.ImageCollection(
646
+ # "projects/glad/HLSDIST/current/VEG-DIST-STATUS"
647
+ # ).mosaic()
648
+
649
+ # # Key for high-confidence alerts (values 3, 6, 7, 8)
650
+ # high_conf_values = [3, 6, 7, 8]
651
+
652
+ # # Create high-confidence mask
653
+ # dist_high_conf = VEGDISTSTATUS.remap(
654
+ # high_conf_values, [1] * len(high_conf_values), 0
655
+ # )
656
+
657
+ # return dist_high_conf.updateMask(jrc_gfc_2020_prep()).rename(
658
+ # "DIST_after_2020"
659
+ # ) # Mask alerts to forest and rename band
660
+
661
+
662
+ # TMF_deg_before_2020
663
+ def g_tmf_deg_before_2020_prep():
664
+ tmf_deg = ee.ImageCollection("projects/JRC/TMF/v1_2024/DegradationYear").mosaic()
665
+ return (tmf_deg.lte(2020)).And(tmf_deg.gte(2000)).rename("TMF_deg_before_2020")
666
+
667
+
668
+ # TMF_deg_after_2020
669
+ def g_tmf_deg_after_2020_prep():
670
+ tmf_deg = ee.ImageCollection("projects/JRC/TMF/v1_2024/DegradationYear").mosaic()
671
+ return tmf_deg.gt(2020).rename("TMF_deg_after_2020")
672
+
673
+
674
+ # tmf_def_before_2020
675
+ def g_tmf_def_before_2020_prep():
676
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DeforestationYear").mosaic()
677
+ return (tmf_def.lte(2020)).And(tmf_def.gte(2000)).rename("TMF_def_before_2020")
678
+
679
+
680
+ # tmf_def_after_2020
681
+ def g_tmf_def_after_2020_prep():
682
+ tmf_def = ee.ImageCollection("projects/JRC/TMF/v1_2024/DeforestationYear").mosaic()
683
+ return tmf_def.gt(2020).rename("TMF_def_after_2020")
684
+
685
+
686
+ # GFC_loss_before_2020 (loss within 10 percent cover; includes 2020; correct for version 11)
687
+ def g_glad_gfc_loss_before_2020_prep():
688
+ # Load the Global Forest Change dataset
689
+ gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
690
+ gfc_loss = (
691
+ gfc.select(["lossyear"]).lte(20).And(gfc.select(["treecover2000"]).gt(10))
692
+ )
693
+ return gfc_loss.rename("GFC_loss_before_2020")
694
+
695
+
696
+ # GFC_loss_after_2020 (loss within 10 percent cover; correct for version 11)
697
+ def g_glad_gfc_loss_after_2020_prep():
698
+ # Load the Global Forest Change dataset
699
+ gfc = ee.Image("UMD/hansen/global_forest_change_2024_v1_12")
700
+ gfc_loss = gfc.select(["lossyear"]).gt(20).And(gfc.select(["treecover2000"]).gt(10))
701
+ return gfc_loss.rename("GFC_loss_after_2020")
702
+
703
+
704
+ # MODIS_fire_before_2020
705
+ def g_modis_fire_before_2020_prep():
706
+ modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
707
+ start_year = 2000
708
+ end_year = 2020
709
+ date_st = str(start_year) + "-01-01"
710
+ date_ed = str(end_year) + "-12-31"
711
+ return (
712
+ modis_fire.filterDate(date_st, date_ed)
713
+ .mosaic()
714
+ .select(["BurnDate"])
715
+ .gte(0)
716
+ .rename("MODIS_fire_before_2020")
717
+ )
718
+
719
+
720
+ # MODIS_fire_after_2020
721
+ def g_modis_fire_after_2020_prep():
722
+ modis_fire = ee.ImageCollection("MODIS/061/MCD64A1")
723
+ start_year = 2021
724
+ end_year = datetime.now().year
725
+ date_st = str(start_year) + "-01-01"
726
+ date_ed = str(end_year) + "-12-31"
727
+ return (
728
+ modis_fire.filterDate(date_st, date_ed)
729
+ .mosaic()
730
+ .select(["BurnDate"])
731
+ .gte(0)
732
+ .rename("MODIS_fire_after_2020")
733
+ )
734
+
735
+
736
+ # ESA_fire_before_2020
737
+ def g_esa_fire_before_2020_prep():
738
+ esa_fire = ee.ImageCollection("ESA/CCI/FireCCI/5_1")
739
+ start_year = 2000
740
+ end_year = 2020
741
+ date_st = str(start_year) + "-01-01"
742
+ date_ed = str(end_year) + "-12-31"
743
+ return (
744
+ esa_fire.filterDate(date_st, date_ed)
745
+ .mosaic()
746
+ .select(["BurnDate"])
747
+ .gte(0)
748
+ .rename("ESA_fire_before_2020")
749
+ )
750
+
751
+
752
+ #########################logging concessions
753
+ # http://data.globalforestwatch.org/datasets?q=logging&sort_by=relevance
754
+ def g_logging_concessions_before_2020_prep():
755
+ RCA = ee.FeatureCollection(
756
+ "projects/ee-whisp/assets/logging/RCA_Permis_dExploitation_et_dAmenagement"
757
+ )
758
+ RCA_binary = ee.Image().paint(RCA, 1)
759
+ CMR = ee.FeatureCollection(
760
+ "projects/ee-whisp/assets/logging/Cameroon_Forest_Management_Units"
761
+ )
762
+ CMR_binary = ee.Image().paint(CMR, 1)
763
+ Eq_G = ee.FeatureCollection(
764
+ "projects/ee-whisp/assets/logging/Equatorial_Guinea_logging_concessions"
765
+ )
766
+ Eq_G_binary = ee.Image().paint(Eq_G, 1)
767
+ DRC = ee.FeatureCollection(
768
+ "projects/ee-whisp/assets/logging/DRC_Forest_concession_agreements"
769
+ )
770
+ DRC_binary = ee.Image().paint(DRC, 1)
771
+ Liberia = ee.FeatureCollection(
772
+ "projects/ee-whisp/assets/logging/Liberia_Forest_Management_Contracts"
773
+ )
774
+ Liberia_binary = ee.Image().paint(Liberia, 1)
775
+ RoC = ee.FeatureCollection(
776
+ "projects/ee-whisp/assets/logging/Republic_of_the_Congo_logging_concessions"
777
+ )
778
+ Roc_binary = ee.Image().paint(RoC, 1)
779
+ Sarawak = ee.FeatureCollection(
780
+ "projects/ee-whisp/assets/logging/Sarawak_logging_concessions"
781
+ )
782
+ Sarawak_binary = ee.Image().paint(Sarawak, 1)
783
+ logging_concessions_binary = ee.ImageCollection(
784
+ [
785
+ RCA_binary,
786
+ CMR_binary,
787
+ Eq_G_binary,
788
+ DRC_binary,
789
+ Liberia_binary,
790
+ Roc_binary,
791
+ Sarawak_binary,
792
+ ]
793
+ ).mosaic()
794
+
795
+ return logging_concessions_binary.rename("GFW_logging_before_2020")
796
+
797
+
798
+ #########################national datasets
799
+
800
+ # nBR Brazil
801
+
802
+ # ### nBR Natural forests in 2020:
803
+
804
+ # %%
805
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
806
+ # Subsetting criteria: primary forests (DN=1) and secondary forests (DN=2) // secondary forests are those recovering from deforestation
807
+ # the resulting dataset shows primary and secondary forest cover in 2020 (mostly by August 2020)
808
+
809
+ ##########################primary forests###############################################
810
+ def nbr_terraclass_amz20_primary_prep():
811
+ tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
812
+ tcamz20_f = tcamz20.eq(1)
813
+ return tcamz20_f.rename("nBR_INPE_TC_primary_forest_Amazon_2020")
814
+
815
+
816
+ # [Official NFMS dataset] Brazilian Forest Service dataset on natural forest cover from PRODES and TerraClass data, base year 2022
817
+ # Subsetting criteria: ano_desmat > 2020 and nom_class = 'Floresta'
818
+ # the resulting datasets show primary forest cover in 2020 for the Pantanal, Caatinga, Atlantic Forest and Pampa biomes.
819
+ # the resulting dataset shows primary and secondary forest cover in 2020 for the Cerrado biome (TerraClass 2020)
820
+ # For the Amazon, best to use Terraclass 2020 directly, because the BFS used TerraClass 2014.
821
+
822
+ # Pantanal
823
+ def nbr_bfs_ptn_f20_prep():
824
+ bfs_fptn20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_ptn_2020")
825
+
826
+ bfs_fptn20_binary = ee.Image().paint(bfs_fptn20, 1)
827
+ return bfs_fptn20_binary.rename("nBR_BFS_primary_forest_Pantanal_2020")
828
+
829
+
830
+ # Caatinga - filtered with QGIS because the original geodatabase is too large to export as a shapefile (GEE accepted format)
831
+ ## couldn't convert it to asset, working on it (Error: Primary geometry of feature '306862' has 2454627 vertices, above the limit of 1000000 vertices. (Error code: 3)
832
+ def nbr_bfs_caat_f20_prep():
833
+ bfs_fcaat20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_caat_2020")
834
+ bfs_fcaat20_binary = ee.Image().paint(bfs_fcaat20, 1)
835
+ return bfs_fcaat20_binary.rename("nBR_BFS_primary_forest_Caatinga_2020")
836
+
837
+
838
+ # Atlantic Forest - filtered with QGIS because the original geodatabase is too large to export as a shapefile (GEE accepted format)
839
+ def nbr_bfs_atlf_f20_prep():
840
+ bfs_fatlf20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_atlf_2020")
841
+ bfs_fatlf20_binary = ee.Image().paint(bfs_fatlf20, 1)
842
+ return bfs_fatlf20_binary.rename("nBR_BFS_primary_forest_AtlanticForest_2020")
843
+
844
+
845
+ # Pampa - filtered in QGIS to save some storage space
846
+ def nbr_bfs_pmp_f20_prep():
847
+ bfs_fpmp20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_pmp_2020")
848
+ bfs_fpmp20_binary = ee.Image().paint(bfs_fpmp20, 1)
849
+ return bfs_fpmp20_binary.rename("nBR_BFS_primary_forest_Pampa_2020")
850
+
851
+
852
+ ##########################secondary forests###############################################
853
+ def nbr_terraclass_amz20_secondary_prep():
854
+ tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
855
+ tcamz20_f = tcamz20.eq(2)
856
+ return tcamz20_f.rename("nBR_INPE_TC_secondary_forest_Amazon_2020")
857
+
858
+
859
+ # Cerrado - filtered with QGIS because the original geodatabase is too large to export as a shapefile (GEE accepted format)
860
+ def nbr_bfs_cer_f20_prep():
861
+ bfs_fcer20 = ee.FeatureCollection("projects/ee-whisp/assets/NBR/bfs_cerr_2020")
862
+ bfs_fcer20_binary = ee.Image().paint(bfs_fcer20, 1)
863
+ return bfs_fcer20_binary.rename("nBR_BFS_primary_and_secondary_forest_Cerrado_2020")
864
+
865
+
866
+ # %%
867
+ # [non-official dataset by MapBiomas multisector initiative]
868
+ # land use/cover from 1985 up to 2023, collection 9
869
+ # Subsetting criteria: classification_2020 = Forest formation (DN=3), Savanna Formation (DN=4, forest according to BR definition), Mangrove (DN=5), Floodable Forest (DN=6), Wooded Sandbank veg (DN=49)
870
+ # the resulting dataset shows forest cover in 2020, without distinguishing between primary and secondary forests
871
+ def nbr_mapbiomasc9_f20_prep():
872
+ mapbiomasc9_20 = ee.Image(
873
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
874
+ ).select("classification_2020")
875
+ mapbiomasc9_20_forest = (
876
+ mapbiomasc9_20.eq(3)
877
+ .Or(mapbiomasc9_20.eq(4))
878
+ .Or(mapbiomasc9_20.eq(5))
879
+ .Or(mapbiomasc9_20.eq(6))
880
+ .Or(mapbiomasc9_20.eq(49))
881
+ )
882
+ return mapbiomasc9_20_forest.rename("nBR_MapBiomas_col9_forest_Brazil_2020")
883
+
884
+
885
+ # ### ########################NBR plantation forest in 2020:#######################################
886
+
887
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
888
+ # Subsetting criteria: silviculture (DN=9)
889
+ # the resulting dataset shows monospecific commercial plantations, mostly eucalyptus and pinus.
890
+ def nbr_terraclass_amz20_silv_prep():
891
+ tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
892
+ tcamz20_silviculture = tcamz20.eq(9)
893
+ return tcamz20_silviculture.rename("nBR_INPE_TCsilviculture_Amazon_2020")
894
+
895
+
896
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
897
+ # Subsetting criteria: silviculture (DN=9)
898
+ # the resulting dataset shows monospecific commercial plantations, mostly eucalyptus and pinus.
899
+ def nbr_terraclass_silv_cer20_prep():
900
+ tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
901
+ tccer20_silviculture = tccer20.eq(9)
902
+ return tccer20_silviculture.rename("nBR_INPE_TCsilviculture_Cerrado_2020")
903
+
904
+
905
+ # [non-official dataset by MapBiomas multisector initiative]
906
+ # land use/cover from 1985 up to 2023, collection 9
907
+ # Subsetting criteria: 'classification_2020' = Forest plantation (DN=9)
908
+ # the resulting dataset shows forest plantation in 2020
909
+ def nbr_mapbiomasc9_silv20_prep():
910
+ mapbiomasc9_20 = ee.Image(
911
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
912
+ ).select("classification_2020")
913
+ mapbiomasc9_20_silviculture = mapbiomasc9_20.eq(9)
914
+ return mapbiomasc9_20_silviculture.rename(
915
+ "nBR_MapBiomas_col9_silviculture_Brazil_2020"
916
+ )
917
+
918
+
919
+ ################ ### NBR Disturbances before 2020:########################################
920
+
921
+ # [Official NFMS dataset] INPE PRODES data up to 2023
922
+ # Subsetting criteria: DN = [0, 2, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60];
923
+
924
+ # the resulting dataset shows deforestation and conversion of OWL and OL up to 2020 (mostly August 2020), including residues (omission errors corrections)
925
+ def nbr_prodes_before_2020_prep():
926
+ prodes = ee.Image("projects/ee-whisp/assets/NBR/prodes_brasil_2023")
927
+ prodes_before_20_dn = [
928
+ 0,
929
+ 2,
930
+ 4,
931
+ 6,
932
+ 7,
933
+ 8,
934
+ 9,
935
+ 10,
936
+ 11,
937
+ 12,
938
+ 13,
939
+ 14,
940
+ 15,
941
+ 16,
942
+ 17,
943
+ 18,
944
+ 19,
945
+ 20,
946
+ 50,
947
+ 51,
948
+ 52,
949
+ 53,
950
+ 54,
951
+ 55,
952
+ 56,
953
+ 57,
954
+ 58,
955
+ 59,
956
+ 60,
957
+ ]
958
+ prodes_before_20_mask = prodes.remap(
959
+ prodes_before_20_dn, [1] * len(prodes_before_20_dn)
960
+ ) # .eq(1)
961
+ prodes_before_20 = prodes_before_20_mask.selfMask()
962
+ return prodes_before_20.rename("nBR_PRODES_deforestation_Brazil_before_2020")
963
+
964
+
965
+ ## Caution: 1) includes deforestation and conversion of other wooded land and grassland
966
+
967
+ # [Official NFMS dataset] INPE.DETER data from 2nd August 2016 up to the 04th of April 2025
968
+ # Subsetting criteria: forest degradation classes ['CICATRIZ_DE_QUEIMADA', 'CS_DESORDENADO', 'DEGRADACAO'] and view_date until 2020-12-31
969
+ # 'CS_GEOMETRICO' excluded to align with FREL
970
+
971
+
972
+ def nbr_deter_amazon_before_2020_prep():
973
+ deteramz = ee.FeatureCollection("projects/ee-whisp/assets/NBR/deter_amz_16apr2025")
974
+ degradation_classes = ["CICATRIZ_DE_QUEIMADA", "CS_DESORDENADO", "DEGRADACAO"]
975
+
976
+ # Add a formatted date field based on VIEW_DATE
977
+ def add_formatted_date(feature):
978
+ return feature.set("formatted_date", ee.Date(feature.get("VIEW_DATE")))
979
+
980
+ deteramz = deteramz.map(add_formatted_date)
981
+
982
+ deter_deg = deteramz.filter(
983
+ ee.Filter.inList("CLASSNAME", degradation_classes)
984
+ ).filter(ee.Filter.lt("formatted_date", ee.Date("2020-12-31")))
985
+
986
+ deter_deg_binary = ee.Image().paint(deter_deg, 1)
987
+ return deter_deg_binary.rename("nBR_DETER_forestdegradation_Amazon_before_2020")
988
+
989
+
990
+ ################ ### NBR Disturbances after 2020:########################################
991
+ # [Official NFMS dataset] INPE PRODES data up to 2023
992
+ # Subsetting criteria: DN = [21, 22, 23, 61, 62, 63];
993
+
994
+ # the resulting dataset shows deforestation and conversion of OWL and OL up to 2020 (mostly August 2020), including residues (omission errors corrections)
995
+
996
+
997
+ def nbr_prodes_after_2020_prep():
998
+ prodes = ee.Image("projects/ee-whisp/assets/NBR/prodes_brasil_2023")
999
+ prodes_after_20_dn = [21, 22, 23, 61, 62, 63]
1000
+ prodes_after_20_mask = prodes.remap(
1001
+ prodes_after_20_dn, [1] * len(prodes_after_20_dn)
1002
+ ) # .eq(1)
1003
+ prodes_after_20 = prodes_after_20_mask.selfMask()
1004
+ return prodes_after_20.rename("nBR_PRODES_deforestation_Brazil_after_2020")
1005
+
1006
+
1007
+ # %%
1008
+ # [Official NFMS dataset] INPE.DETER data from 2nd August 2016 up to the 04th of April 2025
1009
+ # Subsetting criteria: forest degradation classes ['CICATRIZ_DE_QUEIMADA', 'CS_DESORDENADO', 'DEGRADACAO'] and view_date from 2021-01-01 onward
1010
+ # 'CS_GEOMETRICO' excluded to align with FREL
1011
+ def nbr_deter_amazon_after_2020_prep():
1012
+ deteramz = ee.FeatureCollection("projects/ee-whisp/assets/NBR/deter_amz_16apr2025")
1013
+ degradation_classes = ["CICATRIZ_DE_QUEIMADA", "CS_DESORDENADO", "DEGRADACAO"]
1014
+
1015
+ # Add a formatted date field based on VIEW_DATE
1016
+ def add_formatted_date(feature):
1017
+ return feature.set("formatted_date", ee.Date(feature.get("VIEW_DATE")))
1018
+
1019
+ deteramz = deteramz.map(add_formatted_date)
1020
+
1021
+ deter_deg = deteramz.filter(
1022
+ ee.Filter.inList("CLASSNAME", degradation_classes)
1023
+ ).filter(ee.Filter.gt("formatted_date", ee.Date("2021-01-01")))
1024
+
1025
+ deter_deg_binary = ee.Image().paint(deter_deg, 1)
1026
+ return deter_deg_binary.rename("nBR_DETER_forestdegradation_Amazon_after_2020")
1027
+
1028
+
1029
+ # ########################## NBR commodities - permanent/perennial crops in 2020:###############################
1030
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
1031
+ # OR [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
1032
+ # Subsetting criteria: perennial (DN=12) and semi-perennial (DN=13) crops
1033
+ # the resulting dataset shows perennial and semi-perennial crops in 2020
1034
+ def nbr_terraclass_amz_cer20_pc_prep():
1035
+ tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
1036
+ tcamz20_pc = tcamz20.eq(12).Or(tcamz20.eq(13))
1037
+ tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
1038
+ tccer20_pc = tccer20.eq(12).Or(tccer20.eq(13))
1039
+ tc_pc = ee.ImageCollection([tcamz20_pc, tccer20_pc]).mosaic()
1040
+ return tc_pc.rename("nBR_INPE_TCamz_cer_perennial_2020")
1041
+
1042
+
1043
+ # [non-official dataset by MapBiomas multisector initiative]
1044
+ # land use/cover from 1985 up to 2023, collection 9
1045
+ # Subsetting criteria: 'classification_2020' = coffee (DN=46) <================== COFFEE
1046
+ # the resulting dataset shows coffee area in 2020
1047
+ def nbr_mapbiomasc9_cof_prep():
1048
+ mapbiomasc9_20 = ee.Image(
1049
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1050
+ ).select("classification_2020")
1051
+ mapbiomasc9_20_coffee = mapbiomasc9_20.eq(46)
1052
+ return mapbiomasc9_20_coffee.rename("nBR_MapBiomas_col9_coffee_2020")
1053
+
1054
+
1055
+ # [non-official dataset by MapBiomas multisector initiative]
1056
+ # land use/cover from 1985 up to 2023, collection 9
1057
+ # Subsetting criteria: 'classification_2020' = palm oil (DN=35) <================= PALM OIL
1058
+ # the resulting dataset shows palm oil area in 2020
1059
+ def nbr_mapbiomasc9_po_prep():
1060
+ mapbiomasc9_20 = ee.Image(
1061
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1062
+ ).select("classification_2020")
1063
+ mapbiomasc9_20_palm = mapbiomasc9_20.eq(35)
1064
+ return mapbiomasc9_20_palm.rename("nBR_MapBiomas_col9_palmoil_2020")
1065
+
1066
+
1067
+ # [non-official dataset by MapBiomas multisector initiative]
1068
+ # land use/cover from 1985 up to 2023, collection 9
1069
+ # Subsetting criteria: 'classification_2020' = other perennial crops (DN=48)
1070
+ # the resulting dataset shows citrus and perennial crops other than coffee and palm oil in 2020
1071
+ def nbr_mapbiomasc9_pc_prep():
1072
+ mapbiomasc9_20 = ee.Image(
1073
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1074
+ ).select("classification_2020")
1075
+ mapbiomasc9_20_pc = mapbiomasc9_20.eq(35).Or(mapbiomasc9_20.eq(46))
1076
+ return mapbiomasc9_20_pc.rename("nBR_MapBiomas_col9_pc_2020")
1077
+
1078
+
1079
+ # ######################## NBR commodities - annual crops in 2020:##############################
1080
+
1081
+ # %%
1082
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
1083
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
1084
+ # Subsetting criteria: annual/temporary 1 cycle (DN=14) or more than 1 cycle (DN=15)
1085
+ # the resulting dataset shows temporary crop in 2020
1086
+ def nbr_terraclass_amz_cer20_ac_prep():
1087
+ tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
1088
+ tcamz20_ac = tcamz20.eq(14).Or(tcamz20.eq(15))
1089
+ tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
1090
+ tccer20_ac = tccer20.eq(14).Or(tccer20.eq(15))
1091
+ tc_ac = ee.ImageCollection([tcamz20_ac, tccer20_ac]).mosaic()
1092
+ return tc_ac.rename("nBR_INPE_TCamz_cer_annual_2020")
1093
+
1094
+
1095
+ # [non-official dataset by MapBiomas multisector initiative]
1096
+ # land use/cover from 1985 up to 2023, collection 9
1097
+ # Subsetting criteria: 'classification_2020' = soybean (DN=39) <================== SOY
1098
+ # the resulting dataset shows soybean plantation area in 2020
1099
+ def nbr_mapbiomasc9_soy_prep():
1100
+ mapbiomasc9_20 = ee.Image(
1101
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1102
+ ).select("classification_2020")
1103
+ mapbiomasc9_20_soy = mapbiomasc9_20.eq(39)
1104
+ return mapbiomasc9_20_soy.rename("nBR_MapBiomas_col9_soy_2020")
1105
+
1106
+
1107
+ # [non-official dataset by MapBiomas multisector initiative]
1108
+ # land use/cover from 1985 up to 2023, collection 9
1109
+ # Subsetting criteria: 'classification_2020' = other temporary crops (DN=41)
1110
+ # Subsetting criteria: 'classification_2020' = sugar cane (DN=20)
1111
+ # Subsetting criteria: 'classification_2020' = rice (DN=40)
1112
+ # Subsetting criteria: 'classification_2020' = cotton (beta version, DN=62)
1113
+ # the resulting dataset shows temporary crop area other than soy, includes sugar cane, rice, and cotton
1114
+ def nbr_mapbiomasc9_ac_prep():
1115
+ mapbiomasc9_20 = ee.Image(
1116
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1117
+ ).select("classification_2020")
1118
+ mapbiomasc9_20_ac = (
1119
+ mapbiomasc9_20.eq(41)
1120
+ .Or(mapbiomasc9_20.eq(20))
1121
+ .Or(mapbiomasc9_20.eq(40))
1122
+ .Or(mapbiomasc9_20.eq(62))
1123
+ )
1124
+ return mapbiomasc9_20_ac.rename("nBR_MapBiomas_col9_annual_crops_2020")
1125
+
1126
+
1127
+ # ################################### NBR commodities - pasture/livestock in 2020:##############################
1128
+
1129
+ # %%
1130
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Amazon biome, 2020
1131
+ # Subsetting criteria: BUSH/SHRUB PASTURE (DN=10) or HERBACEOUS PASTURE (DN=11)
1132
+
1133
+ # the resulting dataset shows 2020 pasture area in the Amazon
1134
+ def nbr_terraclass_amz20_pasture_prep():
1135
+ tcamz20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_amz_2020")
1136
+ tcamz20_pasture = tcamz20.eq(10).Or(tcamz20.eq(11))
1137
+ return tcamz20_pasture.rename("nBR_INPE_TCamz_pasture_2020")
1138
+
1139
+
1140
+ # %%
1141
+ # [Official NFMS dataset] INPE/EMBRAPA TerraClass land use/cover in the Cerrado biome, 2020
1142
+ # Subsetting criteria: PASTURE (DN=11)
1143
+ # the resulting dataset shows 2020 pasture area in the Cerrado
1144
+
1145
+
1146
+ def nbr_terraclass_cer20_ac_prep():
1147
+ tccer20 = ee.Image("projects/ee-whisp/assets/NBR/terraclass_cer_2020")
1148
+ tccer20_pasture = tccer20.eq(11)
1149
+ return tccer20_pasture.rename("nBR_INPE_TCcer_pasture_2020")
1150
+
1151
+
1152
+ # %%
1153
+ # [non-official dataset by MapBiomas multisector initiative]
1154
+ # land use/cover from 1985 up to 2023, collection 9
1155
+ # Subsetting criteria: 'classification_2020' = pasture (DN=15)
1156
+ # the resulting dataset shows pasture area in 2020 in Brazil
1157
+ def nbr_mapbiomasc9_pasture_prep():
1158
+ mapbiomasc9_20 = ee.Image(
1159
+ "projects/mapbiomas-public/assets/brazil/lulc/collection9/mapbiomas_collection90_integration_v1"
1160
+ ).select("classification_2020")
1161
+ mapbiomasc9_20_pasture = mapbiomasc9_20.eq(15)
1162
+ return mapbiomasc9_20_pasture.rename("nBR_MapBiomas_col9_pasture_2020")
1163
+
1164
+
1165
+ ###################################################################
1166
+ # nCO - Colombia
1167
+
1168
+
1169
+ def nco_ideam_forest_2020_prep():
1170
+ ideam_forest_raw = ee.Image("projects/ee-whisp/assets/nCO/ideam_2020_geo")
1171
+ ideam_forest = ideam_forest_raw.eq(1) # get forest class
1172
+ return ideam_forest.rename("nCO_ideam_forest_2020")
1173
+
1174
+
1175
+ def nco_ideam_eufo_commission_2020_prep():
1176
+ ideam_agroforest_raw = ee.Image("projects/ee-whisp/assets/nCO/ideam_2020_geo_EUFO")
1177
+ ideam_agroforest = ideam_agroforest_raw.eq(4) # get forest class
1178
+ return ideam_agroforest.rename("nCO_ideam_eufo_commission_2020")
1179
+
1180
+
1181
+ # Cocoa_bnetd
1182
+ def nci_ocs2020_prep():
1183
+ return (
1184
+ ee.Image("BNETD/land_cover/v1/2020")
1185
+ .select("classification")
1186
+ .eq(9)
1187
+ .rename("nCI_Cocoa_bnetd")
1188
+ ) # cocoa from national land cover map for Côte d'Ivoire
1189
+
1190
+
1191
+ ###Combining datasets
1192
+
1193
+
1194
+ def combine_datasets(national_codes=None):
1195
+ """Combines datasets into a single multiband image, with fallback if assets are missing."""
1196
+ img_combined = ee.Image(1).rename(geometry_area_column)
1197
+
1198
+ # Combine images directly
1199
+ for img in [func() for func in list_functions(national_codes=national_codes)]:
1200
+ try:
1201
+ img_combined = img_combined.addBands(img)
1202
+ except ee.EEException as e:
1203
+ # logger.error(f"Error adding image: {e}")
1204
+ print(f"Error adding image: {e}")
1205
+
1206
+ try:
1207
+ # Attempt to print band names to check for errors
1208
+ # print(img_combined.bandNames().getInfo())
1209
+ img_combined.bandNames().getInfo()
1210
+
1211
+ except ee.EEException as e:
1212
+ # logger.error(f"Error printing band names: {e}")
1213
+ # logger.info("Running code for filtering to only valid datasets due to error in input")
1214
+ print("using valid datasets filter due to error in input")
1215
+ # Validate images
1216
+ images_to_test = [
1217
+ func() for func in list_functions(national_codes=national_codes)
1218
+ ]
1219
+ valid_imgs = keep_valid_images(images_to_test) # Validate images
1220
+
1221
+ # Retry combining images after validation
1222
+ img_combined = ee.Image(1).rename(geometry_area_column)
1223
+ for img in valid_imgs:
1224
+ img_combined = img_combined.addBands(img)
1225
+
1226
+ img_combined = img_combined.multiply(ee.Image.pixelArea())
1227
+ print("Whisp multiband image compiled")
1228
+
1229
+ return img_combined
1230
+
1231
+
1232
+ ######helper functions to check images
1233
+ # list all functions ending with "_prep" (in the current script)
1234
+ # def list_functions():
1235
+ # # Use the module's globals to get all defined functions
1236
+ # current_module = inspect.getmodule(inspect.currentframe())
1237
+ # functions = [
1238
+ # func
1239
+ # for name, func in inspect.getmembers(current_module, inspect.isfunction)
1240
+ # if name.endswith("_prep")
1241
+ # ]
1242
+ # return functions
1243
+
1244
+
1245
+ def list_functions(national_codes=None):
1246
+ """
1247
+ Returns a list of functions that end with "_prep" and either:
1248
+ - Start with "g_" (global/regional products)
1249
+ - Start with any provided national code prefix (nXX_)
1250
+
1251
+ Args:
1252
+ national_codes: List of ISO2 country codes (without the 'n' prefix)
1253
+ """
1254
+ # Use the module's globals to get all defined functions
1255
+ current_module = inspect.getmodule(inspect.currentframe())
1256
+
1257
+ # If national_codes is None, default to an empty list
1258
+ if national_codes is None:
1259
+ national_codes = []
1260
+
1261
+ # Create prefixes list with proper formatting ('n' + code + '_')
1262
+ allowed_prefixes = ["g_"] + [f"n{code.lower()}_" for code in national_codes]
1263
+
1264
+ # Filter functions in a single pass
1265
+ functions = [
1266
+ func
1267
+ for name, func in inspect.getmembers(current_module, inspect.isfunction)
1268
+ if name.endswith("_prep")
1269
+ and any(name.startswith(prefix) for prefix in allowed_prefixes)
1270
+ ]
1271
+
1272
+ return functions
1273
+
1274
+
1275
+ # # IN PROGRESS - expected behaviour
1276
+ # def filter_by_prefix_list(input_list=None,prefix_list=None):
1277
+
1278
+ # if input_list is None:
1279
+ # print ("No function in list")
1280
+ # if prefix_list is None:
1281
+ # print ("No prefixes listed by which to filter")
1282
+ # if input_list is not None:
1283
+ # for prefix in prefix_list:
1284
+ # if element.startsWith(prefix):
1285
+ # list.
1286
+
1287
+
1288
+ def keep_valid_images(images):
1289
+ """Keeps only valid images."""
1290
+ valid_images = []
1291
+ for img in images:
1292
+ try:
1293
+ img.getInfo() # This will raise an exception if the image is invalid
1294
+ valid_images.append(img)
1295
+ except ee.EEException as e:
1296
+ # logger.error(f"Invalid image: {e}")
1297
+ print(f"Invalid image: {e}")
1298
+ return valid_images
1299
+
1300
+
1301
+ # function to check if an image is valid
1302
+ def ee_image_checker(image):
1303
+ """
1304
+ Tests if the input is a valid ee.Image.
1305
+
1306
+ Args:
1307
+ image: An ee.Image object.
1308
+
1309
+ Returns:
1310
+ bool: True if the input is a valid ee.Image, False otherwise.
1311
+ """
1312
+ try:
1313
+ if ee.Algorithms.ObjectType(image).getInfo() == "Image":
1314
+ # Trigger some action on the image to ensure it's a valid image
1315
+ image.getInfo() # This will raise an exception if the image is invalid
1316
+ return True
1317
+ except ee.EEException as e:
1318
+ print(f"Image validation failed with EEException: {e}")
1319
+ except Exception as e:
1320
+ print(f"Image validation failed with exception: {e}")
1321
+ return False
1322
+
1323
+
1324
+ # preparation steps for multiband image with area per pixel values
1325
+ # function for notebook environment
1326
+ # user provides custom_images dict and custom_bands_info dict
1327
+ def combine_custom_bands(custom_images, custom_bands_info):
1328
+ """
1329
+ Combine custom Earth Engine images into a single multiband image with area conversion.
1330
+
1331
+ Returns
1332
+ -------
1333
+ ee.Image
1334
+ Combined bands converted to area values
1335
+ """
1336
+ # ... existing validation code ...
1337
+
1338
+ # Step 3: Rename and combine images
1339
+ band_names = list(custom_bands_info.keys())
1340
+
1341
+ # Start with first image
1342
+ custom_ee_image = custom_images[band_names[0]].rename(band_names[0])
1343
+
1344
+ # Add remaining images if any
1345
+ for name in band_names[1:]:
1346
+ next_image = custom_images[name].rename(name)
1347
+ custom_ee_image = custom_ee_image.addBands(next_image)
1348
+
1349
+ # Convert to area values
1350
+ custom_ee_image = custom_ee_image.multiply(ee.Image.pixelArea())
1351
+
1352
+ return custom_ee_image # Only return the image