RadGEEToolbox 1.7.2__py3-none-any.whl → 1.7.4__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/CollectionStitch.py +16 -3
- RadGEEToolbox/Export.py +249 -0
- RadGEEToolbox/GenericCollection.py +763 -42
- RadGEEToolbox/LandsatCollection.py +938 -111
- RadGEEToolbox/Sentinel1Collection.py +801 -39
- RadGEEToolbox/Sentinel2Collection.py +869 -75
- RadGEEToolbox/__init__.py +6 -4
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.4.dist-info}/METADATA +11 -7
- radgeetoolbox-1.7.4.dist-info/RECORD +14 -0
- radgeetoolbox-1.7.2.dist-info/RECORD +0 -13
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.4.dist-info}/WHEEL +0 -0
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.4.dist-info}/licenses/LICENSE.txt +0 -0
- {radgeetoolbox-1.7.2.dist-info → radgeetoolbox-1.7.4.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ee
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import numpy as np
|
|
4
|
+
import warnings
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
# ---- Reflectance scaling for Landsat Collection 2 SR ----
|
|
@@ -159,6 +160,11 @@ class LandsatCollection:
|
|
|
159
160
|
self._monthly_max = None
|
|
160
161
|
self._monthly_min = None
|
|
161
162
|
self._monthly_sum = None
|
|
163
|
+
self._yearly_median = None
|
|
164
|
+
self._yearly_mean = None
|
|
165
|
+
self._yearly_max = None
|
|
166
|
+
self._yearly_min = None
|
|
167
|
+
self._yearly_sum = None
|
|
162
168
|
self._mean = None
|
|
163
169
|
self._max = None
|
|
164
170
|
self._min = None
|
|
@@ -181,6 +187,14 @@ class LandsatCollection:
|
|
|
181
187
|
self._PixelAreaSumCollection = None
|
|
182
188
|
self._Reflectance = None
|
|
183
189
|
|
|
190
|
+
def __call__(self):
|
|
191
|
+
"""
|
|
192
|
+
Allows the object to be called as a function, returning itself.
|
|
193
|
+
This enables property-like methods to be accessed with or without parentheses
|
|
194
|
+
(e.g., .mosaicByDate or .mosaicByDate()).
|
|
195
|
+
"""
|
|
196
|
+
return self
|
|
197
|
+
|
|
184
198
|
@staticmethod
|
|
185
199
|
def image_dater(image):
|
|
186
200
|
"""
|
|
@@ -239,17 +253,17 @@ class LandsatCollection:
|
|
|
239
253
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
240
254
|
.rename("ndwi")
|
|
241
255
|
.copyProperties(image)
|
|
242
|
-
.set("threshold", threshold),
|
|
256
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
243
257
|
ndwi_calc.updateMask(ndwi_calc.gte(ng_threshold))
|
|
244
258
|
.rename("ndwi")
|
|
245
259
|
.copyProperties(image)
|
|
246
|
-
.set("threshold", ng_threshold),
|
|
260
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
247
261
|
)
|
|
248
262
|
else:
|
|
249
263
|
water = (
|
|
250
264
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
251
265
|
.rename("ndwi")
|
|
252
|
-
.copyProperties(image)
|
|
266
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
253
267
|
)
|
|
254
268
|
return water
|
|
255
269
|
|
|
@@ -282,17 +296,17 @@ class LandsatCollection:
|
|
|
282
296
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
283
297
|
.rename("mndwi")
|
|
284
298
|
.copyProperties(image)
|
|
285
|
-
.set("threshold", threshold),
|
|
299
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
286
300
|
mndwi_calc.updateMask(mndwi_calc.gte(ng_threshold))
|
|
287
301
|
.rename("mndwi")
|
|
288
302
|
.copyProperties(image)
|
|
289
|
-
.set("threshold", ng_threshold),
|
|
303
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
290
304
|
)
|
|
291
305
|
else:
|
|
292
306
|
water = (
|
|
293
307
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
294
308
|
.rename("mndwi")
|
|
295
|
-
.copyProperties(image)
|
|
309
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
296
310
|
)
|
|
297
311
|
return water
|
|
298
312
|
|
|
@@ -320,17 +334,17 @@ class LandsatCollection:
|
|
|
320
334
|
ndvi_calc.updateMask(ndvi_calc.gte(threshold))
|
|
321
335
|
.rename("ndvi")
|
|
322
336
|
.copyProperties(image)
|
|
323
|
-
.set("threshold", threshold),
|
|
337
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
324
338
|
ndvi_calc.updateMask(ndvi_calc.gte(ng_threshold))
|
|
325
339
|
.rename("ndvi")
|
|
326
340
|
.copyProperties(image)
|
|
327
|
-
.set("threshold", ng_threshold),
|
|
341
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
328
342
|
)
|
|
329
343
|
else:
|
|
330
344
|
vegetation = (
|
|
331
345
|
ndvi_calc.updateMask(ndvi_calc.gte(threshold))
|
|
332
346
|
.rename("ndvi")
|
|
333
|
-
.copyProperties(image)
|
|
347
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
334
348
|
)
|
|
335
349
|
return vegetation
|
|
336
350
|
|
|
@@ -356,17 +370,17 @@ class LandsatCollection:
|
|
|
356
370
|
halite_index.updateMask(halite_index.gte(threshold))
|
|
357
371
|
.rename("halite")
|
|
358
372
|
.copyProperties(image)
|
|
359
|
-
.set("threshold", threshold),
|
|
373
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
360
374
|
halite_index.updateMask(halite_index.gte(ng_threshold))
|
|
361
375
|
.rename("halite")
|
|
362
376
|
.copyProperties(image)
|
|
363
|
-
.set("threshold", ng_threshold),
|
|
377
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
364
378
|
)
|
|
365
379
|
else:
|
|
366
380
|
halite = (
|
|
367
381
|
halite_index.updateMask(halite_index.gte(threshold))
|
|
368
382
|
.rename("halite")
|
|
369
|
-
.copyProperties(image)
|
|
383
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
370
384
|
)
|
|
371
385
|
return halite
|
|
372
386
|
|
|
@@ -392,17 +406,17 @@ class LandsatCollection:
|
|
|
392
406
|
gypsum_index.updateMask(gypsum_index.gte(threshold))
|
|
393
407
|
.rename("gypsum")
|
|
394
408
|
.copyProperties(image)
|
|
395
|
-
.set("threshold", threshold),
|
|
409
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
396
410
|
gypsum_index.updateMask(gypsum_index.gte(ng_threshold))
|
|
397
411
|
.rename("gypsum")
|
|
398
412
|
.copyProperties(image)
|
|
399
|
-
.set("threshold", ng_threshold),
|
|
413
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
400
414
|
)
|
|
401
415
|
else:
|
|
402
416
|
gypsum = (
|
|
403
417
|
gypsum_index.updateMask(gypsum_index.gte(threshold))
|
|
404
418
|
.rename("gypsum")
|
|
405
|
-
.copyProperties(image)
|
|
419
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
406
420
|
)
|
|
407
421
|
return gypsum
|
|
408
422
|
|
|
@@ -427,11 +441,11 @@ class LandsatCollection:
|
|
|
427
441
|
NDTI.updateMask(NDTI.gte(threshold))
|
|
428
442
|
.rename("ndti")
|
|
429
443
|
.copyProperties(image)
|
|
430
|
-
.set("threshold", threshold),
|
|
444
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
431
445
|
NDTI.updateMask(NDTI.gte(ng_threshold))
|
|
432
446
|
.rename("ndti")
|
|
433
447
|
.copyProperties(image)
|
|
434
|
-
.set("threshold", ng_threshold),
|
|
448
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
435
449
|
)
|
|
436
450
|
else:
|
|
437
451
|
turbidity = (
|
|
@@ -471,17 +485,17 @@ class LandsatCollection:
|
|
|
471
485
|
KIVU.updateMask(KIVU.gte(threshold))
|
|
472
486
|
.rename("kivu")
|
|
473
487
|
.copyProperties(image)
|
|
474
|
-
.set("threshold", threshold),
|
|
488
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
475
489
|
KIVU.updateMask(KIVU.gte(ng_threshold))
|
|
476
490
|
.rename("kivu")
|
|
477
491
|
.copyProperties(image)
|
|
478
|
-
.set("threshold", ng_threshold),
|
|
492
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
479
493
|
)
|
|
480
494
|
else:
|
|
481
495
|
chlorophyll = (
|
|
482
496
|
KIVU.updateMask(KIVU.gte(threshold))
|
|
483
497
|
.rename("kivu")
|
|
484
|
-
.copyProperties(image)
|
|
498
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
485
499
|
)
|
|
486
500
|
return chlorophyll
|
|
487
501
|
|
|
@@ -505,8 +519,8 @@ class LandsatCollection:
|
|
|
505
519
|
# otherwise treat as OLI and copy properties after renaming band to "albedo"
|
|
506
520
|
albedo = ee.Algorithms.If(
|
|
507
521
|
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))
|
|
522
|
+
image.expression(TM_expression).rename("albedo").copyProperties(image).set('system:time_start', image.get('system:time_start')),
|
|
523
|
+
image.expression(OLI_expression).rename("albedo").copyProperties(image).set('system:time_start', image.get('system:time_start')))
|
|
510
524
|
return albedo
|
|
511
525
|
|
|
512
526
|
@staticmethod
|
|
@@ -530,14 +544,14 @@ class LandsatCollection:
|
|
|
530
544
|
ndsi_calc.updateMask(ndsi_calc.gte(threshold))
|
|
531
545
|
.rename("ndsi")
|
|
532
546
|
.copyProperties(image)
|
|
533
|
-
.set("threshold", threshold),
|
|
547
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
534
548
|
ndsi_calc.updateMask(ndsi_calc.gte(ng_threshold))
|
|
535
549
|
.rename("ndsi")
|
|
536
550
|
.copyProperties(image)
|
|
537
|
-
.set("threshold", ng_threshold),
|
|
551
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
538
552
|
)
|
|
539
553
|
else:
|
|
540
|
-
ndsi = ndsi_calc.updateMask(ndsi_calc.gte(threshold)).rename("ndsi").copyProperties(image).set("threshold", threshold)
|
|
554
|
+
ndsi = ndsi_calc.updateMask(ndsi_calc.gte(threshold)).rename("ndsi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
541
555
|
return ndsi
|
|
542
556
|
|
|
543
557
|
@staticmethod
|
|
@@ -567,14 +581,14 @@ class LandsatCollection:
|
|
|
567
581
|
evi_calc.updateMask(evi_calc.gte(threshold))
|
|
568
582
|
.rename("evi")
|
|
569
583
|
.copyProperties(image)
|
|
570
|
-
.set("threshold", threshold),
|
|
584
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
571
585
|
evi_calc.updateMask(evi_calc.gte(ng_threshold))
|
|
572
586
|
.rename("evi")
|
|
573
587
|
.copyProperties(image)
|
|
574
|
-
.set("threshold", ng_threshold),
|
|
588
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
575
589
|
)
|
|
576
590
|
else:
|
|
577
|
-
evi = evi_calc.updateMask(evi_calc.gte(threshold)).rename("evi").copyProperties(image).set("threshold", threshold)
|
|
591
|
+
evi = evi_calc.updateMask(evi_calc.gte(threshold)).rename("evi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
578
592
|
return evi
|
|
579
593
|
|
|
580
594
|
@staticmethod
|
|
@@ -600,14 +614,14 @@ class LandsatCollection:
|
|
|
600
614
|
savi_calc.updateMask(savi_calc.gte(threshold))
|
|
601
615
|
.rename("savi")
|
|
602
616
|
.copyProperties(image)
|
|
603
|
-
.set("threshold", threshold),
|
|
617
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
604
618
|
savi_calc.updateMask(savi_calc.gte(ng_threshold))
|
|
605
619
|
.rename("savi")
|
|
606
620
|
.copyProperties(image)
|
|
607
|
-
.set("threshold", ng_threshold),
|
|
621
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
608
622
|
)
|
|
609
623
|
else:
|
|
610
|
-
savi = savi_calc.updateMask(savi_calc.gte(threshold)).rename("savi").copyProperties(image).set("threshold", threshold)
|
|
624
|
+
savi = savi_calc.updateMask(savi_calc.gte(threshold)).rename("savi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
611
625
|
return savi
|
|
612
626
|
|
|
613
627
|
@staticmethod
|
|
@@ -633,14 +647,14 @@ class LandsatCollection:
|
|
|
633
647
|
msavi_calc.updateMask(msavi_calc.gte(threshold))
|
|
634
648
|
.rename("msavi")
|
|
635
649
|
.copyProperties(image)
|
|
636
|
-
.set("threshold", threshold),
|
|
650
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
637
651
|
msavi_calc.updateMask(msavi_calc.gte(ng_threshold))
|
|
638
652
|
.rename("msavi")
|
|
639
653
|
.copyProperties(image)
|
|
640
|
-
.set("threshold", ng_threshold),
|
|
654
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
641
655
|
)
|
|
642
656
|
else:
|
|
643
|
-
msavi = msavi_calc.updateMask(msavi_calc.gte(threshold)).rename("msavi").copyProperties(image).set("threshold", threshold)
|
|
657
|
+
msavi = msavi_calc.updateMask(msavi_calc.gte(threshold)).rename("msavi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
644
658
|
return msavi
|
|
645
659
|
|
|
646
660
|
@staticmethod
|
|
@@ -666,14 +680,14 @@ class LandsatCollection:
|
|
|
666
680
|
ndmi_calc.updateMask(ndmi_calc.gte(threshold))
|
|
667
681
|
.rename("ndmi")
|
|
668
682
|
.copyProperties(image)
|
|
669
|
-
.set("threshold", threshold),
|
|
683
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
670
684
|
ndmi_calc.updateMask(ndmi_calc.gte(ng_threshold))
|
|
671
685
|
.rename("ndmi")
|
|
672
686
|
.copyProperties(image)
|
|
673
|
-
.set("threshold", ng_threshold),
|
|
687
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
674
688
|
)
|
|
675
689
|
else:
|
|
676
|
-
ndmi = ndmi_calc.updateMask(ndmi_calc.gte(threshold)).rename("ndmi").copyProperties(image).set("threshold", threshold)
|
|
690
|
+
ndmi = ndmi_calc.updateMask(ndmi_calc.gte(threshold)).rename("ndmi").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
677
691
|
return ndmi
|
|
678
692
|
|
|
679
693
|
@staticmethod
|
|
@@ -698,14 +712,14 @@ class LandsatCollection:
|
|
|
698
712
|
nbr_calc.updateMask(nbr_calc.gte(threshold))
|
|
699
713
|
.rename("nbr")
|
|
700
714
|
.copyProperties(image)
|
|
701
|
-
.set("threshold", threshold),
|
|
715
|
+
.set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
702
716
|
nbr_calc.updateMask(nbr_calc.gte(ng_threshold))
|
|
703
717
|
.rename("nbr")
|
|
704
718
|
.copyProperties(image)
|
|
705
|
-
.set("threshold", ng_threshold),
|
|
719
|
+
.set("threshold", ng_threshold, 'system:time_start', image.get('system:time_start')),
|
|
706
720
|
)
|
|
707
721
|
else:
|
|
708
|
-
nbr = nbr_calc.updateMask(nbr_calc.gte(threshold)).rename("nbr").copyProperties(image).set("threshold", threshold)
|
|
722
|
+
nbr = nbr_calc.updateMask(nbr_calc.gte(threshold)).rename("nbr").copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start'))
|
|
709
723
|
return nbr
|
|
710
724
|
|
|
711
725
|
@staticmethod
|
|
@@ -767,7 +781,7 @@ class LandsatCollection:
|
|
|
767
781
|
return image.addBands(anomaly_image, overwrite=True).copyProperties(image)
|
|
768
782
|
|
|
769
783
|
@staticmethod
|
|
770
|
-
def
|
|
784
|
+
def maskWater(image):
|
|
771
785
|
"""
|
|
772
786
|
Masks water pixels based on Landsat image QA band.
|
|
773
787
|
|
|
@@ -780,11 +794,21 @@ class LandsatCollection:
|
|
|
780
794
|
WaterBitMask = ee.Number(2).pow(7).int()
|
|
781
795
|
qa = image.select("QA_PIXEL")
|
|
782
796
|
water_extract = qa.bitwiseAnd(WaterBitMask).eq(0)
|
|
783
|
-
masked_image = image.updateMask(water_extract).copyProperties(image)
|
|
797
|
+
masked_image = image.updateMask(water_extract).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
784
798
|
return masked_image
|
|
799
|
+
|
|
800
|
+
@staticmethod
|
|
801
|
+
def MaskWaterLandsat(image):
|
|
802
|
+
warnings.warn(
|
|
803
|
+
"MaskWaterLandsat is deprecated and will be removed in a future release. "
|
|
804
|
+
"Please use maskWater instead.",
|
|
805
|
+
DeprecationWarning,
|
|
806
|
+
stacklevel=2
|
|
807
|
+
)
|
|
808
|
+
return LandsatCollection.maskWater(image)
|
|
785
809
|
|
|
786
810
|
@staticmethod
|
|
787
|
-
def
|
|
811
|
+
def maskWaterByNDWI(image, threshold, ng_threshold=None):
|
|
788
812
|
"""
|
|
789
813
|
Masks water pixels (mask land and cloud pixels) for all bands based on NDWI and a set threshold where
|
|
790
814
|
all pixels less than NDWI threshold are masked out. Can specify separate thresholds for Landsat 5 vs 8&9 images, where the threshold
|
|
@@ -805,23 +829,34 @@ class LandsatCollection:
|
|
|
805
829
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
806
830
|
.rename("ndwi")
|
|
807
831
|
.copyProperties(image)
|
|
832
|
+
.set('system:time_start', image.get('system:time_start'))
|
|
808
833
|
)
|
|
809
834
|
if ng_threshold != None:
|
|
810
835
|
water = ee.Algorithms.If(
|
|
811
836
|
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
|
|
837
|
+
image.updateMask(ndwi_calc.lt(threshold)).copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
838
|
+
image.updateMask(ndwi_calc.lt(ng_threshold)).copyProperties(image).set(
|
|
839
|
+
"threshold", ng_threshold, 'system:time_start', image.get('system:time_start')
|
|
815
840
|
),
|
|
816
841
|
)
|
|
817
842
|
else:
|
|
818
|
-
water = image.updateMask(ndwi_calc.lt(threshold)).set(
|
|
819
|
-
"threshold", threshold
|
|
843
|
+
water = image.updateMask(ndwi_calc.lt(threshold)).copyProperties(image).set(
|
|
844
|
+
"threshold", threshold, 'system:time_start', image.get('system:time_start')
|
|
820
845
|
)
|
|
821
846
|
return water
|
|
822
|
-
|
|
847
|
+
|
|
823
848
|
@staticmethod
|
|
824
|
-
def
|
|
849
|
+
def MaskWaterLandsatByNDWI(image, threshold, ng_threshold=None):
|
|
850
|
+
warnings.warn(
|
|
851
|
+
"MaskWaterLandsatByNDWI is deprecated and will be removed in a future release. "
|
|
852
|
+
"Please use maskWaterByNDWI instead.",
|
|
853
|
+
DeprecationWarning,
|
|
854
|
+
stacklevel=2
|
|
855
|
+
)
|
|
856
|
+
return LandsatCollection.maskWaterByNDWI(image, threshold, ng_threshold=ng_threshold)
|
|
857
|
+
|
|
858
|
+
@staticmethod
|
|
859
|
+
def maskToWater(image):
|
|
825
860
|
"""
|
|
826
861
|
Masks image to water pixels by masking land and cloud pixels based on Landsat image QA band.
|
|
827
862
|
|
|
@@ -834,11 +869,21 @@ class LandsatCollection:
|
|
|
834
869
|
WaterBitMask = ee.Number(2).pow(7).int()
|
|
835
870
|
qa = image.select("QA_PIXEL")
|
|
836
871
|
water_extract = qa.bitwiseAnd(WaterBitMask).neq(0)
|
|
837
|
-
masked_image = image.updateMask(water_extract).copyProperties(image)
|
|
872
|
+
masked_image = image.updateMask(water_extract).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
838
873
|
return masked_image
|
|
874
|
+
|
|
875
|
+
@staticmethod
|
|
876
|
+
def MaskToWaterLandsat(image):
|
|
877
|
+
warnings.warn(
|
|
878
|
+
"MaskToWaterLandsat is deprecated and will be removed in a future release. "
|
|
879
|
+
"Please use maskToWater instead.",
|
|
880
|
+
DeprecationWarning,
|
|
881
|
+
stacklevel=2
|
|
882
|
+
)
|
|
883
|
+
return LandsatCollection.maskToWater(image)
|
|
839
884
|
|
|
840
885
|
@staticmethod
|
|
841
|
-
def
|
|
886
|
+
def maskToWaterByNDWI(image, threshold, ng_threshold=None):
|
|
842
887
|
"""
|
|
843
888
|
Masks water pixels using NDWI based on threshold. Can specify separate thresholds for Landsat 5 vs 8&9 images, where the threshold
|
|
844
889
|
argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9
|
|
@@ -858,20 +903,65 @@ class LandsatCollection:
|
|
|
858
903
|
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
859
904
|
.rename("ndwi")
|
|
860
905
|
.copyProperties(image)
|
|
906
|
+
.set('system:time_start', image.get('system:time_start'))
|
|
861
907
|
)
|
|
862
908
|
if ng_threshold != None:
|
|
863
909
|
water = ee.Algorithms.If(
|
|
864
910
|
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
|
|
911
|
+
image.updateMask(ndwi_calc.gte(threshold)).copyProperties(image).set("threshold", threshold, 'system:time_start', image.get('system:time_start')),
|
|
912
|
+
image.updateMask(ndwi_calc.gte(ng_threshold)).copyProperties(image).set(
|
|
913
|
+
"threshold", ng_threshold, 'system:time_start', image.get('system:time_start')
|
|
868
914
|
),
|
|
869
915
|
)
|
|
870
916
|
else:
|
|
871
|
-
water = image.updateMask(ndwi_calc.gte(threshold)).set(
|
|
872
|
-
"threshold", threshold
|
|
917
|
+
water = image.updateMask(ndwi_calc.gte(threshold)).copyProperties(image).set(
|
|
918
|
+
"threshold", threshold, 'system:time_start', image.get('system:time_start')
|
|
873
919
|
)
|
|
874
920
|
return water
|
|
921
|
+
|
|
922
|
+
@staticmethod
|
|
923
|
+
def MaskToWaterLandsatByNDWI(image, threshold, ng_threshold=None):
|
|
924
|
+
warnings.warn(
|
|
925
|
+
"MaskToWaterLandsatNDWI is deprecated and will be removed in a future release. "
|
|
926
|
+
"Please use maskToWaterByNDWI instead.",
|
|
927
|
+
DeprecationWarning,
|
|
928
|
+
stacklevel=2
|
|
929
|
+
)
|
|
930
|
+
return LandsatCollection.maskToWaterByNDWI(image, threshold, ng_threshold=ng_threshold)
|
|
931
|
+
|
|
932
|
+
# @staticmethod
|
|
933
|
+
# def maskClouds(image):
|
|
934
|
+
# """
|
|
935
|
+
# Masks clouds pixels based on Landsat image QA band.
|
|
936
|
+
|
|
937
|
+
# Args:
|
|
938
|
+
# image (ee.Image): input ee.Image
|
|
939
|
+
|
|
940
|
+
# Returns:
|
|
941
|
+
# ee.Image: ee.Image with cloud pixels masked.
|
|
942
|
+
# """
|
|
943
|
+
# CloudBitMask = ee.Number(2).pow(3).int()
|
|
944
|
+
# qa = image.select("QA_PIXEL")
|
|
945
|
+
# cloud_extract = qa.bitwiseAnd(CloudBitMask).eq(0)
|
|
946
|
+
# masked_image = image.updateMask(cloud_extract).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
947
|
+
# return masked_image
|
|
948
|
+
|
|
949
|
+
# @staticmethod
|
|
950
|
+
# def maskShadows(image):
|
|
951
|
+
# """
|
|
952
|
+
# Masks shadows pixels based on Landsat image QA band.
|
|
953
|
+
|
|
954
|
+
# Args:
|
|
955
|
+
# image (ee.Image): input ee.Image
|
|
956
|
+
|
|
957
|
+
# Returns:
|
|
958
|
+
# ee.Image: ee.Image with cloud pixels masked.
|
|
959
|
+
# """
|
|
960
|
+
# ShadowBitMask = ee.Number(2).pow(4).int()
|
|
961
|
+
# qa = image.select("QA_PIXEL")
|
|
962
|
+
# shadow_extract = qa.bitwiseAnd(ShadowBitMask).eq(0)
|
|
963
|
+
# masked_image = image.updateMask(shadow_extract).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
964
|
+
# return masked_image
|
|
875
965
|
|
|
876
966
|
@staticmethod
|
|
877
967
|
def mask_via_band_fn(image, band_to_mask, band_for_mask, threshold, mask_above=False, add_band_to_original_image=False):
|
|
@@ -899,7 +989,7 @@ class LandsatCollection:
|
|
|
899
989
|
if add_band_to_original_image:
|
|
900
990
|
return image.addBands(band_to_mask_image.updateMask(mask).rename(band_to_mask), overwrite=True)
|
|
901
991
|
else:
|
|
902
|
-
return ee.Image(band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image))
|
|
992
|
+
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
993
|
|
|
904
994
|
@staticmethod
|
|
905
995
|
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 +1025,7 @@ class LandsatCollection:
|
|
|
935
1025
|
mask = band_for_mask_image.gt(threshold)
|
|
936
1026
|
else:
|
|
937
1027
|
mask = band_for_mask_image.lt(threshold)
|
|
938
|
-
return band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image_to_mask)
|
|
1028
|
+
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
1029
|
|
|
940
1030
|
@staticmethod
|
|
941
1031
|
def halite_mask(image, threshold, ng_threshold=None):
|
|
@@ -959,11 +1049,11 @@ class LandsatCollection:
|
|
|
959
1049
|
if ng_threshold != None:
|
|
960
1050
|
mask = ee.Algorithms.If(
|
|
961
1051
|
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),
|
|
1052
|
+
image.updateMask(halite_index.lt(threshold)).copyProperties(image).set('system:time_start', image.get('system:time_start')),
|
|
1053
|
+
image.updateMask(halite_index.lt(ng_threshold)).copyProperties(image).set('system:time_start', image.get('system:time_start')),
|
|
964
1054
|
)
|
|
965
1055
|
else:
|
|
966
|
-
mask = image.updateMask(halite_index.lt(threshold)).copyProperties(image)
|
|
1056
|
+
mask = image.updateMask(halite_index.lt(threshold)).copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
967
1057
|
return mask
|
|
968
1058
|
|
|
969
1059
|
@staticmethod
|
|
@@ -1000,11 +1090,13 @@ class LandsatCollection:
|
|
|
1000
1090
|
gypsum_index.updateMask(halite_index.lt(halite_threshold))
|
|
1001
1091
|
.updateMask(gypsum_index.lt(gypsum_threshold))
|
|
1002
1092
|
.rename("carbonate_muds")
|
|
1003
|
-
.copyProperties(image)
|
|
1093
|
+
.copyProperties(image)
|
|
1094
|
+
.set('system:time_start', image.get('system:time_start')),
|
|
1004
1095
|
gypsum_index.updateMask(halite_index.lt(halite_ng_threshold))
|
|
1005
1096
|
.updateMask(gypsum_index.lt(gypsum_ng_threshold))
|
|
1006
1097
|
.rename("carbonate_muds")
|
|
1007
|
-
.copyProperties(image)
|
|
1098
|
+
.copyProperties(image)
|
|
1099
|
+
.set('system:time_start', image.get('system:time_start')),
|
|
1008
1100
|
)
|
|
1009
1101
|
else:
|
|
1010
1102
|
mask = (
|
|
@@ -1012,11 +1104,12 @@ class LandsatCollection:
|
|
|
1012
1104
|
.updateMask(gypsum_index.lt(gypsum_threshold))
|
|
1013
1105
|
.rename("carbonate_muds")
|
|
1014
1106
|
.copyProperties(image)
|
|
1107
|
+
.set('system:time_start', image.get('system:time_start'))
|
|
1015
1108
|
)
|
|
1016
1109
|
return mask
|
|
1017
1110
|
|
|
1018
1111
|
@staticmethod
|
|
1019
|
-
def
|
|
1112
|
+
def maskClouds(image):
|
|
1020
1113
|
"""
|
|
1021
1114
|
Masks clouds baseed on Landsat 8 QA band.
|
|
1022
1115
|
|
|
@@ -1032,9 +1125,17 @@ class LandsatCollection:
|
|
|
1032
1125
|
cloud_mask = qa.bitwiseAnd(cloudBitMask).eq(0)
|
|
1033
1126
|
cirrus_mask = qa.bitwiseAnd(CirrusBitMask).eq(0)
|
|
1034
1127
|
return image.updateMask(cloud_mask).updateMask(cirrus_mask)
|
|
1128
|
+
|
|
1129
|
+
@staticmethod
|
|
1130
|
+
def maskL8clouds(image):
|
|
1131
|
+
warnings.warn(
|
|
1132
|
+
"maskL8clouds is deprecated and will be removed in a future release. Please use maskClouds instead.",
|
|
1133
|
+
DeprecationWarning,
|
|
1134
|
+
stacklevel=2)
|
|
1135
|
+
return LandsatCollection.maskClouds(image)
|
|
1035
1136
|
|
|
1036
1137
|
@staticmethod
|
|
1037
|
-
def
|
|
1138
|
+
def maskShadows(image):
|
|
1038
1139
|
"""
|
|
1039
1140
|
Masks cloud shadows based on Landsat 8 QA band.
|
|
1040
1141
|
|
|
@@ -1048,6 +1149,14 @@ class LandsatCollection:
|
|
|
1048
1149
|
qa = image.select("QA_PIXEL")
|
|
1049
1150
|
shadow_mask = qa.bitwiseAnd(shadowBitMask).eq(0)
|
|
1050
1151
|
return image.updateMask(shadow_mask)
|
|
1152
|
+
|
|
1153
|
+
@staticmethod
|
|
1154
|
+
def maskL8shadows(image):
|
|
1155
|
+
warnings.warn(
|
|
1156
|
+
"maskL8shadows is deprecated and will be removed in a future release. Please use maskShadows instead.",
|
|
1157
|
+
DeprecationWarning,
|
|
1158
|
+
stacklevel=2)
|
|
1159
|
+
return LandsatCollection.maskShadows(image)
|
|
1051
1160
|
|
|
1052
1161
|
@staticmethod
|
|
1053
1162
|
def temperature_bands(img):
|
|
@@ -1162,7 +1271,7 @@ class LandsatCollection:
|
|
|
1162
1271
|
return out.copyProperties(img)
|
|
1163
1272
|
|
|
1164
1273
|
@staticmethod
|
|
1165
|
-
def
|
|
1274
|
+
def pixelAreaSum(
|
|
1166
1275
|
image, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12
|
|
1167
1276
|
):
|
|
1168
1277
|
"""
|
|
@@ -1221,8 +1330,18 @@ class LandsatCollection:
|
|
|
1221
1330
|
# Call to iterate the calculate_and_set_area function over the list of bands, starting with the original image
|
|
1222
1331
|
final_image = ee.Image(bands.iterate(calculate_and_set_area, image))
|
|
1223
1332
|
return final_image
|
|
1333
|
+
|
|
1334
|
+
@staticmethod
|
|
1335
|
+
def PixelAreaSum(image, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12):
|
|
1336
|
+
warnings.warn(
|
|
1337
|
+
"PixelAreaSum is deprecated and will be removed in a future release. "
|
|
1338
|
+
"Please use pixelAreaSum instead.",
|
|
1339
|
+
DeprecationWarning,
|
|
1340
|
+
stacklevel=2
|
|
1341
|
+
)
|
|
1342
|
+
return LandsatCollection.pixelAreaSum(image, band_name, geometry, threshold, scale, maxPixels)
|
|
1224
1343
|
|
|
1225
|
-
def
|
|
1344
|
+
def pixelAreaSumCollection(
|
|
1226
1345
|
self, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12, output_type='ImageCollection', area_data_export_path=None):
|
|
1227
1346
|
"""
|
|
1228
1347
|
Calculates the geodesic summation of area for pixels of interest (above a specific threshold)
|
|
@@ -1248,7 +1367,7 @@ class LandsatCollection:
|
|
|
1248
1367
|
collection = self.collection
|
|
1249
1368
|
# Area calculation for each image in the collection, using the PixelAreaSum function
|
|
1250
1369
|
AreaCollection = collection.map(
|
|
1251
|
-
lambda image: LandsatCollection.
|
|
1370
|
+
lambda image: LandsatCollection.pixelAreaSum(
|
|
1252
1371
|
image,
|
|
1253
1372
|
band_name=band_name,
|
|
1254
1373
|
geometry=geometry,
|
|
@@ -1264,17 +1383,27 @@ class LandsatCollection:
|
|
|
1264
1383
|
|
|
1265
1384
|
# If an export path is provided, the area data will be exported to a CSV file
|
|
1266
1385
|
if area_data_export_path:
|
|
1267
|
-
LandsatCollection(collection=self._PixelAreaSumCollection).
|
|
1386
|
+
LandsatCollection(collection=self._PixelAreaSumCollection).exportProperties(property_names=prop_names, file_path=area_data_export_path+'.csv')
|
|
1268
1387
|
# Returning the result in the desired format based on output_type argument or raising an error for invalid input
|
|
1269
1388
|
if output_type == 'ImageCollection' or output_type == 'ee.ImageCollection':
|
|
1270
1389
|
return self._PixelAreaSumCollection
|
|
1271
1390
|
elif output_type == 'LandsatCollection':
|
|
1272
1391
|
return LandsatCollection(collection=self._PixelAreaSumCollection)
|
|
1273
1392
|
elif output_type == 'DataFrame' or output_type == 'Pandas' or output_type == 'pd' or output_type == 'dataframe' or output_type == 'df':
|
|
1274
|
-
return LandsatCollection(collection=self._PixelAreaSumCollection).
|
|
1393
|
+
return LandsatCollection(collection=self._PixelAreaSumCollection).exportProperties(property_names=prop_names)
|
|
1275
1394
|
else:
|
|
1276
1395
|
raise ValueError("Incorrect `output_type`. The `output_type` argument must be one of the following: 'ImageCollection', 'ee.ImageCollection', 'LandsatCollection', 'DataFrame', 'Pandas', 'pd', 'dataframe', or 'df'.")
|
|
1277
1396
|
|
|
1397
|
+
def PixelAreaSumCollection(
|
|
1398
|
+
self, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12, output_type='ImageCollection', area_data_export_path=None):
|
|
1399
|
+
warnings.warn(
|
|
1400
|
+
"PixelAreaSumCollection is deprecated and will be removed in a future release. "
|
|
1401
|
+
"Please use pixelAreaSumCollection instead.",
|
|
1402
|
+
DeprecationWarning,
|
|
1403
|
+
stacklevel=2
|
|
1404
|
+
)
|
|
1405
|
+
return self.pixelAreaSumCollection(band_name, geometry, threshold, scale, maxPixels, output_type, area_data_export_path)
|
|
1406
|
+
|
|
1278
1407
|
@staticmethod
|
|
1279
1408
|
def add_month_property_fn(image):
|
|
1280
1409
|
"""
|
|
@@ -1378,7 +1507,12 @@ class LandsatCollection:
|
|
|
1378
1507
|
return LandsatCollection(collection=ee.ImageCollection(paired.map(_pair_two)))
|
|
1379
1508
|
|
|
1380
1509
|
# Preferred path: merge many singleband products into the parent
|
|
1381
|
-
if not isinstance(collections, list) or len(collections) == 0:
|
|
1510
|
+
# if not isinstance(collections, list) or len(collections) == 0:
|
|
1511
|
+
# raise ValueError("Provide a non-empty list of LandsatCollection objects in `collections`.")
|
|
1512
|
+
if not isinstance(collections, list):
|
|
1513
|
+
collections = [collections]
|
|
1514
|
+
|
|
1515
|
+
if len(collections) == 0:
|
|
1382
1516
|
raise ValueError("Provide a non-empty list of LandsatCollection objects in `collections`.")
|
|
1383
1517
|
|
|
1384
1518
|
result = self.collection
|
|
@@ -1505,7 +1639,7 @@ class LandsatCollection:
|
|
|
1505
1639
|
self._dates = dates
|
|
1506
1640
|
return self._dates
|
|
1507
1641
|
|
|
1508
|
-
def
|
|
1642
|
+
def exportProperties(self, property_names, file_path=None):
|
|
1509
1643
|
"""
|
|
1510
1644
|
Fetches and returns specified properties from each image in the collection as a list, and returns a pandas DataFrame and optionally saves the results to a csv file.
|
|
1511
1645
|
|
|
@@ -1560,6 +1694,15 @@ class LandsatCollection:
|
|
|
1560
1694
|
print(f"Properties saved to {file_path}")
|
|
1561
1695
|
|
|
1562
1696
|
return df
|
|
1697
|
+
|
|
1698
|
+
def ExportProperties(self, property_names, file_path=None):
|
|
1699
|
+
warnings.warn(
|
|
1700
|
+
"ExportProperties is deprecated and will be removed in a future release. "
|
|
1701
|
+
"Please use exportProperties instead.",
|
|
1702
|
+
DeprecationWarning,
|
|
1703
|
+
stacklevel=2
|
|
1704
|
+
)
|
|
1705
|
+
return self.exportProperties(property_names, file_path)
|
|
1563
1706
|
|
|
1564
1707
|
def get_filtered_collection(self):
|
|
1565
1708
|
"""
|
|
@@ -1781,6 +1924,7 @@ class LandsatCollection:
|
|
|
1781
1924
|
"""
|
|
1782
1925
|
if self._monthly_mean is None:
|
|
1783
1926
|
collection = self.collection
|
|
1927
|
+
# Capture projection from the first image to restore it after reduction
|
|
1784
1928
|
target_proj = collection.first().projection()
|
|
1785
1929
|
# Get the start and end dates of the entire collection.
|
|
1786
1930
|
date_range = collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
@@ -2092,6 +2236,391 @@ class LandsatCollection:
|
|
|
2092
2236
|
pass
|
|
2093
2237
|
|
|
2094
2238
|
return self._monthly_min
|
|
2239
|
+
|
|
2240
|
+
def yearly_mean_collection(self, start_month=1, end_month=12):
|
|
2241
|
+
"""
|
|
2242
|
+
Creates a yearly mean composite from the collection, with optional monthly filtering.
|
|
2243
|
+
|
|
2244
|
+
This function computes the mean for each year within the collection's date range.
|
|
2245
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2246
|
+
to calculate the mean only using imagery from that specific season for each year.
|
|
2247
|
+
|
|
2248
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2249
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2250
|
+
|
|
2251
|
+
Args:
|
|
2252
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2253
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2254
|
+
|
|
2255
|
+
Returns:
|
|
2256
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly mean composites.
|
|
2257
|
+
"""
|
|
2258
|
+
if self._yearly_mean is None:
|
|
2259
|
+
|
|
2260
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2261
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2262
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2263
|
+
|
|
2264
|
+
start_year = start_date_full.get('year')
|
|
2265
|
+
end_year = end_date_full.get('year')
|
|
2266
|
+
|
|
2267
|
+
if start_month != 1 or end_month != 12:
|
|
2268
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2269
|
+
else:
|
|
2270
|
+
processing_collection = self.collection
|
|
2271
|
+
|
|
2272
|
+
# Capture projection from the first image to restore it after reduction
|
|
2273
|
+
target_proj = self.collection.first().projection()
|
|
2274
|
+
|
|
2275
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2276
|
+
|
|
2277
|
+
def create_yearly_composite(year):
|
|
2278
|
+
year = ee.Number(year)
|
|
2279
|
+
# Define the full calendar year range
|
|
2280
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2281
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2282
|
+
|
|
2283
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2284
|
+
|
|
2285
|
+
# Calculate stats
|
|
2286
|
+
image_count = yearly_subset.size()
|
|
2287
|
+
yearly_reduction = yearly_subset.mean()
|
|
2288
|
+
|
|
2289
|
+
# Define the timestamp for the composite.
|
|
2290
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2291
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2292
|
+
|
|
2293
|
+
return yearly_reduction.set({
|
|
2294
|
+
'system:time_start': composite_date.millis(),
|
|
2295
|
+
'year': year,
|
|
2296
|
+
'month': start_month,
|
|
2297
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2298
|
+
'image_count': image_count,
|
|
2299
|
+
'season_start': start_month,
|
|
2300
|
+
'season_end': end_month
|
|
2301
|
+
}).reproject(target_proj)
|
|
2302
|
+
|
|
2303
|
+
# Map the function over the years list
|
|
2304
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2305
|
+
|
|
2306
|
+
# Convert to Collection
|
|
2307
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2308
|
+
|
|
2309
|
+
# Filter out any composites that were created from zero images.
|
|
2310
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2311
|
+
|
|
2312
|
+
self._yearly_mean = LandsatCollection(collection=final_collection)
|
|
2313
|
+
else:
|
|
2314
|
+
pass
|
|
2315
|
+
return self._yearly_mean
|
|
2316
|
+
|
|
2317
|
+
def yearly_median_collection(self, start_month=1, end_month=12):
|
|
2318
|
+
"""
|
|
2319
|
+
Creates a yearly median composite from the collection, with optional monthly filtering.
|
|
2320
|
+
|
|
2321
|
+
This function computes the median for each year within the collection's date range.
|
|
2322
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2323
|
+
to calculate the median only using imagery from that specific season for each year.
|
|
2324
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2325
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2326
|
+
|
|
2327
|
+
Args:
|
|
2328
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2329
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2330
|
+
|
|
2331
|
+
Returns:
|
|
2332
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly median composites.
|
|
2333
|
+
"""
|
|
2334
|
+
if self._yearly_median is None:
|
|
2335
|
+
|
|
2336
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2337
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2338
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2339
|
+
|
|
2340
|
+
start_year = start_date_full.get('year')
|
|
2341
|
+
end_year = end_date_full.get('year')
|
|
2342
|
+
|
|
2343
|
+
if start_month != 1 or end_month != 12:
|
|
2344
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2345
|
+
else:
|
|
2346
|
+
processing_collection = self.collection
|
|
2347
|
+
|
|
2348
|
+
# Capture projection from the first image to restore it after reduction
|
|
2349
|
+
target_proj = self.collection.first().projection()
|
|
2350
|
+
|
|
2351
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2352
|
+
|
|
2353
|
+
def create_yearly_composite(year):
|
|
2354
|
+
year = ee.Number(year)
|
|
2355
|
+
# Define the full calendar year range
|
|
2356
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2357
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2358
|
+
|
|
2359
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2360
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2361
|
+
|
|
2362
|
+
# Calculate stats
|
|
2363
|
+
image_count = yearly_subset.size()
|
|
2364
|
+
yearly_reduction = yearly_subset.median()
|
|
2365
|
+
|
|
2366
|
+
# Define the timestamp for the composite.
|
|
2367
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2368
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2369
|
+
|
|
2370
|
+
return yearly_reduction.set({
|
|
2371
|
+
'system:time_start': composite_date.millis(),
|
|
2372
|
+
'year': year,
|
|
2373
|
+
'month': start_month,
|
|
2374
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2375
|
+
'image_count': image_count,
|
|
2376
|
+
'season_start': start_month,
|
|
2377
|
+
'season_end': end_month
|
|
2378
|
+
}).reproject(target_proj)
|
|
2379
|
+
|
|
2380
|
+
# Map the function over the years list
|
|
2381
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2382
|
+
|
|
2383
|
+
# Convert to Collection
|
|
2384
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2385
|
+
|
|
2386
|
+
# Filter out any composites that were created from zero images.
|
|
2387
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2388
|
+
|
|
2389
|
+
self._yearly_median = LandsatCollection(collection=final_collection)
|
|
2390
|
+
else:
|
|
2391
|
+
pass
|
|
2392
|
+
return self._yearly_median
|
|
2393
|
+
|
|
2394
|
+
def yearly_max_collection(self, start_month=1, end_month=12):
|
|
2395
|
+
"""
|
|
2396
|
+
Creates a yearly max composite from the collection, with optional monthly filtering.
|
|
2397
|
+
|
|
2398
|
+
This function computes the max for each year within the collection's date range.
|
|
2399
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2400
|
+
to calculate the max only using imagery from that specific season for each year.
|
|
2401
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2402
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2403
|
+
|
|
2404
|
+
Args:
|
|
2405
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2406
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2407
|
+
|
|
2408
|
+
Returns:
|
|
2409
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly max composites.
|
|
2410
|
+
"""
|
|
2411
|
+
if self._yearly_max is None:
|
|
2412
|
+
|
|
2413
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2414
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2415
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2416
|
+
|
|
2417
|
+
start_year = start_date_full.get('year')
|
|
2418
|
+
end_year = end_date_full.get('year')
|
|
2419
|
+
|
|
2420
|
+
if start_month != 1 or end_month != 12:
|
|
2421
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2422
|
+
else:
|
|
2423
|
+
processing_collection = self.collection
|
|
2424
|
+
|
|
2425
|
+
# Capture projection from the first image to restore it after reduction
|
|
2426
|
+
target_proj = self.collection.first().projection()
|
|
2427
|
+
|
|
2428
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2429
|
+
|
|
2430
|
+
def create_yearly_composite(year):
|
|
2431
|
+
year = ee.Number(year)
|
|
2432
|
+
# Define the full calendar year range
|
|
2433
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2434
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2435
|
+
|
|
2436
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2437
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2438
|
+
|
|
2439
|
+
# Calculate stats
|
|
2440
|
+
image_count = yearly_subset.size()
|
|
2441
|
+
yearly_reduction = yearly_subset.max()
|
|
2442
|
+
|
|
2443
|
+
# Define the timestamp for the composite.
|
|
2444
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2445
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2446
|
+
|
|
2447
|
+
return yearly_reduction.set({
|
|
2448
|
+
'system:time_start': composite_date.millis(),
|
|
2449
|
+
'year': year,
|
|
2450
|
+
'month': start_month,
|
|
2451
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2452
|
+
'image_count': image_count,
|
|
2453
|
+
'season_start': start_month,
|
|
2454
|
+
'season_end': end_month
|
|
2455
|
+
}).reproject(target_proj)
|
|
2456
|
+
|
|
2457
|
+
# Map the function over the years list
|
|
2458
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2459
|
+
|
|
2460
|
+
# Convert to Collection
|
|
2461
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2462
|
+
|
|
2463
|
+
# Filter out any composites that were created from zero images.
|
|
2464
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2465
|
+
|
|
2466
|
+
self._yearly_max = LandsatCollection(collection=final_collection)
|
|
2467
|
+
else:
|
|
2468
|
+
pass
|
|
2469
|
+
return self._yearly_max
|
|
2470
|
+
|
|
2471
|
+
def yearly_min_collection(self, start_month=1, end_month=12):
|
|
2472
|
+
"""
|
|
2473
|
+
Creates a yearly min composite from the collection, with optional monthly filtering.
|
|
2474
|
+
|
|
2475
|
+
This function computes the min for each year within the collection's date range.
|
|
2476
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2477
|
+
to calculate the min only using imagery from that specific season for each year.
|
|
2478
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2479
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2480
|
+
|
|
2481
|
+
Args:
|
|
2482
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2483
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2484
|
+
|
|
2485
|
+
Returns:
|
|
2486
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly min composites.
|
|
2487
|
+
"""
|
|
2488
|
+
if self._yearly_min is None:
|
|
2489
|
+
|
|
2490
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2491
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2492
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2493
|
+
|
|
2494
|
+
start_year = start_date_full.get('year')
|
|
2495
|
+
end_year = end_date_full.get('year')
|
|
2496
|
+
|
|
2497
|
+
if start_month != 1 or end_month != 12:
|
|
2498
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2499
|
+
else:
|
|
2500
|
+
processing_collection = self.collection
|
|
2501
|
+
|
|
2502
|
+
# Capture projection from the first image to restore it after reduction
|
|
2503
|
+
target_proj = self.collection.first().projection()
|
|
2504
|
+
|
|
2505
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2506
|
+
|
|
2507
|
+
def create_yearly_composite(year):
|
|
2508
|
+
year = ee.Number(year)
|
|
2509
|
+
# Define the full calendar year range
|
|
2510
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2511
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2512
|
+
|
|
2513
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2514
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2515
|
+
|
|
2516
|
+
# Calculate stats
|
|
2517
|
+
image_count = yearly_subset.size()
|
|
2518
|
+
yearly_reduction = yearly_subset.min()
|
|
2519
|
+
|
|
2520
|
+
# Define the timestamp for the composite.
|
|
2521
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2522
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2523
|
+
|
|
2524
|
+
return yearly_reduction.set({
|
|
2525
|
+
'system:time_start': composite_date.millis(),
|
|
2526
|
+
'year': year,
|
|
2527
|
+
'month': start_month,
|
|
2528
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2529
|
+
'image_count': image_count,
|
|
2530
|
+
'season_start': start_month,
|
|
2531
|
+
'season_end': end_month
|
|
2532
|
+
}).reproject(target_proj)
|
|
2533
|
+
|
|
2534
|
+
# Map the function over the years list
|
|
2535
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2536
|
+
|
|
2537
|
+
# Convert to Collection
|
|
2538
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2539
|
+
|
|
2540
|
+
# Filter out any composites that were created from zero images.
|
|
2541
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2542
|
+
|
|
2543
|
+
self._yearly_min = LandsatCollection(collection=final_collection)
|
|
2544
|
+
else:
|
|
2545
|
+
pass
|
|
2546
|
+
return self._yearly_min
|
|
2547
|
+
|
|
2548
|
+
def yearly_sum_collection(self, start_month=1, end_month=12):
|
|
2549
|
+
"""
|
|
2550
|
+
Creates a yearly sum composite from the collection, with optional monthly filtering.
|
|
2551
|
+
|
|
2552
|
+
This function computes the sum for each year within the collection's date range.
|
|
2553
|
+
You can specify a range of months (e.g., start_month=6, end_month=10 for June-October)
|
|
2554
|
+
to calculate the sum only using imagery from that specific season for each year.
|
|
2555
|
+
The resulting images have 'system:time_start', 'year', 'image_count', 'season_start',
|
|
2556
|
+
'season_end', and 'Date_Filter' properties. Years with no images (after filtering) are excluded.
|
|
2557
|
+
|
|
2558
|
+
Args:
|
|
2559
|
+
start_month (int): The starting month (1-12) for the filter. Defaults to 1 (January).
|
|
2560
|
+
end_month (int): The ending month (1-12) for the filter. Defaults to 12 (December).
|
|
2561
|
+
|
|
2562
|
+
Returns:
|
|
2563
|
+
Object: A new instance of the same class (e.g., LandsatCollection) containing the yearly sum composites.
|
|
2564
|
+
"""
|
|
2565
|
+
if self._yearly_sum is None:
|
|
2566
|
+
|
|
2567
|
+
date_range = self.collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
2568
|
+
start_date_full = ee.Date(date_range.get('min'))
|
|
2569
|
+
end_date_full = ee.Date(date_range.get('max'))
|
|
2570
|
+
|
|
2571
|
+
start_year = start_date_full.get('year')
|
|
2572
|
+
end_year = end_date_full.get('year')
|
|
2573
|
+
|
|
2574
|
+
if start_month != 1 or end_month != 12:
|
|
2575
|
+
processing_collection = self.collection.filter(ee.Filter.calendarRange(start_month, end_month, 'month'))
|
|
2576
|
+
else:
|
|
2577
|
+
processing_collection = self.collection
|
|
2578
|
+
|
|
2579
|
+
# Capture projection from the first image to restore it after reduction
|
|
2580
|
+
target_proj = self.collection.first().projection()
|
|
2581
|
+
|
|
2582
|
+
years = ee.List.sequence(start_year, end_year)
|
|
2583
|
+
|
|
2584
|
+
def create_yearly_composite(year):
|
|
2585
|
+
year = ee.Number(year)
|
|
2586
|
+
# Define the full calendar year range
|
|
2587
|
+
start_of_year = ee.Date.fromYMD(year, 1, 1)
|
|
2588
|
+
end_of_year = start_of_year.advance(1, 'year')
|
|
2589
|
+
|
|
2590
|
+
# Filter to the specific year using the PRE-FILTERED seasonal collection
|
|
2591
|
+
yearly_subset = processing_collection.filterDate(start_of_year, end_of_year)
|
|
2592
|
+
|
|
2593
|
+
# Calculate stats
|
|
2594
|
+
image_count = yearly_subset.size()
|
|
2595
|
+
yearly_reduction = yearly_subset.sum()
|
|
2596
|
+
|
|
2597
|
+
# Define the timestamp for the composite.
|
|
2598
|
+
# We use the start_month of that year to accurately reflect the data start time.
|
|
2599
|
+
composite_date = ee.Date.fromYMD(year, start_month, 1)
|
|
2600
|
+
|
|
2601
|
+
return yearly_reduction.set({
|
|
2602
|
+
'system:time_start': composite_date.millis(),
|
|
2603
|
+
'year': year,
|
|
2604
|
+
'month': start_month,
|
|
2605
|
+
'Date_Filter': composite_date.format('YYYY-MM-dd'),
|
|
2606
|
+
'image_count': image_count,
|
|
2607
|
+
'season_start': start_month,
|
|
2608
|
+
'season_end': end_month
|
|
2609
|
+
}).reproject(target_proj)
|
|
2610
|
+
|
|
2611
|
+
# Map the function over the years list
|
|
2612
|
+
yearly_composites_list = years.map(create_yearly_composite)
|
|
2613
|
+
|
|
2614
|
+
# Convert to Collection
|
|
2615
|
+
yearly_collection = ee.ImageCollection.fromImages(yearly_composites_list)
|
|
2616
|
+
|
|
2617
|
+
# Filter out any composites that were created from zero images.
|
|
2618
|
+
final_collection = yearly_collection.filter(ee.Filter.gt('image_count', 0))
|
|
2619
|
+
|
|
2620
|
+
self._yearly_sum = LandsatCollection(collection=final_collection)
|
|
2621
|
+
else:
|
|
2622
|
+
pass
|
|
2623
|
+
return self._yearly_sum
|
|
2095
2624
|
|
|
2096
2625
|
@property
|
|
2097
2626
|
def ndwi(self):
|
|
@@ -2693,7 +3222,7 @@ class LandsatCollection:
|
|
|
2693
3222
|
LandsatCollection: LandsatCollection image collection
|
|
2694
3223
|
"""
|
|
2695
3224
|
if self._masked_water_collection is None:
|
|
2696
|
-
col = self.collection.map(LandsatCollection.
|
|
3225
|
+
col = self.collection.map(LandsatCollection.maskWater)
|
|
2697
3226
|
self._masked_water_collection = LandsatCollection(collection=col)
|
|
2698
3227
|
return self._masked_water_collection
|
|
2699
3228
|
|
|
@@ -2708,7 +3237,7 @@ class LandsatCollection:
|
|
|
2708
3237
|
LandsatCollection: LandsatCollection image collection
|
|
2709
3238
|
"""
|
|
2710
3239
|
col = self.collection.map(
|
|
2711
|
-
lambda image: LandsatCollection.
|
|
3240
|
+
lambda image: LandsatCollection.maskWaterByNDWI(
|
|
2712
3241
|
image, threshold=threshold
|
|
2713
3242
|
)
|
|
2714
3243
|
)
|
|
@@ -2723,7 +3252,7 @@ class LandsatCollection:
|
|
|
2723
3252
|
LandsatCollection: LandsatCollection image collection
|
|
2724
3253
|
"""
|
|
2725
3254
|
if self._masked_to_water_collection is None:
|
|
2726
|
-
col = self.collection.map(LandsatCollection.
|
|
3255
|
+
col = self.collection.map(LandsatCollection.maskToWater)
|
|
2727
3256
|
self._masked_to_water_collection = LandsatCollection(collection=col)
|
|
2728
3257
|
return self._masked_to_water_collection
|
|
2729
3258
|
|
|
@@ -2738,7 +3267,7 @@ class LandsatCollection:
|
|
|
2738
3267
|
LandsatCollection: LandsatCollection image collection
|
|
2739
3268
|
"""
|
|
2740
3269
|
col = self.collection.map(
|
|
2741
|
-
lambda image: LandsatCollection.
|
|
3270
|
+
lambda image: LandsatCollection.maskToWaterByNDWI(
|
|
2742
3271
|
image, threshold=threshold
|
|
2743
3272
|
)
|
|
2744
3273
|
)
|
|
@@ -2753,7 +3282,7 @@ class LandsatCollection:
|
|
|
2753
3282
|
LandsatCollection: LandsatCollection image collection
|
|
2754
3283
|
"""
|
|
2755
3284
|
if self._masked_clouds_collection is None:
|
|
2756
|
-
col = self.collection.map(LandsatCollection.
|
|
3285
|
+
col = self.collection.map(LandsatCollection.maskClouds)
|
|
2757
3286
|
self._masked_clouds_collection = LandsatCollection(collection=col)
|
|
2758
3287
|
return self._masked_clouds_collection
|
|
2759
3288
|
|
|
@@ -2766,7 +3295,7 @@ class LandsatCollection:
|
|
|
2766
3295
|
LandsatCollection: LandsatCollection image collection
|
|
2767
3296
|
"""
|
|
2768
3297
|
if self._masked_shadows_collection is None:
|
|
2769
|
-
col = self.collection.map(LandsatCollection.
|
|
3298
|
+
col = self.collection.map(LandsatCollection.maskShadows)
|
|
2770
3299
|
self._masked_shadows_collection = LandsatCollection(collection=col)
|
|
2771
3300
|
return self._masked_shadows_collection
|
|
2772
3301
|
|
|
@@ -2835,20 +3364,14 @@ class LandsatCollection:
|
|
|
2835
3364
|
LandsatCollection: masked LandsatCollection image collection
|
|
2836
3365
|
|
|
2837
3366
|
"""
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
mask = ee.Image.constant(1).clip(polygon)
|
|
3367
|
+
# Convert the polygon to a mask
|
|
3368
|
+
mask = ee.Image.constant(1).clip(polygon)
|
|
2841
3369
|
|
|
2842
|
-
|
|
2843
|
-
|
|
3370
|
+
# Update the mask of each image in the collection
|
|
3371
|
+
masked_collection = self.collection.map(lambda img: img.updateMask(mask)\
|
|
3372
|
+
.copyProperties(img).set('system:time_start', img.get('system:time_start')))
|
|
2844
3373
|
|
|
2845
|
-
|
|
2846
|
-
self._geometry_masked_collection = LandsatCollection(
|
|
2847
|
-
collection=masked_collection
|
|
2848
|
-
)
|
|
2849
|
-
|
|
2850
|
-
# Return the updated object
|
|
2851
|
-
return self._geometry_masked_collection
|
|
3374
|
+
return LandsatCollection(collection=masked_collection)
|
|
2852
3375
|
|
|
2853
3376
|
def mask_out_polygon(self, polygon):
|
|
2854
3377
|
"""
|
|
@@ -2861,23 +3384,18 @@ class LandsatCollection:
|
|
|
2861
3384
|
LandsatCollection: masked LandsatCollection image collection
|
|
2862
3385
|
|
|
2863
3386
|
"""
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
full_mask = ee.Image.constant(1)
|
|
2867
|
-
|
|
2868
|
-
# Use paint to set pixels inside polygon as 0
|
|
2869
|
-
area = full_mask.paint(polygon, 0)
|
|
3387
|
+
# Convert the polygon to a mask
|
|
3388
|
+
full_mask = ee.Image.constant(1)
|
|
2870
3389
|
|
|
2871
|
-
|
|
2872
|
-
|
|
3390
|
+
# Use paint to set pixels inside polygon as 0
|
|
3391
|
+
area = full_mask.paint(polygon, 0)
|
|
2873
3392
|
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
)
|
|
3393
|
+
# Update the mask of each image in the collection
|
|
3394
|
+
masked_collection = self.collection.map(lambda img: img.updateMask(area)\
|
|
3395
|
+
.copyProperties(img).set('system:time_start', img.get('system:time_start')))
|
|
2878
3396
|
|
|
2879
3397
|
# Return the updated object
|
|
2880
|
-
return
|
|
3398
|
+
return LandsatCollection(collection=masked_collection)
|
|
2881
3399
|
|
|
2882
3400
|
def mask_halite(self, threshold, ng_threshold=None):
|
|
2883
3401
|
"""
|
|
@@ -2963,20 +3481,24 @@ class LandsatCollection:
|
|
|
2963
3481
|
if classify_above_threshold:
|
|
2964
3482
|
if mask_zeros is True:
|
|
2965
3483
|
col = self.collection.map(
|
|
2966
|
-
lambda image: image.select(band_name).gte(threshold).rename(band_name).selfMask()
|
|
3484
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name).selfMask()
|
|
3485
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2967
3486
|
)
|
|
2968
3487
|
else:
|
|
2969
3488
|
col = self.collection.map(
|
|
2970
|
-
lambda image: image.select(band_name).gte(threshold).rename(band_name)
|
|
3489
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name)
|
|
3490
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2971
3491
|
)
|
|
2972
3492
|
else:
|
|
2973
3493
|
if mask_zeros is True:
|
|
2974
3494
|
col = self.collection.map(
|
|
2975
|
-
lambda image: image.select(band_name).lte(threshold).rename(band_name).selfMask()
|
|
3495
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name).selfMask()
|
|
3496
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2976
3497
|
)
|
|
2977
3498
|
else:
|
|
2978
3499
|
col = self.collection.map(
|
|
2979
|
-
lambda image: image.select(band_name).lte(threshold).rename(band_name)
|
|
3500
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name)
|
|
3501
|
+
.copyProperties(image).set('system:time_start', image.get('system:time_start'))
|
|
2980
3502
|
)
|
|
2981
3503
|
|
|
2982
3504
|
return LandsatCollection(collection=col)
|
|
@@ -3017,6 +3539,237 @@ class LandsatCollection:
|
|
|
3017
3539
|
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
3540
|
return LandsatCollection(collection=col)
|
|
3019
3541
|
|
|
3542
|
+
def mann_kendall_trend(self, target_band=None, join_method='system:time_start', geometry=None):
|
|
3543
|
+
"""
|
|
3544
|
+
Calculates the Mann-Kendall S-value, Variance, Z-Score, and Confidence Level for each pixel in the image collection, in addition to calculating
|
|
3545
|
+
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'.
|
|
3546
|
+
|
|
3547
|
+
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.
|
|
3548
|
+
Note that this function is computationally intensive and may take a long time to run for large image collections or high-resolution images.
|
|
3549
|
+
|
|
3550
|
+
The 's_statistic' band represents the Mann-Kendall S-value, which is a measure of the strength and direction of the trend.
|
|
3551
|
+
The 'variance' band represents the variance of the S-value, which is a measure of the variability of the S-value.
|
|
3552
|
+
The 'z_score' band represents the Z-Score, which is a measure of the significance of the trend.
|
|
3553
|
+
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).
|
|
3554
|
+
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.
|
|
3555
|
+
|
|
3556
|
+
Be sure to select the correct band for the `target_band` parameter, as this will be used to calculate the trend statistics.
|
|
3557
|
+
You may optionally provide an ee.Geometry object for the `geometry` parameter to limit the area over which the trend statistics are calculated.
|
|
3558
|
+
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.
|
|
3559
|
+
|
|
3560
|
+
Args:
|
|
3561
|
+
image_collection (LandsatCollection or ee.ImageCollection): The input image collection for which the Mann-Kendall and Sen's slope trend statistics will be calculated.
|
|
3562
|
+
target_band (str): The band name to be used for the output anomaly image. e.g. 'ndvi'
|
|
3563
|
+
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'.
|
|
3564
|
+
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.
|
|
3565
|
+
|
|
3566
|
+
Returns:
|
|
3567
|
+
ee.Image: An image with the following bands: 's_statistic', 'variance', 'z_score', 'confidence', and 'slope'.
|
|
3568
|
+
"""
|
|
3569
|
+
########## PART 1 - S-VALUE CALCULATION ##########
|
|
3570
|
+
##### https://vsp.pnnl.gov/help/vsample/design_trend_mann_kendall.htm #####
|
|
3571
|
+
image_collection = self
|
|
3572
|
+
if isinstance(image_collection, LandsatCollection):
|
|
3573
|
+
image_collection = image_collection.collection
|
|
3574
|
+
elif isinstance(image_collection, ee.ImageCollection):
|
|
3575
|
+
pass
|
|
3576
|
+
else:
|
|
3577
|
+
raise ValueError(f'The chosen `image_collection`: {image_collection} is not a valid LandsatCollection or ee.ImageCollection object.')
|
|
3578
|
+
|
|
3579
|
+
if target_band is None:
|
|
3580
|
+
raise ValueError('The `target_band` parameter must be specified.')
|
|
3581
|
+
if not isinstance(target_band, str):
|
|
3582
|
+
raise ValueError(f'The chosen `target_band`: {target_band} is not a valid string.')
|
|
3583
|
+
|
|
3584
|
+
if geometry is not None and not isinstance(geometry, ee.Geometry):
|
|
3585
|
+
raise ValueError(f'The chosen `geometry`: {geometry} is not a valid ee.Geometry object.')
|
|
3586
|
+
# define the join, which will join all images newer than the current image
|
|
3587
|
+
# use system:time_start if the image does not have a Date_Filter property
|
|
3588
|
+
if join_method == 'system:time_start':
|
|
3589
|
+
# get all images where the leftField value is less than (before) the rightField value
|
|
3590
|
+
time_filter = ee.Filter.lessThan(leftField='system:time_start',
|
|
3591
|
+
rightField='system:time_start')
|
|
3592
|
+
elif join_method == 'Date_Filter':
|
|
3593
|
+
# get all images where the leftField value is less than (before) the rightField value
|
|
3594
|
+
time_filter = ee.Filter.lessThan(leftField='Date_Filter',
|
|
3595
|
+
rightField='Date_Filter')
|
|
3596
|
+
else:
|
|
3597
|
+
raise ValueError(f'The chosen `join_method`: {join_method} does not match the options of "system:time_start" or "Date_Filter".')
|
|
3598
|
+
|
|
3599
|
+
native_projection = image_collection.first().select(target_band).projection()
|
|
3600
|
+
|
|
3601
|
+
# for any matches during a join, set image as a property key called 'future_image'
|
|
3602
|
+
join = ee.Join.saveAll(matchesKey='future_image')
|
|
3603
|
+
|
|
3604
|
+
# apply the join on the input collection
|
|
3605
|
+
# joining all images newer than the current image with the current image
|
|
3606
|
+
joined_collection = ee.ImageCollection(join.apply(primary=image_collection,
|
|
3607
|
+
secondary=image_collection, condition=time_filter))
|
|
3608
|
+
|
|
3609
|
+
# defining a collection to calculate the partial S value for each match in the join
|
|
3610
|
+
# e.g. t4-t1, t3-t1, t2-1 if there are 4 images
|
|
3611
|
+
def calculate_partial_s(current_image):
|
|
3612
|
+
# select the target band for arithmetic
|
|
3613
|
+
current_val = current_image.select(target_band)
|
|
3614
|
+
# get the joined images from the current image properties and cast the joined images as a list
|
|
3615
|
+
future_image_list = ee.List(current_image.get('future_image'))
|
|
3616
|
+
# convert the joined list to an image collection
|
|
3617
|
+
future_image_collection = ee.ImageCollection(future_image_list)
|
|
3618
|
+
|
|
3619
|
+
# define a function that will calculate the difference between the joined images and the current image,
|
|
3620
|
+
# then calculate the partial S sign based on the value of the difference calculation
|
|
3621
|
+
def get_sign(future_image):
|
|
3622
|
+
# select the target band for arithmetic from the future image
|
|
3623
|
+
future_val = future_image.select(target_band)
|
|
3624
|
+
# calculate the difference, i.e. t2-t1
|
|
3625
|
+
difference = future_val.subtract(current_val)
|
|
3626
|
+
# determine the sign of the difference value (1 if diff > 0, 0 if 0, and -1 if diff < 0)
|
|
3627
|
+
# use .unmask(0) to set any masked pixels as 0 to avoid
|
|
3628
|
+
|
|
3629
|
+
sign = difference.signum().unmask(0)
|
|
3630
|
+
|
|
3631
|
+
return sign
|
|
3632
|
+
|
|
3633
|
+
# map the get_sign() function along the future image col
|
|
3634
|
+
# then sum the values for each pixel to get the partial S value
|
|
3635
|
+
return future_image_collection.map(get_sign).sum()
|
|
3636
|
+
|
|
3637
|
+
# calculate the partial s value for each image in the joined/input image collection
|
|
3638
|
+
partial_s_col = joined_collection.map(calculate_partial_s)
|
|
3639
|
+
|
|
3640
|
+
# convert the image collection to an image of s_statistic values per pixel
|
|
3641
|
+
# where the s_statistic is the sum of partial s values
|
|
3642
|
+
# renaming the band as 's_statistic' for later usage
|
|
3643
|
+
final_s_image = partial_s_col.sum().rename('s_statistic').setDefaultProjection(native_projection)
|
|
3644
|
+
|
|
3645
|
+
|
|
3646
|
+
########## PART 2 - VARIANCE and Z-SCORE ##########
|
|
3647
|
+
# to calculate variance we need to know how many pixels were involved in the partial_s calculations per pixel
|
|
3648
|
+
# we do this by using count() and turn the value to a float for later arithmetic
|
|
3649
|
+
n = image_collection.select(target_band).count().toFloat()
|
|
3650
|
+
|
|
3651
|
+
##### VARIANCE CALCULATION #####
|
|
3652
|
+
# as we are using floating point values with high precision, it is HIGHLY
|
|
3653
|
+
# unlikely that there will be multiple pixel values with the same value.
|
|
3654
|
+
# Thus, we opt to use the simplified variance calculation approach as the
|
|
3655
|
+
# impacts to the output value are negligible and the processing benefits are HUGE
|
|
3656
|
+
# variance = (n * (n - 1) * (2n + 5)) / 18
|
|
3657
|
+
var_s = n.multiply(n.subtract(1))\
|
|
3658
|
+
.multiply(n.multiply(2).add(5))\
|
|
3659
|
+
.divide(18).rename('variance')
|
|
3660
|
+
|
|
3661
|
+
z_score = ee.Image().expression(
|
|
3662
|
+
"""
|
|
3663
|
+
(s > 0) ? (s - 1) / sqrt(var) :
|
|
3664
|
+
(s < 0) ? (s + 1) / sqrt(var) :
|
|
3665
|
+
0
|
|
3666
|
+
""",
|
|
3667
|
+
{'s': final_s_image, 'var': var_s}
|
|
3668
|
+
).rename('z_score')
|
|
3669
|
+
|
|
3670
|
+
confidence = z_score.abs().divide(ee.Number(2).sqrt()).erf().rename('confidence')
|
|
3671
|
+
|
|
3672
|
+
stat_bands = ee.Image([var_s, z_score, confidence])
|
|
3673
|
+
|
|
3674
|
+
mk_stats_image = final_s_image.addBands(stat_bands)
|
|
3675
|
+
|
|
3676
|
+
########## PART 3 - Sen's Slope ##########
|
|
3677
|
+
def add_year_band(image):
|
|
3678
|
+
if join_method == 'Date_Filter':
|
|
3679
|
+
# Get the string 'YYYY-MM-DD'
|
|
3680
|
+
date_string = image.get('Date_Filter')
|
|
3681
|
+
# Parse it into an ee.Date object (handles the conversion to time math)
|
|
3682
|
+
date = ee.Date.parse('YYYY-MM-dd', date_string)
|
|
3683
|
+
else:
|
|
3684
|
+
# Standard way: assumes system:time_start exists
|
|
3685
|
+
date = image.date()
|
|
3686
|
+
years = date.difference(ee.Date('1970-01-01'), 'year')
|
|
3687
|
+
return image.addBands(ee.Image(years).float().rename('year'))
|
|
3688
|
+
|
|
3689
|
+
slope_input = image_collection.map(add_year_band).select(['year', target_band])
|
|
3690
|
+
|
|
3691
|
+
sens_slope = slope_input.reduce(ee.Reducer.sensSlope())
|
|
3692
|
+
|
|
3693
|
+
slope_band = sens_slope.select('slope')
|
|
3694
|
+
|
|
3695
|
+
# add a mask to the final image to remove pixels with less than min_observations
|
|
3696
|
+
# mainly an effort to mask pixels outside of the boundary of the input image collection
|
|
3697
|
+
min_observations = 1
|
|
3698
|
+
valid_mask = n.gte(min_observations)
|
|
3699
|
+
|
|
3700
|
+
final_image = mk_stats_image.addBands(slope_band).updateMask(valid_mask)
|
|
3701
|
+
|
|
3702
|
+
if geometry is not None:
|
|
3703
|
+
mask = ee.Image(1).clip(geometry)
|
|
3704
|
+
final_image = final_image.updateMask(mask)
|
|
3705
|
+
|
|
3706
|
+
return final_image.setDefaultProjection(native_projection)
|
|
3707
|
+
|
|
3708
|
+
def sens_slope_trend(self, target_band=None, join_method='system:time_start', geometry=None):
|
|
3709
|
+
"""
|
|
3710
|
+
Calculates Sen's Slope (trend magnitude) for the collection.
|
|
3711
|
+
This is a lighter-weight alternative to the full `mann_kendall_trend` function if only
|
|
3712
|
+
the direction and magnitude of the trend are needed.
|
|
3713
|
+
|
|
3714
|
+
Be sure to select the correct band for the `target_band` parameter, as this will be used to calculate the trend statistics.
|
|
3715
|
+
You may optionally provide an ee.Geometry object for the `geometry` parameter to limit the area over which the trend statistics are calculated.
|
|
3716
|
+
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.
|
|
3717
|
+
|
|
3718
|
+
Args:
|
|
3719
|
+
target_band (str): The name of the band to analyze. Defaults to 'ndvi'.
|
|
3720
|
+
join_method (str): Property to use for time sorting ('system:time_start' or 'Date_Filter').
|
|
3721
|
+
geometry (ee.Geometry, optional): Geometry to mask the final output.
|
|
3722
|
+
|
|
3723
|
+
Returns:
|
|
3724
|
+
ee.Image: An image containing the 'slope' band.
|
|
3725
|
+
"""
|
|
3726
|
+
image_collection = self
|
|
3727
|
+
if isinstance(image_collection, LandsatCollection):
|
|
3728
|
+
image_collection = image_collection.collection
|
|
3729
|
+
elif isinstance(image_collection, ee.ImageCollection):
|
|
3730
|
+
pass
|
|
3731
|
+
else:
|
|
3732
|
+
raise ValueError(f'The chosen `image_collection`: {image_collection} is not a valid LandsatCollection or ee.ImageCollection object.')
|
|
3733
|
+
|
|
3734
|
+
if target_band is None:
|
|
3735
|
+
raise ValueError('The `target_band` parameter must be specified.')
|
|
3736
|
+
if not isinstance(target_band, str):
|
|
3737
|
+
raise ValueError(f'The chosen `target_band`: {target_band} is not a valid string.')
|
|
3738
|
+
|
|
3739
|
+
if geometry is not None and not isinstance(geometry, ee.Geometry):
|
|
3740
|
+
raise ValueError(f'The chosen `geometry`: {geometry} is not a valid ee.Geometry object.')
|
|
3741
|
+
|
|
3742
|
+
native_projection = image_collection.first().select(target_band).projection()
|
|
3743
|
+
|
|
3744
|
+
# Add Year Band (Time X-Axis)
|
|
3745
|
+
def add_year_band(image):
|
|
3746
|
+
# Handle user-defined date strings vs system time
|
|
3747
|
+
if join_method == 'Date_Filter':
|
|
3748
|
+
date_string = image.get('Date_Filter')
|
|
3749
|
+
date = ee.Date.parse('YYYY-MM-dd', date_string)
|
|
3750
|
+
else:
|
|
3751
|
+
date = image.date()
|
|
3752
|
+
|
|
3753
|
+
# Convert to fractional years relative to epoch
|
|
3754
|
+
years = date.difference(ee.Date('1970-01-01'), 'year')
|
|
3755
|
+
return image.addBands(ee.Image(years).float().rename('year'))
|
|
3756
|
+
|
|
3757
|
+
# Prepare Collection: Select ONLY [Year, Target]
|
|
3758
|
+
# sensSlope expects Band 0 = Independent (X), Band 1 = Dependent (Y)
|
|
3759
|
+
slope_input = self.collection.map(add_year_band).select(['year', target_band])
|
|
3760
|
+
|
|
3761
|
+
# Run the Native Reducer
|
|
3762
|
+
sens_result = slope_input.reduce(ee.Reducer.sensSlope())
|
|
3763
|
+
|
|
3764
|
+
# Extract and Mask
|
|
3765
|
+
slope_band = sens_result.select('slope')
|
|
3766
|
+
|
|
3767
|
+
if geometry is not None:
|
|
3768
|
+
mask = ee.Image(1).clip(geometry)
|
|
3769
|
+
slope_band = slope_band.updateMask(mask)
|
|
3770
|
+
|
|
3771
|
+
return slope_band.setDefaultProjection(native_projection)
|
|
3772
|
+
|
|
3020
3773
|
def mask_via_band(self, band_to_mask, band_for_mask, threshold=-1, mask_above=True, add_band_to_original_image=False):
|
|
3021
3774
|
"""
|
|
3022
3775
|
Masks select pixels of a selected band from an image based on another specified band and threshold (optional).
|
|
@@ -3099,7 +3852,8 @@ class LandsatCollection:
|
|
|
3099
3852
|
)
|
|
3100
3853
|
|
|
3101
3854
|
# guarantee single band + keep properties
|
|
3102
|
-
out = ee.Image(out).select([band_name_to_mask]).copyProperties(prim, prim.propertyNames())
|
|
3855
|
+
out = ee.Image(out).select([band_name_to_mask]).copyProperties(prim, prim.propertyNames())\
|
|
3856
|
+
.set('system:time_start', prim.get('system:time_start'))
|
|
3103
3857
|
out = out.set('Date_Filter', prim.get('Date_Filter'))
|
|
3104
3858
|
return ee.Image(out) # <-- return as Image
|
|
3105
3859
|
|
|
@@ -3181,7 +3935,7 @@ class LandsatCollection:
|
|
|
3181
3935
|
new_col = self.collection.filter(ee.Filter.eq("Date_Filter", img_date))
|
|
3182
3936
|
return new_col.first()
|
|
3183
3937
|
|
|
3184
|
-
def
|
|
3938
|
+
def collectionStitch(self, img_col2):
|
|
3185
3939
|
"""
|
|
3186
3940
|
Function to mosaic two LandsatCollection objects which share image dates.
|
|
3187
3941
|
Mosaics are only formed for dates where both image collections have images.
|
|
@@ -3233,9 +3987,15 @@ class LandsatCollection:
|
|
|
3233
3987
|
|
|
3234
3988
|
# Return a LandsatCollection instance
|
|
3235
3989
|
return LandsatCollection(collection=new_col)
|
|
3990
|
+
|
|
3991
|
+
def CollectionStitch(self, img_col2):
|
|
3992
|
+
warnings.warn(
|
|
3993
|
+
"CollectionStitch is deprecated and will be removed in future versions. Please use the 'collectionStitch' property instead.",
|
|
3994
|
+
DeprecationWarning, stacklevel=2)
|
|
3995
|
+
return self.collectionStitch(img_col2)
|
|
3236
3996
|
|
|
3237
3997
|
@property
|
|
3238
|
-
def
|
|
3998
|
+
def mosaicByDateDepr(self):
|
|
3239
3999
|
"""
|
|
3240
4000
|
Property attribute function to mosaic collection images that share the same date.
|
|
3241
4001
|
|
|
@@ -3300,6 +4060,73 @@ class LandsatCollection:
|
|
|
3300
4060
|
|
|
3301
4061
|
# Convert the list of mosaics to an ImageCollection
|
|
3302
4062
|
return self._MosaicByDate
|
|
4063
|
+
|
|
4064
|
+
@property
|
|
4065
|
+
def mosaicByDate(self):
|
|
4066
|
+
"""
|
|
4067
|
+
Property attribute function to mosaic collection images that share the same date.
|
|
4068
|
+
|
|
4069
|
+
The property CLOUD_COVER for each image is used to calculate an overall mean,
|
|
4070
|
+
which replaces the CLOUD_COVER property for each mosaiced image.
|
|
4071
|
+
Server-side friendly.
|
|
4072
|
+
|
|
4073
|
+
NOTE: if images are removed from the collection from cloud filtering, you may have mosaics composed of only one image.
|
|
4074
|
+
|
|
4075
|
+
Returns:
|
|
4076
|
+
LandsatCollection: LandsatCollection image collection with mosaiced imagery and mean CLOUD_COVER as a property
|
|
4077
|
+
"""
|
|
4078
|
+
if self._MosaicByDate is None:
|
|
4079
|
+
distinct_dates = self.collection.distinct("Date_Filter")
|
|
4080
|
+
|
|
4081
|
+
# Define a join to link images by Date_Filter
|
|
4082
|
+
filter_date = ee.Filter.equals(leftField="Date_Filter", rightField="Date_Filter")
|
|
4083
|
+
join = ee.Join.saveAll(matchesKey="date_matches")
|
|
4084
|
+
|
|
4085
|
+
# Apply the join
|
|
4086
|
+
# Primary: Distinct dates collection
|
|
4087
|
+
# Secondary: The full original collection
|
|
4088
|
+
joined_col = ee.ImageCollection(join.apply(distinct_dates, self.collection, filter_date))
|
|
4089
|
+
|
|
4090
|
+
# Define the mosaicking function
|
|
4091
|
+
def _mosaic_day(img):
|
|
4092
|
+
# Recover the list of images for this day
|
|
4093
|
+
daily_list = ee.List(img.get("date_matches"))
|
|
4094
|
+
daily_col = ee.ImageCollection.fromImages(daily_list)
|
|
4095
|
+
|
|
4096
|
+
# Create the mosaic
|
|
4097
|
+
mosaic = daily_col.mosaic().setDefaultProjection(img.projection())
|
|
4098
|
+
|
|
4099
|
+
# Calculate mean metadata properties
|
|
4100
|
+
cloud_percentage = daily_col.aggregate_mean("CLOUD_COVER")
|
|
4101
|
+
|
|
4102
|
+
# Properties to preserve from the representative image
|
|
4103
|
+
props_of_interest = [
|
|
4104
|
+
"SPACECRAFT_ID",
|
|
4105
|
+
"SENSOR_ID",
|
|
4106
|
+
"PROCESSING_LEVEL",
|
|
4107
|
+
"ACQUISITION_DATE",
|
|
4108
|
+
"system:time_start",
|
|
4109
|
+
"Date_Filter"
|
|
4110
|
+
]
|
|
4111
|
+
|
|
4112
|
+
# Return mosaic with properties set
|
|
4113
|
+
return mosaic.copyProperties(img, props_of_interest).set({
|
|
4114
|
+
"CLOUD_COVER": cloud_percentage
|
|
4115
|
+
})
|
|
4116
|
+
|
|
4117
|
+
# 5. Map the function and wrap the result
|
|
4118
|
+
mosaiced_col = joined_col.map(_mosaic_day)
|
|
4119
|
+
self._MosaicByDate = LandsatCollection(collection=mosaiced_col)
|
|
4120
|
+
|
|
4121
|
+
# Convert the list of mosaics to an ImageCollection
|
|
4122
|
+
return self._MosaicByDate
|
|
4123
|
+
|
|
4124
|
+
@property
|
|
4125
|
+
def MosaicByDate(self):
|
|
4126
|
+
warnings.warn(
|
|
4127
|
+
"MosaicByDate is deprecated and will be removed in future versions. Please use the 'mosaicByDate' property instead.",
|
|
4128
|
+
DeprecationWarning, stacklevel=2)
|
|
4129
|
+
return self.mosaicByDate
|
|
3303
4130
|
|
|
3304
4131
|
@staticmethod
|
|
3305
4132
|
def ee_to_df(
|