RadGEEToolbox 1.6.8__py3-none-any.whl → 1.6.10__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/LandsatCollection.py +620 -13
- RadGEEToolbox/Sentinel1Collection.py +153 -7
- RadGEEToolbox/Sentinel2Collection.py +595 -15
- RadGEEToolbox/__init__.py +1 -1
- {radgeetoolbox-1.6.8.dist-info → radgeetoolbox-1.6.10.dist-info}/METADATA +22 -13
- radgeetoolbox-1.6.10.dist-info/RECORD +12 -0
- radgeetoolbox-1.6.8.dist-info/RECORD +0 -12
- {radgeetoolbox-1.6.8.dist-info → radgeetoolbox-1.6.10.dist-info}/WHEEL +0 -0
- {radgeetoolbox-1.6.8.dist-info → radgeetoolbox-1.6.10.dist-info}/licenses/LICENSE.txt +0 -0
- {radgeetoolbox-1.6.8.dist-info → radgeetoolbox-1.6.10.dist-info}/top_level.txt +0 -0
|
@@ -149,10 +149,12 @@ class LandsatCollection:
|
|
|
149
149
|
self.nbr_threshold = -1
|
|
150
150
|
self._masked_clouds_collection = None
|
|
151
151
|
self._masked_water_collection = None
|
|
152
|
+
self._masked_shadows_collection = None
|
|
152
153
|
self._masked_to_water_collection = None
|
|
153
154
|
self._geometry_masked_collection = None
|
|
154
155
|
self._geometry_masked_out_collection = None
|
|
155
156
|
self._median = None
|
|
157
|
+
self._monthly_median = None
|
|
156
158
|
self._mean = None
|
|
157
159
|
self._max = None
|
|
158
160
|
self._min = None
|
|
@@ -267,25 +269,25 @@ class LandsatCollection:
|
|
|
267
269
|
) # green-SWIR / green+SWIR -- full NDWI image
|
|
268
270
|
water = (
|
|
269
271
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
270
|
-
.rename("
|
|
272
|
+
.rename("mndwi")
|
|
271
273
|
.copyProperties(image)
|
|
272
274
|
)
|
|
273
275
|
if ng_threshold != None:
|
|
274
276
|
water = ee.Algorithms.If(
|
|
275
277
|
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
276
278
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
277
|
-
.rename("
|
|
279
|
+
.rename("mndwi")
|
|
278
280
|
.copyProperties(image)
|
|
279
281
|
.set("threshold", threshold),
|
|
280
282
|
mndwi_calc.updateMask(mndwi_calc.gte(ng_threshold))
|
|
281
|
-
.rename("
|
|
283
|
+
.rename("mndwi")
|
|
282
284
|
.copyProperties(image)
|
|
283
285
|
.set("threshold", ng_threshold),
|
|
284
286
|
)
|
|
285
287
|
else:
|
|
286
288
|
water = (
|
|
287
289
|
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
288
|
-
.rename("
|
|
290
|
+
.rename("mndwi")
|
|
289
291
|
.copyProperties(image)
|
|
290
292
|
)
|
|
291
293
|
return water
|
|
@@ -701,6 +703,59 @@ class LandsatCollection:
|
|
|
701
703
|
else:
|
|
702
704
|
nbr = nbr_calc.updateMask(nbr_calc.gte(threshold)).rename("nbr").copyProperties(image).set("threshold", threshold)
|
|
703
705
|
return nbr
|
|
706
|
+
|
|
707
|
+
@staticmethod
|
|
708
|
+
def anomaly_fn(image, geometry, band_name=None, anomaly_band_name=None, replace=True):
|
|
709
|
+
"""
|
|
710
|
+
Calculates the anomaly of a singleband image compared to the mean of the singleband image.
|
|
711
|
+
|
|
712
|
+
This function computes the anomaly for each band in the input image by
|
|
713
|
+
subtracting the mean value of that band from a provided image.
|
|
714
|
+
The anomaly is a measure of how much the pixel values deviate from the
|
|
715
|
+
average conditions represented by the mean of the image.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
image (ee.Image): An ee.Image for which the anomaly is to be calculated.
|
|
719
|
+
It is assumed that this image is a singleband image.
|
|
720
|
+
geometry (ee.Geometry): The geometry for image reduction to define the mean value to be used for anomaly calculation.
|
|
721
|
+
band_name (str, optional): A string representing the band name to be used for the output anomaly image. If not provided, the band name of the first band of the input image will be used.
|
|
722
|
+
anomaly_band_name (str, optional): A string representing the band name to be used for the output anomaly image. If not provided, the band name of the first band of the input image will be used.
|
|
723
|
+
replace (bool, optional): A boolean indicating whether to replace the original band with the anomaly band in the output image. If True, the output image will contain only the anomaly band. If False, the output image will contain both the original band and the anomaly band. Default is True.
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
ee.Image: An ee.Image where each band represents the anomaly (deviation from
|
|
727
|
+
the mean) for that band. The output image retains the same band name.
|
|
728
|
+
"""
|
|
729
|
+
if band_name:
|
|
730
|
+
band_name = band_name
|
|
731
|
+
else:
|
|
732
|
+
band_name = ee.String(image.bandNames().get(0))
|
|
733
|
+
|
|
734
|
+
image_to_process = image.select([band_name])
|
|
735
|
+
|
|
736
|
+
# Calculate the mean image of the provided collection.
|
|
737
|
+
mean_image = image_to_process.reduceRegion(
|
|
738
|
+
reducer=ee.Reducer.mean(),
|
|
739
|
+
geometry=geometry,
|
|
740
|
+
scale=30,
|
|
741
|
+
maxPixels=1e13
|
|
742
|
+
).toImage()
|
|
743
|
+
|
|
744
|
+
# Compute the anomaly by subtracting the mean image from the input image.
|
|
745
|
+
anomaly_image = image_to_process.subtract(mean_image)
|
|
746
|
+
if anomaly_band_name is None:
|
|
747
|
+
if band_name:
|
|
748
|
+
anomaly_image = anomaly_image.rename(band_name)
|
|
749
|
+
else:
|
|
750
|
+
# Preserve original properties from the input image.
|
|
751
|
+
anomaly_image = anomaly_image.rename(ee.String(image.bandNames().get(0)))
|
|
752
|
+
else:
|
|
753
|
+
anomaly_image = anomaly_image.rename(anomaly_band_name)
|
|
754
|
+
# return anomaly_image
|
|
755
|
+
if replace:
|
|
756
|
+
return anomaly_image.copyProperties(image)
|
|
757
|
+
else:
|
|
758
|
+
return image.addBands(anomaly_image, overwrite=True)
|
|
704
759
|
|
|
705
760
|
@staticmethod
|
|
706
761
|
def MaskWaterLandsat(image):
|
|
@@ -809,6 +864,70 @@ class LandsatCollection:
|
|
|
809
864
|
)
|
|
810
865
|
return water
|
|
811
866
|
|
|
867
|
+
@staticmethod
|
|
868
|
+
def mask_via_band_fn(image, band_to_mask, band_for_mask, threshold, mask_above=False, add_band_to_original_image=False):
|
|
869
|
+
"""
|
|
870
|
+
Masks pixels of interest from a specified band of a target image, based on a specified reference band and threshold.
|
|
871
|
+
Designed for single image input which contains both the target and reference band.
|
|
872
|
+
Example use case is masking vegetation from image when targeting land pixels. Can specify whether to mask pixels above or below the threshold.
|
|
873
|
+
|
|
874
|
+
Args:
|
|
875
|
+
image (ee.Image): input ee.Image
|
|
876
|
+
band_to_mask (str): name of the band which will be masked (target image)
|
|
877
|
+
band_for_mask (str): name of the band to use for the mask (band you want to remove/mask from target image)
|
|
878
|
+
threshold (float): value where pixels less or more than threshold (depending on `mask_above` argument) will be masked
|
|
879
|
+
mask_above (bool): if True, masks pixels above the threshold; if False, masks pixels below the threshold
|
|
880
|
+
|
|
881
|
+
Returns:
|
|
882
|
+
ee.Image: masked ee.Image
|
|
883
|
+
"""
|
|
884
|
+
|
|
885
|
+
band_to_mask_image = image.select(band_to_mask)
|
|
886
|
+
band_for_mask_image = image.select(band_for_mask)
|
|
887
|
+
|
|
888
|
+
mask = band_for_mask_image.lte(threshold) if mask_above else band_for_mask_image.gte(threshold)
|
|
889
|
+
|
|
890
|
+
if add_band_to_original_image:
|
|
891
|
+
return image.addBands(band_to_mask_image.updateMask(mask).rename(band_to_mask), overwrite=True)
|
|
892
|
+
else:
|
|
893
|
+
return ee.Image(band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image))
|
|
894
|
+
|
|
895
|
+
@staticmethod
|
|
896
|
+
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):
|
|
897
|
+
"""
|
|
898
|
+
Masks pixels of interest from a specified band of a target image, based on a specified reference band and threshold.
|
|
899
|
+
Designed for the case where the target and reference bands are in separate images.
|
|
900
|
+
Example use case is masking vegetation from image when targeting land pixels. Can specify whether to mask pixels above or below the threshold.
|
|
901
|
+
|
|
902
|
+
Args:
|
|
903
|
+
image_to_mask (ee.Image): image which will be masked (target image). If multiband, only the first band will be masked.
|
|
904
|
+
image_for_mask (ee.Image): image to use for the mask (image you want to remove/mask from target image). If multiband, only the first band will be used for the masked.
|
|
905
|
+
threshold (float): value where pixels less or more than threshold (depending on `mask_above` argument) will be masked
|
|
906
|
+
band_name_to_mask (str, optional): name of the band in image_to_mask to be masked. If None, the first band will be used.
|
|
907
|
+
band_name_for_mask (str, optional): name of the band in image_for_mask to be used for masking. If None, the first band will be used.
|
|
908
|
+
mask_above (bool): if True, masks pixels above the threshold; if False, masks pixels below the threshold.
|
|
909
|
+
|
|
910
|
+
Returns:
|
|
911
|
+
ee.Image: masked ee.Image
|
|
912
|
+
"""
|
|
913
|
+
if band_name_to_mask is None:
|
|
914
|
+
band_to_mask = ee.String(image_to_mask.bandNames().get(0))
|
|
915
|
+
else:
|
|
916
|
+
band_to_mask = ee.String(band_name_to_mask)
|
|
917
|
+
|
|
918
|
+
if band_name_for_mask is None:
|
|
919
|
+
band_for_mask = ee.String(image_for_mask.bandNames().get(0))
|
|
920
|
+
else:
|
|
921
|
+
band_for_mask = ee.String(band_name_for_mask)
|
|
922
|
+
|
|
923
|
+
band_to_mask_image = image_to_mask.select(band_to_mask)
|
|
924
|
+
band_for_mask_image = image_for_mask.select(band_for_mask)
|
|
925
|
+
if mask_above:
|
|
926
|
+
mask = band_for_mask_image.gt(threshold)
|
|
927
|
+
else:
|
|
928
|
+
mask = band_for_mask_image.lt(threshold)
|
|
929
|
+
return band_to_mask_image.updateMask(mask).rename(band_to_mask).copyProperties(image_to_mask)
|
|
930
|
+
|
|
812
931
|
@staticmethod
|
|
813
932
|
def halite_mask(image, threshold, ng_threshold=None):
|
|
814
933
|
"""
|
|
@@ -905,6 +1024,22 @@ class LandsatCollection:
|
|
|
905
1024
|
cirrus_mask = qa.bitwiseAnd(CirrusBitMask).eq(0)
|
|
906
1025
|
return image.updateMask(cloud_mask).updateMask(cirrus_mask)
|
|
907
1026
|
|
|
1027
|
+
@staticmethod
|
|
1028
|
+
def maskL8shadows(image):
|
|
1029
|
+
"""
|
|
1030
|
+
Masks cloud shadows based on Landsat 8 QA band.
|
|
1031
|
+
|
|
1032
|
+
Args:
|
|
1033
|
+
image (ee.Image): input ee.Image
|
|
1034
|
+
|
|
1035
|
+
Returns:
|
|
1036
|
+
ee.Image: ee.Image
|
|
1037
|
+
"""
|
|
1038
|
+
shadowBitMask = ee.Number(2).pow(4).int()
|
|
1039
|
+
qa = image.select("QA_PIXEL")
|
|
1040
|
+
shadow_mask = qa.bitwiseAnd(shadowBitMask).eq(0)
|
|
1041
|
+
return image.updateMask(shadow_mask)
|
|
1042
|
+
|
|
908
1043
|
@staticmethod
|
|
909
1044
|
def temperature_bands(img):
|
|
910
1045
|
"""
|
|
@@ -958,6 +1093,64 @@ class LandsatCollection:
|
|
|
958
1093
|
},
|
|
959
1094
|
).rename("LST")
|
|
960
1095
|
return image.addBands(LST).copyProperties(image) # Outputs temperature in C
|
|
1096
|
+
|
|
1097
|
+
@staticmethod
|
|
1098
|
+
def C_to_F_fn(LST_image):
|
|
1099
|
+
"""Converts Land Surface Temperature from Celsius to Fahrenheit.
|
|
1100
|
+
|
|
1101
|
+
This function takes an ee.Image with a band for Land Surface Temperature (LST)
|
|
1102
|
+
in degrees Celsius and converts it to degrees Fahrenheit using the formula:
|
|
1103
|
+
F = C * 9/5 + 32.
|
|
1104
|
+
|
|
1105
|
+
Args:
|
|
1106
|
+
LST_image: An ee.Image where each band represents LST in degrees Celsius.
|
|
1107
|
+
|
|
1108
|
+
Returns:
|
|
1109
|
+
ee.Image: An ee.Image where each band represents LST in degrees Fahrenheit. The output image retains the same band names as the input image alongside an additional band named 'LST_F'.
|
|
1110
|
+
"""
|
|
1111
|
+
LST_image = LST_image.select('LST')
|
|
1112
|
+
# Define the conversion formula from Celsius to Fahrenheit.
|
|
1113
|
+
fahrenheit_image = LST_image.multiply(9).divide(5).add(32)
|
|
1114
|
+
|
|
1115
|
+
# Preserve original properties from the input image.
|
|
1116
|
+
# return fahrenheit_image.rename('LST_F').copyProperties(LST_image)
|
|
1117
|
+
return LST_image.addBands(fahrenheit_image.rename('LST_F'), overwrite=True).copyProperties(LST_image)
|
|
1118
|
+
|
|
1119
|
+
@staticmethod
|
|
1120
|
+
def band_rename_fn(image, current_band_name, new_band_name):
|
|
1121
|
+
"""Renames a band in an ee.Image (single- or multi-band) in-place.
|
|
1122
|
+
|
|
1123
|
+
Replaces the band named `current_band_name` with `new_band_name` without
|
|
1124
|
+
retaining the original band name. If the band does not exist, returns the
|
|
1125
|
+
image unchanged.
|
|
1126
|
+
|
|
1127
|
+
Args:
|
|
1128
|
+
image (ee.Image): The input image (can be multiband).
|
|
1129
|
+
current_band_name (str): The existing band name to rename.
|
|
1130
|
+
new_band_name (str): The desired new band name.
|
|
1131
|
+
|
|
1132
|
+
Returns:
|
|
1133
|
+
ee.Image: The image with the band renamed (or unchanged if not found).
|
|
1134
|
+
"""
|
|
1135
|
+
img = ee.Image(image)
|
|
1136
|
+
current = ee.String(current_band_name)
|
|
1137
|
+
new = ee.String(new_band_name)
|
|
1138
|
+
|
|
1139
|
+
band_names = img.bandNames()
|
|
1140
|
+
has_band = band_names.contains(current)
|
|
1141
|
+
|
|
1142
|
+
def _rename():
|
|
1143
|
+
# Build a new band-name list with the target name replaced.
|
|
1144
|
+
new_names = band_names.map(
|
|
1145
|
+
lambda b: ee.String(
|
|
1146
|
+
ee.Algorithms.If(ee.String(b).equals(current), new, b)
|
|
1147
|
+
)
|
|
1148
|
+
)
|
|
1149
|
+
# Rename the image using the updated band-name list.
|
|
1150
|
+
return img.rename(ee.List(new_names))
|
|
1151
|
+
|
|
1152
|
+
out = ee.Image(ee.Algorithms.If(has_band, _rename(), img))
|
|
1153
|
+
return out.copyProperties(img)
|
|
961
1154
|
|
|
962
1155
|
@staticmethod
|
|
963
1156
|
def PixelAreaSum(
|
|
@@ -1071,12 +1264,12 @@ class LandsatCollection:
|
|
|
1071
1264
|
else:
|
|
1072
1265
|
raise ValueError("output_type must be 'ImageCollection' or 'LandsatCollection'")
|
|
1073
1266
|
|
|
1074
|
-
def
|
|
1267
|
+
def combine(self, other):
|
|
1075
1268
|
"""
|
|
1076
|
-
|
|
1269
|
+
Combines the current LandsatCollection with another LandsatCollection, using the `combine` method.
|
|
1077
1270
|
|
|
1078
1271
|
Args:
|
|
1079
|
-
other (LandsatCollection): Another LandsatCollection to
|
|
1272
|
+
other (LandsatCollection): Another LandsatCollection to combine with current collection.
|
|
1080
1273
|
|
|
1081
1274
|
Returns:
|
|
1082
1275
|
LandsatCollection: A new LandsatCollection containing images from both collections.
|
|
@@ -1089,6 +1282,72 @@ class LandsatCollection:
|
|
|
1089
1282
|
merged_collection = self.collection.combine(other.collection)
|
|
1090
1283
|
return LandsatCollection(collection=merged_collection)
|
|
1091
1284
|
|
|
1285
|
+
def merge(self, collections=None, multiband_collection=None, date_key='Date_Filter'):
|
|
1286
|
+
"""
|
|
1287
|
+
Merge many singleband LandsatCollection products into the parent collection,
|
|
1288
|
+
or merge a single multiband collection with parent collection,
|
|
1289
|
+
pairing images by exact Date_Filter and returning one multiband image per date.
|
|
1290
|
+
|
|
1291
|
+
NOTE: if you want to merge two multiband collections, use the `combine` method instead.
|
|
1292
|
+
|
|
1293
|
+
Args:
|
|
1294
|
+
collections (list): List of singleband collections to merge with parent collection, effectively adds one band per collection to each image in parent
|
|
1295
|
+
multiband_collection (LandsatCollection, optional): A multiband collection to merge with parent. Specifying a collection here will override `collections`.
|
|
1296
|
+
date_key (str): image property key for exact pairing (default 'Date_Filter')
|
|
1297
|
+
|
|
1298
|
+
Returns:
|
|
1299
|
+
LandsatCollection: parent with extra single bands attached (one image per date)
|
|
1300
|
+
"""
|
|
1301
|
+
|
|
1302
|
+
if collections is None and multiband_collection is not None:
|
|
1303
|
+
# Exact-date inner-join merge of two collections (adds ALL bands from 'other').
|
|
1304
|
+
join = ee.Join.inner()
|
|
1305
|
+
flt = ee.Filter.equals(leftField=date_key, rightField=date_key)
|
|
1306
|
+
paired = join.apply(self.collection, multiband_collection.collection, flt)
|
|
1307
|
+
|
|
1308
|
+
def _pair_two(f):
|
|
1309
|
+
f = ee.Feature(f)
|
|
1310
|
+
a = ee.Image(f.get('primary'))
|
|
1311
|
+
b = ee.Image(f.get('secondary'))
|
|
1312
|
+
# Overwrite on name collision
|
|
1313
|
+
merged = a.addBands(b, None, True)
|
|
1314
|
+
# Keep parent props + date key
|
|
1315
|
+
merged = merged.copyProperties(a, a.propertyNames())
|
|
1316
|
+
merged = merged.set(date_key, a.get(date_key))
|
|
1317
|
+
return ee.Image(merged)
|
|
1318
|
+
|
|
1319
|
+
return LandsatCollection(collection=ee.ImageCollection(paired.map(_pair_two)))
|
|
1320
|
+
|
|
1321
|
+
# Preferred path: merge many singleband products into the parent
|
|
1322
|
+
if not isinstance(collections, list) or len(collections) == 0:
|
|
1323
|
+
raise ValueError("Provide a non-empty list of LandsatCollection objects in `collections`.")
|
|
1324
|
+
|
|
1325
|
+
result = self.collection
|
|
1326
|
+
for extra in collections:
|
|
1327
|
+
if not isinstance(extra, LandsatCollection):
|
|
1328
|
+
raise ValueError("All items in `collections` must be LandsatCollection objects.")
|
|
1329
|
+
|
|
1330
|
+
join = ee.Join.inner()
|
|
1331
|
+
flt = ee.Filter.equals(leftField=date_key, rightField=date_key)
|
|
1332
|
+
paired = join.apply(result, extra.collection, flt)
|
|
1333
|
+
|
|
1334
|
+
def _attach_one(f):
|
|
1335
|
+
f = ee.Feature(f)
|
|
1336
|
+
parent = ee.Image(f.get('primary'))
|
|
1337
|
+
sb = ee.Image(f.get('secondary'))
|
|
1338
|
+
# Assume singleband product; grab its first band name server-side
|
|
1339
|
+
bname = ee.String(sb.bandNames().get(0))
|
|
1340
|
+
# Add the single band; overwrite if the name already exists in parent
|
|
1341
|
+
merged = parent.addBands(sb.select([bname]).rename([bname]), None, True)
|
|
1342
|
+
# Preserve parent props + date key
|
|
1343
|
+
merged = merged.copyProperties(parent, parent.propertyNames())
|
|
1344
|
+
merged = merged.set(date_key, parent.get(date_key))
|
|
1345
|
+
return ee.Image(merged)
|
|
1346
|
+
|
|
1347
|
+
result = ee.ImageCollection(paired.map(_attach_one))
|
|
1348
|
+
|
|
1349
|
+
return LandsatCollection(collection=result)
|
|
1350
|
+
|
|
1092
1351
|
@staticmethod
|
|
1093
1352
|
def dNDWIPixelAreaSum(image, geometry, band_name="ndwi", scale=30, maxPixels=1e12):
|
|
1094
1353
|
"""
|
|
@@ -1358,6 +1617,84 @@ class LandsatCollection:
|
|
|
1358
1617
|
col = self.collection.min()
|
|
1359
1618
|
self._min = col
|
|
1360
1619
|
return self._min
|
|
1620
|
+
|
|
1621
|
+
@property
|
|
1622
|
+
def monthly_median_collection(self):
|
|
1623
|
+
"""Creates a monthly median composite from a LandsatCollection image collection.
|
|
1624
|
+
|
|
1625
|
+
This function computes the median for each
|
|
1626
|
+
month within the collection's date range, for each band in the collection. It automatically handles the full
|
|
1627
|
+
temporal extent of the input collection.
|
|
1628
|
+
|
|
1629
|
+
The resulting images have a 'system:time_start' property set to the
|
|
1630
|
+
first day of each month and an 'image_count' property indicating how
|
|
1631
|
+
many images were used in the composite. Months with no images are
|
|
1632
|
+
automatically excluded from the final collection.
|
|
1633
|
+
|
|
1634
|
+
NOTE: the day of month for the 'system:time_start' property is set to the earliest date of the first month observed and may not be the first day of the month.
|
|
1635
|
+
|
|
1636
|
+
Returns:
|
|
1637
|
+
LandsatCollection: A new LandsatCollection object with monthly median composites.
|
|
1638
|
+
"""
|
|
1639
|
+
if self._monthly_median is None:
|
|
1640
|
+
collection = self.collection
|
|
1641
|
+
# Get the start and end dates of the entire collection.
|
|
1642
|
+
date_range = collection.reduceColumns(ee.Reducer.minMax(), ["system:time_start"])
|
|
1643
|
+
start_date = ee.Date(date_range.get('min'))
|
|
1644
|
+
end_date = ee.Date(date_range.get('max'))
|
|
1645
|
+
|
|
1646
|
+
# Calculate the total number of months in the date range.
|
|
1647
|
+
# The .round() is important for ensuring we get an integer.
|
|
1648
|
+
num_months = end_date.difference(start_date, 'month').round()
|
|
1649
|
+
|
|
1650
|
+
# Generate a list of starting dates for each month.
|
|
1651
|
+
# This uses a sequence and advances the start date by 'i' months.
|
|
1652
|
+
def get_month_start(i):
|
|
1653
|
+
return start_date.advance(i, 'month')
|
|
1654
|
+
|
|
1655
|
+
month_starts = ee.List.sequence(0, num_months).map(get_month_start)
|
|
1656
|
+
|
|
1657
|
+
# Define a function to map over the list of month start dates.
|
|
1658
|
+
def create_monthly_composite(date):
|
|
1659
|
+
# Cast the input to an ee.Date object.
|
|
1660
|
+
start_of_month = ee.Date(date)
|
|
1661
|
+
# The end date is exclusive, so we advance by 1 month.
|
|
1662
|
+
end_of_month = start_of_month.advance(1, 'month')
|
|
1663
|
+
|
|
1664
|
+
# Filter the original collection to get images for the current month.
|
|
1665
|
+
monthly_subset = collection.filterDate(start_of_month, end_of_month)
|
|
1666
|
+
|
|
1667
|
+
# Count the number of images in the monthly subset.
|
|
1668
|
+
image_count = monthly_subset.size()
|
|
1669
|
+
|
|
1670
|
+
# Compute the median. This is robust to outliers like clouds.
|
|
1671
|
+
monthly_median = monthly_subset.median()
|
|
1672
|
+
|
|
1673
|
+
# Set essential properties on the resulting composite image.
|
|
1674
|
+
# The timestamp is crucial for time-series analysis and charting.
|
|
1675
|
+
# The image_count is useful metadata for quality assessment.
|
|
1676
|
+
return monthly_median.set({
|
|
1677
|
+
'system:time_start': start_of_month.millis(),
|
|
1678
|
+
'month': start_of_month.get('month'),
|
|
1679
|
+
'year': start_of_month.get('year'),
|
|
1680
|
+
'Date_Filter': start_of_month.format('YYYY-MM-dd'),
|
|
1681
|
+
'image_count': image_count
|
|
1682
|
+
})
|
|
1683
|
+
|
|
1684
|
+
# Map the composite function over the list of month start dates.
|
|
1685
|
+
monthly_composites_list = month_starts.map(create_monthly_composite)
|
|
1686
|
+
|
|
1687
|
+
# Convert the list of images into an ee.ImageCollection.
|
|
1688
|
+
monthly_collection = ee.ImageCollection.fromImages(monthly_composites_list)
|
|
1689
|
+
|
|
1690
|
+
# Filter out any composites that were created from zero images.
|
|
1691
|
+
# This prevents empty/masked images from being in the final collection.
|
|
1692
|
+
final_collection = LandsatCollection(collection=monthly_collection.filter(ee.Filter.gt('image_count', 0)))
|
|
1693
|
+
self._monthly_median = final_collection
|
|
1694
|
+
else:
|
|
1695
|
+
pass
|
|
1696
|
+
|
|
1697
|
+
return self._monthly_median
|
|
1361
1698
|
|
|
1362
1699
|
@property
|
|
1363
1700
|
def ndwi(self):
|
|
@@ -2022,6 +2359,19 @@ class LandsatCollection:
|
|
|
2022
2359
|
col = self.collection.map(LandsatCollection.maskL8clouds)
|
|
2023
2360
|
self._masked_clouds_collection = LandsatCollection(collection=col)
|
|
2024
2361
|
return self._masked_clouds_collection
|
|
2362
|
+
|
|
2363
|
+
@property
|
|
2364
|
+
def masked_shadows_collection(self):
|
|
2365
|
+
"""
|
|
2366
|
+
Property attribute to mask shadows and return collection as class object.
|
|
2367
|
+
|
|
2368
|
+
Returns:
|
|
2369
|
+
LandsatCollection: LandsatCollection image collection
|
|
2370
|
+
"""
|
|
2371
|
+
if self._masked_shadows_collection is None:
|
|
2372
|
+
col = self.collection.map(LandsatCollection.maskL8shadows)
|
|
2373
|
+
self._masked_shadows_collection = LandsatCollection(collection=col)
|
|
2374
|
+
return self._masked_shadows_collection
|
|
2025
2375
|
|
|
2026
2376
|
@property
|
|
2027
2377
|
def LST(self):
|
|
@@ -2062,6 +2412,18 @@ class LandsatCollection:
|
|
|
2062
2412
|
.map(LandsatCollection.image_dater)
|
|
2063
2413
|
)
|
|
2064
2414
|
return LandsatCollection(collection=col)
|
|
2415
|
+
|
|
2416
|
+
def C_to_F(self):
|
|
2417
|
+
"""
|
|
2418
|
+
Function to convert an LST collection from Celcius to Fahrenheit, adding a new band 'LST_F' to each image in the collection.
|
|
2419
|
+
|
|
2420
|
+
Returns:
|
|
2421
|
+
LandsatCollection: A LandsatCollection image collection with LST in Fahrenheit as band titled 'LST_F'.
|
|
2422
|
+
"""
|
|
2423
|
+
if self._LST is None:
|
|
2424
|
+
raise ValueError("LST has not been calculated yet. Access the LST property first.")
|
|
2425
|
+
col = self._LST.collection.map(LandsatCollection.C_to_F_fn)
|
|
2426
|
+
return LandsatCollection(collection=col)
|
|
2065
2427
|
|
|
2066
2428
|
def mask_to_polygon(self, polygon):
|
|
2067
2429
|
"""
|
|
@@ -2169,14 +2531,17 @@ class LandsatCollection:
|
|
|
2169
2531
|
)
|
|
2170
2532
|
return LandsatCollection(collection=col)
|
|
2171
2533
|
|
|
2172
|
-
def binary_mask(self, threshold=None, band_name=None):
|
|
2534
|
+
def binary_mask(self, threshold=None, band_name=None, classify_above_threshold=True, mask_zeros=False):
|
|
2173
2535
|
"""
|
|
2174
2536
|
Function to create a binary mask (value of 1 for pixels above set threshold and value of 0 for all other pixels) of the LandsatCollection image collection based on a specified band.
|
|
2175
2537
|
If a singleband image is provided, the band name is automatically determined.
|
|
2176
2538
|
If multiple bands are available, the user must specify the band name to use for masking.
|
|
2177
2539
|
|
|
2178
2540
|
Args:
|
|
2541
|
+
threshold (float, optional): The threshold value for creating the binary mask. Defaults to None.
|
|
2179
2542
|
band_name (str, optional): The name of the band to use for masking. Defaults to None.
|
|
2543
|
+
classifiy_above_threshold (bool, optional): If True, pixels above the threshold are classified as 1. If False, pixels below the threshold are classified as 1. Defaults to True.
|
|
2544
|
+
mask_zeros (bool, optional): If True, pixels with a value of 0 after the binary mask are masked out in the output binary mask. Useful for classifications. Defaults to False.
|
|
2180
2545
|
|
|
2181
2546
|
Returns:
|
|
2182
2547
|
LandsatCollection: LandsatCollection singleband image collection with binary masks applied.
|
|
@@ -2195,11 +2560,175 @@ class LandsatCollection:
|
|
|
2195
2560
|
if threshold is None:
|
|
2196
2561
|
raise ValueError("Threshold must be specified for binary masking.")
|
|
2197
2562
|
|
|
2563
|
+
if classify_above_threshold:
|
|
2564
|
+
if mask_zeros:
|
|
2565
|
+
col = self.collection.map(
|
|
2566
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name).updateMask(image.select(band_name).gt(0)).copyProperties(image)
|
|
2567
|
+
)
|
|
2568
|
+
else:
|
|
2569
|
+
col = self.collection.map(
|
|
2570
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name).copyProperties(image)
|
|
2571
|
+
)
|
|
2572
|
+
else:
|
|
2573
|
+
if mask_zeros:
|
|
2574
|
+
col = self.collection.map(
|
|
2575
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name).updateMask(image.select(band_name).gt(0)).copyProperties(image)
|
|
2576
|
+
)
|
|
2577
|
+
else:
|
|
2578
|
+
col = self.collection.map(
|
|
2579
|
+
lambda image: image.select(band_name).lte(threshold).rename(band_name).copyProperties(image)
|
|
2580
|
+
)
|
|
2581
|
+
return LandsatCollection(collection=col)
|
|
2582
|
+
|
|
2583
|
+
def anomaly(self, geometry, band_name=None, anomaly_band_name=None, replace=True):
|
|
2584
|
+
"""
|
|
2585
|
+
Calculates the anomaly of each image in a collection compared to the mean of each image.
|
|
2586
|
+
|
|
2587
|
+
This function computes the anomaly for each band in the input image by
|
|
2588
|
+
subtracting the mean value of that band from a provided ImageCollection.
|
|
2589
|
+
The anomaly is a measure of how much the pixel values deviate from the
|
|
2590
|
+
average conditions represented by the collection.
|
|
2591
|
+
|
|
2592
|
+
Args:
|
|
2593
|
+
geometry (ee.Geometry): The geometry for image reduction to define the mean value to be used for anomaly calculation.
|
|
2594
|
+
band_name (str, optional): A string representing the band name to be used for the output anomaly image. If not provided, the band name of the first band of the input image will be used.
|
|
2595
|
+
anomaly_band_name (str, optional): A string representing the band name to be used for the output anomaly image. If not provided, the band name of the first band of the input image will be used.
|
|
2596
|
+
replace (bool, optional): A boolean indicating whether to replace the original band with the anomaly band. If True, the output image will only contain the anomaly band. If False, the output image will retain all original bands and add the anomaly band. Default is True.
|
|
2597
|
+
|
|
2598
|
+
Returns:
|
|
2599
|
+
LandsatCollection: A LandsatCollection where each image represents the anomaly (deviation from
|
|
2600
|
+
the mean) for the chosen band. The output images retain the same band name.
|
|
2601
|
+
"""
|
|
2602
|
+
if self.collection.size().eq(0).getInfo():
|
|
2603
|
+
raise ValueError("The collection is empty.")
|
|
2604
|
+
if band_name is None:
|
|
2605
|
+
first_image = self.collection.first()
|
|
2606
|
+
band_names = first_image.bandNames()
|
|
2607
|
+
if band_names.size().getInfo() == 0:
|
|
2608
|
+
raise ValueError("No bands available in the collection.")
|
|
2609
|
+
elif band_names.size().getInfo() > 1:
|
|
2610
|
+
band_name = band_names.get(0).getInfo()
|
|
2611
|
+
print("Multiple bands available, will be using the first band in the collection for anomaly calculation. Please specify a band name if you wish to use a different band.")
|
|
2612
|
+
else:
|
|
2613
|
+
band_name = band_names.get(0).getInfo()
|
|
2614
|
+
|
|
2615
|
+
col = self.collection.map(lambda image: LandsatCollection.anomaly_fn(image, geometry=geometry, band_name=band_name, anomaly_band_name=anomaly_band_name, replace=replace))
|
|
2616
|
+
return LandsatCollection(collection=col)
|
|
2617
|
+
|
|
2618
|
+
def mask_via_band(self, band_to_mask, band_for_mask, threshold=-1, mask_above=True, add_band_to_original_image=False):
|
|
2619
|
+
"""
|
|
2620
|
+
Masks select pixels of a selected band from an image based on another specified band and threshold (optional).
|
|
2621
|
+
Example use case is masking vegetation from image when targeting land pixels. Can specify whether to mask pixels above or below the threshold.
|
|
2622
|
+
|
|
2623
|
+
Args:
|
|
2624
|
+
band_to_mask (str): name of the band which will be masked (target image)
|
|
2625
|
+
band_for_mask (str): name of the band to use for the mask (band you want to remove/mask from target image)
|
|
2626
|
+
threshold (float): value between -1 and 1 where pixels less than threshold will be masked; defaults to -1 assuming input band is already classified (masked to pixels of interest).
|
|
2627
|
+
mask_above (bool): if True, masks pixels above the threshold; if False, masks pixels below the threshold
|
|
2628
|
+
|
|
2629
|
+
Returns:
|
|
2630
|
+
LandsatCollection: A new LandsatCollection with the specified band masked to pixels excluding from `band_for_mask`.
|
|
2631
|
+
"""
|
|
2632
|
+
if self.collection.size().eq(0).getInfo():
|
|
2633
|
+
raise ValueError("The collection is empty.")
|
|
2634
|
+
|
|
2198
2635
|
col = self.collection.map(
|
|
2199
|
-
lambda image:
|
|
2636
|
+
lambda image: LandsatCollection.mask_via_band_fn(
|
|
2637
|
+
image,
|
|
2638
|
+
band_to_mask=band_to_mask,
|
|
2639
|
+
band_for_mask=band_for_mask,
|
|
2640
|
+
threshold=threshold,
|
|
2641
|
+
mask_above=mask_above,
|
|
2642
|
+
add_band_to_original_image=add_band_to_original_image
|
|
2643
|
+
)
|
|
2200
2644
|
)
|
|
2201
2645
|
return LandsatCollection(collection=col)
|
|
2202
2646
|
|
|
2647
|
+
def mask_via_singleband_image(self, image_collection_for_mask, band_name_to_mask, band_name_for_mask, threshold=-1, mask_above=False, add_band_to_original_image=False):
|
|
2648
|
+
"""
|
|
2649
|
+
Masks select pixels of a selected band from an image collection based on another specified singleband image collection and threshold (optional).
|
|
2650
|
+
Example use case is masking vegetation from image when targeting land pixels. Can specify whether to mask pixels above or below the threshold.
|
|
2651
|
+
This function pairs images from the two collections based on an exact match of the 'Date_Filter' property.
|
|
2652
|
+
|
|
2653
|
+
Args:
|
|
2654
|
+
image_collection_for_mask (LandsatCollection): LandsatCollection image collection to use for masking (source of pixels that will be used to mask the parent image collection)
|
|
2655
|
+
band_name_to_mask (str): name of the band which will be masked (target image)
|
|
2656
|
+
band_name_for_mask (str): name of the band to use for the mask (band which contains pixels the user wants to remove/mask from target image)
|
|
2657
|
+
threshold (float): threshold value where pixels less (or more, depending on `mask_above`) than threshold will be masked; defaults to -1.
|
|
2658
|
+
mask_above (bool): if True, masks pixels above the threshold; if False, masks pixels below the threshold
|
|
2659
|
+
add_band_to_original_image (bool): if True, adds the band used for masking to the original image as an additional band; if False, only the masked band is retained in the output image.
|
|
2660
|
+
|
|
2661
|
+
Returns:
|
|
2662
|
+
LandsatCollection: A new LandsatCollection with the specified band masked to pixels excluding from `band_for_mask`.
|
|
2663
|
+
"""
|
|
2664
|
+
|
|
2665
|
+
if self.collection.size().eq(0).getInfo():
|
|
2666
|
+
raise ValueError("The collection is empty.")
|
|
2667
|
+
if not isinstance(image_collection_for_mask, LandsatCollection):
|
|
2668
|
+
raise ValueError("image_collection_for_mask must be a LandsatCollection object.")
|
|
2669
|
+
size1 = self.collection.size().getInfo()
|
|
2670
|
+
size2 = image_collection_for_mask.collection.size().getInfo()
|
|
2671
|
+
if size1 != size2:
|
|
2672
|
+
raise ValueError(f"Warning: Collections have different sizes ({size1} vs {size2}). Please ensure both collections have the same number of images and matching dates.")
|
|
2673
|
+
if size1 == 0 or size2 == 0:
|
|
2674
|
+
raise ValueError("Warning: One of the input collections is empty.")
|
|
2675
|
+
|
|
2676
|
+
# Pair by exact Date_Filter property
|
|
2677
|
+
primary = self.collection.select([band_name_to_mask])
|
|
2678
|
+
secondary = image_collection_for_mask.collection.select([band_name_for_mask])
|
|
2679
|
+
join = ee.Join.inner()
|
|
2680
|
+
flt = ee.Filter.equals(leftField='Date_Filter', rightField='Date_Filter')
|
|
2681
|
+
paired = join.apply(primary, secondary, flt)
|
|
2682
|
+
|
|
2683
|
+
def _map_pair(f):
|
|
2684
|
+
f = ee.Feature(f) # <-- treat as Feature
|
|
2685
|
+
prim = ee.Image(f.get('primary')) # <-- get the primary Image
|
|
2686
|
+
sec = ee.Image(f.get('secondary')) # <-- get the secondary Image
|
|
2687
|
+
|
|
2688
|
+
merged = prim.addBands(sec.select([band_name_for_mask]))
|
|
2689
|
+
|
|
2690
|
+
out = LandsatCollection.mask_via_band_fn(
|
|
2691
|
+
merged,
|
|
2692
|
+
band_to_mask=band_name_to_mask,
|
|
2693
|
+
band_for_mask=band_name_for_mask,
|
|
2694
|
+
threshold=threshold,
|
|
2695
|
+
mask_above=mask_above,
|
|
2696
|
+
add_band_to_original_image=add_band_to_original_image
|
|
2697
|
+
)
|
|
2698
|
+
|
|
2699
|
+
# guarantee single band + keep properties
|
|
2700
|
+
out = ee.Image(out).select([band_name_to_mask]).copyProperties(prim, prim.propertyNames())
|
|
2701
|
+
out = out.set('Date_Filter', prim.get('Date_Filter'))
|
|
2702
|
+
return ee.Image(out) # <-- return as Image
|
|
2703
|
+
|
|
2704
|
+
col = ee.ImageCollection(paired.map(_map_pair))
|
|
2705
|
+
return LandsatCollection(collection=col)
|
|
2706
|
+
|
|
2707
|
+
def band_rename(self, current_band_name, new_band_name):
|
|
2708
|
+
"""Renames a band in all images of the LandsatCollection in-place.
|
|
2709
|
+
|
|
2710
|
+
Replaces the band named `current_band_name` with `new_band_name` without
|
|
2711
|
+
retaining the original band name. If the band does not exist in an image,
|
|
2712
|
+
that image is returned unchanged.
|
|
2713
|
+
|
|
2714
|
+
Args:
|
|
2715
|
+
current_band_name (str): The existing band name to rename.
|
|
2716
|
+
new_band_name (str): The desired new band name.
|
|
2717
|
+
|
|
2718
|
+
Returns:
|
|
2719
|
+
LandsatCollection: The LandsatCollection with the band renamed in all images.
|
|
2720
|
+
"""
|
|
2721
|
+
# check if `current_band_name` exists in the first image
|
|
2722
|
+
first_image = self.collection.first()
|
|
2723
|
+
has_band = first_image.bandNames().contains(current_band_name).getInfo()
|
|
2724
|
+
if not has_band:
|
|
2725
|
+
raise ValueError(f"Band '{current_band_name}' does not exist in the collection.")
|
|
2726
|
+
|
|
2727
|
+
renamed_collection = self.collection.map(
|
|
2728
|
+
lambda img: self.band_rename_fn(img, current_band_name, new_band_name)
|
|
2729
|
+
)
|
|
2730
|
+
return LandsatCollection(collection=renamed_collection)
|
|
2731
|
+
|
|
2203
2732
|
def image_grab(self, img_selector):
|
|
2204
2733
|
"""
|
|
2205
2734
|
Selects ("grabs") an image by index from the collection. Easy way to get latest image or browse imagery one-by-one.
|
|
@@ -2879,6 +3408,7 @@ class LandsatCollection:
|
|
|
2879
3408
|
def iterate_zonal_stats(
|
|
2880
3409
|
self,
|
|
2881
3410
|
geometries,
|
|
3411
|
+
band=None,
|
|
2882
3412
|
reducer_type="mean",
|
|
2883
3413
|
scale=30,
|
|
2884
3414
|
geometry_names=None,
|
|
@@ -2894,6 +3424,7 @@ class LandsatCollection:
|
|
|
2894
3424
|
|
|
2895
3425
|
Args:
|
|
2896
3426
|
geometries (ee.Geometry, ee.Feature, ee.FeatureCollection, list, or tuple): Input geometries for which to extract statistics. Can be a single ee.Geometry, an ee.Feature, an ee.FeatureCollection, a list of (lon, lat) tuples, or a list of ee.Geometry objects. Be careful to NOT provide coordinates as (lat, lon)!
|
|
3427
|
+
band (str, optional): The name of the band to use for statistics. If None, the first band is used. Defaults to None.
|
|
2897
3428
|
reducer_type (str, optional): The ee.Reducer to use, e.g., 'mean', 'median', 'max', 'sum'. Defaults to 'mean'. Any ee.Reducer method can be used.
|
|
2898
3429
|
scale (int, optional): Pixel scale in meters for the reduction. Defaults to 30.
|
|
2899
3430
|
geometry_names (list, optional): A list of string names for the geometries. If provided, must match the number of geometries. Defaults to None.
|
|
@@ -2911,6 +3442,12 @@ class LandsatCollection:
|
|
|
2911
3442
|
TypeError: If geometries input type is unsupported.
|
|
2912
3443
|
"""
|
|
2913
3444
|
img_collection_obj = self
|
|
3445
|
+
if band:
|
|
3446
|
+
img_collection_obj = LandsatCollection(collection=img_collection_obj.collection.select(band))
|
|
3447
|
+
else:
|
|
3448
|
+
first_image = img_collection_obj.image_grab(0)
|
|
3449
|
+
first_band = first_image.bandNames().get(0)
|
|
3450
|
+
img_collection_obj = LandsatCollection(collection=img_collection_obj.collection.select([first_band]))
|
|
2914
3451
|
# Filter collection by dates if provided
|
|
2915
3452
|
if dates:
|
|
2916
3453
|
img_collection_obj = LandsatCollection(
|
|
@@ -2993,18 +3530,33 @@ class LandsatCollection:
|
|
|
2993
3530
|
stats_fc = image.reduceRegions(
|
|
2994
3531
|
collection=features, reducer=reducer, scale=scale, tileScale=tileScale
|
|
2995
3532
|
)
|
|
2996
|
-
|
|
3533
|
+
|
|
3534
|
+
def guarantee_reducer_property(f):
|
|
3535
|
+
has_property = f.propertyNames().contains(reducer_type)
|
|
3536
|
+
return ee.Algorithms.If(has_property, f, f.set(reducer_type, -9999))
|
|
3537
|
+
fixed_stats_fc = stats_fc.map(guarantee_reducer_property)
|
|
3538
|
+
|
|
3539
|
+
return fixed_stats_fc.map(lambda f: f.set('image_date', image_date))
|
|
2997
3540
|
|
|
2998
3541
|
results_fc = ee.FeatureCollection(img_collection_obj.collection.map(calculate_stats_for_image)).flatten()
|
|
2999
3542
|
df = LandsatCollection.ee_to_df(results_fc, remove_geom=True)
|
|
3000
3543
|
|
|
3001
3544
|
# Checking for issues
|
|
3002
3545
|
if df.empty:
|
|
3003
|
-
print("No results found for the given parameters. Check if the geometries intersect with the images, if the dates filter is too restrictive, or if the provided bands are empty.")
|
|
3004
|
-
return df
|
|
3546
|
+
# print("No results found for the given parameters. Check if the geometries intersect with the images, if the dates filter is too restrictive, or if the provided bands are empty.")
|
|
3547
|
+
# return df
|
|
3548
|
+
raise ValueError("No results found for the given parameters. Check if the geometries intersect with the images, if the dates filter is too restrictive, or if the provided bands are empty.")
|
|
3005
3549
|
if reducer_type not in df.columns:
|
|
3006
3550
|
print(f"Warning: Reducer '{reducer_type}' not found in results.")
|
|
3007
|
-
return df
|
|
3551
|
+
# return df
|
|
3552
|
+
|
|
3553
|
+
# Get the number of rows before dropping nulls for a helpful message
|
|
3554
|
+
initial_rows = len(df)
|
|
3555
|
+
df.dropna(subset=[reducer_type], inplace=True)
|
|
3556
|
+
df = df[df[reducer_type] != -9999]
|
|
3557
|
+
dropped_rows = initial_rows - len(df)
|
|
3558
|
+
if dropped_rows > 0:
|
|
3559
|
+
print(f"Warning: Discarded {dropped_rows} results due to failed reductions (e.g., no valid pixels in geometry).")
|
|
3008
3560
|
|
|
3009
3561
|
# Reshape DataFrame to have dates as index and geometry names as columns
|
|
3010
3562
|
pivot_df = df.pivot(index='image_date', columns='geo_name', values=reducer_type)
|
|
@@ -3018,6 +3570,61 @@ class LandsatCollection:
|
|
|
3018
3570
|
return
|
|
3019
3571
|
return pivot_df
|
|
3020
3572
|
|
|
3573
|
+
def export_to_asset_collection(
|
|
3574
|
+
self,
|
|
3575
|
+
asset_collection_path,
|
|
3576
|
+
region,
|
|
3577
|
+
scale,
|
|
3578
|
+
dates=None,
|
|
3579
|
+
filename_prefix="",
|
|
3580
|
+
crs=None,
|
|
3581
|
+
max_pixels=int(1e13),
|
|
3582
|
+
description_prefix="export"
|
|
3583
|
+
):
|
|
3584
|
+
"""
|
|
3585
|
+
Exports an image collection to a Google Earth Engine asset collection. The asset collection will be created if it does not already exist,
|
|
3586
|
+
and each image exported will be named according to the provided filename prefix and date.
|
|
3587
|
+
|
|
3588
|
+
Args:
|
|
3589
|
+
asset_collection_path (str): The path to the asset collection.
|
|
3590
|
+
region (ee.Geometry): The region to export.
|
|
3591
|
+
scale (int): The scale of the export.
|
|
3592
|
+
dates (list, optional): The dates to export. Defaults to None.
|
|
3593
|
+
filename_prefix (str, optional): The filename prefix. Defaults to "", i.e. blank.
|
|
3594
|
+
crs (str, optional): The coordinate reference system. Defaults to None, which will use the image's CRS.
|
|
3595
|
+
max_pixels (int, optional): The maximum number of pixels. Defaults to int(1e13).
|
|
3596
|
+
description_prefix (str, optional): The description prefix. Defaults to "export".
|
|
3597
|
+
|
|
3598
|
+
Returns:
|
|
3599
|
+
None: (queues export tasks)
|
|
3600
|
+
"""
|
|
3601
|
+
ic = self.collection
|
|
3602
|
+
if dates is None:
|
|
3603
|
+
dates = self.dates
|
|
3604
|
+
try:
|
|
3605
|
+
ee.data.createAsset({'type': 'ImageCollection'}, asset_collection_path)
|
|
3606
|
+
except Exception:
|
|
3607
|
+
pass
|
|
3608
|
+
|
|
3609
|
+
for date_str in dates:
|
|
3610
|
+
img = ee.Image(ic.filter(ee.Filter.eq('Date_Filter', date_str)).first())
|
|
3611
|
+
asset_id = asset_collection_path + "/" + filename_prefix + date_str
|
|
3612
|
+
desc = description_prefix + "_" + filename_prefix + date_str
|
|
3613
|
+
|
|
3614
|
+
params = {
|
|
3615
|
+
'image': img,
|
|
3616
|
+
'description': desc,
|
|
3617
|
+
'assetId': asset_id,
|
|
3618
|
+
'region': region,
|
|
3619
|
+
'scale': scale,
|
|
3620
|
+
'maxPixels': max_pixels
|
|
3621
|
+
}
|
|
3622
|
+
if crs:
|
|
3623
|
+
params['crs'] = crs
|
|
3624
|
+
|
|
3625
|
+
ee.batch.Export.image.toAsset(**params).start()
|
|
3626
|
+
|
|
3627
|
+
print("Queued", len(dates), "export tasks to", asset_collection_path)
|
|
3021
3628
|
|
|
3022
3629
|
|
|
3023
3630
|
|