RadGEEToolbox 1.7.2__py3-none-any.whl → 1.7.3__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.
- RadGEEToolbox/Export.py +233 -0
- RadGEEToolbox/GenericCollection.py +637 -12
- RadGEEToolbox/LandsatCollection.py +692 -64
- RadGEEToolbox/Sentinel1Collection.py +627 -7
- RadGEEToolbox/Sentinel2Collection.py +670 -27
- RadGEEToolbox/__init__.py +3 -1
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.3.dist-info}/METADATA +11 -7
- radgeetoolbox-1.7.3.dist-info/RECORD +14 -0
- radgeetoolbox-1.7.2.dist-info/RECORD +0 -13
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.3.dist-info}/WHEEL +0 -0
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.3.dist-info}/licenses/LICENSE.txt +0 -0
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.3.dist-info}/top_level.txt +0 -0
|
@@ -159,6 +159,11 @@ class LandsatCollection:
|
|
|
159
159
|
self._monthly_max = None
|
|
160
160
|
self._monthly_min = None
|
|
161
161
|
self._monthly_sum = None
|
|
162
|
+
self._yearly_median = None
|
|
163
|
+
self._yearly_mean = None
|
|
164
|
+
self._yearly_max = None
|
|
165
|
+
self._yearly_min = None
|
|
166
|
+
self._yearly_sum = None
|
|
162
167
|
self._mean = None
|
|
163
168
|
self._max = None
|
|
164
169
|
self._min = None
|
|
@@ -239,17 +244,17 @@ class LandsatCollection:
|
|
|
239
244
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
240
245
|
.rename("ndwi")
|
|
241
246
|
.copyProperties(image)
|
|
242
|
-
.set("threshold", threshold),
|
|
247
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
243
248
|
ndwi_calc.updateMask(ndwi_calc.gte(ng_threshold))
|
|
244
249
|
.rename("ndwi")
|
|
245
250
|
.copyProperties(image)
|
|
246
|
-
.set("threshold", ng_threshold),
|
|
251
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
247
252
|
)
|
|
248
253
|
else:
|
|
249
254
|
water = (
|
|
250
255
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
251
256
|
.rename("ndwi")
|
|
252
|
-
.copyProperties(image)
|
|
257
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
253
258
|
)
|
|
254
259
|
return water
|
|
255
260
|
|
|
@@ -282,17 +287,17 @@ class LandsatCollection:
|
|
|
282
287
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
283
288
|
.rename("mndwi")
|
|
284
289
|
.copyProperties(image)
|
|
285
|
-
.set("threshold", threshold),
|
|
290
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
286
291
|
mndwi_calc.updateMask(mndwi_calc.gte(ng_threshold))
|
|
287
292
|
.rename("mndwi")
|
|
288
293
|
.copyProperties(image)
|
|
289
|
-
.set("threshold", ng_threshold),
|
|
294
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
290
295
|
)
|
|
291
296
|
else:
|
|
292
297
|
water = (
|
|
293
298
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
294
299
|
.rename("mndwi")
|
|
295
|
-
.copyProperties(image)
|
|
300
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
296
301
|
)
|
|
297
302
|
return water
|
|
298
303
|
|
|
@@ -320,17 +325,17 @@ class LandsatCollection:
|
|
|
320
325
|
ndvi_calc.updateMask(ndvi_calc.gte(threshold))
|
|
321
326
|
.rename("ndvi")
|
|
322
327
|
.copyProperties(image)
|
|
323
|
-
.set("threshold", threshold),
|
|
328
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
324
329
|
ndvi_calc.updateMask(ndvi_calc.gte(ng_threshold))
|
|
325
330
|
.rename("ndvi")
|
|
326
331
|
.copyProperties(image)
|
|
327
|
-
.set("threshold", ng_threshold),
|
|
332
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
328
333
|
)
|
|
329
334
|
else:
|
|
330
335
|
vegetation = (
|
|
331
336
|
ndvi_calc.updateMask(ndvi_calc.gte(threshold))
|
|
332
337
|
.rename("ndvi")
|
|
333
|
-
.copyProperties(image)
|
|
338
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
334
339
|
)
|
|
335
340
|
return vegetation
|
|
336
341
|
|
|
@@ -356,17 +361,17 @@ class LandsatCollection:
|
|
|
356
361
|
halite_index.updateMask(halite_index.gte(threshold))
|
|
357
362
|
.rename("halite")
|
|
358
363
|
.copyProperties(image)
|
|
359
|
-
.set("threshold", threshold),
|
|
364
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
360
365
|
halite_index.updateMask(halite_index.gte(ng_threshold))
|
|
361
366
|
.rename("halite")
|
|
362
367
|
.copyProperties(image)
|
|
363
|
-
.set("threshold", ng_threshold),
|
|
368
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
364
369
|
)
|
|
365
370
|
else:
|
|
366
371
|
halite = (
|
|
367
372
|
halite_index.updateMask(halite_index.gte(threshold))
|
|
368
373
|
.rename("halite")
|
|
369
|
-
.copyProperties(image)
|
|
374
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
370
375
|
)
|
|
371
376
|
return halite
|
|
372
377
|
|
|
@@ -392,17 +397,17 @@ class LandsatCollection:
|
|
|
392
397
|
gypsum_index.updateMask(gypsum_index.gte(threshold))
|
|
393
398
|
.rename("gypsum")
|
|
394
399
|
.copyProperties(image)
|
|
395
|
-
.set("threshold", threshold),
|
|
400
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
396
401
|
gypsum_index.updateMask(gypsum_index.gte(ng_threshold))
|
|
397
402
|
.rename("gypsum")
|
|
398
403
|
.copyProperties(image)
|
|
399
|
-
.set("threshold", ng_threshold),
|
|
404
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
400
405
|
)
|
|
401
406
|
else:
|
|
402
407
|
gypsum = (
|
|
403
408
|
gypsum_index.updateMask(gypsum_index.gte(threshold))
|
|
404
409
|
.rename("gypsum")
|
|
405
|
-
.copyProperties(image)
|
|
410
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
406
411
|
)
|
|
407
412
|
return gypsum
|
|
408
413
|
|
|
@@ -427,11 +432,11 @@ class LandsatCollection:
|
|
|
427
432
|
NDTI.updateMask(NDTI.gte(threshold))
|
|
428
433
|
.rename("ndti")
|
|
429
434
|
.copyProperties(image)
|
|
430
|
-
.set("threshold", threshold),
|
|
435
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
431
436
|
NDTI.updateMask(NDTI.gte(ng_threshold))
|
|
432
437
|
.rename("ndti")
|
|
433
438
|
.copyProperties(image)
|
|
434
|
-
.set("threshold", ng_threshold),
|
|
439
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
435
440
|
)
|
|
436
441
|
else:
|
|
437
442
|
turbidity = (
|
|
@@ -471,17 +476,17 @@ class LandsatCollection:
|
|
|
471
476
|
KIVU.updateMask(KIVU.gte(threshold))
|
|
472
477
|
.rename("kivu")
|
|
473
478
|
.copyProperties(image)
|
|
474
|
-
.set("threshold", threshold),
|
|
479
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
475
480
|
KIVU.updateMask(KIVU.gte(ng_threshold))
|
|
476
481
|
.rename("kivu")
|
|
477
482
|
.copyProperties(image)
|
|
478
|
-
.set("threshold", ng_threshold),
|
|
483
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
479
484
|
)
|
|
480
485
|
else:
|
|
481
486
|
chlorophyll = (
|
|
482
487
|
KIVU.updateMask(KIVU.gte(threshold))
|
|
483
488
|
.rename("kivu")
|
|
484
|
-
.copyProperties(image)
|
|
489
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
485
490
|
)
|
|
486
491
|
return chlorophyll
|
|
487
492
|
|
|
@@ -505,8 +510,8 @@ class LandsatCollection:
|
|
|
505
510
|
# otherwise treat as OLI and copy properties after renaming band to "albedo"
|
|
506
511
|
albedo = ee.Algorithms.If(
|
|
507
512
|
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
508
|
-
image.expression(TM_expression).rename("albedo").copyProperties(image),
|
|
509
|
-
image.expression(OLI_expression).rename("albedo").copyProperties(image))
|
|
513
|
+
image.expression(TM_expression).rename("albedo").copyProperties(image).set('system:time_start', image.get('system:time_start')),
|
|
514
|
+
image.expression(OLI_expression).rename("albedo").copyProperties(image).set('system:time_start', image.get('system:time_start')))
|
|
510
515
|
return albedo
|
|
511
516
|
|
|
512
517
|
@staticmethod
|
|
@@ -530,14 +535,14 @@ class LandsatCollection:
|
|
|
530
535
|
ndsi_calc.updateMask(ndsi_calc.gte(threshold))
|
|
531
536
|
.rename("ndsi")
|
|
532
537
|
.copyProperties(image)
|
|
533
|
-
.set("threshold", threshold),
|
|
538
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
534
539
|
ndsi_calc.updateMask(ndsi_calc.gte(ng_threshold))
|
|
535
540
|
.rename("ndsi")
|
|
536
541
|
.copyProperties(image)
|
|
537
|
-
.set("threshold", ng_threshold),
|
|
542
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
538
543
|
)
|
|
539
544
|
else:
|
|
540
|
-
ndsi = ndsi_calc.updateMask(ndsi_calc.gte(threshold)).rename("ndsi").copyProperties(image).set("threshold", threshold)
|
|
545
|
+
ndsi = ndsi_calc.updateMask(ndsi_calc.gte(threshold)).rename("ndsi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
541
546
|
return ndsi
|
|
542
547
|
|
|
543
548
|
@staticmethod
|
|
@@ -567,14 +572,14 @@ class LandsatCollection:
|
|
|
567
572
|
evi_calc.updateMask(evi_calc.gte(threshold))
|
|
568
573
|
.rename("evi")
|
|
569
574
|
.copyProperties(image)
|
|
570
|
-
.set("threshold", threshold),
|
|
575
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
571
576
|
evi_calc.updateMask(evi_calc.gte(ng_threshold))
|
|
572
577
|
.rename("evi")
|
|
573
578
|
.copyProperties(image)
|
|
574
|
-
.set("threshold", ng_threshold),
|
|
579
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
575
580
|
)
|
|
576
581
|
else:
|
|
577
|
-
evi = evi_calc.updateMask(evi_calc.gte(threshold)).rename("evi").copyProperties(image).set("threshold", threshold)
|
|
582
|
+
evi = evi_calc.updateMask(evi_calc.gte(threshold)).rename("evi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
578
583
|
return evi
|
|
579
584
|
|
|
580
585
|
@staticmethod
|
|
@@ -600,14 +605,14 @@ class LandsatCollection:
|
|
|
600
605
|
savi_calc.updateMask(savi_calc.gte(threshold))
|
|
601
606
|
.rename("savi")
|
|
602
607
|
.copyProperties(image)
|
|
603
|
-
.set("threshold", threshold),
|
|
608
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
604
609
|
savi_calc.updateMask(savi_calc.gte(ng_threshold))
|
|
605
610
|
.rename("savi")
|
|
606
611
|
.copyProperties(image)
|
|
607
|
-
.set("threshold", ng_threshold),
|
|
612
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
608
613
|
)
|
|
609
614
|
else:
|
|
610
|
-
savi = savi_calc.updateMask(savi_calc.gte(threshold)).rename("savi").copyProperties(image).set("threshold", threshold)
|
|
615
|
+
savi = savi_calc.updateMask(savi_calc.gte(threshold)).rename("savi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
611
616
|
return savi
|
|
612
617
|
|
|
613
618
|
@staticmethod
|
|
@@ -633,14 +638,14 @@ class LandsatCollection:
|
|
|
633
638
|
msavi_calc.updateMask(msavi_calc.gte(threshold))
|
|
634
639
|
.rename("msavi")
|
|
635
640
|
.copyProperties(image)
|
|
636
|
-
.set("threshold", threshold),
|
|
641
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
637
642
|
msavi_calc.updateMask(msavi_calc.gte(ng_threshold))
|
|
638
643
|
.rename("msavi")
|
|
639
644
|
.copyProperties(image)
|
|
640
|
-
.set("threshold", ng_threshold),
|
|
645
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
641
646
|
)
|
|
642
647
|
else:
|
|
643
|
-
msavi = msavi_calc.updateMask(msavi_calc.gte(threshold)).rename("msavi").copyProperties(image).set("threshold", threshold)
|
|
648
|
+
msavi = msavi_calc.updateMask(msavi_calc.gte(threshold)).rename("msavi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
644
649
|
return msavi
|
|
645
650
|
|
|
646
651
|
@staticmethod
|
|
@@ -666,14 +671,14 @@ class LandsatCollection:
|
|
|
666
671
|
ndmi_calc.updateMask(ndmi_calc.gte(threshold))
|
|
667
672
|
.rename("ndmi")
|
|
668
673
|
.copyProperties(image)
|
|
669
|
-
.set("threshold", threshold),
|
|
674
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
670
675
|
ndmi_calc.updateMask(ndmi_calc.gte(ng_threshold))
|
|
671
676
|
.rename("ndmi")
|
|
672
677
|
.copyProperties(image)
|
|
673
|
-
.set("threshold", ng_threshold),
|
|
678
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
674
679
|
)
|
|
675
680
|
else:
|
|
676
|
-
ndmi = ndmi_calc.updateMask(ndmi_calc.gte(threshold)).rename("ndmi").copyProperties(image).set("threshold", threshold)
|
|
681
|
+
ndmi = ndmi_calc.updateMask(ndmi_calc.gte(threshold)).rename("ndmi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
677
682
|
return ndmi
|
|
678
683
|
|
|
679
684
|
@staticmethod
|
|
@@ -698,14 +703,14 @@ class LandsatCollection:
|
|
|
698
703
|
nbr_calc.updateMask(nbr_calc.gte(threshold))
|
|
699
704
|
.rename("nbr")
|
|
700
705
|
.copyProperties(image)
|
|
701
|
-
.set("threshold", threshold),
|
|
706
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
702
707
|
nbr_calc.updateMask(nbr_calc.gte(ng_threshold))
|
|
703
708
|
.rename("nbr")
|
|
704
709
|
.copyProperties(image)
|
|
705
|
-
.set("threshold", ng_threshold),
|
|
710
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
706
711
|
)
|
|
707
712
|
else:
|
|
708
|
-
nbr = nbr_calc.updateMask(nbr_calc.gte(threshold)).rename("nbr").copyProperties(image).set("threshold", threshold)
|
|
713
|
+
nbr = nbr_calc.updateMask(nbr_calc.gte(threshold)).rename("nbr").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
709
714
|
return nbr
|
|
710
715
|
|
|
711
716
|
@staticmethod
|
|
@@ -780,7 +785,7 @@ class LandsatCollection:
|
|
|
780
785
|
WaterBitMask = ee.Number(2).pow(7).int()
|
|
781
786
|
qa = image.select("QA_PIXEL")
|
|
782
787
|
water_extract = qa.bitwiseAnd(WaterBitMask).eq(0)
|
|
783
|
-
masked_image = image.updateMask(water_extract).copyProperties(image)
|
|
788
|
+
masked_image = image.updateMask(water_extract).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
784
789
|
return masked_image
|
|
785
790
|
|
|
786
791
|
@staticmethod
|
|
@@ -805,18 +810,19 @@ class LandsatCollection:
|
|
|
805
810
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
806
811
|
.rename("ndwi")
|
|
807
812
|
.copyProperties(image)
|
|
813
|
+
.set('system:time_start', image.get('system:time_start'))
|
|
808
814
|
)
|
|
809
815
|
if ng_threshold != None:
|
|
810
816
|
water = ee.Algorithms.If(
|
|
811
817
|
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
812
|
-
image.updateMask(ndwi_calc.lt(threshold)).set("threshold", threshold),
|
|
813
|
-
image.updateMask(ndwi_calc.lt(ng_threshold)).set(
|
|
814
|
-
"threshold", ng_threshold
|
|
818
|
+
image.updateMask(ndwi_calc.lt(threshold)).copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
819
|
+
image.updateMask(ndwi_calc.lt(ng_threshold)).copyProperties(image).set(
|
|
820
|
+
"threshold", ng_threshold, 'system:time_start', image.get('system:time_start')
|
|
815
821
|
),
|
|
816
822
|
)
|
|
817
823
|
else:
|
|
818
|
-
water = image.updateMask(ndwi_calc.lt(threshold)).set(
|
|
819
|
-
"threshold", threshold
|
|
824
|
+
water = image.updateMask(ndwi_calc.lt(threshold)).copyProperties(image).set(
|
|
825
|
+
"threshold", threshold, 'system:time_start', image.get('system:time_start')
|
|
820
826
|
)
|
|
821
827
|
return water
|
|
822
828
|
|
|
@@ -834,7 +840,7 @@ class LandsatCollection:
|
|
|
834
840
|
WaterBitMask = ee.Number(2).pow(7).int()
|
|
835
841
|
qa = image.select("QA_PIXEL")
|
|
836
842
|
water_extract = qa.bitwiseAnd(WaterBitMask).neq(0)
|
|
837
|
-
masked_image = image.updateMask(water_extract).copyProperties(image)
|
|
843
|
+
masked_image = image.updateMask(water_extract).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
838
844
|
return masked_image
|
|
839
845
|
|
|
840
846
|
@staticmethod
|
|
@@ -858,18 +864,19 @@ class LandsatCollection:
|
|
|
858
864
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
859
865
|
.rename("ndwi")
|
|
860
866
|
.copyProperties(image)
|
|
867
|
+
.set('system:time_start', image.get('system:time_start'))
|
|
861
868
|
)
|
|
862
869
|
if ng_threshold != None:
|
|
863
870
|
water = ee.Algorithms.If(
|
|
864
871
|
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
865
|
-
image.updateMask(ndwi_calc.gte(threshold)).set("threshold", threshold),
|
|
866
|
-
image.updateMask(ndwi_calc.gte(ng_threshold)).set(
|
|
867
|
-
"threshold", ng_threshold
|
|
872
|
+
image.updateMask(ndwi_calc.gte(threshold)).copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
873
|
+
image.updateMask(ndwi_calc.gte(ng_threshold)).copyProperties(image).set(
|
|
874
|
+
"threshold", ng_threshold, 'system:time_start', image.get('system:time_start')
|
|
868
875
|
),
|
|
869
876
|
)
|
|
870
877
|
else:
|
|
871
|
-
water = image.updateMask(ndwi_calc.gte(threshold)).set(
|
|
872
|
-
"threshold", threshold
|
|
878
|
+
water = image.updateMask(ndwi_calc.gte(threshold)).copyProperties(image).set(
|
|
879
|
+
"threshold", threshold, 'system:time_start', image.get('system:time_start')
|
|
873
880
|
)
|
|
874
881
|
return water
|
|
875
882
|
|
|
@@ -899,7 +906,7 @@ class LandsatCollection:
|
|
|
899
906
|
if add_band_to_original_image:
|
|
900
907
|
return image.addBands(band_to_mask_image.updateMask(mask).rename(band_to_mask), overwrite=True)
|
|
901
908
|
else:
|
|
902
|
-
return ee.Image(band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image))
|
|
909
|
+
return ee.Image(band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image).set('system:time_start', image.get('system:time_start')))
|
|
903
910
|
|
|
904
911
|
@staticmethod
|
|
905
912
|
def mask_via_singleband_image_fn(image_to_mask, image_for_mask, threshold, band_name_to_mask=None, band_name_for_mask=None, mask_above=True):
|
|
@@ -935,7 +942,7 @@ class LandsatCollection:
|
|
|
935
942
|
mask = band_for_mask_image.gt(threshold)
|
|
936
943
|
else:
|
|
937
944
|
mask = band_for_mask_image.lt(threshold)
|
|
938
|
-
return band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image_to_mask)
|
|
945
|
+
return band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image_to_mask).set('system:time_start', image_to_mask.get('system:time_start'))
|
|
939
946
|
|
|
940
947
|
@staticmethod
|
|
941
948
|
def halite_mask(image, threshold, ng_threshold=None):
|
|
@@ -959,11 +966,11 @@ class LandsatCollection:
|
|
|
959
966
|
if ng_threshold != None:
|
|
960
967
|
mask = ee.Algorithms.If(
|
|
961
968
|
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
962
|
-
image.updateMask(halite_index.lt(threshold)).copyProperties(image),
|
|
963
|
-
image.updateMask(halite_index.lt(ng_threshold)).copyProperties(image),
|
|
969
|
+
image.updateMask(halite_index.lt(threshold)).copyProperties(image).set('system:time_start', image.get('system:time_start')),
|
|
970
|
+
image.updateMask(halite_index.lt(ng_threshold)).copyProperties(image).set('system:time_start', image.get('system:time_start')),
|
|
964
971
|
)
|
|
965
972
|
else:
|
|
966
|
-
mask = image.updateMask(halite_index.lt(threshold)).copyProperties(image)
|
|
973
|
+
mask = image.updateMask(halite_index.lt(threshold)).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
967
974
|
return mask
|
|
968
975
|
|
|
969
976
|
@staticmethod
|
|
@@ -1000,11 +1007,13 @@ class LandsatCollection:
|
|
|
1000
1007
|
gypsum_index.updateMask(halite_index.lt(halite_threshold))
|
|
1001
1008
|
.updateMask(gypsum_index.lt(gypsum_threshold))
|
|
1002
1009
|
.rename("carbonate_muds")
|
|
1003
|
-
.copyProperties(image)
|
|
1010
|
+
.copyProperties(image)
|
|
1011
|
+
.set('system:time_start', image.get('system:time_start')),
|
|
1004
1012
|
gypsum_index.updateMask(halite_index.lt(halite_ng_threshold))
|
|
1005
1013
|
.updateMask(gypsum_index.lt(gypsum_ng_threshold))
|
|
1006
1014
|
.rename("carbonate_muds")
|
|
1007
|
-
.copyProperties(image)
|
|
1015
|
+
.copyProperties(image)
|
|
1016
|
+
.set('system:time_start', image.get('system:time_start')),
|
|
1008
1017
|
)
|
|
1009
1018
|
else:
|
|
1010
1019
|
mask = (
|
|
@@ -1012,6 +1021,7 @@ class LandsatCollection:
|
|
|
1012
1021
|
.updateMask(gypsum_index.lt(gypsum_threshold))
|
|
1013
1022
|
.rename("carbonate_muds")
|
|
1014
1023
|
.copyProperties(image)
|
|
1024
|
+
.set('system:time_start', image.get('system:time_start'))
|
|
1015
1025
|
)
|
|
1016
1026
|
return mask
|
|
1017
1027
|
|
|
@@ -1781,6 +1791,7 @@ class LandsatCollection:
|
|
|
1781
1791
|
"""
|
|
1782
1792
|
if self._monthly_mean is None:
|
|
1783
1793
|
collection = self.collection
|
|
1794
|
+
# Capture projection from the first image to restore it after reduction
|
|
1784
1795
|
target_proj = collection.first().projection()
|
|
1785
1796
|
# Get the start and end dates of the entire collection.
|
|
1786
1797
|
date_range = collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
@@ -2092,6 +2103,391 @@ class LandsatCollection:
|
|
|
2092
2103
|
pass
|
|
2093
2104
|
|
|
2094
2105
|
return self._monthly_min
|
|
2106
|
+
|
|
2107
|
+
def yearly_mean_collection(self, start_month=1, end_month=12):
|
|
2108
|
+
"""
|
|
2109
|
+
Creates a yearly mean composite from the collection, with optional monthly filtering.
|
|
2110
|
+
|
|
2111
|
+
This function computes the mean for each year within the collection's date range.
|
|
2112
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2113
|
+
to calculate the mean only using imagery from that specific season for each year.
|
|
2114
|
+
|
|
2115
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2116
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2117
|
+
|
|
2118
|
+
Args:
|
|
2119
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2120
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2121
|
+
|
|
2122
|
+
Returns:
|
|
2123
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly mean composites.
|
|
2124
|
+
"""
|
|
2125
|
+
if self._yearly_mean is None:
|
|
2126
|
+
|
|
2127
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2128
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2129
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2130
|
+
|
|
2131
|
+
start_year = start_date_full.get('year')
|
|
2132
|
+
end_year = end_date_full.get('year')
|
|
2133
|
+
|
|
2134
|
+
if start_month != 1 or end_month != 12:
|
|
2135
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2136
|
+
else:
|
|
2137
|
+
processing_collection = self.collection
|
|
2138
|
+
|
|
2139
|
+
# Capture projection from the first image to restore it after reduction
|
|
2140
|
+
target_proj = self.collection.first().projection()
|
|
2141
|
+
|
|
2142
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2143
|
+
|
|
2144
|
+
def create_yearly_composite(year):
|
|
2145
|
+
year = ee.Number(year)
|
|
2146
|
+
# Define the full calendar year range
|
|
2147
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2148
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2149
|
+
|
|
2150
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2151
|
+
|
|
2152
|
+
# Calculate stats
|
|
2153
|
+
image_count = yearly_subset.size()
|
|
2154
|
+
yearly_reduction = yearly_subset.mean()
|
|
2155
|
+
|
|
2156
|
+
# Define the timestamp for the composite.
|
|
2157
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2158
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2159
|
+
|
|
2160
|
+
return yearly_reduction.set({
|
|
2161
|
+
'system:time_start': composite_date.millis(),
|
|
2162
|
+
'year': year,
|
|
2163
|
+
'month': start_month,
|
|
2164
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2165
|
+
'image_count': image_count,
|
|
2166
|
+
'season_start': start_month,
|
|
2167
|
+
'season_end': end_month
|
|
2168
|
+
}).reproject(target_proj)
|
|
2169
|
+
|
|
2170
|
+
# Map the function over the years list
|
|
2171
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2172
|
+
|
|
2173
|
+
# Convert to Collection
|
|
2174
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2175
|
+
|
|
2176
|
+
# Filter out any composites that were created from zero images.
|
|
2177
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2178
|
+
|
|
2179
|
+
self._yearly_mean = LandsatCollection(collection=final_collection)
|
|
2180
|
+
else:
|
|
2181
|
+
pass
|
|
2182
|
+
return self._yearly_mean
|
|
2183
|
+
|
|
2184
|
+
def yearly_median_collection(self, start_month=1, end_month=12):
|
|
2185
|
+
"""
|
|
2186
|
+
Creates a yearly median composite from the collection, with optional monthly filtering.
|
|
2187
|
+
|
|
2188
|
+
This function computes the median for each year within the collection's date range.
|
|
2189
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2190
|
+
to calculate the median only using imagery from that specific season for each year.
|
|
2191
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2192
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2193
|
+
|
|
2194
|
+
Args:
|
|
2195
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2196
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2197
|
+
|
|
2198
|
+
Returns:
|
|
2199
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly median composites.
|
|
2200
|
+
"""
|
|
2201
|
+
if self._yearly_median is None:
|
|
2202
|
+
|
|
2203
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2204
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2205
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2206
|
+
|
|
2207
|
+
start_year = start_date_full.get('year')
|
|
2208
|
+
end_year = end_date_full.get('year')
|
|
2209
|
+
|
|
2210
|
+
if start_month != 1 or end_month != 12:
|
|
2211
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2212
|
+
else:
|
|
2213
|
+
processing_collection = self.collection
|
|
2214
|
+
|
|
2215
|
+
# Capture projection from the first image to restore it after reduction
|
|
2216
|
+
target_proj = self.collection.first().projection()
|
|
2217
|
+
|
|
2218
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2219
|
+
|
|
2220
|
+
def create_yearly_composite(year):
|
|
2221
|
+
year = ee.Number(year)
|
|
2222
|
+
# Define the full calendar year range
|
|
2223
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2224
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2225
|
+
|
|
2226
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2227
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2228
|
+
|
|
2229
|
+
# Calculate stats
|
|
2230
|
+
image_count = yearly_subset.size()
|
|
2231
|
+
yearly_reduction = yearly_subset.median()
|
|
2232
|
+
|
|
2233
|
+
# Define the timestamp for the composite.
|
|
2234
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2235
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2236
|
+
|
|
2237
|
+
return yearly_reduction.set({
|
|
2238
|
+
'system:time_start': composite_date.millis(),
|
|
2239
|
+
'year': year,
|
|
2240
|
+
'month': start_month,
|
|
2241
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2242
|
+
'image_count': image_count,
|
|
2243
|
+
'season_start': start_month,
|
|
2244
|
+
'season_end': end_month
|
|
2245
|
+
}).reproject(target_proj)
|
|
2246
|
+
|
|
2247
|
+
# Map the function over the years list
|
|
2248
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2249
|
+
|
|
2250
|
+
# Convert to Collection
|
|
2251
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2252
|
+
|
|
2253
|
+
# Filter out any composites that were created from zero images.
|
|
2254
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2255
|
+
|
|
2256
|
+
self._yearly_median = LandsatCollection(collection=final_collection)
|
|
2257
|
+
else:
|
|
2258
|
+
pass
|
|
2259
|
+
return self._yearly_median
|
|
2260
|
+
|
|
2261
|
+
def yearly_max_collection(self, start_month=1, end_month=12):
|
|
2262
|
+
"""
|
|
2263
|
+
Creates a yearly max composite from the collection, with optional monthly filtering.
|
|
2264
|
+
|
|
2265
|
+
This function computes the max for each year within the collection's date range.
|
|
2266
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2267
|
+
to calculate the max only using imagery from that specific season for each year.
|
|
2268
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2269
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2270
|
+
|
|
2271
|
+
Args:
|
|
2272
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2273
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2274
|
+
|
|
2275
|
+
Returns:
|
|
2276
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly max composites.
|
|
2277
|
+
"""
|
|
2278
|
+
if self._yearly_max is None:
|
|
2279
|
+
|
|
2280
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2281
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2282
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2283
|
+
|
|
2284
|
+
start_year = start_date_full.get('year')
|
|
2285
|
+
end_year = end_date_full.get('year')
|
|
2286
|
+
|
|
2287
|
+
if start_month != 1 or end_month != 12:
|
|
2288
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2289
|
+
else:
|
|
2290
|
+
processing_collection = self.collection
|
|
2291
|
+
|
|
2292
|
+
# Capture projection from the first image to restore it after reduction
|
|
2293
|
+
target_proj = self.collection.first().projection()
|
|
2294
|
+
|
|
2295
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2296
|
+
|
|
2297
|
+
def create_yearly_composite(year):
|
|
2298
|
+
year = ee.Number(year)
|
|
2299
|
+
# Define the full calendar year range
|
|
2300
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2301
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2302
|
+
|
|
2303
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2304
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2305
|
+
|
|
2306
|
+
# Calculate stats
|
|
2307
|
+
image_count = yearly_subset.size()
|
|
2308
|
+
yearly_reduction = yearly_subset.max()
|
|
2309
|
+
|
|
2310
|
+
# Define the timestamp for the composite.
|
|
2311
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2312
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2313
|
+
|
|
2314
|
+
return yearly_reduction.set({
|
|
2315
|
+
'system:time_start': composite_date.millis(),
|
|
2316
|
+
'year': year,
|
|
2317
|
+
'month': start_month,
|
|
2318
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2319
|
+
'image_count': image_count,
|
|
2320
|
+
'season_start': start_month,
|
|
2321
|
+
'season_end': end_month
|
|
2322
|
+
}).reproject(target_proj)
|
|
2323
|
+
|
|
2324
|
+
# Map the function over the years list
|
|
2325
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2326
|
+
|
|
2327
|
+
# Convert to Collection
|
|
2328
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2329
|
+
|
|
2330
|
+
# Filter out any composites that were created from zero images.
|
|
2331
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2332
|
+
|
|
2333
|
+
self._yearly_max = LandsatCollection(collection=final_collection)
|
|
2334
|
+
else:
|
|
2335
|
+
pass
|
|
2336
|
+
return self._yearly_max
|
|
2337
|
+
|
|
2338
|
+
def yearly_min_collection(self, start_month=1, end_month=12):
|
|
2339
|
+
"""
|
|
2340
|
+
Creates a yearly min composite from the collection, with optional monthly filtering.
|
|
2341
|
+
|
|
2342
|
+
This function computes the min for each year within the collection's date range.
|
|
2343
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2344
|
+
to calculate the min only using imagery from that specific season for each year.
|
|
2345
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2346
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2347
|
+
|
|
2348
|
+
Args:
|
|
2349
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2350
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2351
|
+
|
|
2352
|
+
Returns:
|
|
2353
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly min composites.
|
|
2354
|
+
"""
|
|
2355
|
+
if self._yearly_min is None:
|
|
2356
|
+
|
|
2357
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2358
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2359
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2360
|
+
|
|
2361
|
+
start_year = start_date_full.get('year')
|
|
2362
|
+
end_year = end_date_full.get('year')
|
|
2363
|
+
|
|
2364
|
+
if start_month != 1 or end_month != 12:
|
|
2365
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2366
|
+
else:
|
|
2367
|
+
processing_collection = self.collection
|
|
2368
|
+
|
|
2369
|
+
# Capture projection from the first image to restore it after reduction
|
|
2370
|
+
target_proj = self.collection.first().projection()
|
|
2371
|
+
|
|
2372
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2373
|
+
|
|
2374
|
+
def create_yearly_composite(year):
|
|
2375
|
+
year = ee.Number(year)
|
|
2376
|
+
# Define the full calendar year range
|
|
2377
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2378
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2379
|
+
|
|
2380
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2381
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2382
|
+
|
|
2383
|
+
# Calculate stats
|
|
2384
|
+
image_count = yearly_subset.size()
|
|
2385
|
+
yearly_reduction = yearly_subset.min()
|
|
2386
|
+
|
|
2387
|
+
# Define the timestamp for the composite.
|
|
2388
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2389
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2390
|
+
|
|
2391
|
+
return yearly_reduction.set({
|
|
2392
|
+
'system:time_start': composite_date.millis(),
|
|
2393
|
+
'year': year,
|
|
2394
|
+
'month': start_month,
|
|
2395
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2396
|
+
'image_count': image_count,
|
|
2397
|
+
'season_start': start_month,
|
|
2398
|
+
'season_end': end_month
|
|
2399
|
+
}).reproject(target_proj)
|
|
2400
|
+
|
|
2401
|
+
# Map the function over the years list
|
|
2402
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2403
|
+
|
|
2404
|
+
# Convert to Collection
|
|
2405
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2406
|
+
|
|
2407
|
+
# Filter out any composites that were created from zero images.
|
|
2408
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2409
|
+
|
|
2410
|
+
self._yearly_min = LandsatCollection(collection=final_collection)
|
|
2411
|
+
else:
|
|
2412
|
+
pass
|
|
2413
|
+
return self._yearly_min
|
|
2414
|
+
|
|
2415
|
+
def yearly_sum_collection(self, start_month=1, end_month=12):
|
|
2416
|
+
"""
|
|
2417
|
+
Creates a yearly sum composite from the collection, with optional monthly filtering.
|
|
2418
|
+
|
|
2419
|
+
This function computes the sum for each year within the collection's date range.
|
|
2420
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2421
|
+
to calculate the sum only using imagery from that specific season for each year.
|
|
2422
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2423
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2424
|
+
|
|
2425
|
+
Args:
|
|
2426
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2427
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2428
|
+
|
|
2429
|
+
Returns:
|
|
2430
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly sum composites.
|
|
2431
|
+
"""
|
|
2432
|
+
if self._yearly_sum is None:
|
|
2433
|
+
|
|
2434
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2435
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2436
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2437
|
+
|
|
2438
|
+
start_year = start_date_full.get('year')
|
|
2439
|
+
end_year = end_date_full.get('year')
|
|
2440
|
+
|
|
2441
|
+
if start_month != 1 or end_month != 12:
|
|
2442
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2443
|
+
else:
|
|
2444
|
+
processing_collection = self.collection
|
|
2445
|
+
|
|
2446
|
+
# Capture projection from the first image to restore it after reduction
|
|
2447
|
+
target_proj = self.collection.first().projection()
|
|
2448
|
+
|
|
2449
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2450
|
+
|
|
2451
|
+
def create_yearly_composite(year):
|
|
2452
|
+
year = ee.Number(year)
|
|
2453
|
+
# Define the full calendar year range
|
|
2454
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2455
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2456
|
+
|
|
2457
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2458
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2459
|
+
|
|
2460
|
+
# Calculate stats
|
|
2461
|
+
image_count = yearly_subset.size()
|
|
2462
|
+
yearly_reduction = yearly_subset.sum()
|
|
2463
|
+
|
|
2464
|
+
# Define the timestamp for the composite.
|
|
2465
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2466
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2467
|
+
|
|
2468
|
+
return yearly_reduction.set({
|
|
2469
|
+
'system:time_start': composite_date.millis(),
|
|
2470
|
+
'year': year,
|
|
2471
|
+
'month': start_month,
|
|
2472
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2473
|
+
'image_count': image_count,
|
|
2474
|
+
'season_start': start_month,
|
|
2475
|
+
'season_end': end_month
|
|
2476
|
+
}).reproject(target_proj)
|
|
2477
|
+
|
|
2478
|
+
# Map the function over the years list
|
|
2479
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2480
|
+
|
|
2481
|
+
# Convert to Collection
|
|
2482
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2483
|
+
|
|
2484
|
+
# Filter out any composites that were created from zero images.
|
|
2485
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2486
|
+
|
|
2487
|
+
self._yearly_sum = LandsatCollection(collection=final_collection)
|
|
2488
|
+
else:
|
|
2489
|
+
pass
|
|
2490
|
+
return self._yearly_sum
|
|
2095
2491
|
|
|
2096
2492
|
@property
|
|
2097
2493
|
def ndwi(self):
|
|
@@ -2963,20 +3359,24 @@ class LandsatCollection:
|
|
|
2963
3359
|
if classify_above_threshold:
|
|
2964
3360
|
if mask_zeros is True:
|
|
2965
3361
|
col = self.collection.map(
|
|
2966
|
-
lambda image: image.select(band_name).gte(threshold).rename(band_name).selfMask()
|
|
3362
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name).selfMask()
|
|
3363
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2967
3364
|
)
|
|
2968
3365
|
else:
|
|
2969
3366
|
col = self.collection.map(
|
|
2970
|
-
lambda image: image.select(band_name).gte(threshold).rename(band_name)
|
|
3367
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name)
|
|
3368
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2971
3369
|
)
|
|
2972
3370
|
else:
|
|
2973
3371
|
if mask_zeros is True:
|
|
2974
3372
|
col = self.collection.map(
|
|
2975
|
-
lambda image: image.select(band_name).lte(threshold).rename(band_name).selfMask()
|
|
3373
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name).selfMask()
|
|
3374
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2976
3375
|
)
|
|
2977
3376
|
else:
|
|
2978
3377
|
col = self.collection.map(
|
|
2979
|
-
lambda image: image.select(band_name).lte(threshold).rename(band_name)
|
|
3378
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name)
|
|
3379
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2980
3380
|
)
|
|
2981
3381
|
|
|
2982
3382
|
return LandsatCollection(collection=col)
|
|
@@ -3017,6 +3417,233 @@ class LandsatCollection:
|
|
|
3017
3417
|
col = self.collection.map(lambda image: LandsatCollection.anomaly_fn(image, geometry=geometry, band_name=band_name, anomaly_band_name=anomaly_band_name, replace=replace, scale=scale))
|
|
3018
3418
|
return LandsatCollection(collection=col)
|
|
3019
3419
|
|
|
3420
|
+
def mann_kendall_trend(self, target_band=None, join_method='system:time_start', geometry=None):
|
|
3421
|
+
"""
|
|
3422
|
+
Calculates the Mann-Kendall S-value, Variance, Z-Score, and Confidence Level for each pixel in the image collection, in addition to calculating
|
|
3423
|
+
the Sen's slope for each pixel in the image collection. The output is an image with the following bands: 's_statistic', 'variance', 'z_score', 'confidence', and 'slope'.
|
|
3424
|
+
|
|
3425
|
+
This function can be used to identify trends in the image collection over time, such as increasing or decreasing values in the target band, and can be used to assess the significance of these trends.
|
|
3426
|
+
Note that this function is computationally intensive and may take a long time to run for large image collections or high-resolution images.
|
|
3427
|
+
|
|
3428
|
+
The 's_statistic' band represents the Mann-Kendall S-value, which is a measure of the strength and direction of the trend.
|
|
3429
|
+
The 'variance' band represents the variance of the S-value, which is a measure of the variability of the S-value.
|
|
3430
|
+
The 'z_score' band represents the Z-Score, which is a measure of the significance of the trend.
|
|
3431
|
+
The 'confidence' band represents the confidence level of the trend based on the z_score, which is a probabilistic measure of the confidence in the trend (percentage).
|
|
3432
|
+
The 'slope' band represents the Sen's slope, which is a measure of the rate of change in the target band over time. This value can be small as multispectral indices commonly range from -1 to 1, so a slope may have values of <0.2 for most cases.
|
|
3433
|
+
|
|
3434
|
+
Be sure to select the correct band for the `target_band` parameter, as this will be used to calculate the trend statistics.
|
|
3435
|
+
You may optionally provide an ee.Geometry object for the `geometry` parameter to limit the area over which the trend statistics are calculated.
|
|
3436
|
+
The `geometry` parameter is optional and defaults to None, which means that the trend statistics will be calculated over the entire footprint of the image collection.
|
|
3437
|
+
|
|
3438
|
+
Args:
|
|
3439
|
+
image_collection (LandsatCollection or ee.ImageCollection): The input image collection for which the Mann-Kendall and Sen's slope trend statistics will be calculated.
|
|
3440
|
+
target_band (str): The band name to be used for the output anomaly image. e.g. 'ndvi'
|
|
3441
|
+
join_method (str, optional): The method used to join images in the collection. Options are 'system:time_start' or 'Date_Filter'. Default is 'system:time_start'.
|
|
3442
|
+
geometry (ee.Geometry, optional): An ee.Geometry object to limit the area over which the trend statistics are calculated and mask the output image. Default is None.
|
|
3443
|
+
|
|
3444
|
+
Returns:
|
|
3445
|
+
ee.Image: An image with the following bands: 's_statistic', 'variance', 'z_score', 'confidence', and 'slope'.
|
|
3446
|
+
"""
|
|
3447
|
+
########## PART 1 - S-VALUE CALCULATION ##########
|
|
3448
|
+
##### https://vsp.pnnl.gov/help/vsample/design_trend_mann_kendall.htm #####
|
|
3449
|
+
image_collection = self
|
|
3450
|
+
if isinstance(image_collection, LandsatCollection):
|
|
3451
|
+
image_collection = image_collection.collection
|
|
3452
|
+
elif isinstance(image_collection, ee.ImageCollection):
|
|
3453
|
+
pass
|
|
3454
|
+
else:
|
|
3455
|
+
raise ValueError(f'The chosen `image_collection`: {image_collection} is not a valid LandsatCollection or ee.ImageCollection object.')
|
|
3456
|
+
|
|
3457
|
+
if target_band is None:
|
|
3458
|
+
raise ValueError('The `target_band` parameter must be specified.')
|
|
3459
|
+
if not isinstance(target_band, str):
|
|
3460
|
+
raise ValueError(f'The chosen `target_band`: {target_band} is not a valid string.')
|
|
3461
|
+
|
|
3462
|
+
if geometry is not None and not isinstance(geometry, ee.Geometry):
|
|
3463
|
+
raise ValueError(f'The chosen `geometry`: {geometry} is not a valid ee.Geometry object.')
|
|
3464
|
+
# define the join, which will join all images newer than the current image
|
|
3465
|
+
# use system:time_start if the image does not have a Date_Filter property
|
|
3466
|
+
if join_method == 'system:time_start':
|
|
3467
|
+
# get all images where the leftField value is less than (before) the rightField value
|
|
3468
|
+
time_filter = ee.Filter.lessThan(leftField='system:time_start',
|
|
3469
|
+
rightField='system:time_start')
|
|
3470
|
+
elif join_method == 'Date_Filter':
|
|
3471
|
+
# get all images where the leftField value is less than (before) the rightField value
|
|
3472
|
+
time_filter = ee.Filter.lessThan(leftField='Date_Filter',
|
|
3473
|
+
rightField='Date_Filter')
|
|
3474
|
+
else:
|
|
3475
|
+
raise ValueError(f'The chosen `join_method`: {join_method} does not match the options of "system:time_start" or "Date_Filter".')
|
|
3476
|
+
|
|
3477
|
+
# for any matches during a join, set image as a property key called 'future_image'
|
|
3478
|
+
join = ee.Join.saveAll(matchesKey='future_image')
|
|
3479
|
+
|
|
3480
|
+
# apply the join on the input collection
|
|
3481
|
+
# joining all images newer than the current image with the current image
|
|
3482
|
+
joined_collection = ee.ImageCollection(join.apply(primary=image_collection,
|
|
3483
|
+
secondary=image_collection, condition=time_filter))
|
|
3484
|
+
|
|
3485
|
+
# defining a collection to calculate the partial S value for each match in the join
|
|
3486
|
+
# e.g. t4-t1, t3-t1, t2-1 if there are 4 images
|
|
3487
|
+
def calculate_partial_s(current_image):
|
|
3488
|
+
# select the target band for arithmetic
|
|
3489
|
+
current_val = current_image.select(target_band)
|
|
3490
|
+
# get the joined images from the current image properties and cast the joined images as a list
|
|
3491
|
+
future_image_list = ee.List(current_image.get('future_image'))
|
|
3492
|
+
# convert the joined list to an image collection
|
|
3493
|
+
future_image_collection = ee.ImageCollection(future_image_list)
|
|
3494
|
+
|
|
3495
|
+
# define a function that will calculate the difference between the joined images and the current image,
|
|
3496
|
+
# then calculate the partial S sign based on the value of the difference calculation
|
|
3497
|
+
def get_sign(future_image):
|
|
3498
|
+
# select the target band for arithmetic from the future image
|
|
3499
|
+
future_val = future_image.select(target_band)
|
|
3500
|
+
# calculate the difference, i.e. t2-t1
|
|
3501
|
+
difference = future_val.subtract(current_val)
|
|
3502
|
+
# determine the sign of the difference value (1 if diff > 0, 0 if 0, and -1 if diff < 0)
|
|
3503
|
+
# use .unmask(0) to set any masked pixels as 0 to avoid
|
|
3504
|
+
|
|
3505
|
+
sign = difference.signum().unmask(0)
|
|
3506
|
+
|
|
3507
|
+
return sign
|
|
3508
|
+
|
|
3509
|
+
# map the get_sign() function along the future image col
|
|
3510
|
+
# then sum the values for each pixel to get the partial S value
|
|
3511
|
+
return future_image_collection.map(get_sign).sum()
|
|
3512
|
+
|
|
3513
|
+
# calculate the partial s value for each image in the joined/input image collection
|
|
3514
|
+
partial_s_col = joined_collection.map(calculate_partial_s)
|
|
3515
|
+
|
|
3516
|
+
# convert the image collection to an image of s_statistic values per pixel
|
|
3517
|
+
# where the s_statistic is the sum of partial s values
|
|
3518
|
+
# renaming the band as 's_statistic' for later usage
|
|
3519
|
+
final_s_image = partial_s_col.sum().rename('s_statistic')
|
|
3520
|
+
|
|
3521
|
+
|
|
3522
|
+
########## PART 2 - VARIANCE and Z-SCORE ##########
|
|
3523
|
+
# to calculate variance we need to know how many pixels were involved in the partial_s calculations per pixel
|
|
3524
|
+
# we do this by using count() and turn the value to a float for later arithmetic
|
|
3525
|
+
n = image_collection.select(target_band).count().toFloat()
|
|
3526
|
+
|
|
3527
|
+
##### VARIANCE CALCULATION #####
|
|
3528
|
+
# as we are using floating point values with high precision, it is HIGHLY
|
|
3529
|
+
# unlikely that there will be multiple pixel values with the same value.
|
|
3530
|
+
# Thus, we opt to use the simplified variance calculation approach as the
|
|
3531
|
+
# impacts to the output value are negligible and the processing benefits are HUGE
|
|
3532
|
+
# variance = (n * (n - 1) * (2n + 5)) / 18
|
|
3533
|
+
var_s = n.multiply(n.subtract(1))\
|
|
3534
|
+
.multiply(n.multiply(2).add(5))\
|
|
3535
|
+
.divide(18).rename('variance')
|
|
3536
|
+
|
|
3537
|
+
z_score = ee.Image().expression(
|
|
3538
|
+
"""
|
|
3539
|
+
(s > 0) ? (s - 1) / sqrt(var) :
|
|
3540
|
+
(s < 0) ? (s + 1) / sqrt(var) :
|
|
3541
|
+
0
|
|
3542
|
+
""",
|
|
3543
|
+
{'s': final_s_image, 'var': var_s}
|
|
3544
|
+
).rename('z_score')
|
|
3545
|
+
|
|
3546
|
+
confidence = z_score.abs().divide(ee.Number(2).sqrt()).erf().rename('confidence')
|
|
3547
|
+
|
|
3548
|
+
stat_bands = ee.Image([var_s, z_score, confidence])
|
|
3549
|
+
|
|
3550
|
+
mk_stats_image = final_s_image.addBands(stat_bands)
|
|
3551
|
+
|
|
3552
|
+
########## PART 3 - Sen's Slope ##########
|
|
3553
|
+
def add_year_band(image):
|
|
3554
|
+
if join_method == 'Date_Filter':
|
|
3555
|
+
# Get the string 'YYYY-MM-DD'
|
|
3556
|
+
date_string = image.get('Date_Filter')
|
|
3557
|
+
# Parse it into an ee.Date object (handles the conversion to time math)
|
|
3558
|
+
date = ee.Date.parse('YYYY-MM-dd', date_string)
|
|
3559
|
+
else:
|
|
3560
|
+
# Standard way: assumes system:time_start exists
|
|
3561
|
+
date = image.date()
|
|
3562
|
+
years = date.difference(ee.Date('1970-01-01'), 'year')
|
|
3563
|
+
return image.addBands(ee.Image(years).float().rename('year'))
|
|
3564
|
+
|
|
3565
|
+
slope_input = image_collection.map(add_year_band).select(['year', target_band])
|
|
3566
|
+
|
|
3567
|
+
sens_slope = slope_input.reduce(ee.Reducer.sensSlope())
|
|
3568
|
+
|
|
3569
|
+
slope_band = sens_slope.select('slope')
|
|
3570
|
+
|
|
3571
|
+
# add a mask to the final image to remove pixels with less than min_observations
|
|
3572
|
+
# mainly an effort to mask pixels outside of the boundary of the input image collection
|
|
3573
|
+
min_observations = 1
|
|
3574
|
+
valid_mask = n.gte(min_observations)
|
|
3575
|
+
|
|
3576
|
+
final_image = mk_stats_image.addBands(slope_band).updateMask(valid_mask)
|
|
3577
|
+
|
|
3578
|
+
if geometry is not None:
|
|
3579
|
+
mask = ee.Image(1).clip(geometry)
|
|
3580
|
+
final_image = final_image.updateMask(mask)
|
|
3581
|
+
|
|
3582
|
+
return final_image
|
|
3583
|
+
|
|
3584
|
+
def sens_slope_trend(self, target_band=None, join_method='system:time_start', geometry=None):
|
|
3585
|
+
"""
|
|
3586
|
+
Calculates Sen's Slope (trend magnitude) for the collection.
|
|
3587
|
+
This is a lighter-weight alternative to the full `mann_kendall_trend` function if only
|
|
3588
|
+
the direction and magnitude of the trend are needed.
|
|
3589
|
+
|
|
3590
|
+
Be sure to select the correct band for the `target_band` parameter, as this will be used to calculate the trend statistics.
|
|
3591
|
+
You may optionally provide an ee.Geometry object for the `geometry` parameter to limit the area over which the trend statistics are calculated.
|
|
3592
|
+
The `geometry` parameter is optional and defaults to None, which means that the trend statistics will be calculated over the entire footprint of the image collection.
|
|
3593
|
+
|
|
3594
|
+
Args:
|
|
3595
|
+
target_band (str): The name of the band to analyze. Defaults to 'ndvi'.
|
|
3596
|
+
join_method (str): Property to use for time sorting ('system:time_start' or 'Date_Filter').
|
|
3597
|
+
geometry (ee.Geometry, optional): Geometry to mask the final output.
|
|
3598
|
+
|
|
3599
|
+
Returns:
|
|
3600
|
+
ee.Image: An image containing the 'slope' band.
|
|
3601
|
+
"""
|
|
3602
|
+
image_collection = self
|
|
3603
|
+
if isinstance(image_collection, LandsatCollection):
|
|
3604
|
+
image_collection = image_collection.collection
|
|
3605
|
+
elif isinstance(image_collection, ee.ImageCollection):
|
|
3606
|
+
pass
|
|
3607
|
+
else:
|
|
3608
|
+
raise ValueError(f'The chosen `image_collection`: {image_collection} is not a valid LandsatCollection or ee.ImageCollection object.')
|
|
3609
|
+
|
|
3610
|
+
if target_band is None:
|
|
3611
|
+
raise ValueError('The `target_band` parameter must be specified.')
|
|
3612
|
+
if not isinstance(target_band, str):
|
|
3613
|
+
raise ValueError(f'The chosen `target_band`: {target_band} is not a valid string.')
|
|
3614
|
+
|
|
3615
|
+
if geometry is not None and not isinstance(geometry, ee.Geometry):
|
|
3616
|
+
raise ValueError(f'The chosen `geometry`: {geometry} is not a valid ee.Geometry object.')
|
|
3617
|
+
|
|
3618
|
+
# Add Year Band (Time X-Axis)
|
|
3619
|
+
def add_year_band(image):
|
|
3620
|
+
# Handle user-defined date strings vs system time
|
|
3621
|
+
if join_method == 'Date_Filter':
|
|
3622
|
+
date_string = image.get('Date_Filter')
|
|
3623
|
+
date = ee.Date.parse('YYYY-MM-dd', date_string)
|
|
3624
|
+
else:
|
|
3625
|
+
date = image.date()
|
|
3626
|
+
|
|
3627
|
+
# Convert to fractional years relative to epoch
|
|
3628
|
+
years = date.difference(ee.Date('1970-01-01'), 'year')
|
|
3629
|
+
return image.addBands(ee.Image(years).float().rename('year'))
|
|
3630
|
+
|
|
3631
|
+
# Prepare Collection: Select ONLY [Year, Target]
|
|
3632
|
+
# sensSlope expects Band 0 = Independent (X), Band 1 = Dependent (Y)
|
|
3633
|
+
slope_input = self.collection.map(add_year_band).select(['year', target_band])
|
|
3634
|
+
|
|
3635
|
+
# Run the Native Reducer
|
|
3636
|
+
sens_result = slope_input.reduce(ee.Reducer.sensSlope())
|
|
3637
|
+
|
|
3638
|
+
# Extract and Mask
|
|
3639
|
+
slope_band = sens_result.select('slope')
|
|
3640
|
+
|
|
3641
|
+
if geometry is not None:
|
|
3642
|
+
mask = ee.Image(1).clip(geometry)
|
|
3643
|
+
slope_band = slope_band.updateMask(mask)
|
|
3644
|
+
|
|
3645
|
+
return slope_band
|
|
3646
|
+
|
|
3020
3647
|
def mask_via_band(self, band_to_mask, band_for_mask, threshold=-1, mask_above=True, add_band_to_original_image=False):
|
|
3021
3648
|
"""
|
|
3022
3649
|
Masks select pixels of a selected band from an image based on another specified band and threshold (optional).
|
|
@@ -3099,7 +3726,8 @@ class LandsatCollection:
|
|
|
3099
3726
|
)
|
|
3100
3727
|
|
|
3101
3728
|
# guarantee single band + keep properties
|
|
3102
|
-
out = ee.Image(out).select([band_name_to_mask]).copyProperties(prim, prim.propertyNames())
|
|
3729
|
+
out = ee.Image(out).select([band_name_to_mask]).copyProperties(prim, prim.propertyNames())\
|
|
3730
|
+
.set('system:time_start', prim.get('system:time_start'))
|
|
3103
3731
|
out = out.set('Date_Filter', prim.get('Date_Filter'))
|
|
3104
3732
|
return ee.Image(out) # <-- return as Image
|
|
3105
3733
|
|