glam-processing 0.2.0__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.
@@ -0,0 +1,733 @@
1
+ import os
2
+ import logging
3
+
4
+ import h5py
5
+
6
+ import rasterio
7
+ from rasterio.io import MemoryFile
8
+
9
+ from rio_cogeo.cogeo import cog_translate
10
+ from rio_cogeo.profiles import cog_profiles
11
+
12
+ import numpy as np
13
+
14
+ from .spectral import calc_ndvi, calc_ndwi
15
+ from . import exceptions
16
+
17
+ logging.basicConfig(
18
+ format="%(asctime)s - %(message)s",
19
+ datefmt="%d-%b-%y %H:%M:%S",
20
+ level=logging.INFO,
21
+ )
22
+ log = logging.getLogger(__name__)
23
+
24
+ SUPPORTED_DATASETS = [
25
+ # MODIS
26
+ "MOD09Q1",
27
+ "MYD09Q1",
28
+ "MOD13Q1",
29
+ "MYD13Q1",
30
+ "MOD09A1",
31
+ "MYD09A1",
32
+ "MYD09GA",
33
+ "MOD09Q1N",
34
+ "MOD13Q4N",
35
+ "MOD09CMG",
36
+ "MCD12Q1",
37
+ # VIIRS/NPP
38
+ "VNP09H1",
39
+ "VNP09A1",
40
+ "VNP09GA",
41
+ "VNP09CMG",
42
+ "VNP21A2",
43
+ # MERRA-2
44
+ "M2SDNXSLV",
45
+ ]
46
+
47
+
48
+ # WKT of default Sinusoidal Projection
49
+ SINUS_WKT = 'PROJCS["unnamed",GEOGCS["Unknown datum based upon the custom spheroid",DATUM["Not_specified_based_on_custom_spheroid",SPHEROID["Custom spheroid",6371007.181,0]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Sinusoidal"],PARAMETER["longitude_of_center",0],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]'
50
+
51
+
52
+ def authenticate(strategy="interactive", persist=True):
53
+ """Authenticate with earthaccess"""
54
+
55
+ from earthaccess import Auth
56
+
57
+ auth = Auth()
58
+ auth.login(strategy=strategy, persist=persist)
59
+ return auth.authenticated
60
+
61
+
62
+ def get_granules(product, start_date, end_date):
63
+ # get list of granules for a product
64
+ # start_date and end_date should be in YYYY-MM-DD format
65
+ return None
66
+
67
+
68
+ def get_dtype_from_sds_name(sds_name):
69
+ # given name of sds return string representation of dtype
70
+ if "sur_refl_b" in sds_name:
71
+ return "int16"
72
+ elif sds_name in ["sur_refl_qc_500m"]:
73
+ return "uint32"
74
+ elif sds_name in [
75
+ "sur_refl_szen",
76
+ "sur_refl_vzen",
77
+ "sur_refl_raz",
78
+ "RelativeAzimuth",
79
+ "SolarZenith",
80
+ "SensorZenith",
81
+ "SurfReflect_I1",
82
+ "SurfReflect_I2",
83
+ "SurfReflect_I3",
84
+ "SurfReflect_I1_1",
85
+ "SurfReflect_I2_1",
86
+ ]:
87
+ return "int16"
88
+ elif sds_name in [
89
+ "sur_refl_state_500m",
90
+ "sur_refl_day_of_year",
91
+ "sur_refl_state_250m",
92
+ "sur_refl_qc_250m",
93
+ "SurfReflect_Day_Of_Year",
94
+ "SurfReflect_State_500m",
95
+ "SurfReflect_QC_500m",
96
+ ]:
97
+ return "uint16"
98
+ elif sds_name in ["LC_Type1"]:
99
+ return "uint8"
100
+ elif sds_name in ["HOUR_NO_RAIN", "T2MMAX", "T2MMIN", "T2MMEAN", "TPRECMAX"]:
101
+ return "float32"
102
+
103
+
104
+ def get_h5_geo_info(file):
105
+ # Get info from the StructMetadata Object
106
+ metadata = file["HDFEOS INFORMATION"]["StructMetadata.0"][()].split()
107
+ # Info returned is of type Byte, must convert to string before using it
108
+ metadata_byte2str = [s.decode("utf-8") for s in metadata]
109
+ # Get upper left points
110
+ ulc = [i for i in metadata_byte2str if "UpperLeftPointMtrs" in i]
111
+ ulc_lon = float(
112
+ ulc[0].replace("=", ",").replace("(", "").replace(")", "").split(",")[1]
113
+ )
114
+ ulc_lat = float(
115
+ ulc[0].replace("=", ",").replace("(", "").replace(")", "").split(",")[2]
116
+ )
117
+ return (ulc_lon, 0.0, ulc_lat, 0.0)
118
+
119
+
120
+ def is_nrt(product):
121
+ if product[-1] == "N":
122
+ nrt = True
123
+ else:
124
+ nrt = False
125
+ return nrt
126
+
127
+
128
+ def get_sds(dataset, name):
129
+ for sds in dataset.subdatasets:
130
+ sds_parts = sds.split(":")
131
+ if sds_parts[0] == "HDF4_EOS":
132
+ if sds_parts[-1] == name:
133
+ band = rasterio.open(sds)
134
+ return (band.read(), band.nodata)
135
+ elif sds_parts[0] == "HDF5":
136
+ if sds_parts[-1].split("/")[-1] == name:
137
+ band = rasterio.open(sds)
138
+ return (band.read(), band.nodata)
139
+ elif sds_parts[0] == "netcdf":
140
+ if sds_parts[-1] == name:
141
+ band = rasterio.open(sds)
142
+ return (band.read(), band.nodata)
143
+
144
+
145
+ def get_sds_path(dataset, name):
146
+ for sds in dataset.subdatasets:
147
+ sds_parts = sds.split(":")
148
+ if sds_parts[0] == "HDF4_EOS":
149
+ if sds_parts[-1] == name:
150
+ return sds
151
+ elif sds_parts[0] == "HDF5":
152
+ if sds_parts[-1].split("/")[-1] == name:
153
+ return sds
154
+
155
+
156
+ def apply_mask(in_array, source_dataset, nodata):
157
+ """
158
+ This function removes non-clear pixels from an input array,
159
+ including clouds, cloud shadow, and water.
160
+
161
+ For M*D CMG files, removes pixels ranked below "8" in
162
+ MOD13Q1 compositing method, as well as water.
163
+
164
+ Returns a cleaned array.
165
+
166
+ ...
167
+
168
+ Parameters
169
+ ----------
170
+
171
+ in_array: numpy.array
172
+ The array to be cleaned. This must have the same dimensions
173
+ as source_dataset, and preferably have been extracted from the
174
+ stack.
175
+ source_dataset: str
176
+ Path to a hierarchical data file containing QA layers with
177
+ which to perform the masking. Currently valid formats include
178
+ MOD09Q1 hdf and VNP09H1 files.
179
+ """
180
+
181
+ # get file extension and product suffix
182
+ file_path = source_dataset.name
183
+ file_name, ext = os.path.splitext(os.path.basename(file_path))
184
+ suffix = file_name.split(".")[0]
185
+
186
+ # product-conditional behavior
187
+
188
+ # MODIS pre-generated VI masking
189
+ if suffix in ["MOD13Q1", "MOD13Q4", "MOD13Q4N"]:
190
+ if suffix[-1] == "1":
191
+ pr_arr, pr_nodata = get_sds(
192
+ source_dataset, "250m 16 days pixel reliability"
193
+ )
194
+ qa_arr, qa_nodata = get_sds(source_dataset, "250m 16 days VI Quality")
195
+ else:
196
+ pr_arr, pr_nodata = get_sds(source_dataset, "250m 8 days pixel reliability")
197
+ qa_arr, qa_nodata = get_sds(source_dataset, "250m 8 days VI Quality")
198
+
199
+ # in_array[(pr_arr != 0) & (pr_arr != 1)] = nodata
200
+
201
+ # mask clouds
202
+ in_array[(qa_arr & 0b11) > 1] = nodata # bits 0-1 > 01 = Cloudy
203
+
204
+ # mask Aerosol
205
+ in_array[(qa_arr & 0b11000000) == 0] = nodata # climatology
206
+ in_array[(qa_arr & 0b11000000) == 192] = nodata # high
207
+
208
+ # mask water
209
+ in_array[
210
+ ((qa_arr & 0b11100000000000) != 2048)
211
+ & ((qa_arr & 0b11100000000000) != 4096)
212
+ & ((qa_arr & 0b11100000000000) != 8192)
213
+ ] = nodata
214
+ # 001 = land, 010 = coastline, 100 = ephemeral water
215
+
216
+ # mask snow/ice
217
+ in_array[(qa_arr & 0b100000000000000) != 0] = nodata # bit 14
218
+
219
+ # mask cloud shadow
220
+ in_array[(qa_arr & 0b1000000000000000) != 0] = nodata # bit 15
221
+
222
+ # mask cloud adjacent pixels
223
+ in_array[(qa_arr & 0b100000000) != 0] = nodata # bit 8
224
+
225
+ # MODIS and VIIRS surface reflectance masking
226
+ # CMG
227
+ elif "CMG" in suffix:
228
+ if ext == ".hdf": # MOD09CMG
229
+ qa_arr, qa_nodata = get_sds(source_dataset, "Coarse Resolution QA")
230
+ state_arr, state_nodata = get_sds(
231
+ source_dataset, "Coarse Resolution State QA"
232
+ )
233
+ vang_arr, vang_nodata = get_sds(
234
+ source_dataset, "Coarse Resolution View Zenith Angle"
235
+ )
236
+ vang_arr[vang_arr <= 0] = 9999
237
+ sang_arr, sang_nodata = get_sds(
238
+ source_dataset, "Coarse Resolution Solar Zenith Angle"
239
+ )
240
+ rank_arr = np.full(qa_arr.shape, 10) # empty rank array
241
+
242
+ # perform the ranking!
243
+ logging.debug("--rank 9: SNOW")
244
+ SNOW = (state_arr & 0b1000000000000) | (
245
+ state_arr & 0b1000000000000000
246
+ ) # state bit 12 OR 15
247
+ rank_arr[SNOW > 0] = 9 # snow
248
+ del SNOW
249
+ logging.debug("--rank 8: HIGHAEROSOL")
250
+ HIGHAEROSOL = state_arr & 0b11000000 # state bits 6 AND 7
251
+ rank_arr[HIGHAEROSOL == 192] = 8
252
+ del HIGHAEROSOL
253
+ logging.debug("--rank 7: CLIMAEROSOL")
254
+ CLIMAEROSOL = state_arr & 0b11000000 # state bits 6 & 7
255
+ # CLIMAEROSOL=(cloudMask & 0b100000000000000) # cloudMask bit 14
256
+ rank_arr[CLIMAEROSOL == 0] = 7 # default aerosol level
257
+ del CLIMAEROSOL
258
+ logging.debug("--rank 6: UNCORRECTED")
259
+ UNCORRECTED = qa_arr & 0b11 # qa bits 0 AND 1
260
+ rank_arr[UNCORRECTED == 3] = 6 # flagged uncorrected
261
+ del UNCORRECTED
262
+ logging.debug("--rank 5: SHADOW")
263
+ SHADOW = state_arr & 0b100 # state bit 2
264
+ rank_arr[SHADOW == 4] = 5 # cloud shadow
265
+ del SHADOW
266
+ logging.debug("--rank 4: CLOUDY")
267
+ # set adj to 11 and internal to 12 to verify in qa output
268
+ # state bit 0 OR bit 1 OR bit 10 OR bit 13
269
+ CLOUDY = state_arr & 0b11
270
+ # rank_arr[CLOUDY!=0]=4 # cloud pixel
271
+ del CLOUDY
272
+ CLOUDADJ = state_arr & 0b10000000000000
273
+ # rank_arr[CLOUDADJ>0]=4 # adjacent to cloud
274
+ del CLOUDADJ
275
+ CLOUDINT = state_arr & 0b10000000000
276
+ rank_arr[CLOUDINT > 0] = 4
277
+ del CLOUDINT
278
+ logging.debug("--rank 3: HIGHVIEW")
279
+ rank_arr[sang_arr > (85 / 0.01)] = 3 # HIGHVIEW
280
+ logging.debug("--rank 2: LOWSUN")
281
+ rank_arr[vang_arr > (60 / 0.01)] = 2 # LOWSUN
282
+ # BAD pixels
283
+ # qa bits (2-5 OR 6-9 == 1110)
284
+ logging.debug("--rank 1: BAD pixels")
285
+ BAD = (qa_arr & 0b111100) | (qa_arr & 0b1110000000)
286
+ rank_arr[BAD == 112] = 1
287
+ rank_arr[BAD == 896] = 1
288
+ rank_arr[BAD == 952] = 1
289
+ del BAD
290
+
291
+ logging.debug("-building water mask")
292
+ water = state_arr & 0b111000 # check bits
293
+ water[water == 56] = 1 # deep ocean
294
+ water[water == 48] = 1 # continental/moderate ocean
295
+ water[water == 24] = 1 # shallow inland water
296
+ water[water == 40] = 1 # deep inland water
297
+ water[water == 0] = 1 # shallow ocean
298
+ rank_arr[water == 1] = 0
299
+ vang_arr[water == 32] = 9999 # ephemeral water???
300
+ water[state_arr == 0] = 0
301
+ water[water != 1] = 0 # set non-water to zero
302
+ in_array[rank_arr <= 7] = nodata
303
+ elif ext == ".h5": # VNP09CMG
304
+ qf2, qf2_nodata = get_sds(source_dataset, "SurfReflect_QF2")
305
+ qf4, qf4_nodata = get_sds(source_dataset, "SurfReflect_QF4")
306
+ state_arr, state_nodata = get_sds(source_dataset, "State_QA")
307
+ vang_arr, vang_nodata = get_sds(source_dataset, "SensorZenith")
308
+ vang_arr[vang_arr <= 0] = 9999
309
+ sang_arr, sang_nodata = get_sds(source_dataset, "SolarZenith")
310
+ rank_arr = np.full(state_arr.shape, 10) # empty rank array
311
+
312
+ # perform the ranking!
313
+ logging.debug("--rank 9: SNOW")
314
+ SNOW = state_arr & 0b1000000000000000 # state bit 15
315
+ rank_arr[SNOW > 0] = 9 # snow
316
+ del SNOW
317
+ logging.debug("--rank 8: HIGHAEROSOL")
318
+ HIGHAEROSOL = qf2 & 0b10000 # qf2 bit 4
319
+ rank_arr[HIGHAEROSOL != 0] = 8
320
+ del HIGHAEROSOL
321
+ logging.debug("--rank 7: AEROSOL")
322
+ CLIMAEROSOL = state_arr & 0b1000000 # state bit 6
323
+ # CLIMAEROSOL=(cloudMask & 0b100000000000000) # cloudMask bit 14
324
+ # rank_arr[CLIMAEROSOL==0]=7 # "No"
325
+ del CLIMAEROSOL
326
+ # logging.debug("--rank 6: UNCORRECTED")
327
+ # UNCORRECTED = (qa_arr & 0b11) # qa bits 0 AND 1
328
+ # rank_arr[UNCORRECTED==3]=6 # flagged uncorrected
329
+ # del UNCORRECTED
330
+ logging.debug("--rank 5: SHADOW")
331
+ SHADOW = state_arr & 0b100 # state bit 2
332
+ rank_arr[SHADOW != 0] = 5 # cloud shadow
333
+ del SHADOW
334
+ logging.debug("--rank 4: CLOUDY")
335
+ # set adj to 11 and internal to 12 to verify in qa output
336
+ # CLOUDY = ((state_arr & 0b11)) # state bit 0 OR bit 1 OR bit 10 OR bit 13
337
+ # rank_arr[CLOUDY!=0]=4 # cloud pixel
338
+ # del CLOUDY
339
+ # CLOUDADJ = (state_arr & 0b10000000000) # nonexistent for viirs
340
+ # #rank_arr[CLOUDADJ>0]=4 # adjacent to cloud
341
+ # del CLOUDADJ
342
+ CLOUDINT = state_arr & 0b10000000000 # state bit 10
343
+ rank_arr[CLOUDINT > 0] = 4
344
+ del CLOUDINT
345
+ logging.debug("--rank 3: HIGHVIEW")
346
+ rank_arr[sang_arr > (85 / 0.01)] = 3 # HIGHVIEW
347
+ logging.debug("--rank 2: LOWSUN")
348
+ rank_arr[vang_arr > (60 / 0.01)] = 2 # LOWSUN
349
+ # BAD pixels
350
+ # qa bits (2-5 OR 6-9 == 1110)
351
+ logging.debug("--rank 1: BAD pixels")
352
+ BAD = qf4 & 0b110
353
+ rank_arr[BAD != 0] = 1
354
+ del BAD
355
+
356
+ logging.debug("-building water mask")
357
+ water = state_arr & 0b111000 # check bits 3-5
358
+ water[water == 40] = 0 # "coastal" = 101
359
+ water[water > 8] = 1 # sea water = 011; inland water = 010
360
+ # water[water==16]=1 # inland water = 010
361
+ # water[state_arr==0]=0
362
+ water[water != 1] = 0 # set non-water to zero
363
+ water[water != 0] = 1
364
+ rank_arr[water == 1] = 0
365
+ in_array[rank_arr <= 7] = nodata
366
+ else:
367
+ raise exceptions.FileTypeError("File must be of format .hdf or .h5")
368
+ elif "MERRA2" in suffix:
369
+ pass
370
+ # standard
371
+ else:
372
+ # viirs
373
+ if ext == ".h5":
374
+ qa_arr, qa_nodata = get_sds(source_dataset, "SurfReflect_QC_500m")
375
+ state_arr, state_nodata = get_sds(source_dataset, "SurfReflect_State_500m")
376
+ # MOD09A1
377
+ elif suffix == "MOD09A1":
378
+ qa_arr, qa_nodata = get_sds(source_dataset, "sur_refl_qc_500m")
379
+ state_arr, state_nodata = get_sds(source_dataset, "sur_refl_state_500m")
380
+ elif suffix == "MOD09GA":
381
+ qa_arr, qa_nodata = get_sds(source_dataset, "QC_500m_1")
382
+ state_arr, state_nodata = get_sds(source_dataset, "state_1km_1")
383
+ # all other MODIS products
384
+ elif ext == ".hdf":
385
+ qa_arr, qa_nodata = get_sds(source_dataset, "sur_refl_qc_250m")
386
+ state_arr, state_nodata = get_sds(source_dataset, "sur_refl_state_250m")
387
+ else:
388
+ raise exceptions.FileTypeError("File must be of format .hdf or .h5")
389
+
390
+ # mask clouds
391
+ in_array[(state_arr & 0b11) != 0] = nodata
392
+ in_array[(state_arr & 0b10000000000) != 0] = -3000 # internal cloud mask
393
+
394
+ # mask cloud shadow
395
+ in_array[(state_arr & 0b100) != 0] = nodata
396
+
397
+ # mask cloud adjacent pixels
398
+ in_array[(state_arr & 0b10000000000000) != 0] = nodata
399
+
400
+ # mask aerosols
401
+ in_array[(state_arr & 0b11000000) == 0] = nodata # climatology
402
+ # high; known to be an unreliable flag in MODIS collection 6
403
+ in_array[(state_arr & 0b11000000) == 192] = nodata
404
+
405
+ # mask snow/ice
406
+ in_array[(state_arr & 0b1000000000000) != 0] = nodata
407
+
408
+ # mask water
409
+ # checks against three 'allowed' land/water classes and excludes pixels that don't match
410
+ in_array[
411
+ ((state_arr & 0b111000) != 8)
412
+ & ((state_arr & 0b111000) != 16)
413
+ & ((state_arr & 0b111000) != 32)
414
+ ] = nodata
415
+
416
+ # mask bad solar zenith
417
+ # in_array[(qa_arr & 0b11100000) != 0] = nodata
418
+
419
+ # return output
420
+ return in_array
421
+
422
+
423
+ def get_ndvi_array(dataset):
424
+ file_path = dataset.name
425
+ file_name = os.path.basename(file_path)
426
+ suffix = file_name.split(".")[0][3:]
427
+ f, ext = os.path.splitext(file_name)
428
+
429
+ if suffix in ["09Q4", "13Q4", "13Q4N"]:
430
+ band_name = "250m 8 days NDVI"
431
+ ndvi_array, ndvi_nodata = get_sds(dataset, band_name)
432
+ return (ndvi_array, ndvi_nodata)
433
+ elif suffix == "13Q1":
434
+ band_name = "250m 16 days NDVI"
435
+ ndvi_array, ndvi_nodata = get_sds(dataset, band_name)
436
+ return (ndvi_array, ndvi_nodata)
437
+ elif suffix == "09CM":
438
+ if ext == ".hdf":
439
+ red_name = "Coarse Resolution Surface Reflectance Band 1"
440
+ nir_name = "Coarse Resolution Surface Reflectance Band 2"
441
+ elif ext == ".h5":
442
+ red_name = "SurfReflect_I1"
443
+ nir_name = "SurfReflect_I2"
444
+
445
+ red_band, red_nodata = get_sds(dataset, red_name)
446
+ nir_band, nir_nodata = get_sds(dataset, nir_name)
447
+
448
+ ndvi_array = calc_ndvi(red_band, nir_band)
449
+ return (ndvi_array, red_nodata)
450
+ elif suffix == "09GA":
451
+ if ext == ".hdf":
452
+ red_name = "sur_refl_b01_1"
453
+ nir_name = "sur_refl_b02_1"
454
+ elif ext == ".h5":
455
+ red_name = "SurfReflect_I1_1"
456
+ nir_name = "SurfReflect_I2_1"
457
+ else:
458
+ raise exceptions.FileTypeError("File must be of type .hdf or .h5")
459
+
460
+ # Discovered negative surface reflectance values in MOD09 & MYD09
461
+ # that threw off NDVI calculations
462
+ # clip values to (0,10000)
463
+
464
+ # get numpy array from red band dataset
465
+ red_band, red_nodata = get_sds(dataset, red_name)
466
+ # dont clip nodata values
467
+ red_band[red_band != red_nodata] = np.clip(
468
+ red_band[red_band != red_nodata], 0, 10000
469
+ )
470
+
471
+ # get numpy array from nir band dataset
472
+ nir_band, nir_nodata = get_sds(dataset, nir_name)
473
+ nir_band[nir_band != nir_nodata] = np.clip(
474
+ nir_band[nir_band != nir_nodata], 0, 10000
475
+ )
476
+
477
+ ndvi_array = calc_ndvi(red_band, nir_band)
478
+
479
+ return (ndvi_array, red_nodata)
480
+
481
+ else:
482
+ if ext == ".hdf":
483
+ red_name = "sur_refl_b01"
484
+ nir_name = "sur_refl_b02"
485
+ elif ext == ".h5":
486
+ red_name = "SurfReflect_I1"
487
+ nir_name = "SurfReflect_I2"
488
+ else:
489
+ raise exceptions.FileTypeError("File must be of type .hdf or .h5")
490
+
491
+ # Discovered negative surface reflectance values in MOD09 & MYD09
492
+ # that threw off NDVI calculations
493
+ # clip values to (0,10000)
494
+
495
+ # get numpy array from red band dataset
496
+ red_band, red_nodata = get_sds(dataset, red_name)
497
+ # dont clip nodata values
498
+ red_band[red_band != red_nodata] = np.clip(
499
+ red_band[red_band != red_nodata], 0, 10000
500
+ )
501
+
502
+ # get numpy array from nir band dataset
503
+ nir_band, nir_nodata = get_sds(dataset, nir_name)
504
+ nir_band[nir_band != nir_nodata] = np.clip(
505
+ nir_band[nir_band != nir_nodata], 0, 10000
506
+ )
507
+
508
+ ndvi_array = calc_ndvi(red_band, nir_band)
509
+
510
+ return (ndvi_array, red_nodata)
511
+
512
+
513
+ def get_ndwi_array(dataset):
514
+ file_path = dataset.name
515
+ file_name = os.path.basename(file_path)
516
+ suffix = file_name.split(".")[0][3:]
517
+ f, ext = os.path.splitext(file_name)
518
+
519
+ if suffix == "09A1":
520
+ nir_name = "sur_refl_b02"
521
+ swir_name = "sur_refl_b06"
522
+
523
+ # Discovered negative surface reflectance values in MOD09 & MYD09
524
+ # that threw off NDVI calculations
525
+ # clip values to (0,10000)
526
+
527
+ nir_band, nir_nodata = get_sds(dataset, nir_name)
528
+ nir_band[nir_band != nir_nodata] = np.clip(
529
+ nir_band[nir_band != nir_nodata], 0, 10000
530
+ )
531
+
532
+ swir_band, swir_nodata = get_sds(dataset, swir_name)
533
+ swir_band[swir_band != swir_nodata] = np.clip(
534
+ swir_band[swir_band != swir_nodata], 0, 10000
535
+ )
536
+
537
+ ndwi_array = calc_ndwi(nir_band, swir_band)
538
+ else:
539
+ raise exceptions.UnsupportedError(
540
+ "Only MOD09A1 imagery is supported for NDWI generation"
541
+ )
542
+
543
+ return (ndwi_array, nir_nodata)
544
+
545
+
546
+ def create_ndvi_geotiff(file, out_dir):
547
+ dataset = rasterio.open(file)
548
+
549
+ file_path = dataset.name
550
+ file_name = os.path.basename(file_path)
551
+ f, ext = os.path.splitext(file_name)
552
+
553
+ # calculate ndvi and export to geotiff
554
+ ndvi_array, ndvi_nodata = get_ndvi_array(dataset)
555
+
556
+ # apply mask
557
+ ndvi_array = apply_mask(ndvi_array, dataset, ndvi_nodata)
558
+
559
+ out_name = file_name.replace(ext, ".ndvi.tif")
560
+ output = os.path.join(out_dir, out_name)
561
+
562
+ # coerce dtype to int16
563
+ dtype = "int16"
564
+
565
+ ndvi_array = ndvi_array.astype(dtype)
566
+
567
+ profile = rasterio.open(dataset.subdatasets[0]).profile.copy()
568
+ profile.update({"driver": "GTiff", "dtype": dtype, "nodata": ndvi_nodata})
569
+
570
+ if "VNP" in file_name:
571
+ f = h5py.File(file_path, "r")
572
+ geo_info = get_h5_geo_info(f)
573
+ if profile["height"] == 1200: # VIIRS VNP09A1, VNP09GA - 1km
574
+ yRes = -926.6254330555555
575
+ xRes = 926.6254330555555
576
+ elif profile["height"] == 2400: # VIIRS VNP09H1, VNP09GA - 500m
577
+ yRes = -463.31271652777775
578
+ xRes = 463.31271652777775
579
+ new_transform = rasterio.Affine(
580
+ xRes, geo_info[1], geo_info[0], geo_info[3], yRes, geo_info[2]
581
+ )
582
+ profile.update({"transform": new_transform})
583
+
584
+ # assign CRS if None
585
+ if profile["crs"] == None:
586
+ profile.update({"crs": SINUS_WKT})
587
+
588
+ # create cog
589
+ with MemoryFile() as memfile:
590
+ with memfile.open(**profile) as mem:
591
+ mem.write(ndvi_array)
592
+ dst_profile = cog_profiles.get("deflate")
593
+ cog_translate(
594
+ mem,
595
+ output,
596
+ dst_profile,
597
+ in_memory=True,
598
+ quiet=True,
599
+ )
600
+
601
+ return output
602
+
603
+
604
+ def create_ndwi_geotiff(file, out_dir):
605
+ dataset = rasterio.open(file)
606
+
607
+ file_path = dataset.name
608
+ file_name = os.path.basename(file_path)
609
+ f, ext = os.path.splitext(file_name)
610
+
611
+ # calculate ndvi and export to geotiff
612
+ ndwi_array, ndwi_nodata = get_ndwi_array(dataset)
613
+
614
+ # apply mask
615
+ ndwi_array = apply_mask(ndwi_array, dataset, ndwi_nodata)
616
+
617
+ out_name = file_name.replace(ext, ".ndwi.tif")
618
+ output = os.path.join(out_dir, out_name)
619
+
620
+ # coerce dtype to int16
621
+ dtype = "int16"
622
+
623
+ ndwi_array = ndwi_array.astype(dtype)
624
+
625
+ profile = rasterio.open(dataset.subdatasets[0]).profile.copy()
626
+ profile.update({"driver": "GTiff", "dtype": dtype, "nodata": ndwi_nodata})
627
+
628
+ if "VNP" in file_name:
629
+ f = h5py.File(file_path, "r")
630
+ geo_info = get_h5_geo_info(f)
631
+ if profile["height"] == 1200: # VIIRS VNP09A1, VNP09GA - 1km
632
+ yRes = -926.6254330555555
633
+ xRes = 926.6254330555555
634
+ elif profile["height"] == 2400: # VIIRS VNP09H1, VNP09GA - 500m
635
+ yRes = -463.31271652777775
636
+ xRes = 463.31271652777775
637
+ new_transform = rasterio.Affine(
638
+ xRes, geo_info[1], geo_info[0], geo_info[3], yRes, geo_info[2]
639
+ )
640
+ profile.update({"transform": new_transform})
641
+
642
+ # assign CRS if None
643
+ if profile["crs"] == None:
644
+ profile.update({"crs": SINUS_WKT})
645
+
646
+ # create cog
647
+ with MemoryFile() as memfile:
648
+ with memfile.open(**profile) as mem:
649
+ mem.write(ndwi_array)
650
+ dst_profile = cog_profiles.get("deflate")
651
+ cog_translate(
652
+ mem,
653
+ output,
654
+ dst_profile,
655
+ in_memory=True,
656
+ quiet=True,
657
+ )
658
+
659
+ return output
660
+
661
+
662
+ def create_sds_geotiff(file, product, sds_name, out_dir, mask=True):
663
+ filename, ext = os.path.splitext(file)
664
+ if ext in [".nc4", ".nc"]:
665
+ dataset = rasterio.open(f"netcdf:{file}")
666
+ else:
667
+ dataset = rasterio.open(file)
668
+
669
+ file_path = dataset.name
670
+ file_name = os.path.basename(file_path)
671
+
672
+ # get sds array and nodata value
673
+ sds_array, sds_nodata = get_sds(dataset, sds_name)
674
+
675
+ # apply mask
676
+ if mask:
677
+ sds_array = apply_mask(sds_array, dataset, sds_nodata)
678
+
679
+ # if band is surface reflectance then clip values (exclude nodata)
680
+ if sds_name.lower().find("refl") > -1 and product != "VNP09A1":
681
+ sds_array[sds_array != sds_nodata] = np.clip(
682
+ sds_array[sds_array != sds_nodata], 0, 10000
683
+ )
684
+
685
+ out_name = file_name.replace(ext, f".{sds_name}.tif")
686
+ output = os.path.join(out_dir, out_name)
687
+
688
+ dtype = get_dtype_from_sds_name(sds_name)
689
+
690
+ sds_array = sds_array.astype(dtype)
691
+
692
+ profile = rasterio.open(dataset.subdatasets[0]).profile.copy()
693
+ profile.update({"driver": "GTiff", "dtype": dtype, "nodata": sds_nodata})
694
+
695
+ if "VNP" in product:
696
+ f = h5py.File(file_path, "r")
697
+ geo_info = get_h5_geo_info(f)
698
+ out_name = file_name.replace(".h5", f".{sds_name}.tif")
699
+ output = os.path.join(out_dir, out_name)
700
+ if profile["height"] == 1200: # VIIRS VNP09A1, VNP09GA - 1km
701
+ yRes = -926.6254330555555
702
+ xRes = 926.6254330555555
703
+ elif profile["height"] == 2400: # VIIRS VNP09H1, VNP09GA - 500m
704
+ yRes = -463.31271652777775
705
+ xRes = 463.31271652777775
706
+ new_transform = rasterio.Affine(
707
+ xRes, geo_info[1], geo_info[0], geo_info[3], yRes, geo_info[2]
708
+ )
709
+ profile.update({"transform": new_transform})
710
+ tags = dataset.tags()
711
+ for tag in tags:
712
+ if sds_name + "__FillValue" in tag:
713
+ sds_nodata = tags[tag]
714
+ profile.update({"nodata": int(sds_nodata)})
715
+
716
+ # assign CRS if None
717
+ if profile["crs"] == None:
718
+ profile.update({"crs": SINUS_WKT})
719
+
720
+ # create cog
721
+ with MemoryFile() as memfile:
722
+ with memfile.open(**profile) as mem:
723
+ mem.write(sds_array)
724
+ dst_profile = cog_profiles.get("deflate")
725
+ cog_translate(
726
+ mem,
727
+ output,
728
+ dst_profile,
729
+ in_memory=True,
730
+ quiet=True,
731
+ )
732
+
733
+ return output