RadGEEToolbox 1.6.3__py3-none-any.whl → 1.6.5__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 +20 -10
- RadGEEToolbox/GetPalette.py +109 -15
- RadGEEToolbox/LandsatCollection.py +924 -359
- RadGEEToolbox/Sentinel1Collection.py +568 -292
- RadGEEToolbox/Sentinel2Collection.py +685 -258
- RadGEEToolbox/VisParams.py +83 -83
- RadGEEToolbox/__init__.py +2 -2
- {radgeetoolbox-1.6.3.dist-info → radgeetoolbox-1.6.5.dist-info}/METADATA +26 -7
- radgeetoolbox-1.6.5.dist-info/RECORD +12 -0
- radgeetoolbox-1.6.3.dist-info/RECORD +0 -12
- {radgeetoolbox-1.6.3.dist-info → radgeetoolbox-1.6.5.dist-info}/WHEEL +0 -0
- {radgeetoolbox-1.6.3.dist-info → radgeetoolbox-1.6.5.dist-info}/licenses/LICENSE.txt +0 -0
- {radgeetoolbox-1.6.3.dist-info → radgeetoolbox-1.6.5.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import ee
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# ---- Reflectance scaling for Landsat Collection 2 SR ----
|
|
7
|
+
_LS_SR_BANDS = ["SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7"]
|
|
8
|
+
_LS_SCALE = 0.0000275
|
|
9
|
+
_LS_OFFSET = -0.2
|
|
10
|
+
|
|
11
|
+
def _scale_landsat_sr(img):
|
|
12
|
+
"""
|
|
13
|
+
Converts Landsat C2 SR DN values to reflectance values for SR_B1..SR_B7 (overwrite bands).
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
img (ee.Image): Input Landsat image without scaled bands.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
ee.Image: Image with scaled reflectance bands.
|
|
20
|
+
"""
|
|
21
|
+
img = ee.Image(img)
|
|
22
|
+
already = ee.String(img.get('rgt:scaled')).eq('landsat_sr')
|
|
23
|
+
scaled = img.select(_LS_SR_BANDS).multiply(_LS_SCALE).add(_LS_OFFSET)
|
|
24
|
+
scaled = img.addBands(scaled, None, True).set('rgt:scaled','landsat_sr')
|
|
25
|
+
return ee.Image(ee.Algorithms.If(already, img, scaled))
|
|
26
|
+
|
|
4
27
|
class LandsatCollection:
|
|
5
28
|
"""
|
|
6
29
|
Represents a user-defined collection of NASA/USGS Landsat 5, 8, and 9 TM & OLI surface reflectance satellite images at 30 m/px from Google Earth Engine (GEE).
|
|
7
30
|
|
|
8
31
|
This class enables simplified definition, filtering, masking, and processing of multispectral Landsat imagery.
|
|
9
|
-
It supports multiple spatial and temporal filters, caching for efficient computation, and direct computation of
|
|
32
|
+
It supports multiple spatial and temporal filters, caching for efficient computation, and direct computation of
|
|
10
33
|
key spectral indices like NDWI, NDVI, halite index, and more. It also includes utilities for cloud masking,
|
|
11
34
|
mosaicking, zonal statistics, and transect analysis.
|
|
12
35
|
|
|
@@ -22,10 +45,11 @@ class LandsatCollection:
|
|
|
22
45
|
cloud_percentage_threshold (int, optional): Max allowed cloud cover percentage. Defaults to 100.
|
|
23
46
|
boundary (ee.Geometry, optional): A geometry for filtering to images that intersect with the boundary shape. Overrides `tile_path` and `tile_row` if provided.
|
|
24
47
|
collection (ee.ImageCollection, optional): A pre-filtered Landsat ee.ImageCollection object to be converted to a LandsatCollection object. Overrides all other filters.
|
|
48
|
+
scale_bands (bool, optional): If True, all SR bands will be scaled from DN values to reflectance values. Defaults to False.
|
|
25
49
|
|
|
26
50
|
Attributes:
|
|
27
|
-
collection (ee.ImageCollection): The filtered or user-supplied image collection converted to an ee.ImageCollection object.
|
|
28
|
-
|
|
51
|
+
collection (ee.ImageCollection): The filtered or user-supplied image collection converted to an ee.ImageCollection object.
|
|
52
|
+
|
|
29
53
|
Raises:
|
|
30
54
|
ValueError: Raised if required filter parameters are missing, or if both `collection` and other filters are provided.
|
|
31
55
|
|
|
@@ -48,11 +72,31 @@ class LandsatCollection:
|
|
|
48
72
|
>>> latest_image = cloud_masked.image_grab(-1)
|
|
49
73
|
>>> ndwi_collection = image_collection.ndwi
|
|
50
74
|
"""
|
|
51
|
-
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
start_date=None,
|
|
79
|
+
end_date=None,
|
|
80
|
+
tile_row=None,
|
|
81
|
+
tile_path=None,
|
|
82
|
+
cloud_percentage_threshold=None,
|
|
83
|
+
boundary=None,
|
|
84
|
+
collection=None,
|
|
85
|
+
scale_bands=False,
|
|
86
|
+
):
|
|
52
87
|
if collection is None and (start_date is None or end_date is None):
|
|
53
|
-
raise ValueError(
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
raise ValueError(
|
|
89
|
+
"Either provide all required fields (start_date, end_date, tile_row, tile_path ; or boundary in place of tiles) or provide a collection."
|
|
90
|
+
)
|
|
91
|
+
if (
|
|
92
|
+
tile_row is None
|
|
93
|
+
and tile_path is None
|
|
94
|
+
and boundary is None
|
|
95
|
+
and collection is None
|
|
96
|
+
):
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"Provide either tile or boundary/geometry specifications to filter the image collection"
|
|
99
|
+
)
|
|
56
100
|
if collection is None:
|
|
57
101
|
self.start_date = start_date
|
|
58
102
|
self.end_date = end_date
|
|
@@ -85,11 +129,13 @@ class LandsatCollection:
|
|
|
85
129
|
self.collection = self.get_boundary_filtered_collection()
|
|
86
130
|
else:
|
|
87
131
|
self.collection = collection
|
|
132
|
+
if scale_bands:
|
|
133
|
+
self.collection = self.collection.map(_scale_landsat_sr)
|
|
88
134
|
|
|
89
|
-
|
|
90
135
|
self._dates_list = None
|
|
91
136
|
self._dates = None
|
|
92
137
|
self.ndwi_threshold = -1
|
|
138
|
+
self.mndwi_threshold = -1
|
|
93
139
|
self.ndvi_threshold = -1
|
|
94
140
|
self.halite_threshold = -1
|
|
95
141
|
self.gypsum_threshold = -1
|
|
@@ -105,6 +151,7 @@ class LandsatCollection:
|
|
|
105
151
|
self._max = None
|
|
106
152
|
self._min = None
|
|
107
153
|
self._ndwi = None
|
|
154
|
+
self._mndwi = None
|
|
108
155
|
self._ndvi = None
|
|
109
156
|
self._halite = None
|
|
110
157
|
self._gypsum = None
|
|
@@ -113,42 +160,88 @@ class LandsatCollection:
|
|
|
113
160
|
self._LST = None
|
|
114
161
|
self._MosaicByDate = None
|
|
115
162
|
self._PixelAreaSumCollection = None
|
|
163
|
+
self._Reflectance = None
|
|
116
164
|
|
|
117
165
|
@staticmethod
|
|
118
166
|
def image_dater(image):
|
|
119
167
|
"""
|
|
120
168
|
Adds date to image properties as 'Date_Filter'.
|
|
121
169
|
|
|
122
|
-
Args:
|
|
170
|
+
Args:
|
|
123
171
|
image (ee.Image): Input image
|
|
124
172
|
|
|
125
|
-
Returns:
|
|
173
|
+
Returns:
|
|
126
174
|
ee.Image: Image with date in properties.
|
|
127
175
|
"""
|
|
128
|
-
date = ee.Number(image.date().format(
|
|
129
|
-
return image.set({
|
|
130
|
-
|
|
176
|
+
date = ee.Number(image.date().format("YYYY-MM-dd"))
|
|
177
|
+
return image.set({"Date_Filter": date})
|
|
178
|
+
|
|
131
179
|
@staticmethod
|
|
132
180
|
def landsat5bandrename(img):
|
|
133
181
|
"""
|
|
134
182
|
Renames Landsat 5 bands to match Landsat 8 & 9.
|
|
135
183
|
|
|
136
|
-
Args:
|
|
184
|
+
Args:
|
|
137
185
|
image (ee.Image): input image
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
140
188
|
ee.Image: image with renamed bands
|
|
141
189
|
"""
|
|
142
|
-
return img.select(
|
|
143
|
-
|
|
190
|
+
return img.select(
|
|
191
|
+
"SR_B1", "SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B7", "QA_PIXEL"
|
|
192
|
+
).rename("SR_B2", "SR_B3", "SR_B4", "SR_B5", "SR_B6", "SR_B7", "QA_PIXEL")
|
|
193
|
+
|
|
144
194
|
@staticmethod
|
|
145
195
|
def landsat_ndwi_fn(image, threshold, ng_threshold=None):
|
|
146
196
|
"""
|
|
147
|
-
Calculates ndwi from GREEN and NIR bands (McFeeters, 1996 - https://doi.org/10.1080/01431169608948714) for Landsat imagery and mask image based on threshold.
|
|
148
|
-
|
|
197
|
+
Calculates ndwi from GREEN and NIR bands (McFeeters, 1996 - https://doi.org/10.1080/01431169608948714) for Landsat imagery and mask image based on threshold.
|
|
198
|
+
|
|
199
|
+
Can specify separate thresholds for Landsat 5 vs 8 & 9 images, where the threshold argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8 & 9.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
image (ee.Image): input image
|
|
203
|
+
threshold (float): value between -1 and 1 where pixels less than threshold will be masked, applies to landsat 5 when ng_threshold is also set.
|
|
204
|
+
ng_threshold (float, optional): integer threshold to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
ee.Image: ndwi image
|
|
208
|
+
"""
|
|
209
|
+
ndwi_calc = image.normalizedDifference(
|
|
210
|
+
["SR_B3", "SR_B5"]
|
|
211
|
+
) # green-NIR / green+NIR -- full NDWI image
|
|
212
|
+
water = (
|
|
213
|
+
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
214
|
+
.rename("ndwi")
|
|
215
|
+
.copyProperties(image)
|
|
216
|
+
)
|
|
217
|
+
if ng_threshold != None:
|
|
218
|
+
water = ee.Algorithms.If(
|
|
219
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
220
|
+
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
221
|
+
.rename("ndwi")
|
|
222
|
+
.copyProperties(image)
|
|
223
|
+
.set("threshold", threshold),
|
|
224
|
+
ndwi_calc.updateMask(ndwi_calc.gte(ng_threshold))
|
|
225
|
+
.rename("ndwi")
|
|
226
|
+
.copyProperties(image)
|
|
227
|
+
.set("threshold", ng_threshold),
|
|
228
|
+
)
|
|
229
|
+
else:
|
|
230
|
+
water = (
|
|
231
|
+
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
232
|
+
.rename("ndwi")
|
|
233
|
+
.copyProperties(image)
|
|
234
|
+
)
|
|
235
|
+
return water
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def landsat_mndwi_fn(image, threshold, ng_threshold=None):
|
|
239
|
+
"""
|
|
240
|
+
Calculates Modified Normalized Difference Water Index (MNDWI) from GREEN and SWIR bands for Landsat imagery and mask image based on threshold.
|
|
241
|
+
|
|
149
242
|
Can specify separate thresholds for Landsat 5 vs 8 & 9 images, where the threshold argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8 & 9.
|
|
150
243
|
|
|
151
|
-
Args:
|
|
244
|
+
Args:
|
|
152
245
|
image (ee.Image): input image
|
|
153
246
|
threshold (float): value between -1 and 1 where pixels less than threshold will be masked, applies to landsat 5 when ng_threshold is also set.
|
|
154
247
|
ng_threshold (float, optional): integer threshold to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
@@ -156,14 +249,32 @@ class LandsatCollection:
|
|
|
156
249
|
Returns:
|
|
157
250
|
ee.Image: ndwi image
|
|
158
251
|
"""
|
|
159
|
-
|
|
160
|
-
|
|
252
|
+
mndwi_calc = image.normalizedDifference(
|
|
253
|
+
["SR_B3", "SR_B6"]
|
|
254
|
+
) # green-SWIR / green+SWIR -- full NDWI image
|
|
255
|
+
water = (
|
|
256
|
+
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
257
|
+
.rename("ndwi")
|
|
258
|
+
.copyProperties(image)
|
|
259
|
+
)
|
|
161
260
|
if ng_threshold != None:
|
|
162
|
-
water = ee.Algorithms.If(
|
|
163
|
-
|
|
164
|
-
|
|
261
|
+
water = ee.Algorithms.If(
|
|
262
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
263
|
+
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
264
|
+
.rename("ndwi")
|
|
265
|
+
.copyProperties(image)
|
|
266
|
+
.set("threshold", threshold),
|
|
267
|
+
mndwi_calc.updateMask(mndwi_calc.gte(ng_threshold))
|
|
268
|
+
.rename("ndwi")
|
|
269
|
+
.copyProperties(image)
|
|
270
|
+
.set("threshold", ng_threshold),
|
|
271
|
+
)
|
|
165
272
|
else:
|
|
166
|
-
water =
|
|
273
|
+
water = (
|
|
274
|
+
mndwi_calc.updateMask(mndwi_calc.gte(threshold))
|
|
275
|
+
.rename("ndwi")
|
|
276
|
+
.copyProperties(image)
|
|
277
|
+
)
|
|
167
278
|
return water
|
|
168
279
|
|
|
169
280
|
@staticmethod
|
|
@@ -181,20 +292,34 @@ class LandsatCollection:
|
|
|
181
292
|
Returns:
|
|
182
293
|
ee.Image: ndvi ee.Image
|
|
183
294
|
"""
|
|
184
|
-
ndvi_calc = image.normalizedDifference(
|
|
295
|
+
ndvi_calc = image.normalizedDifference(
|
|
296
|
+
["SR_B5", "SR_B4"]
|
|
297
|
+
) # NIR-RED/NIR+RED -- full NDVI image
|
|
185
298
|
if ng_threshold != None:
|
|
186
|
-
vegetation = ee.Algorithms.If(
|
|
187
|
-
|
|
188
|
-
|
|
299
|
+
vegetation = ee.Algorithms.If(
|
|
300
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
301
|
+
ndvi_calc.updateMask(ndvi_calc.gte(threshold))
|
|
302
|
+
.rename("ndvi")
|
|
303
|
+
.copyProperties(image)
|
|
304
|
+
.set("threshold", threshold),
|
|
305
|
+
ndvi_calc.updateMask(ndvi_calc.gte(ng_threshold))
|
|
306
|
+
.rename("ndvi")
|
|
307
|
+
.copyProperties(image)
|
|
308
|
+
.set("threshold", ng_threshold),
|
|
309
|
+
)
|
|
189
310
|
else:
|
|
190
|
-
vegetation =
|
|
311
|
+
vegetation = (
|
|
312
|
+
ndvi_calc.updateMask(ndvi_calc.gte(threshold))
|
|
313
|
+
.rename("ndvi")
|
|
314
|
+
.copyProperties(image)
|
|
315
|
+
)
|
|
191
316
|
return vegetation
|
|
192
|
-
|
|
317
|
+
|
|
193
318
|
@staticmethod
|
|
194
319
|
def landsat_halite_fn(image, threshold, ng_threshold=None):
|
|
195
320
|
"""
|
|
196
321
|
Calculates multispectral halite index from RED and SWIR1 bands (Radwin & Bowen, 2021 - https://onlinelibrary.wiley.com/doi/10.1002/esp.5089) for Landsat imagery and mask image based on threshold.
|
|
197
|
-
|
|
322
|
+
|
|
198
323
|
Can specify separate thresholds for Landsat 5 vs 8 & 9 images, where the threshold argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8 & 9.
|
|
199
324
|
|
|
200
325
|
Args:
|
|
@@ -205,20 +330,32 @@ class LandsatCollection:
|
|
|
205
330
|
Returns:
|
|
206
331
|
ee.Image: halite ee.Image
|
|
207
332
|
"""
|
|
208
|
-
halite_index = image.normalizedDifference([
|
|
333
|
+
halite_index = image.normalizedDifference(["SR_B4", "SR_B6"])
|
|
209
334
|
if ng_threshold != None:
|
|
210
|
-
halite = ee.Algorithms.If(
|
|
211
|
-
|
|
212
|
-
|
|
335
|
+
halite = ee.Algorithms.If(
|
|
336
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
337
|
+
halite_index.updateMask(halite_index.gte(threshold))
|
|
338
|
+
.rename("halite")
|
|
339
|
+
.copyProperties(image)
|
|
340
|
+
.set("threshold", threshold),
|
|
341
|
+
halite_index.updateMask(halite_index.gte(ng_threshold))
|
|
342
|
+
.rename("halite")
|
|
343
|
+
.copyProperties(image)
|
|
344
|
+
.set("threshold", ng_threshold),
|
|
345
|
+
)
|
|
213
346
|
else:
|
|
214
|
-
halite =
|
|
215
|
-
|
|
216
|
-
|
|
347
|
+
halite = (
|
|
348
|
+
halite_index.updateMask(halite_index.gte(threshold))
|
|
349
|
+
.rename("halite")
|
|
350
|
+
.copyProperties(image)
|
|
351
|
+
)
|
|
352
|
+
return halite
|
|
353
|
+
|
|
217
354
|
@staticmethod
|
|
218
355
|
def landsat_gypsum_fn(image, threshold, ng_threshold=None):
|
|
219
356
|
"""
|
|
220
357
|
Calculates multispectral gypsum index from SWIR1 and SWIR2 bands(Radwin & Bowen, 2024 - https://onlinelibrary.wiley.com/doi/10.1002/esp.5089) for Landsat imagery and mask image based on threshold.
|
|
221
|
-
|
|
358
|
+
|
|
222
359
|
Can specify separate thresholds for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9.
|
|
223
360
|
|
|
224
361
|
Args:
|
|
@@ -229,19 +366,31 @@ class LandsatCollection:
|
|
|
229
366
|
Returns:
|
|
230
367
|
ee.Image: gypsum ee.Image
|
|
231
368
|
"""
|
|
232
|
-
gypsum_index = image.normalizedDifference([
|
|
369
|
+
gypsum_index = image.normalizedDifference(["SR_B6", "SR_B7"])
|
|
233
370
|
if ng_threshold != None:
|
|
234
|
-
gypsum = ee.Algorithms.If(
|
|
235
|
-
|
|
236
|
-
|
|
371
|
+
gypsum = ee.Algorithms.If(
|
|
372
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
373
|
+
gypsum_index.updateMask(gypsum_index.gte(threshold))
|
|
374
|
+
.rename("gypsum")
|
|
375
|
+
.copyProperties(image)
|
|
376
|
+
.set("threshold", threshold),
|
|
377
|
+
gypsum_index.updateMask(gypsum_index.gte(ng_threshold))
|
|
378
|
+
.rename("gypsum")
|
|
379
|
+
.copyProperties(image)
|
|
380
|
+
.set("threshold", ng_threshold),
|
|
381
|
+
)
|
|
237
382
|
else:
|
|
238
|
-
gypsum =
|
|
383
|
+
gypsum = (
|
|
384
|
+
gypsum_index.updateMask(gypsum_index.gte(threshold))
|
|
385
|
+
.rename("gypsum")
|
|
386
|
+
.copyProperties(image)
|
|
387
|
+
)
|
|
239
388
|
return gypsum
|
|
240
|
-
|
|
389
|
+
|
|
241
390
|
@staticmethod
|
|
242
391
|
def landsat_ndti_fn(image, threshold, ng_threshold=None):
|
|
243
392
|
"""
|
|
244
|
-
Calculates turbidity of water pixels using Normalized Difference Turbidity Index (NDTI; Lacaux et al., 2007 - https://doi.org/10.1016/j.rse.2006.07.012)
|
|
393
|
+
Calculates turbidity of water pixels using Normalized Difference Turbidity Index (NDTI; Lacaux et al., 2007 - https://doi.org/10.1016/j.rse.2006.07.012)
|
|
245
394
|
and mask image based on threshold. Can specify separate thresholds for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9.
|
|
246
395
|
|
|
247
396
|
Args:
|
|
@@ -252,20 +401,32 @@ class LandsatCollection:
|
|
|
252
401
|
Returns:
|
|
253
402
|
ee.Image: turbidity ee.Image
|
|
254
403
|
"""
|
|
255
|
-
NDTI = image.normalizedDifference([
|
|
404
|
+
NDTI = image.normalizedDifference(["SR_B4", "SR_B3"])
|
|
256
405
|
if ng_threshold != None:
|
|
257
|
-
turbidity = ee.Algorithms.If(
|
|
258
|
-
|
|
259
|
-
|
|
406
|
+
turbidity = ee.Algorithms.If(
|
|
407
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
408
|
+
NDTI.updateMask(NDTI.gte(threshold))
|
|
409
|
+
.rename("ndti")
|
|
410
|
+
.copyProperties(image)
|
|
411
|
+
.set("threshold", threshold),
|
|
412
|
+
NDTI.updateMask(NDTI.gte(ng_threshold))
|
|
413
|
+
.rename("ndti")
|
|
414
|
+
.copyProperties(image)
|
|
415
|
+
.set("threshold", ng_threshold),
|
|
416
|
+
)
|
|
260
417
|
else:
|
|
261
|
-
turbidity =
|
|
418
|
+
turbidity = (
|
|
419
|
+
NDTI.updateMask(NDTI.gte(threshold))
|
|
420
|
+
.rename("ndti")
|
|
421
|
+
.copyProperties(image)
|
|
422
|
+
)
|
|
262
423
|
return turbidity
|
|
263
|
-
|
|
424
|
+
|
|
264
425
|
@staticmethod
|
|
265
426
|
def landsat_kivu_chla_fn(image, threshold, ng_threshold=None):
|
|
266
427
|
"""
|
|
267
|
-
Calculates relative chlorophyll-a concentrations of water pixels using 3BDA/KIVU index
|
|
268
|
-
(see Boucher et al., 2018 for review - https://esajournals.onlinelibrary.wiley.com/doi/10.1002/eap.1708) and mask image based on threshold. Can specify separate thresholds
|
|
428
|
+
Calculates relative chlorophyll-a concentrations of water pixels using 3BDA/KIVU index
|
|
429
|
+
(see Boucher et al., 2018 for review - https://esajournals.onlinelibrary.wiley.com/doi/10.1002/eap.1708) and mask image based on threshold. Can specify separate thresholds
|
|
269
430
|
for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5 and the ng_threshold
|
|
270
431
|
argument applies to Landsat 8&9.
|
|
271
432
|
|
|
@@ -277,16 +438,34 @@ class LandsatCollection:
|
|
|
277
438
|
Returns:
|
|
278
439
|
ee.Image: chlorophyll-a ee.Image
|
|
279
440
|
"""
|
|
280
|
-
KIVU = image.expression(
|
|
441
|
+
KIVU = image.expression(
|
|
442
|
+
"(BLUE - RED) / GREEN",
|
|
443
|
+
{
|
|
444
|
+
"BLUE": image.select("SR_B2"),
|
|
445
|
+
"RED": image.select("SR_B4"),
|
|
446
|
+
"GREEN": image.select("SR_B3"),
|
|
447
|
+
},
|
|
448
|
+
)
|
|
281
449
|
if ng_threshold != None:
|
|
282
|
-
chlorophyll = ee.Algorithms.If(
|
|
283
|
-
|
|
284
|
-
|
|
450
|
+
chlorophyll = ee.Algorithms.If(
|
|
451
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
452
|
+
KIVU.updateMask(KIVU.gte(threshold))
|
|
453
|
+
.rename("kivu")
|
|
454
|
+
.copyProperties(image)
|
|
455
|
+
.set("threshold", threshold),
|
|
456
|
+
KIVU.updateMask(KIVU.gte(ng_threshold))
|
|
457
|
+
.rename("kivu")
|
|
458
|
+
.copyProperties(image)
|
|
459
|
+
.set("threshold", ng_threshold),
|
|
460
|
+
)
|
|
285
461
|
else:
|
|
286
|
-
chlorophyll =
|
|
462
|
+
chlorophyll = (
|
|
463
|
+
KIVU.updateMask(KIVU.gte(threshold))
|
|
464
|
+
.rename("kivu")
|
|
465
|
+
.copyProperties(image)
|
|
466
|
+
)
|
|
287
467
|
return chlorophyll
|
|
288
468
|
|
|
289
|
-
|
|
290
469
|
@staticmethod
|
|
291
470
|
def MaskWaterLandsat(image):
|
|
292
471
|
"""
|
|
@@ -299,11 +478,11 @@ class LandsatCollection:
|
|
|
299
478
|
ee.Image: ee.Image with water pixels masked.
|
|
300
479
|
"""
|
|
301
480
|
WaterBitMask = ee.Number(2).pow(7).int()
|
|
302
|
-
qa = image.select(
|
|
481
|
+
qa = image.select("QA_PIXEL")
|
|
303
482
|
water_extract = qa.bitwiseAnd(WaterBitMask).eq(0)
|
|
304
483
|
masked_image = image.updateMask(water_extract).copyProperties(image)
|
|
305
484
|
return masked_image
|
|
306
|
-
|
|
485
|
+
|
|
307
486
|
@staticmethod
|
|
308
487
|
def MaskWaterLandsatByNDWI(image, threshold, ng_threshold=None):
|
|
309
488
|
"""
|
|
@@ -311,24 +490,36 @@ class LandsatCollection:
|
|
|
311
490
|
all pixels less than NDWI threshold are masked out. Can specify separate thresholds for Landsat 5 vs 8&9 images, where the threshold
|
|
312
491
|
argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9
|
|
313
492
|
|
|
314
|
-
Args:
|
|
493
|
+
Args:
|
|
315
494
|
image (ee.Image): input image
|
|
316
495
|
threshold (float): value between -1 and 1 where NDWI pixels greater than threshold will be masked, applies to landsat 5 when ng_threshold is also set.
|
|
317
496
|
ng_threshold (float, optional): integer threshold to be applied to landsat 8 or 9 where NDWI pixels greater than threshold are masked
|
|
318
|
-
|
|
497
|
+
|
|
319
498
|
Returns:
|
|
320
499
|
ee.Image: ee.Image with water pixels masked
|
|
321
500
|
"""
|
|
322
|
-
ndwi_calc = image.normalizedDifference(
|
|
323
|
-
|
|
501
|
+
ndwi_calc = image.normalizedDifference(
|
|
502
|
+
["SR_B3", "SR_B5"]
|
|
503
|
+
) # green-NIR / green+NIR -- full NDWI image
|
|
504
|
+
water = (
|
|
505
|
+
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
506
|
+
.rename("ndwi")
|
|
507
|
+
.copyProperties(image)
|
|
508
|
+
)
|
|
324
509
|
if ng_threshold != None:
|
|
325
|
-
water = ee.Algorithms.If(
|
|
326
|
-
|
|
327
|
-
|
|
510
|
+
water = ee.Algorithms.If(
|
|
511
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
512
|
+
image.updateMask(ndwi_calc.lt(threshold)).set("threshold", threshold),
|
|
513
|
+
image.updateMask(ndwi_calc.lt(ng_threshold)).set(
|
|
514
|
+
"threshold", ng_threshold
|
|
515
|
+
),
|
|
516
|
+
)
|
|
328
517
|
else:
|
|
329
|
-
water = image.updateMask(ndwi_calc.lt(threshold)).set(
|
|
518
|
+
water = image.updateMask(ndwi_calc.lt(threshold)).set(
|
|
519
|
+
"threshold", threshold
|
|
520
|
+
)
|
|
330
521
|
return water
|
|
331
|
-
|
|
522
|
+
|
|
332
523
|
@staticmethod
|
|
333
524
|
def MaskToWaterLandsat(image):
|
|
334
525
|
"""
|
|
@@ -341,86 +532,123 @@ class LandsatCollection:
|
|
|
341
532
|
ee.Image: ee.Image with water pixels masked.
|
|
342
533
|
"""
|
|
343
534
|
WaterBitMask = ee.Number(2).pow(7).int()
|
|
344
|
-
qa = image.select(
|
|
535
|
+
qa = image.select("QA_PIXEL")
|
|
345
536
|
water_extract = qa.bitwiseAnd(WaterBitMask).neq(0)
|
|
346
537
|
masked_image = image.updateMask(water_extract).copyProperties(image)
|
|
347
538
|
return masked_image
|
|
348
|
-
|
|
539
|
+
|
|
349
540
|
@staticmethod
|
|
350
541
|
def MaskToWaterLandsatByNDWI(image, threshold, ng_threshold=None):
|
|
351
542
|
"""
|
|
352
543
|
Masks water pixels using NDWI based on threshold. Can specify separate thresholds for Landsat 5 vs 8&9 images, where the threshold
|
|
353
544
|
argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9
|
|
354
545
|
|
|
355
|
-
Args:
|
|
546
|
+
Args:
|
|
356
547
|
image (ee.Image): input image
|
|
357
548
|
threshold (float): value between -1 and 1 where NDWI pixels less than threshold will be masked, applies to landsat 5 when ng_threshold is also set.
|
|
358
549
|
ng_threshold (float, optional): integer threshold to be applied to landsat 8 or 9 where NDWI pixels less than threshold are masked
|
|
359
|
-
|
|
550
|
+
|
|
360
551
|
Returns:
|
|
361
552
|
ee.Image: ee.Image with water pixels masked.
|
|
362
553
|
"""
|
|
363
|
-
ndwi_calc = image.normalizedDifference(
|
|
364
|
-
|
|
554
|
+
ndwi_calc = image.normalizedDifference(
|
|
555
|
+
["SR_B3", "SR_B5"]
|
|
556
|
+
) # green-NIR / green+NIR -- full NDWI image
|
|
557
|
+
water = (
|
|
558
|
+
ndwi_calc.updateMask(ndwi_calc.gte(threshold))
|
|
559
|
+
.rename("ndwi")
|
|
560
|
+
.copyProperties(image)
|
|
561
|
+
)
|
|
365
562
|
if ng_threshold != None:
|
|
366
|
-
water = ee.Algorithms.If(
|
|
367
|
-
|
|
368
|
-
|
|
563
|
+
water = ee.Algorithms.If(
|
|
564
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
565
|
+
image.updateMask(ndwi_calc.gte(threshold)).set("threshold", threshold),
|
|
566
|
+
image.updateMask(ndwi_calc.gte(ng_threshold)).set(
|
|
567
|
+
"threshold", ng_threshold
|
|
568
|
+
),
|
|
569
|
+
)
|
|
369
570
|
else:
|
|
370
|
-
water = image.updateMask(ndwi_calc.gte(threshold)).set(
|
|
571
|
+
water = image.updateMask(ndwi_calc.gte(threshold)).set(
|
|
572
|
+
"threshold", threshold
|
|
573
|
+
)
|
|
371
574
|
return water
|
|
372
575
|
|
|
373
576
|
@staticmethod
|
|
374
577
|
def halite_mask(image, threshold, ng_threshold=None):
|
|
375
578
|
"""
|
|
376
|
-
Masks halite pixels after specifying index to isolate/mask-to halite pixels.
|
|
377
|
-
|
|
579
|
+
Masks halite pixels after specifying index to isolate/mask-to halite pixels.
|
|
580
|
+
|
|
378
581
|
Can specify separate thresholds for Landsat 5 vs 8&9 images where the threshold
|
|
379
582
|
argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9.
|
|
380
583
|
|
|
381
584
|
Args:
|
|
382
585
|
image (ee.Image): input ee.Image
|
|
383
586
|
threshold (float): value between -1 and 1 where pixels less than threshold will be masked, applies to landsat 5 when ng_threshold is also set.
|
|
384
|
-
ng_threshold (float, optional): integer threshold to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
587
|
+
ng_threshold (float, optional): integer threshold to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
385
588
|
|
|
386
589
|
Returns:
|
|
387
590
|
image (ee.Image): masked ee.Image
|
|
388
591
|
"""
|
|
389
|
-
halite_index = image.normalizedDifference(
|
|
592
|
+
halite_index = image.normalizedDifference(
|
|
593
|
+
["SR_B4", "SR_B6"]
|
|
594
|
+
) # red-swir1 / red+swir1
|
|
390
595
|
if ng_threshold != None:
|
|
391
|
-
mask = ee.Algorithms.If(
|
|
392
|
-
|
|
393
|
-
|
|
596
|
+
mask = ee.Algorithms.If(
|
|
597
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
598
|
+
image.updateMask(halite_index.lt(threshold)).copyProperties(image),
|
|
599
|
+
image.updateMask(halite_index.lt(ng_threshold)).copyProperties(image),
|
|
600
|
+
)
|
|
394
601
|
else:
|
|
395
602
|
mask = image.updateMask(halite_index.lt(threshold)).copyProperties(image)
|
|
396
|
-
return mask
|
|
397
|
-
|
|
603
|
+
return mask
|
|
604
|
+
|
|
398
605
|
@staticmethod
|
|
399
|
-
def gypsum_and_halite_mask(
|
|
606
|
+
def gypsum_and_halite_mask(
|
|
607
|
+
image,
|
|
608
|
+
halite_threshold,
|
|
609
|
+
gypsum_threshold,
|
|
610
|
+
halite_ng_threshold=None,
|
|
611
|
+
gypsum_ng_threshold=None,
|
|
612
|
+
):
|
|
400
613
|
"""
|
|
401
|
-
Masks both gypsum and halite pixels. Must specify threshold for isolating halite and gypsum pixels.
|
|
402
|
-
|
|
403
|
-
Can specify separate thresholds for Landsat 5 vs 8&9 images where the threshold argument applies to Landsat 5
|
|
614
|
+
Masks both gypsum and halite pixels. Must specify threshold for isolating halite and gypsum pixels.
|
|
615
|
+
|
|
616
|
+
Can specify separate thresholds for Landsat 5 vs 8&9 images where the threshold argument applies to Landsat 5
|
|
404
617
|
and the ng_threshold argument applies to Landsat 8&9.
|
|
405
618
|
|
|
406
619
|
Args:
|
|
407
620
|
image (ee.Image): input ee.Image
|
|
408
621
|
halite_threshold (float): integer threshold for halite where pixels less than threshold are masked, applies to landsat 5 when ng_threshold is also set.
|
|
409
622
|
gypsum_threshold (float): integer threshold for gypsum where pixels less than threshold are masked, applies to landsat 5 when ng_threshold is also set.
|
|
410
|
-
halite_ng_threshold (float, optional): integer threshold for halite to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
411
|
-
gypsum_ng_threshold (float, optional): integer threshold for gypsum to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
623
|
+
halite_ng_threshold (float, optional): integer threshold for halite to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
624
|
+
gypsum_ng_threshold (float, optional): integer threshold for gypsum to be applied to landsat 8 or 9 where pixels less than threshold are masked
|
|
412
625
|
|
|
413
626
|
Returns:
|
|
414
627
|
image (ee.Image): masked ee.Image
|
|
415
628
|
"""
|
|
416
|
-
halite_index = image.normalizedDifference(
|
|
417
|
-
|
|
629
|
+
halite_index = image.normalizedDifference(
|
|
630
|
+
["SR_B4", "SR_B6"]
|
|
631
|
+
) # red-swir1 / red+swir1
|
|
632
|
+
gypsum_index = image.normalizedDifference(["SR_B6", "SR_B7"])
|
|
418
633
|
if halite_ng_threshold and gypsum_ng_threshold != None:
|
|
419
|
-
mask = ee.Algorithms.If(
|
|
420
|
-
|
|
421
|
-
|
|
634
|
+
mask = ee.Algorithms.If(
|
|
635
|
+
ee.String(image.get("SPACECRAFT_ID")).equals("LANDSAT_5"),
|
|
636
|
+
gypsum_index.updateMask(halite_index.lt(halite_threshold))
|
|
637
|
+
.updateMask(gypsum_index.lt(gypsum_threshold))
|
|
638
|
+
.rename("carbonate_muds")
|
|
639
|
+
.copyProperties(image),
|
|
640
|
+
gypsum_index.updateMask(halite_index.lt(halite_ng_threshold))
|
|
641
|
+
.updateMask(gypsum_index.lt(gypsum_ng_threshold))
|
|
642
|
+
.rename("carbonate_muds")
|
|
643
|
+
.copyProperties(image),
|
|
644
|
+
)
|
|
422
645
|
else:
|
|
423
|
-
mask =
|
|
646
|
+
mask = (
|
|
647
|
+
gypsum_index.updateMask(halite_index.lt(halite_threshold))
|
|
648
|
+
.updateMask(gypsum_index.lt(gypsum_threshold))
|
|
649
|
+
.rename("carbonate_muds")
|
|
650
|
+
.copyProperties(image)
|
|
651
|
+
)
|
|
424
652
|
return mask
|
|
425
653
|
|
|
426
654
|
@staticmethod
|
|
@@ -436,11 +664,11 @@ class LandsatCollection:
|
|
|
436
664
|
"""
|
|
437
665
|
cloudBitMask = ee.Number(2).pow(3).int()
|
|
438
666
|
CirrusBitMask = ee.Number(2).pow(2).int()
|
|
439
|
-
qa = image.select(
|
|
667
|
+
qa = image.select("QA_PIXEL")
|
|
440
668
|
cloud_mask = qa.bitwiseAnd(cloudBitMask).eq(0)
|
|
441
669
|
cirrus_mask = qa.bitwiseAnd(CirrusBitMask).eq(0)
|
|
442
670
|
return image.updateMask(cloud_mask).updateMask(cirrus_mask)
|
|
443
|
-
|
|
671
|
+
|
|
444
672
|
@staticmethod
|
|
445
673
|
def temperature_bands(img):
|
|
446
674
|
"""
|
|
@@ -452,44 +680,53 @@ class LandsatCollection:
|
|
|
452
680
|
Returns:
|
|
453
681
|
ee.Image: ee.Image
|
|
454
682
|
"""
|
|
455
|
-
#date = ee.Number(img.date().format('YYYY-MM-dd'))
|
|
456
|
-
scale1 = [
|
|
457
|
-
scale2 = [
|
|
458
|
-
scale1_names = [
|
|
459
|
-
scale2_names = [
|
|
460
|
-
scale1_bands =
|
|
461
|
-
|
|
683
|
+
# date = ee.Number(img.date().format('YYYY-MM-dd'))
|
|
684
|
+
scale1 = ["ST_ATRAN", "ST_EMIS"]
|
|
685
|
+
scale2 = ["ST_DRAD", "ST_TRAD", "ST_URAD"]
|
|
686
|
+
scale1_names = ["transmittance", "emissivity"]
|
|
687
|
+
scale2_names = ["downwelling", "B10_radiance", "upwelling"]
|
|
688
|
+
scale1_bands = (
|
|
689
|
+
img.select(scale1).multiply(0.0001).rename(scale1_names)
|
|
690
|
+
) # Scaled to new L8 collection
|
|
691
|
+
scale2_bands = (
|
|
692
|
+
img.select(scale2).multiply(0.001).rename(scale2_names)
|
|
693
|
+
) # Scaled to new L8 collection
|
|
462
694
|
return img.addBands(scale1_bands).addBands(scale2_bands).copyProperties(img)
|
|
463
|
-
|
|
695
|
+
|
|
464
696
|
@staticmethod
|
|
465
697
|
def landsat_LST(image):
|
|
466
698
|
"""
|
|
467
|
-
Calculates land surface temperature (LST) from landsat TIR bands.
|
|
699
|
+
Calculates land surface temperature (LST) from landsat TIR bands.
|
|
468
700
|
Based on Sekertekin, A., & Bonafoni, S. (2020) https://doi.org/10.3390/rs12020294
|
|
469
701
|
|
|
470
702
|
Args:
|
|
471
703
|
image (ee.Image): input ee.Image
|
|
472
704
|
|
|
473
705
|
Returns:
|
|
474
|
-
ee.Image: LST ee.Image
|
|
706
|
+
ee.Image: LST ee.Image
|
|
475
707
|
"""
|
|
476
708
|
# Based on Sekertekin, A., & Bonafoni, S. (2020) https://doi.org/10.3390/rs12020294
|
|
477
|
-
|
|
709
|
+
|
|
478
710
|
k1 = 774.89
|
|
479
711
|
k2 = 1321.08
|
|
480
712
|
LST = image.expression(
|
|
481
|
-
|
|
482
|
-
{
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
713
|
+
"(k2/log((k1/((B10_rad - upwelling - transmittance*(1 - emissivity)*downwelling)/(transmittance*emissivity)))+1)) - 273.15",
|
|
714
|
+
{
|
|
715
|
+
"k1": k1,
|
|
716
|
+
"k2": k2,
|
|
717
|
+
"B10_rad": image.select("B10_radiance"),
|
|
718
|
+
"upwelling": image.select("upwelling"),
|
|
719
|
+
"transmittance": image.select("transmittance"),
|
|
720
|
+
"emissivity": image.select("emissivity"),
|
|
721
|
+
"downwelling": image.select("downwelling"),
|
|
722
|
+
},
|
|
723
|
+
).rename("LST")
|
|
724
|
+
return image.addBands(LST).copyProperties(image) # Outputs temperature in C
|
|
725
|
+
|
|
491
726
|
@staticmethod
|
|
492
|
-
def PixelAreaSum(
|
|
727
|
+
def PixelAreaSum(
|
|
728
|
+
image, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12
|
|
729
|
+
):
|
|
493
730
|
"""
|
|
494
731
|
Calculates the summation of area for pixels of interest (above a specific threshold) in a geometry
|
|
495
732
|
and store the value as image property (matching name of chosen band).
|
|
@@ -501,27 +738,35 @@ class LandsatCollection:
|
|
|
501
738
|
threshold (float): integer threshold to specify masking of pixels below threshold (defaults to -1)
|
|
502
739
|
scale (int): integer scale of image resolution (meters) (defaults to 30)
|
|
503
740
|
maxPixels (int): integer denoting maximum number of pixels for calculations
|
|
504
|
-
|
|
741
|
+
|
|
505
742
|
Returns:
|
|
506
743
|
ee.Image: ee.Image with area calculation stored as property matching name of band
|
|
507
744
|
"""
|
|
508
745
|
area_image = ee.Image.pixelArea()
|
|
509
746
|
mask = image.select(band_name).gte(threshold)
|
|
510
747
|
final = image.addBands(area_image)
|
|
511
|
-
stats =
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
748
|
+
stats = (
|
|
749
|
+
final.select("area")
|
|
750
|
+
.updateMask(mask)
|
|
751
|
+
.rename(band_name)
|
|
752
|
+
.reduceRegion(
|
|
753
|
+
reducer=ee.Reducer.sum(),
|
|
754
|
+
geometry=geometry,
|
|
755
|
+
scale=scale,
|
|
756
|
+
maxPixels=maxPixels,
|
|
757
|
+
)
|
|
758
|
+
)
|
|
516
759
|
return image.set(band_name, stats.get(band_name))
|
|
517
|
-
|
|
518
|
-
def PixelAreaSumCollection(
|
|
760
|
+
|
|
761
|
+
def PixelAreaSumCollection(
|
|
762
|
+
self, band_name, geometry, threshold=-1, scale=30, maxPixels=1e12
|
|
763
|
+
):
|
|
519
764
|
"""
|
|
520
|
-
Calculates the summation of area for pixels of interest (above a specific threshold)
|
|
765
|
+
Calculates the summation of area for pixels of interest (above a specific threshold)
|
|
521
766
|
within a geometry and store the value as image property (matching name of chosen band) for an entire
|
|
522
767
|
image collection.
|
|
523
768
|
|
|
524
|
-
The resulting value has units of square meters.
|
|
769
|
+
The resulting value has units of square meters.
|
|
525
770
|
|
|
526
771
|
Args:
|
|
527
772
|
band_name (string): name of band (string) for calculating area
|
|
@@ -529,18 +774,27 @@ class LandsatCollection:
|
|
|
529
774
|
threshold (float): integer threshold to specify masking of pixels below threshold (defaults to -1)
|
|
530
775
|
scale (int): integer scale of image resolution (meters) (defaults to 30)
|
|
531
776
|
maxPixels (int): integer denoting maximum number of pixels for calculations
|
|
532
|
-
|
|
777
|
+
|
|
533
778
|
Returns:
|
|
534
779
|
ee.ImageCollection: Image with area calculation stored as property matching name of band.
|
|
535
780
|
"""
|
|
536
781
|
if self._PixelAreaSumCollection is None:
|
|
537
782
|
collection = self.collection
|
|
538
|
-
AreaCollection = collection.map(
|
|
783
|
+
AreaCollection = collection.map(
|
|
784
|
+
lambda image: LandsatCollection.PixelAreaSum(
|
|
785
|
+
image,
|
|
786
|
+
band_name=band_name,
|
|
787
|
+
geometry=geometry,
|
|
788
|
+
threshold=threshold,
|
|
789
|
+
scale=scale,
|
|
790
|
+
maxPixels=maxPixels,
|
|
791
|
+
)
|
|
792
|
+
)
|
|
539
793
|
self._PixelAreaSumCollection = AreaCollection
|
|
540
794
|
return self._PixelAreaSumCollection
|
|
541
795
|
|
|
542
796
|
@staticmethod
|
|
543
|
-
def dNDWIPixelAreaSum(image, geometry, band_name=
|
|
797
|
+
def dNDWIPixelAreaSum(image, geometry, band_name="ndwi", scale=30, maxPixels=1e12):
|
|
544
798
|
"""
|
|
545
799
|
Dynamically calulates the summation of area for water pixels of interest and store the value as image property named 'ndwi'
|
|
546
800
|
Uses Otsu thresholding to dynamically choose the best threshold rather than needing to specify threshold.
|
|
@@ -556,9 +810,10 @@ class LandsatCollection:
|
|
|
556
810
|
Returns:
|
|
557
811
|
ee.Image: ee.Image with area calculation stored as property matching name of band
|
|
558
812
|
"""
|
|
813
|
+
|
|
559
814
|
def OtsuThreshold(histogram):
|
|
560
|
-
counts = ee.Array(ee.Dictionary(histogram).get(
|
|
561
|
-
means = ee.Array(ee.Dictionary(histogram).get(
|
|
815
|
+
counts = ee.Array(ee.Dictionary(histogram).get("histogram"))
|
|
816
|
+
means = ee.Array(ee.Dictionary(histogram).get("bucketMeans"))
|
|
562
817
|
size = means.length().get([0])
|
|
563
818
|
total = counts.reduce(ee.Reducer.sum(), [0]).get([0])
|
|
564
819
|
sum = means.multiply(counts).reduce(ee.Reducer.sum(), [0]).get([0])
|
|
@@ -578,27 +833,35 @@ class LandsatCollection:
|
|
|
578
833
|
bCount = total.subtract(aCount)
|
|
579
834
|
bMean = sum.subtract(aCount.multiply(aMean)).divide(bCount)
|
|
580
835
|
return aCount.multiply(aMean.subtract(mean).pow(2)).add(
|
|
581
|
-
bCount.multiply(bMean.subtract(mean).pow(2))
|
|
836
|
+
bCount.multiply(bMean.subtract(mean).pow(2))
|
|
837
|
+
)
|
|
582
838
|
|
|
583
839
|
bss = indices.map(func_xxx)
|
|
584
840
|
return means.sort(bss).get([-1])
|
|
585
841
|
|
|
586
842
|
area_image = ee.Image.pixelArea()
|
|
587
843
|
histogram = image.select(band_name).reduceRegion(
|
|
588
|
-
reducer
|
|
589
|
-
geometry
|
|
590
|
-
scale
|
|
591
|
-
bestEffort=
|
|
844
|
+
reducer=ee.Reducer.histogram(255, 2),
|
|
845
|
+
geometry=geometry.geometry().buffer(6000),
|
|
846
|
+
scale=scale,
|
|
847
|
+
bestEffort=True,
|
|
848
|
+
)
|
|
592
849
|
threshold = OtsuThreshold(histogram.get(band_name)).add(0.15)
|
|
593
850
|
mask = image.select(band_name).gte(threshold)
|
|
594
851
|
final = image.addBands(area_image)
|
|
595
|
-
stats =
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
852
|
+
stats = (
|
|
853
|
+
final.select("area")
|
|
854
|
+
.updateMask(mask)
|
|
855
|
+
.rename(band_name)
|
|
856
|
+
.reduceRegion(
|
|
857
|
+
reducer=ee.Reducer.sum(),
|
|
858
|
+
geometry=geometry,
|
|
859
|
+
scale=scale,
|
|
860
|
+
maxPixels=maxPixels,
|
|
861
|
+
)
|
|
862
|
+
)
|
|
600
863
|
return image.set(band_name, stats.get(band_name))
|
|
601
|
-
|
|
864
|
+
|
|
602
865
|
@property
|
|
603
866
|
def dates_list(self):
|
|
604
867
|
"""
|
|
@@ -608,7 +871,7 @@ class LandsatCollection:
|
|
|
608
871
|
ee.List: Server-side ee.List of dates.
|
|
609
872
|
"""
|
|
610
873
|
if self._dates_list is None:
|
|
611
|
-
dates = self.collection.aggregate_array(
|
|
874
|
+
dates = self.collection.aggregate_array("Date_Filter")
|
|
612
875
|
self._dates_list = dates
|
|
613
876
|
return self._dates_list
|
|
614
877
|
|
|
@@ -621,7 +884,7 @@ class LandsatCollection:
|
|
|
621
884
|
list: list of date strings.
|
|
622
885
|
"""
|
|
623
886
|
if self._dates_list is None:
|
|
624
|
-
dates = self.collection.aggregate_array(
|
|
887
|
+
dates = self.collection.aggregate_array("Date_Filter")
|
|
625
888
|
self._dates_list = dates
|
|
626
889
|
if self._dates is None:
|
|
627
890
|
dates = self._dates_list.getInfo()
|
|
@@ -637,11 +900,25 @@ class LandsatCollection:
|
|
|
637
900
|
"""
|
|
638
901
|
landsat8 = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2")
|
|
639
902
|
landsat9 = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2")
|
|
640
|
-
landsat5 = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2").map(
|
|
641
|
-
|
|
642
|
-
|
|
903
|
+
landsat5 = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2").map(
|
|
904
|
+
LandsatCollection.landsat5bandrename
|
|
905
|
+
) # Replace with the correct Landsat 5 collection ID
|
|
906
|
+
filtered_collection = (
|
|
907
|
+
landsat8.merge(landsat9)
|
|
908
|
+
.merge(landsat5)
|
|
909
|
+
.filterDate(self.start_date, self.end_date)
|
|
910
|
+
.filter(
|
|
911
|
+
ee.Filter.And(
|
|
912
|
+
ee.Filter.inList("WRS_PATH", self.tile_path),
|
|
913
|
+
ee.Filter.inList("WRS_ROW", self.tile_row),
|
|
914
|
+
)
|
|
915
|
+
)
|
|
916
|
+
.filter(ee.Filter.lte("CLOUD_COVER", self.cloud_percentage_threshold))
|
|
917
|
+
.map(LandsatCollection.image_dater)
|
|
918
|
+
.sort("Date_Filter")
|
|
919
|
+
)
|
|
643
920
|
return filtered_collection
|
|
644
|
-
|
|
921
|
+
|
|
645
922
|
def get_boundary_filtered_collection(self):
|
|
646
923
|
"""
|
|
647
924
|
Filters and masks image collection based on LandsatCollection class arguments. Automatically calculated when using collection method, depending on provided class arguments (when boundary info is provided).
|
|
@@ -652,10 +929,33 @@ class LandsatCollection:
|
|
|
652
929
|
"""
|
|
653
930
|
landsat8 = ee.ImageCollection("LANDSAT/LC08/C02/T1_L2")
|
|
654
931
|
landsat9 = ee.ImageCollection("LANDSAT/LC09/C02/T1_L2")
|
|
655
|
-
landsat5 = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2").map(
|
|
656
|
-
|
|
932
|
+
landsat5 = ee.ImageCollection("LANDSAT/LT05/C02/T1_L2").map(
|
|
933
|
+
LandsatCollection.landsat5bandrename
|
|
934
|
+
) # Replace with the correct Landsat 5 collection ID
|
|
935
|
+
filtered_collection = (
|
|
936
|
+
landsat8.merge(landsat9)
|
|
937
|
+
.merge(landsat5)
|
|
938
|
+
.filterDate(self.start_date, self.end_date)
|
|
939
|
+
.filterBounds(self.boundary)
|
|
940
|
+
.filter(ee.Filter.lte("CLOUD_COVER", self.cloud_percentage_threshold))
|
|
941
|
+
.map(LandsatCollection.image_dater)
|
|
942
|
+
.sort("Date_Filter")
|
|
943
|
+
)
|
|
657
944
|
return filtered_collection
|
|
658
945
|
|
|
946
|
+
@property
|
|
947
|
+
def scale_to_reflectance(self):
|
|
948
|
+
"""
|
|
949
|
+
Scales each band in the Landsat collection from DN values to surface reflectance values.
|
|
950
|
+
|
|
951
|
+
Returns:
|
|
952
|
+
LandsatCollection: A new LandsatCollection object with bands scaled to reflectance.
|
|
953
|
+
"""
|
|
954
|
+
if self._Reflectance is None:
|
|
955
|
+
self._Reflectance = self.collection.map(_scale_landsat_sr)
|
|
956
|
+
return LandsatCollection(collection=self._Reflectance)
|
|
957
|
+
|
|
958
|
+
|
|
659
959
|
@property
|
|
660
960
|
def median(self):
|
|
661
961
|
"""
|
|
@@ -668,7 +968,7 @@ class LandsatCollection:
|
|
|
668
968
|
col = self.collection.median()
|
|
669
969
|
self._median = col
|
|
670
970
|
return self._median
|
|
671
|
-
|
|
971
|
+
|
|
672
972
|
@property
|
|
673
973
|
def mean(self):
|
|
674
974
|
"""
|
|
@@ -682,7 +982,7 @@ class LandsatCollection:
|
|
|
682
982
|
col = self.collection.mean()
|
|
683
983
|
self._mean = col
|
|
684
984
|
return self._mean
|
|
685
|
-
|
|
985
|
+
|
|
686
986
|
@property
|
|
687
987
|
def max(self):
|
|
688
988
|
"""
|
|
@@ -695,7 +995,7 @@ class LandsatCollection:
|
|
|
695
995
|
col = self.collection.max()
|
|
696
996
|
self._max = col
|
|
697
997
|
return self._max
|
|
698
|
-
|
|
998
|
+
|
|
699
999
|
@property
|
|
700
1000
|
def min(self):
|
|
701
1001
|
"""
|
|
@@ -708,28 +1008,43 @@ class LandsatCollection:
|
|
|
708
1008
|
col = self.collection.min()
|
|
709
1009
|
self._min = col
|
|
710
1010
|
return self._min
|
|
711
|
-
|
|
1011
|
+
|
|
712
1012
|
@property
|
|
713
1013
|
def ndwi(self):
|
|
714
1014
|
"""
|
|
715
|
-
Property attribute to calculate and access the NDWI (Normalized Difference Water Index) imagery of the LandsatCollection.
|
|
716
|
-
This property initiates the calculation of NDWI using a default threshold of -1 (or a previously set threshold of self.ndwi_threshold)
|
|
717
|
-
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1015
|
+
Property attribute to calculate and access the NDWI (Normalized Difference Water Index) imagery of the LandsatCollection.
|
|
1016
|
+
This property initiates the calculation of NDWI using a default threshold of -1 (or a previously set threshold of self.ndwi_threshold)
|
|
1017
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
718
1018
|
on subsequent accesses.
|
|
719
1019
|
|
|
720
1020
|
Returns:
|
|
721
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1021
|
+
LandsatCollection: A LandsatCollection image collection
|
|
722
1022
|
"""
|
|
723
1023
|
if self._ndwi is None:
|
|
724
1024
|
self._ndwi = self.ndwi_collection(self.ndwi_threshold)
|
|
725
1025
|
return self._ndwi
|
|
726
1026
|
|
|
1027
|
+
@property
|
|
1028
|
+
def mndwi(self):
|
|
1029
|
+
"""
|
|
1030
|
+
Property attribute to calculate and access the MNDWI (Modified Normalized Difference Water Index) imagery of the LandsatCollection.
|
|
1031
|
+
This property initiates the calculation of MNDWI using a default threshold of -1 (or a previously set threshold of self.mndwi_threshold)
|
|
1032
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1033
|
+
on subsequent accesses.
|
|
1034
|
+
|
|
1035
|
+
Returns:
|
|
1036
|
+
LandsatCollection: A LandsatCollection image collection
|
|
1037
|
+
"""
|
|
1038
|
+
if self._mndwi is None:
|
|
1039
|
+
self._mndwi = self.mndwi_collection(self.mndwi_threshold)
|
|
1040
|
+
return self._mndwi
|
|
1041
|
+
|
|
727
1042
|
def ndwi_collection(self, threshold, ng_threshold=None):
|
|
728
1043
|
"""
|
|
729
1044
|
Calculates ndwi and returns collection as class object, allows specifying threshold(s) for masking.
|
|
730
|
-
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
731
|
-
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
732
|
-
by default when using the ndwi property attribute.
|
|
1045
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1046
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1047
|
+
by default when using the ndwi property attribute.
|
|
733
1048
|
|
|
734
1049
|
Args:
|
|
735
1050
|
threshold (float): specify threshold for NDWI function (values less than threshold are masked)
|
|
@@ -740,23 +1055,54 @@ class LandsatCollection:
|
|
|
740
1055
|
first_image = self.collection.first()
|
|
741
1056
|
available_bands = first_image.bandNames()
|
|
742
1057
|
|
|
743
|
-
if available_bands.contains(
|
|
1058
|
+
if available_bands.contains("SR_B3") and available_bands.contains("SR_B5"):
|
|
744
1059
|
pass
|
|
745
1060
|
else:
|
|
746
1061
|
raise ValueError("Insufficient Bands for ndwi calculation")
|
|
747
|
-
col = self.collection.map(
|
|
1062
|
+
col = self.collection.map(
|
|
1063
|
+
lambda image: LandsatCollection.landsat_ndwi_fn(
|
|
1064
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1065
|
+
)
|
|
1066
|
+
)
|
|
748
1067
|
return LandsatCollection(collection=col)
|
|
749
1068
|
|
|
1069
|
+
def mndwi_collection(self, threshold, ng_threshold=None):
|
|
1070
|
+
"""
|
|
1071
|
+
Calculates mndwi and returns collection as class object, allows specifying threshold(s) for masking.
|
|
1072
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1073
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1074
|
+
by default when using the mndwi property attribute.
|
|
1075
|
+
|
|
1076
|
+
Args:
|
|
1077
|
+
threshold (float): specify threshold for MNDWI function (values less than threshold are masked)
|
|
1078
|
+
|
|
1079
|
+
Returns:
|
|
1080
|
+
LandsatCollection: A LandsatCollection image collection
|
|
1081
|
+
"""
|
|
1082
|
+
first_image = self.collection.first()
|
|
1083
|
+
available_bands = first_image.bandNames()
|
|
1084
|
+
|
|
1085
|
+
if available_bands.contains("SR_B3") and available_bands.contains("SR_B6"):
|
|
1086
|
+
pass
|
|
1087
|
+
else:
|
|
1088
|
+
raise ValueError("Insufficient bands for mndwi calculation")
|
|
1089
|
+
col = self.collection.map(
|
|
1090
|
+
lambda image: LandsatCollection.landsat_mndwi_fn(
|
|
1091
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1092
|
+
)
|
|
1093
|
+
)
|
|
1094
|
+
return LandsatCollection(collection=col)
|
|
1095
|
+
|
|
750
1096
|
@property
|
|
751
1097
|
def ndvi(self):
|
|
752
1098
|
"""
|
|
753
|
-
Property attribute to calculate and access the NDVI (Normalized Difference Vegetation Index) imagery of the LandsatCollection.
|
|
754
|
-
This property initiates the calculation of NDVI using a default threshold of -1 (or a previously set threshold of self.ndvi_threshold)
|
|
755
|
-
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1099
|
+
Property attribute to calculate and access the NDVI (Normalized Difference Vegetation Index) imagery of the LandsatCollection.
|
|
1100
|
+
This property initiates the calculation of NDVI using a default threshold of -1 (or a previously set threshold of self.ndvi_threshold)
|
|
1101
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
756
1102
|
on subsequent accesses.
|
|
757
1103
|
|
|
758
1104
|
Returns:
|
|
759
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1105
|
+
LandsatCollection: A LandsatCollection image collection
|
|
760
1106
|
"""
|
|
761
1107
|
if self._ndvi is None:
|
|
762
1108
|
self._ndvi = self.ndvi_collection(self.ndvi_threshold)
|
|
@@ -765,35 +1111,39 @@ class LandsatCollection:
|
|
|
765
1111
|
def ndvi_collection(self, threshold, ng_threshold=None):
|
|
766
1112
|
"""
|
|
767
1113
|
Function to calculate the NDVI (Normalized Difference Vegetation Index) and return collection as class object, allows specifying threshold(s) for masking.
|
|
768
|
-
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
769
|
-
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1114
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1115
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
770
1116
|
by default when using the ndwi property attribute.
|
|
771
1117
|
|
|
772
1118
|
Args:
|
|
773
1119
|
threshold (float): specify threshold for NDVI function (values less than threshold are masked)
|
|
774
1120
|
|
|
775
1121
|
Returns:
|
|
776
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1122
|
+
LandsatCollection: A LandsatCollection image collection
|
|
777
1123
|
"""
|
|
778
1124
|
first_image = self.collection.first()
|
|
779
1125
|
available_bands = first_image.bandNames()
|
|
780
|
-
if available_bands.contains(
|
|
1126
|
+
if available_bands.contains("SR_B4") and available_bands.contains("SR_B5"):
|
|
781
1127
|
pass
|
|
782
1128
|
else:
|
|
783
1129
|
raise ValueError("Insufficient Bands for ndwi calculation")
|
|
784
|
-
col = self.collection.map(
|
|
1130
|
+
col = self.collection.map(
|
|
1131
|
+
lambda image: LandsatCollection.landsat_ndvi_fn(
|
|
1132
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1133
|
+
)
|
|
1134
|
+
)
|
|
785
1135
|
return LandsatCollection(collection=col)
|
|
786
1136
|
|
|
787
1137
|
@property
|
|
788
1138
|
def halite(self):
|
|
789
1139
|
"""
|
|
790
|
-
Property attribute to calculate and access the halite index (see Radwin & Bowen, 2021) imagery of the LandsatCollection.
|
|
791
|
-
This property initiates the calculation of halite using a default threshold of -1 (or a previously set threshold of self.halite_threshold)
|
|
792
|
-
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1140
|
+
Property attribute to calculate and access the halite index (see Radwin & Bowen, 2021) imagery of the LandsatCollection.
|
|
1141
|
+
This property initiates the calculation of halite using a default threshold of -1 (or a previously set threshold of self.halite_threshold)
|
|
1142
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
793
1143
|
on subsequent accesses.
|
|
794
1144
|
|
|
795
1145
|
Returns:
|
|
796
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1146
|
+
LandsatCollection: A LandsatCollection image collection
|
|
797
1147
|
"""
|
|
798
1148
|
if self._halite is None:
|
|
799
1149
|
self._halite = self.halite_collection(self.halite_threshold)
|
|
@@ -802,8 +1152,8 @@ class LandsatCollection:
|
|
|
802
1152
|
def halite_collection(self, threshold, ng_threshold=None):
|
|
803
1153
|
"""
|
|
804
1154
|
Function to calculate the halite index (see Radwin & Bowen, 2021) and return collection as class object, allows specifying threshold(s) for masking.
|
|
805
|
-
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
806
|
-
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1155
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1156
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
807
1157
|
by default when using the ndwi property attribute.
|
|
808
1158
|
|
|
809
1159
|
Args:
|
|
@@ -811,27 +1161,31 @@ class LandsatCollection:
|
|
|
811
1161
|
ng_threshold (float, optional): specify threshold for Landsat 8&9 halite function (values less than threshold are masked)
|
|
812
1162
|
|
|
813
1163
|
Returns:
|
|
814
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1164
|
+
LandsatCollection: A LandsatCollection image collection
|
|
815
1165
|
"""
|
|
816
1166
|
first_image = self.collection.first()
|
|
817
1167
|
available_bands = first_image.bandNames()
|
|
818
|
-
if available_bands.contains(
|
|
1168
|
+
if available_bands.contains("SR_B4") and available_bands.contains("SR_B6"):
|
|
819
1169
|
pass
|
|
820
1170
|
else:
|
|
821
1171
|
raise ValueError("Insufficient Bands for halite calculation")
|
|
822
|
-
col = self.collection.map(
|
|
1172
|
+
col = self.collection.map(
|
|
1173
|
+
lambda image: LandsatCollection.landsat_halite_fn(
|
|
1174
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1175
|
+
)
|
|
1176
|
+
)
|
|
823
1177
|
return LandsatCollection(collection=col)
|
|
824
1178
|
|
|
825
1179
|
@property
|
|
826
1180
|
def gypsum(self):
|
|
827
1181
|
"""
|
|
828
|
-
Property attribute to calculate and access the gypsum/sulfate index (see Radwin & Bowen, 2021) imagery of the LandsatCollection.
|
|
829
|
-
This property initiates the calculation of gypsum using a default threshold of -1 (or a previously set threshold of self.gypsum_threshold)
|
|
830
|
-
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1182
|
+
Property attribute to calculate and access the gypsum/sulfate index (see Radwin & Bowen, 2021) imagery of the LandsatCollection.
|
|
1183
|
+
This property initiates the calculation of gypsum using a default threshold of -1 (or a previously set threshold of self.gypsum_threshold)
|
|
1184
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
831
1185
|
on subsequent accesses.
|
|
832
1186
|
|
|
833
1187
|
Returns:
|
|
834
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1188
|
+
LandsatCollection: A LandsatCollection image collection
|
|
835
1189
|
"""
|
|
836
1190
|
if self._gypsum is None:
|
|
837
1191
|
self._gypsum = self.gypsum_collection(self.gypsum_threshold)
|
|
@@ -840,8 +1194,8 @@ class LandsatCollection:
|
|
|
840
1194
|
def gypsum_collection(self, threshold, ng_threshold=None):
|
|
841
1195
|
"""
|
|
842
1196
|
Function to calculate the gypsum index (see Radwin & Bowen, 2021) and return collection as class object, allows specifying threshold(s) for masking.
|
|
843
|
-
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
844
|
-
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1197
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1198
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
845
1199
|
by default when using the ndwi property attribute.
|
|
846
1200
|
|
|
847
1201
|
Args:
|
|
@@ -849,27 +1203,31 @@ class LandsatCollection:
|
|
|
849
1203
|
ng_threshold (float, optional): specify threshold for Landsat 8&9 gypsum function (values less than threshold are masked)
|
|
850
1204
|
|
|
851
1205
|
Returns:
|
|
852
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1206
|
+
LandsatCollection: A LandsatCollection image collection
|
|
853
1207
|
"""
|
|
854
1208
|
first_image = self.collection.first()
|
|
855
1209
|
available_bands = first_image.bandNames()
|
|
856
|
-
if available_bands.contains(
|
|
1210
|
+
if available_bands.contains("SR_B6") and available_bands.contains("SR_B7"):
|
|
857
1211
|
pass
|
|
858
1212
|
else:
|
|
859
1213
|
raise ValueError("Insufficient Bands for gypsum calculation")
|
|
860
|
-
col = self.collection.map(
|
|
1214
|
+
col = self.collection.map(
|
|
1215
|
+
lambda image: LandsatCollection.landsat_gypsum_fn(
|
|
1216
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1217
|
+
)
|
|
1218
|
+
)
|
|
861
1219
|
return LandsatCollection(collection=col)
|
|
862
|
-
|
|
1220
|
+
|
|
863
1221
|
@property
|
|
864
1222
|
def turbidity(self):
|
|
865
1223
|
"""
|
|
866
|
-
Property attribute to calculate and access the turbidity (NDTI) imagery of the LandsatCollection.
|
|
867
|
-
This property initiates the calculation of turbidity using a default threshold of -1 (or a previously set threshold of self.turbidity_threshold)
|
|
868
|
-
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1224
|
+
Property attribute to calculate and access the turbidity (NDTI) imagery of the LandsatCollection.
|
|
1225
|
+
This property initiates the calculation of turbidity using a default threshold of -1 (or a previously set threshold of self.turbidity_threshold)
|
|
1226
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
869
1227
|
on subsequent accesses.
|
|
870
1228
|
|
|
871
1229
|
Returns:
|
|
872
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1230
|
+
LandsatCollection: A LandsatCollection image collection
|
|
873
1231
|
"""
|
|
874
1232
|
if self._turbidity is None:
|
|
875
1233
|
self._turbidity = self.turbidity_collection(self.turbidity_threshold)
|
|
@@ -878,8 +1236,8 @@ class LandsatCollection:
|
|
|
878
1236
|
def turbidity_collection(self, threshold, ng_threshold=None):
|
|
879
1237
|
"""
|
|
880
1238
|
Calculates the turbidity (NDTI) index and return collection as class object, allows specifying threshold(s) for masking.
|
|
881
|
-
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
882
|
-
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1239
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1240
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
883
1241
|
by default when using the ndwi property attribute.
|
|
884
1242
|
|
|
885
1243
|
Args:
|
|
@@ -887,28 +1245,32 @@ class LandsatCollection:
|
|
|
887
1245
|
ng_threshold (float, optional): specify threshold for Landsat 8&9 turbidity function (values less than threshold are masked)
|
|
888
1246
|
|
|
889
1247
|
Returns:
|
|
890
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1248
|
+
LandsatCollection: A LandsatCollection image collection
|
|
891
1249
|
"""
|
|
892
1250
|
first_image = self.collection.first()
|
|
893
1251
|
available_bands = first_image.bandNames()
|
|
894
|
-
if available_bands.contains(
|
|
1252
|
+
if available_bands.contains("SR_B4") and available_bands.contains("SR_B3"):
|
|
895
1253
|
pass
|
|
896
1254
|
else:
|
|
897
1255
|
raise ValueError("Insufficient Bands for turbidity calculation")
|
|
898
|
-
col = self.collection.map(
|
|
1256
|
+
col = self.collection.map(
|
|
1257
|
+
lambda image: LandsatCollection.landsat_ndti_fn(
|
|
1258
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1259
|
+
)
|
|
1260
|
+
)
|
|
899
1261
|
|
|
900
1262
|
return LandsatCollection(collection=col)
|
|
901
|
-
|
|
1263
|
+
|
|
902
1264
|
@property
|
|
903
1265
|
def chlorophyll(self):
|
|
904
1266
|
"""
|
|
905
|
-
Property attribute to calculate and access the chlorophyll (NDTI) imagery of the LandsatCollection.
|
|
906
|
-
This property initiates the calculation of chlorophyll using a default threshold of -1 (or a previously set threshold of self.chlorophyll_threshold)
|
|
907
|
-
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
1267
|
+
Property attribute to calculate and access the chlorophyll (NDTI) imagery of the LandsatCollection.
|
|
1268
|
+
This property initiates the calculation of chlorophyll using a default threshold of -1 (or a previously set threshold of self.chlorophyll_threshold)
|
|
1269
|
+
and caches the result. The calculation is performed only once when the property is first accessed, and the cached result is returned
|
|
908
1270
|
on subsequent accesses.
|
|
909
1271
|
|
|
910
1272
|
Returns:
|
|
911
|
-
LandsatCollection: A LandsatCollection image collection
|
|
1273
|
+
LandsatCollection: A LandsatCollection image collection
|
|
912
1274
|
"""
|
|
913
1275
|
if self._chlorophyll is None:
|
|
914
1276
|
self._chlorophyll = self.chlorophyll_collection(self.chlorophyll_threshold)
|
|
@@ -917,8 +1279,8 @@ class LandsatCollection:
|
|
|
917
1279
|
def chlorophyll_collection(self, threshold, ng_threshold=None):
|
|
918
1280
|
"""
|
|
919
1281
|
Calculates the KIVU chlorophyll index and return collection as class object, allows specifying threshold(s) for masking.
|
|
920
|
-
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
921
|
-
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
1282
|
+
Thresholds can be specified for Landsat 5 vs 8&9 images, where the threshold argument applies to Landsat 5
|
|
1283
|
+
and the ng_threshold argument applies to Landsat 8&9. This function can be called as a method but is called
|
|
922
1284
|
by default when using the ndwi property attribute.
|
|
923
1285
|
|
|
924
1286
|
Args:
|
|
@@ -930,26 +1292,34 @@ class LandsatCollection:
|
|
|
930
1292
|
"""
|
|
931
1293
|
first_image = self.collection.first()
|
|
932
1294
|
available_bands = first_image.bandNames()
|
|
933
|
-
if
|
|
1295
|
+
if (
|
|
1296
|
+
available_bands.contains("SR_B4")
|
|
1297
|
+
and available_bands.contains("SR_B3")
|
|
1298
|
+
and available_bands.contains("SR_B2")
|
|
1299
|
+
):
|
|
934
1300
|
pass
|
|
935
1301
|
else:
|
|
936
1302
|
raise ValueError("Insufficient Bands for chlorophyll calculation")
|
|
937
|
-
col = self.collection.map(
|
|
1303
|
+
col = self.collection.map(
|
|
1304
|
+
lambda image: LandsatCollection.landsat_kivu_chla_fn(
|
|
1305
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1306
|
+
)
|
|
1307
|
+
)
|
|
938
1308
|
return LandsatCollection(collection=col)
|
|
939
|
-
|
|
1309
|
+
|
|
940
1310
|
@property
|
|
941
1311
|
def masked_water_collection(self):
|
|
942
1312
|
"""
|
|
943
1313
|
Property attribute to mask water and return collection as class object.
|
|
944
1314
|
|
|
945
1315
|
Returns:
|
|
946
|
-
LandsatCollection: LandsatCollection image collection
|
|
1316
|
+
LandsatCollection: LandsatCollection image collection
|
|
947
1317
|
"""
|
|
948
1318
|
if self._masked_water_collection is None:
|
|
949
1319
|
col = self.collection.map(LandsatCollection.MaskWaterLandsat)
|
|
950
1320
|
self._masked_water_collection = LandsatCollection(collection=col)
|
|
951
1321
|
return self._masked_water_collection
|
|
952
|
-
|
|
1322
|
+
|
|
953
1323
|
def masked_water_collection_NDWI(self, threshold):
|
|
954
1324
|
"""
|
|
955
1325
|
Masks water pixels based on NDWI and user set threshold.
|
|
@@ -958,11 +1328,15 @@ class LandsatCollection:
|
|
|
958
1328
|
threshold (float): specify threshold for NDWI function (values greater than threshold are masked)
|
|
959
1329
|
|
|
960
1330
|
Returns:
|
|
961
|
-
LandsatCollection: LandsatCollection image collection
|
|
1331
|
+
LandsatCollection: LandsatCollection image collection
|
|
962
1332
|
"""
|
|
963
|
-
col = self.collection.map(
|
|
1333
|
+
col = self.collection.map(
|
|
1334
|
+
lambda image: LandsatCollection.MaskWaterLandsatByNDWI(
|
|
1335
|
+
image, threshold=threshold
|
|
1336
|
+
)
|
|
1337
|
+
)
|
|
964
1338
|
return LandsatCollection(collection=col)
|
|
965
|
-
|
|
1339
|
+
|
|
966
1340
|
@property
|
|
967
1341
|
def masked_to_water_collection(self):
|
|
968
1342
|
"""
|
|
@@ -975,7 +1349,7 @@ class LandsatCollection:
|
|
|
975
1349
|
col = self.collection.map(LandsatCollection.MaskToWaterLandsat)
|
|
976
1350
|
self._masked_to_water_collection = LandsatCollection(collection=col)
|
|
977
1351
|
return self._masked_to_water_collection
|
|
978
|
-
|
|
1352
|
+
|
|
979
1353
|
def masked_to_water_collection_NDWI(self, threshold):
|
|
980
1354
|
"""
|
|
981
1355
|
Function to mask all but water pixels based on NDWI and user set threshold.
|
|
@@ -984,29 +1358,33 @@ class LandsatCollection:
|
|
|
984
1358
|
threshold (float): specify threshold for NDWI function (values less than threshold are masked)
|
|
985
1359
|
|
|
986
1360
|
Returns:
|
|
987
|
-
LandsatCollection: LandsatCollection image collection
|
|
1361
|
+
LandsatCollection: LandsatCollection image collection
|
|
988
1362
|
"""
|
|
989
|
-
col = self.collection.map(
|
|
1363
|
+
col = self.collection.map(
|
|
1364
|
+
lambda image: LandsatCollection.MaskToWaterLandsatByNDWI(
|
|
1365
|
+
image, threshold=threshold
|
|
1366
|
+
)
|
|
1367
|
+
)
|
|
990
1368
|
return LandsatCollection(collection=col)
|
|
991
|
-
|
|
1369
|
+
|
|
992
1370
|
@property
|
|
993
1371
|
def masked_clouds_collection(self):
|
|
994
1372
|
"""
|
|
995
1373
|
Property attribute to mask clouds and return collection as class object.
|
|
996
1374
|
|
|
997
1375
|
Returns:
|
|
998
|
-
LandsatCollection: LandsatCollection image collection
|
|
1376
|
+
LandsatCollection: LandsatCollection image collection
|
|
999
1377
|
"""
|
|
1000
1378
|
if self._masked_clouds_collection is None:
|
|
1001
1379
|
col = self.collection.map(LandsatCollection.maskL8clouds)
|
|
1002
1380
|
self._masked_clouds_collection = LandsatCollection(collection=col)
|
|
1003
1381
|
return self._masked_clouds_collection
|
|
1004
|
-
|
|
1382
|
+
|
|
1005
1383
|
@property
|
|
1006
1384
|
def LST(self):
|
|
1007
1385
|
"""
|
|
1008
|
-
Property attribute to calculate and access the LST (Land Surface Temperature - in Celcius) imagery of the LandsatCollection.
|
|
1009
|
-
This property initiates the calculation of LST and caches the result. The calculation is performed only once
|
|
1386
|
+
Property attribute to calculate and access the LST (Land Surface Temperature - in Celcius) imagery of the LandsatCollection.
|
|
1387
|
+
This property initiates the calculation of LST and caches the result. The calculation is performed only once
|
|
1010
1388
|
when the property is first accessed, and the cached result is returned on subsequent accesses.
|
|
1011
1389
|
|
|
1012
1390
|
Returns:
|
|
@@ -1015,7 +1393,7 @@ class LandsatCollection:
|
|
|
1015
1393
|
if self._LST is None:
|
|
1016
1394
|
self._LST = self.surface_temperature_collection()
|
|
1017
1395
|
return self._LST
|
|
1018
|
-
|
|
1396
|
+
|
|
1019
1397
|
def surface_temperature_collection(self):
|
|
1020
1398
|
"""
|
|
1021
1399
|
Function to calculate LST (Land Surface Temperature - in Celcius) and return collection as class object.
|
|
@@ -1025,13 +1403,23 @@ class LandsatCollection:
|
|
|
1025
1403
|
"""
|
|
1026
1404
|
first_image = self.collection.first()
|
|
1027
1405
|
available_bands = first_image.bandNames()
|
|
1028
|
-
if
|
|
1406
|
+
if (
|
|
1407
|
+
available_bands.contains("ST_ATRAN")
|
|
1408
|
+
and available_bands.contains("ST_EMIS")
|
|
1409
|
+
and available_bands.contains("ST_DRAD")
|
|
1410
|
+
and available_bands.contains("ST_TRAD")
|
|
1411
|
+
and available_bands.contains("ST_URAD")
|
|
1412
|
+
):
|
|
1029
1413
|
pass
|
|
1030
1414
|
else:
|
|
1031
1415
|
raise ValueError("Insufficient Bands for temperature calculation")
|
|
1032
|
-
col =
|
|
1416
|
+
col = (
|
|
1417
|
+
self.collection.map(LandsatCollection.temperature_bands)
|
|
1418
|
+
.map(LandsatCollection.landsat_LST)
|
|
1419
|
+
.map(LandsatCollection.image_dater)
|
|
1420
|
+
)
|
|
1033
1421
|
return LandsatCollection(collection=col)
|
|
1034
|
-
|
|
1422
|
+
|
|
1035
1423
|
def mask_to_polygon(self, polygon):
|
|
1036
1424
|
"""
|
|
1037
1425
|
Function to mask LandsatCollection image collection by a polygon (ee.Geometry), where pixels outside the polygon are masked out.
|
|
@@ -1041,21 +1429,23 @@ class LandsatCollection:
|
|
|
1041
1429
|
|
|
1042
1430
|
Returns:
|
|
1043
1431
|
LandsatCollection: masked LandsatCollection image collection
|
|
1044
|
-
|
|
1432
|
+
|
|
1045
1433
|
"""
|
|
1046
1434
|
if self._geometry_masked_collection is None:
|
|
1047
1435
|
# Convert the polygon to a mask
|
|
1048
1436
|
mask = ee.Image.constant(1).clip(polygon)
|
|
1049
|
-
|
|
1437
|
+
|
|
1050
1438
|
# Update the mask of each image in the collection
|
|
1051
1439
|
masked_collection = self.collection.map(lambda img: img.updateMask(mask))
|
|
1052
|
-
|
|
1440
|
+
|
|
1053
1441
|
# Update the internal collection state
|
|
1054
|
-
self._geometry_masked_collection = LandsatCollection(
|
|
1055
|
-
|
|
1442
|
+
self._geometry_masked_collection = LandsatCollection(
|
|
1443
|
+
collection=masked_collection
|
|
1444
|
+
)
|
|
1445
|
+
|
|
1056
1446
|
# Return the updated object
|
|
1057
1447
|
return self._geometry_masked_collection
|
|
1058
|
-
|
|
1448
|
+
|
|
1059
1449
|
def mask_out_polygon(self, polygon):
|
|
1060
1450
|
"""
|
|
1061
1451
|
Function to mask LandsatCollection image collection by a polygon (ee.Geometry), where pixels inside the polygon are masked out.
|
|
@@ -1065,7 +1455,7 @@ class LandsatCollection:
|
|
|
1065
1455
|
|
|
1066
1456
|
Returns:
|
|
1067
1457
|
LandsatCollection: masked LandsatCollection image collection
|
|
1068
|
-
|
|
1458
|
+
|
|
1069
1459
|
"""
|
|
1070
1460
|
if self._geometry_masked_out_collection is None:
|
|
1071
1461
|
# Convert the polygon to a mask
|
|
@@ -1073,19 +1463,21 @@ class LandsatCollection:
|
|
|
1073
1463
|
|
|
1074
1464
|
# Use paint to set pixels inside polygon as 0
|
|
1075
1465
|
area = full_mask.paint(polygon, 0)
|
|
1076
|
-
|
|
1466
|
+
|
|
1077
1467
|
# Update the mask of each image in the collection
|
|
1078
1468
|
masked_collection = self.collection.map(lambda img: img.updateMask(area))
|
|
1079
|
-
|
|
1469
|
+
|
|
1080
1470
|
# Update the internal collection state
|
|
1081
|
-
self._geometry_masked_out_collection = LandsatCollection(
|
|
1082
|
-
|
|
1471
|
+
self._geometry_masked_out_collection = LandsatCollection(
|
|
1472
|
+
collection=masked_collection
|
|
1473
|
+
)
|
|
1474
|
+
|
|
1083
1475
|
# Return the updated object
|
|
1084
1476
|
return self._geometry_masked_out_collection
|
|
1085
1477
|
|
|
1086
1478
|
def mask_halite(self, threshold, ng_threshold=None):
|
|
1087
1479
|
"""
|
|
1088
|
-
Masks halite and returns collection as class object. Can specify separate thresholds for Landsat 5 vs 8&9 images
|
|
1480
|
+
Masks halite and returns collection as class object. Can specify separate thresholds for Landsat 5 vs 8&9 images
|
|
1089
1481
|
where the threshold argument applies to Landsat 5 and the ng_threshold argument applies to Landsat 8&9.
|
|
1090
1482
|
|
|
1091
1483
|
Args:
|
|
@@ -1095,12 +1487,22 @@ class LandsatCollection:
|
|
|
1095
1487
|
Returns:
|
|
1096
1488
|
LandsatCollection: LandsatCollection image collection
|
|
1097
1489
|
"""
|
|
1098
|
-
col = self.collection.map(
|
|
1490
|
+
col = self.collection.map(
|
|
1491
|
+
lambda image: LandsatCollection.halite_mask(
|
|
1492
|
+
image, threshold=threshold, ng_threshold=ng_threshold
|
|
1493
|
+
)
|
|
1494
|
+
)
|
|
1099
1495
|
return LandsatCollection(collection=col)
|
|
1100
|
-
|
|
1101
|
-
def mask_halite_and_gypsum(
|
|
1496
|
+
|
|
1497
|
+
def mask_halite_and_gypsum(
|
|
1498
|
+
self,
|
|
1499
|
+
halite_threshold,
|
|
1500
|
+
gypsum_threshold,
|
|
1501
|
+
halite_ng_threshold=None,
|
|
1502
|
+
gypsum_ng_threshold=None,
|
|
1503
|
+
):
|
|
1102
1504
|
"""
|
|
1103
|
-
Masks halite and gypsum and returns collection as class object.
|
|
1505
|
+
Masks halite and gypsum and returns collection as class object.
|
|
1104
1506
|
Can specify separate thresholds for Landsat 5 vs 8&9 images where the threshold argument applies to Landsat 5
|
|
1105
1507
|
and the ng_threshold argument applies to Landsat 8&9.
|
|
1106
1508
|
|
|
@@ -1111,9 +1513,48 @@ class LandsatCollection:
|
|
|
1111
1513
|
gypsum_ng_threshold (float, optional): specify threshold for Landsat 8&9 gypsum function (values less than threshold are masked)
|
|
1112
1514
|
|
|
1113
1515
|
Returns:
|
|
1114
|
-
LandsatCollection: LandsatCollection image collection
|
|
1516
|
+
LandsatCollection: LandsatCollection image collection
|
|
1115
1517
|
"""
|
|
1116
|
-
col = self.collection.map(
|
|
1518
|
+
col = self.collection.map(
|
|
1519
|
+
lambda image: LandsatCollection.gypsum_and_halite_mask(
|
|
1520
|
+
image,
|
|
1521
|
+
halite_threshold=halite_threshold,
|
|
1522
|
+
gypsum_threshold=gypsum_threshold,
|
|
1523
|
+
halite_ng_threshold=halite_ng_threshold,
|
|
1524
|
+
gypsum_ng_threshold=gypsum_ng_threshold,
|
|
1525
|
+
)
|
|
1526
|
+
)
|
|
1527
|
+
return LandsatCollection(collection=col)
|
|
1528
|
+
|
|
1529
|
+
def binary_mask(self, threshold=None, band_name=None):
|
|
1530
|
+
"""
|
|
1531
|
+
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.
|
|
1532
|
+
If a singleband image is provided, the band name is automatically determined.
|
|
1533
|
+
If multiple bands are available, the user must specify the band name to use for masking.
|
|
1534
|
+
|
|
1535
|
+
Args:
|
|
1536
|
+
band_name (str, optional): The name of the band to use for masking. Defaults to None.
|
|
1537
|
+
|
|
1538
|
+
Returns:
|
|
1539
|
+
LandsatCollection: LandsatCollection singleband image collection with binary masks applied.
|
|
1540
|
+
"""
|
|
1541
|
+
if self.collection.size().eq(0).getInfo():
|
|
1542
|
+
raise ValueError("The collection is empty. Cannot create a binary mask.")
|
|
1543
|
+
if band_name is None:
|
|
1544
|
+
first_image = self.collection.first()
|
|
1545
|
+
band_names = first_image.bandNames()
|
|
1546
|
+
if band_names.size().getInfo() == 0:
|
|
1547
|
+
raise ValueError("No bands available in the collection.")
|
|
1548
|
+
if band_names.size().getInfo() > 1:
|
|
1549
|
+
raise ValueError("Multiple bands available, please specify a band name.")
|
|
1550
|
+
else:
|
|
1551
|
+
band_name = band_names.get(0).getInfo()
|
|
1552
|
+
if threshold is None:
|
|
1553
|
+
raise ValueError("Threshold must be specified for binary masking.")
|
|
1554
|
+
|
|
1555
|
+
col = self.collection.map(
|
|
1556
|
+
lambda image: image.select(band_name).gte(threshold).rename(band_name)
|
|
1557
|
+
)
|
|
1117
1558
|
return LandsatCollection(collection=col)
|
|
1118
1559
|
|
|
1119
1560
|
def image_grab(self, img_selector):
|
|
@@ -1122,7 +1563,7 @@ class LandsatCollection:
|
|
|
1122
1563
|
|
|
1123
1564
|
Args:
|
|
1124
1565
|
img_selector: index of image in the collection for which user seeks to select/"grab".
|
|
1125
|
-
|
|
1566
|
+
|
|
1126
1567
|
Returns:
|
|
1127
1568
|
ee.Image: ee.Image of selected image
|
|
1128
1569
|
"""
|
|
@@ -1141,7 +1582,7 @@ class LandsatCollection:
|
|
|
1141
1582
|
Args:
|
|
1142
1583
|
img_col: ee.ImageCollection with same dates as another LandsatCollection image collection object.
|
|
1143
1584
|
img_selector: index of image in list of dates for which user seeks to "select".
|
|
1144
|
-
|
|
1585
|
+
|
|
1145
1586
|
Returns:
|
|
1146
1587
|
ee.Image: ee.Image of selected image
|
|
1147
1588
|
"""
|
|
@@ -1152,7 +1593,7 @@ class LandsatCollection:
|
|
|
1152
1593
|
image = ee.Image(image_list.get(img_selector))
|
|
1153
1594
|
|
|
1154
1595
|
return image
|
|
1155
|
-
|
|
1596
|
+
|
|
1156
1597
|
def image_pick(self, img_date):
|
|
1157
1598
|
"""
|
|
1158
1599
|
Selects ("grabs") image of a specific date in format of 'YYYY-MM-DD' - will not work correctly if collection is composed of multiple images of the same date.
|
|
@@ -1163,13 +1604,13 @@ class LandsatCollection:
|
|
|
1163
1604
|
Returns:
|
|
1164
1605
|
ee.Image: ee.Image of selected image
|
|
1165
1606
|
"""
|
|
1166
|
-
new_col = self.collection.filter(ee.Filter.eq(
|
|
1607
|
+
new_col = self.collection.filter(ee.Filter.eq("Date_Filter", img_date))
|
|
1167
1608
|
return new_col.first()
|
|
1168
1609
|
|
|
1169
1610
|
def CollectionStitch(self, img_col2):
|
|
1170
1611
|
"""
|
|
1171
|
-
Function to mosaic two LandsatCollection objects which share image dates.
|
|
1172
|
-
Mosaics are only formed for dates where both image collections have images.
|
|
1612
|
+
Function to mosaic two LandsatCollection objects which share image dates.
|
|
1613
|
+
Mosaics are only formed for dates where both image collections have images.
|
|
1173
1614
|
Image properties are copied from the primary collection. Server-side friendly.
|
|
1174
1615
|
|
|
1175
1616
|
Args:
|
|
@@ -1178,26 +1619,38 @@ class LandsatCollection:
|
|
|
1178
1619
|
Returns:
|
|
1179
1620
|
LandsatCollection: LandsatCollection image collection
|
|
1180
1621
|
"""
|
|
1181
|
-
dates_list =
|
|
1622
|
+
dates_list = (
|
|
1623
|
+
ee.List(self._dates_list).cat(ee.List(img_col2.dates_list)).distinct()
|
|
1624
|
+
)
|
|
1182
1625
|
filtered_dates1 = self._dates_list
|
|
1183
1626
|
filtered_dates2 = img_col2._dates_list
|
|
1184
1627
|
|
|
1185
|
-
filtered_col2 = img_col2.collection.filter(
|
|
1186
|
-
|
|
1628
|
+
filtered_col2 = img_col2.collection.filter(
|
|
1629
|
+
ee.Filter.inList("Date_Filter", filtered_dates1)
|
|
1630
|
+
)
|
|
1631
|
+
filtered_col1 = self.collection.filter(
|
|
1632
|
+
ee.Filter.inList(
|
|
1633
|
+
"Date_Filter", filtered_col2.aggregate_array("Date_Filter")
|
|
1634
|
+
)
|
|
1635
|
+
)
|
|
1187
1636
|
|
|
1188
1637
|
# Create a function that will be mapped over filtered_col1
|
|
1189
1638
|
def mosaic_images(img):
|
|
1190
1639
|
# Get the date of the image
|
|
1191
|
-
date = img.get(
|
|
1192
|
-
|
|
1640
|
+
date = img.get("Date_Filter")
|
|
1641
|
+
|
|
1193
1642
|
# Get the corresponding image from filtered_col2
|
|
1194
|
-
img2 = filtered_col2.filter(ee.Filter.equals(
|
|
1643
|
+
img2 = filtered_col2.filter(ee.Filter.equals("Date_Filter", date)).first()
|
|
1195
1644
|
|
|
1196
1645
|
# Create a mosaic of the two images
|
|
1197
1646
|
mosaic = ee.ImageCollection.fromImages([img, img2]).mosaic()
|
|
1198
1647
|
|
|
1199
1648
|
# Copy properties from the first image and set the 'Date_Filter' property
|
|
1200
|
-
mosaic =
|
|
1649
|
+
mosaic = (
|
|
1650
|
+
mosaic.copyProperties(img)
|
|
1651
|
+
.set("Date_Filter", date)
|
|
1652
|
+
.set("system:time_start", img.get("system:time_start"))
|
|
1653
|
+
)
|
|
1201
1654
|
|
|
1202
1655
|
return mosaic
|
|
1203
1656
|
|
|
@@ -1206,16 +1659,16 @@ class LandsatCollection:
|
|
|
1206
1659
|
|
|
1207
1660
|
# Return a LandsatCollection instance
|
|
1208
1661
|
return LandsatCollection(collection=new_col)
|
|
1209
|
-
|
|
1662
|
+
|
|
1210
1663
|
@property
|
|
1211
1664
|
def MosaicByDate(self):
|
|
1212
1665
|
"""
|
|
1213
1666
|
Property attribute function to mosaic collection images that share the same date.
|
|
1214
1667
|
|
|
1215
|
-
The property CLOUD_COVER for each image is used to calculate an overall mean,
|
|
1216
|
-
which replaces the CLOUD_COVER property for each mosaiced image.
|
|
1217
|
-
Server-side friendly.
|
|
1218
|
-
|
|
1668
|
+
The property CLOUD_COVER for each image is used to calculate an overall mean,
|
|
1669
|
+
which replaces the CLOUD_COVER property for each mosaiced image.
|
|
1670
|
+
Server-side friendly.
|
|
1671
|
+
|
|
1219
1672
|
NOTE: if images are removed from the collection from cloud filtering, you may have mosaics composed of only one image.
|
|
1220
1673
|
|
|
1221
1674
|
Returns:
|
|
@@ -1223,11 +1676,12 @@ class LandsatCollection:
|
|
|
1223
1676
|
"""
|
|
1224
1677
|
if self._MosaicByDate is None:
|
|
1225
1678
|
input_collection = self.collection
|
|
1679
|
+
|
|
1226
1680
|
# Function to mosaic images of the same date and accumulate them
|
|
1227
1681
|
def mosaic_and_accumulate(date, list_accumulator):
|
|
1228
1682
|
# date = ee.Date(date)
|
|
1229
1683
|
list_accumulator = ee.List(list_accumulator)
|
|
1230
|
-
date_filter = ee.Filter.eq(
|
|
1684
|
+
date_filter = ee.Filter.eq("Date_Filter", date)
|
|
1231
1685
|
date_collection = input_collection.filter(date_filter)
|
|
1232
1686
|
# Convert the collection to a list
|
|
1233
1687
|
image_list = date_collection.toList(date_collection.size())
|
|
@@ -1235,24 +1689,30 @@ class LandsatCollection:
|
|
|
1235
1689
|
# Get the image at the specified index
|
|
1236
1690
|
first_image = ee.Image(image_list.get(0))
|
|
1237
1691
|
# Create mosaic
|
|
1238
|
-
mosaic = date_collection.mosaic().set(
|
|
1692
|
+
mosaic = date_collection.mosaic().set("Date_Filter", date)
|
|
1239
1693
|
|
|
1240
1694
|
# Calculate cumulative cloud and no data percentages
|
|
1241
|
-
cloud_percentage = date_collection.aggregate_mean(
|
|
1695
|
+
cloud_percentage = date_collection.aggregate_mean("CLOUD_COVER")
|
|
1242
1696
|
|
|
1243
|
-
props_of_interest = [
|
|
1697
|
+
props_of_interest = [
|
|
1698
|
+
"SPACECRAFT_ID",
|
|
1699
|
+
"SENSOR_ID",
|
|
1700
|
+
"PROCESSING_LEVEL",
|
|
1701
|
+
"ACQUISITION_DATE",
|
|
1702
|
+
"system:time_start",
|
|
1703
|
+
]
|
|
1244
1704
|
|
|
1245
1705
|
# mosaic = mosaic.copyProperties(self.image_grab(0), props_of_interest).set({
|
|
1246
1706
|
# 'CLOUD_COVER': cloud_percentage
|
|
1247
1707
|
# })
|
|
1248
|
-
mosaic = mosaic.copyProperties(first_image, props_of_interest).set(
|
|
1249
|
-
|
|
1250
|
-
|
|
1708
|
+
mosaic = mosaic.copyProperties(first_image, props_of_interest).set(
|
|
1709
|
+
{"CLOUD_COVER": cloud_percentage}
|
|
1710
|
+
)
|
|
1251
1711
|
|
|
1252
1712
|
return list_accumulator.add(mosaic)
|
|
1253
1713
|
|
|
1254
1714
|
# Get distinct dates
|
|
1255
|
-
distinct_dates = input_collection.aggregate_array(
|
|
1715
|
+
distinct_dates = input_collection.aggregate_array("Date_Filter").distinct()
|
|
1256
1716
|
|
|
1257
1717
|
# Initialize an empty list as the accumulator
|
|
1258
1718
|
initial = ee.List([])
|
|
@@ -1266,9 +1726,11 @@ class LandsatCollection:
|
|
|
1266
1726
|
|
|
1267
1727
|
# Convert the list of mosaics to an ImageCollection
|
|
1268
1728
|
return self._MosaicByDate
|
|
1269
|
-
|
|
1729
|
+
|
|
1270
1730
|
@staticmethod
|
|
1271
|
-
def ee_to_df(
|
|
1731
|
+
def ee_to_df(
|
|
1732
|
+
ee_object, columns=None, remove_geom=True, sort_columns=False, **kwargs
|
|
1733
|
+
):
|
|
1272
1734
|
"""Converts an ee.FeatureCollection to pandas dataframe. Adapted from the geemap package (https://geemap.org/common/#geemap.common.ee_to_df)
|
|
1273
1735
|
|
|
1274
1736
|
Args:
|
|
@@ -1318,8 +1780,19 @@ class LandsatCollection:
|
|
|
1318
1780
|
raise Exception(e)
|
|
1319
1781
|
|
|
1320
1782
|
@staticmethod
|
|
1321
|
-
def extract_transect(
|
|
1322
|
-
|
|
1783
|
+
def extract_transect(
|
|
1784
|
+
image,
|
|
1785
|
+
line,
|
|
1786
|
+
reducer="mean",
|
|
1787
|
+
n_segments=100,
|
|
1788
|
+
dist_interval=None,
|
|
1789
|
+
scale=None,
|
|
1790
|
+
crs=None,
|
|
1791
|
+
crsTransform=None,
|
|
1792
|
+
tileScale=1.0,
|
|
1793
|
+
to_pandas=False,
|
|
1794
|
+
**kwargs,
|
|
1795
|
+
):
|
|
1323
1796
|
"""Extracts transect from an image. Adapted from the geemap package (https://geemap.org/common/#geemap.common.extract_transect). Exists as an alternative to RadGEEToolbox 'transect' function.
|
|
1324
1797
|
|
|
1325
1798
|
Args:
|
|
@@ -1383,9 +1856,17 @@ class LandsatCollection:
|
|
|
1383
1856
|
|
|
1384
1857
|
except Exception as e:
|
|
1385
1858
|
raise Exception(e)
|
|
1386
|
-
|
|
1859
|
+
|
|
1387
1860
|
@staticmethod
|
|
1388
|
-
def transect(
|
|
1861
|
+
def transect(
|
|
1862
|
+
image,
|
|
1863
|
+
lines,
|
|
1864
|
+
line_names,
|
|
1865
|
+
reducer="mean",
|
|
1866
|
+
n_segments=None,
|
|
1867
|
+
dist_interval=30,
|
|
1868
|
+
to_pandas=True,
|
|
1869
|
+
):
|
|
1389
1870
|
"""Computes and stores the values along a transect for each line in a list of lines. Builds off of the extract_transect function from the geemap package
|
|
1390
1871
|
where checks are ran to ensure that the reducer column is present in the transect data. If the reducer column is not present, a column of NaNs is created.
|
|
1391
1872
|
An ee reducer is used to aggregate the values along the transect, depending on the number of segments or distance interval specified. Defaults to 'mean' reducer.
|
|
@@ -1402,46 +1883,71 @@ class LandsatCollection:
|
|
|
1402
1883
|
Returns:
|
|
1403
1884
|
pd.DataFrame or ee.FeatureCollection: organized list of values along the transect(s)
|
|
1404
1885
|
"""
|
|
1405
|
-
#Create empty dataframe
|
|
1886
|
+
# Create empty dataframe
|
|
1406
1887
|
transects_df = pd.DataFrame()
|
|
1407
1888
|
|
|
1408
|
-
#Check if line is a list of lines or a single line - if single line, convert to list
|
|
1889
|
+
# Check if line is a list of lines or a single line - if single line, convert to list
|
|
1409
1890
|
if isinstance(lines, list):
|
|
1410
1891
|
pass
|
|
1411
1892
|
else:
|
|
1412
1893
|
lines = [lines]
|
|
1413
|
-
|
|
1894
|
+
|
|
1414
1895
|
for i, line in enumerate(lines):
|
|
1415
1896
|
if n_segments is None:
|
|
1416
|
-
transect_data = LandsatCollection.extract_transect(
|
|
1897
|
+
transect_data = LandsatCollection.extract_transect(
|
|
1898
|
+
image=image,
|
|
1899
|
+
line=line,
|
|
1900
|
+
reducer=reducer,
|
|
1901
|
+
dist_interval=dist_interval,
|
|
1902
|
+
to_pandas=to_pandas,
|
|
1903
|
+
)
|
|
1417
1904
|
if reducer in transect_data.columns:
|
|
1418
1905
|
# Extract the 'mean' column and rename it
|
|
1419
|
-
mean_column = transect_data[[
|
|
1906
|
+
mean_column = transect_data[["mean"]]
|
|
1420
1907
|
else:
|
|
1421
1908
|
# Handle the case where 'mean' column is not present
|
|
1422
|
-
print(
|
|
1909
|
+
print(
|
|
1910
|
+
f"{reducer} column not found in transect data for line {line_names[i]}"
|
|
1911
|
+
)
|
|
1423
1912
|
# Create a column of NaNs with the same length as the longest column in transects_df
|
|
1424
1913
|
max_length = max(transects_df.shape[0], transect_data.shape[0])
|
|
1425
1914
|
mean_column = pd.Series([np.nan] * max_length)
|
|
1426
1915
|
else:
|
|
1427
|
-
transect_data = LandsatCollection.extract_transect(
|
|
1916
|
+
transect_data = LandsatCollection.extract_transect(
|
|
1917
|
+
image=image,
|
|
1918
|
+
line=line,
|
|
1919
|
+
reducer=reducer,
|
|
1920
|
+
n_segments=n_segments,
|
|
1921
|
+
to_pandas=to_pandas,
|
|
1922
|
+
)
|
|
1428
1923
|
if reducer in transect_data.columns:
|
|
1429
1924
|
# Extract the 'mean' column and rename it
|
|
1430
|
-
mean_column = transect_data[[
|
|
1925
|
+
mean_column = transect_data[["mean"]]
|
|
1431
1926
|
else:
|
|
1432
1927
|
# Handle the case where 'mean' column is not present
|
|
1433
|
-
print(
|
|
1928
|
+
print(
|
|
1929
|
+
f"{reducer} column not found in transect data for line {line_names[i]}"
|
|
1930
|
+
)
|
|
1434
1931
|
# Create a column of NaNs with the same length as the longest column in transects_df
|
|
1435
1932
|
max_length = max(transects_df.shape[0], transect_data.shape[0])
|
|
1436
1933
|
mean_column = pd.Series([np.nan] * max_length)
|
|
1437
|
-
|
|
1934
|
+
|
|
1438
1935
|
transects_df = pd.concat([transects_df, mean_column], axis=1)
|
|
1439
1936
|
|
|
1440
1937
|
transects_df.columns = line_names
|
|
1441
|
-
|
|
1938
|
+
|
|
1442
1939
|
return transects_df
|
|
1443
|
-
|
|
1444
|
-
def transect_iterator(
|
|
1940
|
+
|
|
1941
|
+
def transect_iterator(
|
|
1942
|
+
self,
|
|
1943
|
+
lines,
|
|
1944
|
+
line_names,
|
|
1945
|
+
save_folder_path,
|
|
1946
|
+
reducer="mean",
|
|
1947
|
+
n_segments=None,
|
|
1948
|
+
dist_interval=30,
|
|
1949
|
+
to_pandas=True,
|
|
1950
|
+
):
|
|
1445
1951
|
"""Computes and stores the values along a transect for each line in a list of lines for each image in a LandsatCollection image collection, then saves the data for each image to a csv file. Builds off of the extract_transect function from the geemap package
|
|
1446
1952
|
where checks are ran to ensure that the reducer column is present in the transect data. If the reducer column is not present, a column of NaNs is created.
|
|
1447
1953
|
An ee reducer is used to aggregate the values along the transect, depending on the number of segments or distance interval specified. Defaults to 'mean' reducer.
|
|
@@ -1462,22 +1968,38 @@ class LandsatCollection:
|
|
|
1462
1968
|
Returns:
|
|
1463
1969
|
csv file: file for each image with an organized list of values along the transect(s)
|
|
1464
1970
|
"""
|
|
1465
|
-
image_collection = self
|
|
1971
|
+
image_collection = self # .collection
|
|
1466
1972
|
# image_collection_dates = self._dates
|
|
1467
1973
|
image_collection_dates = self.dates
|
|
1468
1974
|
for i, date in enumerate(image_collection_dates):
|
|
1469
1975
|
try:
|
|
1470
1976
|
print(f"Processing image {i+1}/{len(image_collection_dates)}: {date}")
|
|
1471
1977
|
image = image_collection.image_grab(i)
|
|
1472
|
-
transects_df = LandsatCollection.transect(
|
|
1978
|
+
transects_df = LandsatCollection.transect(
|
|
1979
|
+
image,
|
|
1980
|
+
lines,
|
|
1981
|
+
line_names,
|
|
1982
|
+
reducer=reducer,
|
|
1983
|
+
n_segments=n_segments,
|
|
1984
|
+
dist_interval=dist_interval,
|
|
1985
|
+
to_pandas=to_pandas,
|
|
1986
|
+
)
|
|
1473
1987
|
image_id = date
|
|
1474
|
-
transects_df.to_csv(f
|
|
1475
|
-
print(f
|
|
1988
|
+
transects_df.to_csv(f"{save_folder_path}{image_id}_transects.csv")
|
|
1989
|
+
print(f"{image_id}_transects saved to csv")
|
|
1476
1990
|
except Exception as e:
|
|
1477
1991
|
print(f"An error occurred while processing image {i+1}: {e}")
|
|
1478
1992
|
|
|
1479
1993
|
@staticmethod
|
|
1480
|
-
def extract_zonal_stats_from_buffer(
|
|
1994
|
+
def extract_zonal_stats_from_buffer(
|
|
1995
|
+
image,
|
|
1996
|
+
coordinates,
|
|
1997
|
+
buffer_size=1,
|
|
1998
|
+
reducer_type="mean",
|
|
1999
|
+
scale=30,
|
|
2000
|
+
tileScale=1,
|
|
2001
|
+
coordinate_names=None,
|
|
2002
|
+
):
|
|
1481
2003
|
"""
|
|
1482
2004
|
Function to extract spatial statistics from an image for a list of coordinates, providing individual statistics for each location.
|
|
1483
2005
|
A radial buffer is applied around each coordinate to extract the statistics, which defaults to 1 meter.
|
|
@@ -1499,15 +2021,26 @@ class LandsatCollection:
|
|
|
1499
2021
|
# Check if coordinates is a single tuple and convert it to a list of tuples if necessary
|
|
1500
2022
|
if isinstance(coordinates, tuple) and len(coordinates) == 2:
|
|
1501
2023
|
coordinates = [coordinates]
|
|
1502
|
-
elif not (
|
|
1503
|
-
|
|
1504
|
-
|
|
2024
|
+
elif not (
|
|
2025
|
+
isinstance(coordinates, list)
|
|
2026
|
+
and all(
|
|
2027
|
+
isinstance(coord, tuple) and len(coord) == 2 for coord in coordinates
|
|
2028
|
+
)
|
|
2029
|
+
):
|
|
2030
|
+
raise ValueError(
|
|
2031
|
+
"Coordinates must be a list of tuples with two elements each (latitude, longitude)."
|
|
2032
|
+
)
|
|
2033
|
+
|
|
1505
2034
|
# Check if coordinate_names is a list of strings
|
|
1506
2035
|
if coordinate_names is not None:
|
|
1507
|
-
if not isinstance(coordinate_names, list) or not all(
|
|
2036
|
+
if not isinstance(coordinate_names, list) or not all(
|
|
2037
|
+
isinstance(name, str) for name in coordinate_names
|
|
2038
|
+
):
|
|
1508
2039
|
raise ValueError("coordinate_names must be a list of strings.")
|
|
1509
2040
|
if len(coordinate_names) != len(coordinates):
|
|
1510
|
-
raise ValueError(
|
|
2041
|
+
raise ValueError(
|
|
2042
|
+
"coordinate_names must have the same length as the coordinates list."
|
|
2043
|
+
)
|
|
1511
2044
|
else:
|
|
1512
2045
|
coordinate_names = [f"Location {i+1}" for i in range(len(coordinates))]
|
|
1513
2046
|
|
|
@@ -1519,73 +2052,95 @@ class LandsatCollection:
|
|
|
1519
2052
|
# image = ee.Image(check_singleband(image))
|
|
1520
2053
|
image = ee.Image(check_singleband(image))
|
|
1521
2054
|
|
|
1522
|
-
#Convert coordinates to ee.Geometry.Point, buffer them, and add label/name to feature
|
|
1523
|
-
points = [
|
|
2055
|
+
# Convert coordinates to ee.Geometry.Point, buffer them, and add label/name to feature
|
|
2056
|
+
points = [
|
|
2057
|
+
ee.Feature(
|
|
2058
|
+
ee.Geometry.Point([coord[0], coord[1]]).buffer(buffer_size),
|
|
2059
|
+
{"name": str(coordinate_names[i])},
|
|
2060
|
+
)
|
|
2061
|
+
for i, coord in enumerate(coordinates)
|
|
2062
|
+
]
|
|
1524
2063
|
# Create a feature collection from the buffered points
|
|
1525
2064
|
features = ee.FeatureCollection(points)
|
|
1526
2065
|
# Reduce the image to the buffered points - handle different reducer types
|
|
1527
|
-
if reducer_type ==
|
|
2066
|
+
if reducer_type == "mean":
|
|
1528
2067
|
img_stats = image.reduceRegions(
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
2068
|
+
collection=features,
|
|
2069
|
+
reducer=ee.Reducer.mean(),
|
|
2070
|
+
scale=scale,
|
|
2071
|
+
tileScale=tileScale,
|
|
2072
|
+
)
|
|
1533
2073
|
mean_values = img_stats.getInfo()
|
|
1534
2074
|
means = []
|
|
1535
2075
|
names = []
|
|
1536
|
-
for feature in mean_values[
|
|
1537
|
-
names.append(feature[
|
|
1538
|
-
means.append(feature[
|
|
2076
|
+
for feature in mean_values["features"]:
|
|
2077
|
+
names.append(feature["properties"]["name"])
|
|
2078
|
+
means.append(feature["properties"]["mean"])
|
|
1539
2079
|
organized_values = pd.DataFrame([means], columns=names)
|
|
1540
|
-
elif reducer_type ==
|
|
2080
|
+
elif reducer_type == "median":
|
|
1541
2081
|
img_stats = image.reduceRegions(
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
2082
|
+
collection=features,
|
|
2083
|
+
reducer=ee.Reducer.median(),
|
|
2084
|
+
scale=scale,
|
|
2085
|
+
tileScale=tileScale,
|
|
2086
|
+
)
|
|
1546
2087
|
median_values = img_stats.getInfo()
|
|
1547
2088
|
medians = []
|
|
1548
2089
|
names = []
|
|
1549
|
-
for feature in median_values[
|
|
1550
|
-
names.append(feature[
|
|
1551
|
-
medians.append(feature[
|
|
2090
|
+
for feature in median_values["features"]:
|
|
2091
|
+
names.append(feature["properties"]["name"])
|
|
2092
|
+
medians.append(feature["properties"]["median"])
|
|
1552
2093
|
organized_values = pd.DataFrame([medians], columns=names)
|
|
1553
|
-
elif reducer_type ==
|
|
2094
|
+
elif reducer_type == "min":
|
|
1554
2095
|
img_stats = image.reduceRegions(
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
2096
|
+
collection=features,
|
|
2097
|
+
reducer=ee.Reducer.min(),
|
|
2098
|
+
scale=scale,
|
|
2099
|
+
tileScale=tileScale,
|
|
2100
|
+
)
|
|
1559
2101
|
min_values = img_stats.getInfo()
|
|
1560
2102
|
mins = []
|
|
1561
2103
|
names = []
|
|
1562
|
-
for feature in min_values[
|
|
1563
|
-
names.append(feature[
|
|
1564
|
-
mins.append(feature[
|
|
2104
|
+
for feature in min_values["features"]:
|
|
2105
|
+
names.append(feature["properties"]["name"])
|
|
2106
|
+
mins.append(feature["properties"]["min"])
|
|
1565
2107
|
organized_values = pd.DataFrame([mins], columns=names)
|
|
1566
|
-
elif reducer_type ==
|
|
2108
|
+
elif reducer_type == "max":
|
|
1567
2109
|
img_stats = image.reduceRegions(
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
2110
|
+
collection=features,
|
|
2111
|
+
reducer=ee.Reducer.max(),
|
|
2112
|
+
scale=scale,
|
|
2113
|
+
tileScale=tileScale,
|
|
2114
|
+
)
|
|
1572
2115
|
max_values = img_stats.getInfo()
|
|
1573
2116
|
maxs = []
|
|
1574
2117
|
names = []
|
|
1575
|
-
for feature in max_values[
|
|
1576
|
-
names.append(feature[
|
|
1577
|
-
maxs.append(feature[
|
|
2118
|
+
for feature in max_values["features"]:
|
|
2119
|
+
names.append(feature["properties"]["name"])
|
|
2120
|
+
maxs.append(feature["properties"]["max"])
|
|
1578
2121
|
organized_values = pd.DataFrame([maxs], columns=names)
|
|
1579
2122
|
else:
|
|
1580
|
-
raise ValueError(
|
|
2123
|
+
raise ValueError(
|
|
2124
|
+
"reducer_type must be one of 'mean', 'median', 'min', or 'max'."
|
|
2125
|
+
)
|
|
1581
2126
|
return organized_values
|
|
1582
2127
|
|
|
1583
|
-
def iterate_zonal_stats(
|
|
2128
|
+
def iterate_zonal_stats(
|
|
2129
|
+
self,
|
|
2130
|
+
coordinates,
|
|
2131
|
+
buffer_size=1,
|
|
2132
|
+
reducer_type="mean",
|
|
2133
|
+
scale=30,
|
|
2134
|
+
tileScale=1,
|
|
2135
|
+
coordinate_names=None,
|
|
2136
|
+
file_path=None,
|
|
2137
|
+
dates=None,
|
|
2138
|
+
):
|
|
1584
2139
|
"""
|
|
1585
2140
|
Function to iterate over a collection of images and extract spatial statistics for a list of coordinates (defaults to mean). Individual statistics are provided for each location.
|
|
1586
2141
|
A radial buffer is applied around each coordinate to extract the statistics, which defaults to 1 meter.
|
|
1587
2142
|
The function returns a pandas DataFrame with the statistics for each coordinate and date, or optionally exports the data to a table in .csv format.
|
|
1588
|
-
|
|
2143
|
+
|
|
1589
2144
|
Args:
|
|
1590
2145
|
coordinates (list): Single tuple or a list of tuples with the coordinates as decimal degrees in the format of (longitude, latitude) for which to extract the statistics. NOTE the format needs to be [(x1, y1), (x2, y2), ...].
|
|
1591
2146
|
buffer_size (int, optional): The radial buffer size in meters around the coordinates. Defaults to 1.
|
|
@@ -1601,22 +2156,32 @@ class LandsatCollection:
|
|
|
1601
2156
|
.csv file: Optionally exports the data to a table in .csv format. If file_path is None, the function returns the DataFrame - otherwise the function will only export the csv file.
|
|
1602
2157
|
"""
|
|
1603
2158
|
img_collection = self
|
|
1604
|
-
#Create empty DataFrame to accumulate results
|
|
2159
|
+
# Create empty DataFrame to accumulate results
|
|
1605
2160
|
accumulated_df = pd.DataFrame()
|
|
1606
|
-
#Check if dates is None, if not use the dates provided
|
|
2161
|
+
# Check if dates is None, if not use the dates provided
|
|
1607
2162
|
if dates is None:
|
|
1608
2163
|
dates = img_collection.dates
|
|
1609
2164
|
else:
|
|
1610
2165
|
dates = dates
|
|
1611
|
-
#Iterate over the dates and extract the zonal statistics for each date
|
|
2166
|
+
# Iterate over the dates and extract the zonal statistics for each date
|
|
1612
2167
|
for date in dates:
|
|
1613
|
-
image = img_collection.collection.filter(
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
single_df.
|
|
2168
|
+
image = img_collection.collection.filter(
|
|
2169
|
+
ee.Filter.eq("Date_Filter", date)
|
|
2170
|
+
).first()
|
|
2171
|
+
single_df = LandsatCollection.extract_zonal_stats_from_buffer(
|
|
2172
|
+
image,
|
|
2173
|
+
coordinates,
|
|
2174
|
+
buffer_size=buffer_size,
|
|
2175
|
+
reducer_type=reducer_type,
|
|
2176
|
+
scale=scale,
|
|
2177
|
+
tileScale=tileScale,
|
|
2178
|
+
coordinate_names=coordinate_names,
|
|
2179
|
+
)
|
|
2180
|
+
single_df["Date"] = date
|
|
2181
|
+
single_df.set_index("Date", inplace=True)
|
|
1617
2182
|
accumulated_df = pd.concat([accumulated_df, single_df])
|
|
1618
|
-
#Return the DataFrame or export the data to a .csv file
|
|
2183
|
+
# Return the DataFrame or export the data to a .csv file
|
|
1619
2184
|
if file_path is None:
|
|
1620
2185
|
return accumulated_df
|
|
1621
2186
|
else:
|
|
1622
|
-
return accumulated_df.to_csv(f
|
|
2187
|
+
return accumulated_df.to_csv(f"{file_path}.csv")
|