ECOv003-L2T-STARS 1.0.0__py3-none-any.whl → 1.1.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.
Files changed (75) hide show
  1. ECOv003_L2T_STARS/BRDF/BRDF.py +57 -0
  2. ECOv003_L2T_STARS/BRDF/SZA.py +65 -0
  3. ECOv003_L2T_STARS/BRDF/__init__.py +1 -0
  4. ECOv003_L2T_STARS/BRDF/statistical_radiative_transport.txt +90 -0
  5. ECOv003_L2T_STARS/BRDF/version.txt +1 -0
  6. ECOv003_L2T_STARS/ECOv003_DL.py +527 -0
  7. ECOv003_L2T_STARS/ECOv003_DL.xml +47 -0
  8. ECOv003_L2T_STARS/ECOv003_L2T_STARS.py +162 -0
  9. ECOv003_L2T_STARS/ECOv003_L2T_STARS.xml +47 -0
  10. ECOv003_L2T_STARS/L2TSTARSConfig.py +188 -0
  11. ECOv003_L2T_STARS/L2T_STARS.py +489 -0
  12. ECOv003_L2T_STARS/LPDAAC/LPDAACDataPool.py +444 -0
  13. ECOv003_L2T_STARS/LPDAAC/__init__.py +9 -0
  14. ECOv003_L2T_STARS/LPDAAC/version.txt +1 -0
  15. ECOv003_L2T_STARS/Manifest.toml +2332 -0
  16. ECOv003_L2T_STARS/Project.toml +14 -0
  17. ECOv003_L2T_STARS/VIIRS/VIIRSDataPool.py +294 -0
  18. ECOv003_L2T_STARS/VIIRS/VIIRSDownloader.py +26 -0
  19. ECOv003_L2T_STARS/VIIRS/VIIRS_CMR_LOGIN.py +36 -0
  20. ECOv003_L2T_STARS/VIIRS/VNP09GA.py +1277 -0
  21. ECOv003_L2T_STARS/VIIRS/VNP43IA4.py +288 -0
  22. ECOv003_L2T_STARS/VIIRS/VNP43MA3.py +323 -0
  23. ECOv003_L2T_STARS/VIIRS/__init__.py +9 -0
  24. ECOv003_L2T_STARS/VIIRS/version.txt +1 -0
  25. ECOv003_L2T_STARS/VNP43NRT/VNP43NRT.py +863 -0
  26. ECOv003_L2T_STARS/VNP43NRT/__init__.py +1 -0
  27. ECOv003_L2T_STARS/VNP43NRT/process_VNP43NRT.jl +169 -0
  28. ECOv003_L2T_STARS/VNP43NRT/version.txt +1 -0
  29. ECOv003_L2T_STARS/VNP43NRT_jl/Manifest.toml +995 -0
  30. ECOv003_L2T_STARS/VNP43NRT_jl/Project.toml +15 -0
  31. ECOv003_L2T_STARS/VNP43NRT_jl/__init__.py +0 -0
  32. ECOv003_L2T_STARS/VNP43NRT_jl/instantiate.jl +25 -0
  33. ECOv003_L2T_STARS/VNP43NRT_jl/instantiate.py +13 -0
  34. ECOv003_L2T_STARS/VNP43NRT_jl/src/VNP43NRT.jl +411 -0
  35. ECOv003_L2T_STARS/VNP43NRT_jl/src/__init__.py +0 -0
  36. ECOv003_L2T_STARS/__init__.py +3 -0
  37. ECOv003_L2T_STARS/calibrate_fine_to_coarse.py +60 -0
  38. ECOv003_L2T_STARS/constants.py +38 -0
  39. ECOv003_L2T_STARS/daterange/__init__.py +1 -0
  40. ECOv003_L2T_STARS/daterange/daterange.py +35 -0
  41. ECOv003_L2T_STARS/generate_L2T_STARS_runconfig.py +249 -0
  42. ECOv003_L2T_STARS/generate_NDVI_coarse_directory.py +21 -0
  43. ECOv003_L2T_STARS/generate_NDVI_coarse_image.py +30 -0
  44. ECOv003_L2T_STARS/generate_NDVI_fine_directory.py +14 -0
  45. ECOv003_L2T_STARS/generate_NDVI_fine_image.py +28 -0
  46. ECOv003_L2T_STARS/generate_STARS_inputs.py +231 -0
  47. ECOv003_L2T_STARS/generate_albedo_coarse_directory.py +18 -0
  48. ECOv003_L2T_STARS/generate_albedo_coarse_image.py +30 -0
  49. ECOv003_L2T_STARS/generate_albedo_fine_directory.py +17 -0
  50. ECOv003_L2T_STARS/generate_albedo_fine_image.py +30 -0
  51. ECOv003_L2T_STARS/generate_filename.py +37 -0
  52. ECOv003_L2T_STARS/generate_input_staging_directory.py +23 -0
  53. ECOv003_L2T_STARS/generate_model_state_tile_date_directory.py +28 -0
  54. ECOv003_L2T_STARS/generate_output_directory.py +28 -0
  55. ECOv003_L2T_STARS/install_STARS_jl.py +43 -0
  56. ECOv003_L2T_STARS/instantiate_STARS_jl.py +38 -0
  57. ECOv003_L2T_STARS/load_prior.py +248 -0
  58. ECOv003_L2T_STARS/prior.py +56 -0
  59. ECOv003_L2T_STARS/process_ECOSTRESS_data_fusion_distributed_bias.jl +420 -0
  60. ECOv003_L2T_STARS/process_STARS_product.py +507 -0
  61. ECOv003_L2T_STARS/process_julia_data_fusion.py +110 -0
  62. ECOv003_L2T_STARS/retrieve_STARS_sources.py +101 -0
  63. ECOv003_L2T_STARS/runconfig.py +70 -0
  64. ECOv003_L2T_STARS/timer/__init__.py +1 -0
  65. ECOv003_L2T_STARS/timer/timer.py +77 -0
  66. ECOv003_L2T_STARS/version.py +8 -0
  67. ECOv003_L2T_STARS/version.txt +1 -0
  68. {ECOv003_L2T_STARS-1.0.0.dist-info → ecov003_l2t_stars-1.1.0.dist-info}/METADATA +30 -23
  69. ecov003_l2t_stars-1.1.0.dist-info/RECORD +73 -0
  70. {ECOv003_L2T_STARS-1.0.0.dist-info → ecov003_l2t_stars-1.1.0.dist-info}/WHEEL +1 -1
  71. ecov003_l2t_stars-1.1.0.dist-info/entry_points.txt +3 -0
  72. ecov003_l2t_stars-1.1.0.dist-info/top_level.txt +1 -0
  73. ECOv003_L2T_STARS-1.0.0.dist-info/RECORD +0 -5
  74. ECOv003_L2T_STARS-1.0.0.dist-info/top_level.txt +0 -1
  75. {ECOv003_L2T_STARS-1.0.0.dist-info → ecov003_l2t_stars-1.1.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,863 @@
1
+ import argparse
2
+ import json
3
+ import logging
4
+ import shutil
5
+ import subprocess
6
+ import sys
7
+ from datetime import date, timedelta, datetime
8
+ from glob import glob
9
+ from os.path import abspath, expanduser, join, basename, splitext, exists, dirname
10
+ from typing import Union, List
11
+ import dateutil
12
+ import numpy as np
13
+ from dateutil import parser
14
+ from matplotlib.colors import Colormap
15
+
16
+ import colored_logging as cl
17
+
18
+ import rasters
19
+ from rasters import Raster, RasterGeometry, Point, Polygon
20
+ from GEOS5FP import GEOS5FP, FailedGEOS5FPDownload
21
+ from modland import find_modland_tiles, parsehv, generate_modland_grid
22
+
23
+ from ..BRDF import bidirectional_reflectance
24
+ from ..BRDF.SZA import calculate_SZA
25
+ from ..VIIRS import VIIRSDownloaderAlbedo, VIIRSDownloaderNDVI
26
+ from ..VIIRS.VNP09GA import VNP09GA, VNP09GAGranule, ALBEDO_COLORMAP, NDVI_COLORMAP, VIIRSUnavailableError
27
+ from ..daterange import date_range
28
+ from ..timer import Timer
29
+
30
+ DEFAULT_WEIGHTED = True
31
+ DEFAULT_SCALE = 1.87
32
+
33
+ with open(join(abspath(dirname(__file__)), "version.txt")) as f:
34
+ version = f.read()
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ def install_VNP43NRT_jl(
39
+ package_location: str = "https://github.com/STARS-Data-Fusion/VNP43NRT.jl",
40
+ environment_name: str = "@ECOv002-L2T-STARS"):
41
+ """
42
+ Installs the VNP43NRT.jl package from GitHub into a shared environment.
43
+
44
+ Args:
45
+ github_url: The URL of the GitHub repository containing VNP43NRT.jl.
46
+ Defaults to "https://github.com/STARS-Data-Fusion/VNP43NRT.jl".
47
+ environment_name: The name of the shared Julia environment to install the
48
+ package into. Defaults to "@ECOv002-L2T-STARS".
49
+
50
+ Returns:
51
+ A CompletedProcess object containing information about the execution of the Julia command.
52
+ """
53
+
54
+ julia_command = [
55
+ "julia",
56
+ "-e",
57
+ f'using Pkg; Pkg.activate("{environment_name}"); Pkg.develop(url="{package_location}")'
58
+ ]
59
+
60
+ result = subprocess.run(julia_command, capture_output=True, text=True)
61
+
62
+ if result.returncode == 0:
63
+ print(f"VNP43NRT.jl installed successfully in environment '{environment_name}'!")
64
+ else:
65
+ print("Error installing VNP43NRT.jl:")
66
+ print(result.stderr)
67
+
68
+ return result
69
+
70
+ def instantiate_VNP43NRT_jl(package_location: str):
71
+ """
72
+ Activates the package_location directory as the active project and instantiates it.
73
+
74
+ Args:
75
+ package_location: The directory of the Julia package to activate and instantiate.
76
+
77
+ Returns:
78
+ A CompletedProcess object containing information about the execution of the Julia command.
79
+ """
80
+
81
+ julia_command = [
82
+ "julia",
83
+ "-e",
84
+ f'using Pkg; Pkg.activate("{package_location}"); Pkg.instantiate()'
85
+ ]
86
+
87
+ result = subprocess.run(julia_command, capture_output=True, text=True)
88
+
89
+ if result.returncode == 0:
90
+ print(f"VNP43NRT.jl instantiated successfully in directory '{package_location}'!")
91
+ else:
92
+ print("Error instantiating VNP43NRT.jl:")
93
+ print(result.stderr)
94
+
95
+ return result
96
+
97
+ def process_julia_BRDF(
98
+ band: str,
99
+ h: int,
100
+ v: int,
101
+ tile_width_cells: int,
102
+ start_date: date,
103
+ end_date: date,
104
+ reflectance_directory: str,
105
+ solar_zenith_directory: str,
106
+ sensor_zenith_directory: str,
107
+ relative_azimuth_directory: str,
108
+ SZA_filename: str,
109
+ output_directory: str):
110
+ parent_directory = abspath(join(dirname(__file__), ".."))
111
+ julia_source_directory = join(parent_directory, "VNP43NRT_jl")
112
+ julia_script_filename = join(abspath(dirname(__file__)), "process_VNP43NRT.jl")
113
+
114
+ instantiate_VNP43NRT_jl(julia_source_directory)
115
+
116
+ command = f'julia "{julia_script_filename}" "{band}" "{h}" "{v}" "{tile_width_cells}" "{start_date:%Y-%m-%d}" "{end_date:%Y-%m-%d}" "{reflectance_directory}" "{solar_zenith_directory}" "{sensor_zenith_directory}" "{relative_azimuth_directory}" "{SZA_filename}" "{output_directory}"'
117
+ logger.info(command)
118
+ subprocess.run(command, shell=True)
119
+
120
+ class BRDFRetrievalFailed(RuntimeError):
121
+ pass
122
+
123
+ class BRDFParameters:
124
+ def __init__(
125
+ self,
126
+ WSA: Raster,
127
+ BSA: Raster,
128
+ NBAR: Raster,
129
+ WSA_SE: Raster,
130
+ BSA_SE: Raster,
131
+ NBAR_SE: Raster,
132
+ BRDF_SE: Raster,
133
+ BRDF_R2: Raster,
134
+ count: Raster,
135
+ filter_invalid=True):
136
+
137
+ if filter_invalid:
138
+ WSA = rasters.where((WSA < 0) | (WSA > 1), np.nan, WSA)
139
+ BSA = rasters.where((BSA < 0) | (BSA > 1), np.nan, BSA)
140
+ NBAR = rasters.where((NBAR < 0) | (NBAR > 1), np.nan, NBAR)
141
+
142
+ self.WSA = WSA
143
+ self.BSA = BSA
144
+ self.NBAR = NBAR
145
+ self.WSA_SE = WSA_SE
146
+ self.BSA_SE = BSA_SE
147
+ self.NBAR_SE = NBAR_SE
148
+ self.BRDF_SE = BRDF_SE
149
+ self.BRDF_R2 = BRDF_R2
150
+ self.count = count
151
+
152
+
153
+ class VNP43NRTGranule:
154
+ def __init__(self, directory: str):
155
+ self._directory = abspath(directory)
156
+
157
+ def __repr__(self):
158
+ return f"VNP43NRTGranule({self.directory})"
159
+
160
+ @property
161
+ def directory(self):
162
+ return self._directory
163
+
164
+ @property
165
+ def granule_ID(self) -> str:
166
+ return splitext(basename(self.directory))[0]
167
+
168
+ @property
169
+ def tile(self):
170
+ return self.granule_ID.split("_")[2]
171
+
172
+ @property
173
+ def hv(self):
174
+ return parsehv(self.tile)
175
+
176
+ @property
177
+ def h(self):
178
+ return self.hv[0]
179
+
180
+ @property
181
+ def v(self):
182
+ return self.hv[1]
183
+
184
+ @property
185
+ def date_UTC(self):
186
+ return datetime.strptime(self.granule_ID.split("_")[1][1:], "%Y%j")
187
+
188
+ @property
189
+ def variables(self) -> List[str]:
190
+ return [
191
+ splitext(basename(filename))[0].split("_")[-1]
192
+ for filename
193
+ in glob(join(self.directory, "*.tif"))
194
+ ]
195
+
196
+ def variable_filename(self, variable_name: str) -> str:
197
+ return join(self.directory, f"{self.granule_ID}_{variable_name}.tif")
198
+
199
+ def variable(self, variable_name: str, geometry: RasterGeometry = None, cmap: Colormap = None) -> Raster:
200
+ return Raster.open(self.variable_filename(variable_name), geometry=geometry, cmap=cmap)
201
+
202
+ def get_NDVI(self, geometry: RasterGeometry = None, cmap: Colormap = NDVI_COLORMAP) -> Raster:
203
+ return self.variable("NDVI", geometry=geometry, cmap=cmap)
204
+
205
+ NDVI = property(get_NDVI)
206
+
207
+ def get_albedo(self, geometry: RasterGeometry = None, cmap: Colormap = ALBEDO_COLORMAP) -> Raster:
208
+ return self.variable("albedo", geometry=geometry, cmap=cmap)
209
+
210
+ albedo = property(get_albedo)
211
+
212
+ def BSA(self, band: int, geometry: RasterGeometry = None) -> Raster:
213
+ return self.variable(f"BSA_M{band}", geometry=geometry)
214
+
215
+ def WSA(self, band: int, geometry: RasterGeometry = None) -> Raster:
216
+ return self.variable(f"WSA_M{band}", geometry=geometry)
217
+
218
+ def NBAR(self, band: int, geometry: RasterGeometry = None) -> Raster:
219
+ return self.variable(f"NBAR_I{band}", geometry=geometry)
220
+
221
+ def add_layer(self, variable_name: str, image: Raster) -> str:
222
+ filename = self.variable_filename(variable_name)
223
+ logger.info(f"adding VNP43NRT layer at {cl.place(self.tile)} on {cl.time(self.date_UTC)}: {cl.file(filename)}")
224
+ image.to_COG(filename)
225
+
226
+ return filename
227
+
228
+ @property
229
+ def complete(self) -> bool:
230
+ required_variables = [
231
+ "NDVI",
232
+ "NBAR_I1",
233
+ "NBAR_I2",
234
+ "albedo"
235
+ ]
236
+
237
+ for band in [1, 2, 3, 4, 5, 7, 8, 10, 11]:
238
+ required_variables.append(f"BSA_M{band}")
239
+ required_variables.append(f"WSA_M{band}")
240
+
241
+ for variable in required_variables:
242
+ filename = self.variable_filename(variable)
243
+
244
+ if not exists(filename):
245
+ return False
246
+
247
+ return True
248
+
249
+
250
+ class AuxiliaryDownloadFailed(ConnectionError):
251
+ pass
252
+
253
+
254
+ class VNP43NRT(VIIRSDownloaderAlbedo, VIIRSDownloaderNDVI):
255
+ DEFAULT_VNP09GA_DIRECTORY = "VNP09GA_products"
256
+ DEFAULT_VNP43NRT_DIRECTORY = "VNP43NRT_products"
257
+
258
+ def __init__(
259
+ self,
260
+ working_directory: str = None,
261
+ download_directory: str = None,
262
+ VNP09GA_directory: str = None,
263
+ VNP43NRT_directory: str = None,
264
+ mosaic_directory: str = None,
265
+ VNP43NRT_staging_directory: str = None,
266
+ GEOS5FP_connection: GEOS5FP = None,
267
+ GEOS5FP_download: str = None,
268
+ GEOS5FP_products: str = None):
269
+ if working_directory is None:
270
+ working_directory = VNP09GA.DEFAULT_WORKING_DIRECTORY
271
+
272
+ working_directory = abspath(expanduser(working_directory))
273
+
274
+ if VNP43NRT_staging_directory is None:
275
+ VNP43NRT_staging_directory = join(working_directory, "VNP43NRT_staging")
276
+
277
+ if VNP09GA_directory is None:
278
+ VNP09GA_directory = join(working_directory, self.DEFAULT_VNP09GA_DIRECTORY)
279
+
280
+ VNP09GA_directory = abspath(expanduser(VNP09GA_directory))
281
+
282
+ if VNP43NRT_directory is None:
283
+ VNP43NRT_directory = join(working_directory, self.DEFAULT_VNP43NRT_DIRECTORY)
284
+
285
+ VNP43NRT_directory = abspath(expanduser(VNP43NRT_directory))
286
+
287
+ self.vnp09ga = VNP09GA(
288
+ working_directory=working_directory,
289
+ download_directory=download_directory,
290
+ products_directory=VNP09GA_directory,
291
+ mosaic_directory=mosaic_directory
292
+ )
293
+
294
+ if GEOS5FP_connection is None:
295
+ GEOS5FP_connection = GEOS5FP(
296
+ working_directory=working_directory,
297
+ download_directory=GEOS5FP_download,
298
+ # products_directory=GEOS5FP_products
299
+ )
300
+
301
+ self.VNP09GA_directory = VNP09GA_directory
302
+ self.VNP43NRT_directory = VNP43NRT_directory
303
+ self.GEOS5FP = GEOS5FP_connection
304
+ self.VNP43NRT_staging_directory = VNP43NRT_staging_directory
305
+
306
+ def __repr__(self):
307
+ display_dict = {
308
+ "download_directory": self.vnp09ga.download_directory,
309
+ "VNP09GA_directory": self.VNP09GA_directory,
310
+ "VNP43NRT_directory": self.VNP43NRT_directory,
311
+ "mosaic_directory": self.vnp09ga.mosaic_directory
312
+ }
313
+
314
+ display_string = json.dumps(display_dict, indent=2)
315
+
316
+ return display_string
317
+
318
+ def VNP09GA(
319
+ self,
320
+ date_UTC: Union[date, str],
321
+ tile: str) -> VNP09GAGranule:
322
+ return self.vnp09ga.granule(
323
+ date_UTC=date_UTC,
324
+ tile=tile,
325
+ )
326
+
327
+ def prefetch_VNP09GA(
328
+ self,
329
+ start_date: Union[date, str],
330
+ end_date: Union[date, str],
331
+ geometry: Point or Polygon or RasterGeometry = None):
332
+ self.vnp09ga.prefetch_VNP09GA(
333
+ start_date,
334
+ end_date,
335
+ geometry,
336
+ )
337
+
338
+ def generate_staging_directory(self, tile: str, variable: str) -> str:
339
+ return join(self.VNP43NRT_staging_directory, tile, variable)
340
+
341
+ def generate_staging_filename(self, tile: str, processing_date, variable: str) -> str:
342
+ return join(self.generate_staging_directory(tile, variable), f"{processing_date:%Y-%m-%d}_{variable}.tif")
343
+
344
+ def BRDF_parameters(
345
+ self,
346
+ date_UTC: Union[date, str],
347
+ tile: str,
348
+ band: str):
349
+ DTYPE = np.float64
350
+
351
+ if isinstance(date_UTC, str):
352
+ date_UTC = parser.parse(date_UTC).date()
353
+
354
+ logger.info(f"processing BRDF for band {band} at tile {tile} on date {cl.time(date_UTC)}")
355
+
356
+ end_date = date_UTC
357
+ start_date = date_UTC - timedelta(days=16)
358
+
359
+ band_type = band[0]
360
+
361
+ h = int(tile[1:3])
362
+ v = int(tile[4:6])
363
+
364
+ if band_type == "I":
365
+ tile_width_cells = 2400
366
+ elif band_type == "M":
367
+ tile_width_cells = 1200
368
+ else:
369
+ raise ValueError(f"invalid band: {band}")
370
+
371
+ grid = generate_modland_grid(h, v, tile_width_cells)
372
+
373
+ # reflectance_list = []
374
+ # solar_zenith_list = []
375
+ # sensor_zenith_list = []
376
+ # relative_azimuth_list = []
377
+
378
+ granule = self.VNP09GA(date_UTC, tile)
379
+ geometry = granule.geometry(band)
380
+
381
+ for processing_date in date_range(start_date, end_date):
382
+ logger.info(f"retrieving VNP09GA for VNP43NRT at {cl.place(tile)} on {cl.time(date_UTC)}")
383
+
384
+ # TODO replace the lists with directories of GeoTIFFs staged for VNP43NRT_jl
385
+ try:
386
+ granule = self.VNP09GA(processing_date, tile)
387
+
388
+ reflectance_filename = self.generate_staging_filename(tile, processing_date, band)
389
+
390
+ if exists(reflectance_filename):
391
+ logger.info(f"previously generated {band} reflectance on {processing_date}: {reflectance_filename}")
392
+ # reflectance_raster = Raster.open(reflectance_filename)
393
+ else:
394
+ logger.info(f"generating {band} reflectance on {processing_date}")
395
+ reflectance_raster = granule.band(band)
396
+ logger.info(f"writing {band} reflectance on {processing_date}: {reflectance_filename}")
397
+ reflectance_raster.to_geotiff(reflectance_filename)
398
+
399
+ # reflectance_list.append(np.array(reflectance_raster).flatten())
400
+
401
+ solar_zenith_filename = self.generate_staging_filename(tile, processing_date, f"{band_type}_solar_zenith")
402
+
403
+ if exists(solar_zenith_filename):
404
+ logger.info(f"previously generated solar zenith on {processing_date}: {solar_zenith_filename}")
405
+ # solar_zenith_raster = Raster.open(solar_zenith_filename)
406
+ else:
407
+ logger.info(f"generating solar zenith on {processing_date}")
408
+ solar_zenith_raster = granule.solar_zenith(band)
409
+ logger.info(f"writing solar zenith on {processing_date}: {solar_zenith_filename}")
410
+ solar_zenith_raster.to_geotiff(solar_zenith_filename)
411
+
412
+ # solar_zenith_list.append(np.array(solar_zenith_raster).flatten())
413
+
414
+ sensor_zenith_filename = self.generate_staging_filename(tile, processing_date, f"{band_type}_sensor_zenith")
415
+
416
+ if exists(sensor_zenith_filename):
417
+ logger.info(f"previously generated sensor zenith on {processing_date}: {sensor_zenith_filename}")
418
+ # sensor_zenith_raster = Raster.open(sensor_zenith_filename)
419
+ else:
420
+ logger.info(f"generating sensor zenith on {processing_date}")
421
+ sensor_zenith_raster = granule.sensor_zenith(band)
422
+ logger.info(f"writing sensor zenith on {processing_date}: {sensor_zenith_filename}")
423
+ sensor_zenith_raster.to_geotiff(sensor_zenith_filename)
424
+
425
+ # sensor_zenith_list.append(np.array(sensor_zenith_raster).flatten())
426
+
427
+ relative_azimuth_filename = self.generate_staging_filename(tile, processing_date, f"{band_type}_relative_azimuth")
428
+
429
+ if exists(relative_azimuth_filename):
430
+ logger.info(f"previously generated sensor zenith on {processing_date}: {relative_azimuth_filename}")
431
+ # relative_azimuth_raster = Raster.open(relative_azimuth_filename)
432
+ else:
433
+ logger.info(f"generating sensor zenith on {processing_date}")
434
+ solar_azimuth = granule.solar_azimuth(band)
435
+ sensor_azimuth = granule.sensor_azimuth(band)
436
+ relative_azimuth_raster = Raster(np.abs(solar_azimuth - sensor_azimuth), geometry=sensor_azimuth.geometry)
437
+ logger.info(f"writing sensor zenith on {processing_date}: {relative_azimuth_filename}")
438
+ relative_azimuth_raster.to_geotiff(relative_azimuth_filename)
439
+
440
+ # relative_azimuth_list.append(np.array(relative_azimuth_raster).flatten())
441
+ except VIIRSUnavailableError as e:
442
+ if (datetime.utcnow().date() - processing_date).days > 4:
443
+ logger.warning(e)
444
+ continue
445
+ else:
446
+ raise e
447
+
448
+ # Y = np.stack(reflectance_list).T.astype(DTYPE)
449
+ # sz = np.stack(solar_zenith_list).T.astype(DTYPE)
450
+ # vz = np.stack(sensor_zenith_list).T.astype(DTYPE)
451
+ # rz = np.stack(relative_azimuth_list).T.astype(DTYPE)
452
+
453
+ # SZA_filename = self.generate_SZA_filename(date_UTC, band_type)
454
+ SZA_filename = self.generate_staging_filename(tile, date_UTC, f"{band_type}_solar_zenith_noon")
455
+
456
+ if exists(SZA_filename):
457
+ logger.info(f"solar zenith noon file already exists: {SZA_filename}")
458
+ else:
459
+ doy = date_UTC.timetuple().tm_yday
460
+ SZA = calculate_SZA(doy, 12, grid)
461
+ logger.info(f"writing solar zenith noon: {SZA_filename}")
462
+ SZA.to_geotiff(SZA_filename)
463
+
464
+ # soz_noon = np.array(SZA).flatten()
465
+
466
+ logger.info(f"started processing VNP43NRT BRDF parameters at {cl.place(tile)} on {cl.time(date_UTC)}")
467
+ timer = Timer()
468
+
469
+ # TODO replace this with call to VNP43NRT_jl
470
+ try:
471
+ # cpp_results = NRT_BRDF_all(
472
+ # Y=Y,
473
+ # sz=sz,
474
+ # vz=vz,
475
+ # rz=rz,
476
+ # soz_noon=soz_noon
477
+ # )
478
+ reflectance_directory = self.generate_staging_directory(tile, band)
479
+ solar_zenith_directory = self.generate_staging_directory(tile, f"{band_type}_solar_zenith")
480
+ sensor_zenith_directory = self.generate_staging_directory(tile, f"{band_type}_sensor_zenith")
481
+ relative_azimuth_directory = self.generate_staging_directory(tile, f"{band_type}_relative_azimuth")
482
+ output_directory = self.generate_staging_directory(tile, "output")
483
+
484
+ process_julia_BRDF(
485
+ band=band,
486
+ h=h,
487
+ v=v,
488
+ tile_width_cells=tile_width_cells,
489
+ start_date=start_date,
490
+ end_date=end_date,
491
+ reflectance_directory=reflectance_directory,
492
+ solar_zenith_directory=solar_zenith_directory,
493
+ sensor_zenith_directory=sensor_zenith_directory,
494
+ relative_azimuth_directory=relative_azimuth_directory,
495
+ SZA_filename=SZA_filename,
496
+ output_directory=output_directory
497
+ )
498
+
499
+ WSA = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_WSA.tif"))
500
+ BSA = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_BSA.tif"))
501
+ NBAR = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_NBAR.tif"))
502
+ WSA_SE = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_WSA_SE.tif"))
503
+ BSA_SE = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_BSA_SE.tif"))
504
+ NBAR_SE = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_NBAR_SE.tif"))
505
+ BRDF_SE = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_BRDF_SE.tif"))
506
+ BRDF_R2 = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_BRDF_R2.tif"))
507
+ count = Raster.open(join(output_directory, f"{date_UTC:%Y-%m-%d}_count.tif"))
508
+
509
+ logger.info(f"removing output directory: {output_directory}")
510
+ shutil.rmtree(output_directory)
511
+ except RuntimeError as e:
512
+ logger.exception(e)
513
+ # raise BRDFRetrievalFailed(f"BRDF retrival failed for {cl.place(tile)} on {cl.time(date_UTC)} ({cl.time(timer)})")
514
+ WSA = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
515
+ BSA = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
516
+ NBAR = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
517
+ WSA_SE = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
518
+ BSA_SE = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
519
+ NBAR_SE = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
520
+ BRDF_SE = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
521
+ BRDF_R2 = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
522
+ count = Raster(np.full(geometry.shape, np.nan, np.float32), geometry=geometry)
523
+
524
+ BRDF_parameters = BRDFParameters(
525
+ WSA=WSA,
526
+ BSA=BSA,
527
+ NBAR=NBAR,
528
+ NBAR_SE=NBAR_SE,
529
+ WSA_SE=WSA_SE,
530
+ BSA_SE=BSA_SE,
531
+ BRDF_SE=BRDF_SE,
532
+ BRDF_R2=BRDF_R2,
533
+ count=count
534
+ )
535
+
536
+ return BRDF_parameters
537
+
538
+ logger.info(
539
+ f"finished processing VNP43NRT BRDF parameters at {cl.place(tile)} on {cl.time(date_UTC)} ({cl.time(timer)})")
540
+
541
+ # TODO replace this with loading the GeoTIFFs written by VNP43NRT_jl
542
+ # WSA = Raster(cpp_results[:, 0].reshape(geometry.shape), geometry=geometry)
543
+ # BSA = Raster(cpp_results[:, 1].reshape(geometry.shape), geometry=geometry)
544
+ # NBAR = Raster(cpp_results[:, 2].reshape(geometry.shape), geometry=geometry)
545
+ # WSA_SE = Raster(cpp_results[:, 3].reshape(geometry.shape), geometry=geometry)
546
+ # BSA_SE = Raster(cpp_results[:, 4].reshape(geometry.shape), geometry=geometry)
547
+ # NBAR_SE = Raster(cpp_results[:, 5].reshape(geometry.shape), geometry=geometry)
548
+ # BRDF_SE = Raster(cpp_results[:, 6].reshape(geometry.shape), geometry=geometry)
549
+ # BRDF_R2 = Raster(cpp_results[:, 7].reshape(geometry.shape), geometry=geometry)
550
+ # count = Raster(cpp_results[:, 8].reshape(geometry.shape), geometry=geometry)
551
+
552
+ BRDF_parameters = BRDFParameters(
553
+ WSA=WSA,
554
+ BSA=BSA,
555
+ NBAR=NBAR,
556
+ NBAR_SE=NBAR_SE,
557
+ WSA_SE=WSA_SE,
558
+ BSA_SE=BSA_SE,
559
+ BRDF_SE=BRDF_SE,
560
+ BRDF_R2=BRDF_R2,
561
+ count=count
562
+ )
563
+
564
+ # logger.info(f"removing staging_directory: {self.VNP43NRT_staging_directory}")
565
+ # shutil.rmtree(self.VNP43NRT_staging_directory, ignore_errors=True)
566
+
567
+ return BRDF_parameters
568
+
569
+ def granule_ID(
570
+ self,
571
+ date_UTC: Union[date, str],
572
+ tile: str) -> str:
573
+ if isinstance(date_UTC, str):
574
+ date_UTC = parser.parse(date_UTC).date()
575
+
576
+ granule_ID = f"VNP43NRT_A{date_UTC:%Y%j}_{tile}"
577
+
578
+ return granule_ID
579
+
580
+ def granule_directory(
581
+ self,
582
+ date_UTC: Union[date, str],
583
+ tile: str) -> str:
584
+ return join(self.VNP43NRT_directory, self.granule_ID(date_UTC, tile))
585
+
586
+ def AOT(self, time_UTC: datetime, geometry: RasterGeometry = None, resampling: str = None) -> Raster:
587
+ try:
588
+ return self.GEOS5FP.AOT(time_UTC=time_UTC, geometry=geometry, resampling=resampling)
589
+ except FailedGEOS5FPDownload as e:
590
+ raise AuxiliaryDownloadFailed("unable to retrieve AOT from GEOS5-FP for VNP43NRT")
591
+
592
+ def VNP43NRT(
593
+ self,
594
+ date_UTC: Union[date, str],
595
+ tile: str,
596
+ diagnostics: bool = False) -> VNP43NRTGranule:
597
+ if isinstance(date_UTC, str):
598
+ date_UTC = parser.parse(date_UTC).date()
599
+
600
+ logger.info(f"started processing VNP43NRT at {cl.place(tile)} on {cl.time(date_UTC)}")
601
+ timer = Timer()
602
+
603
+ directory = self.granule_directory(
604
+ date_UTC=date_UTC,
605
+ tile=tile
606
+ )
607
+
608
+ granule = VNP43NRTGranule(directory)
609
+
610
+ if granule.complete:
611
+ return granule
612
+
613
+ for i in (1, 2):
614
+ BRDF_parameters = self.BRDF_parameters(
615
+ date_UTC=date_UTC,
616
+ tile=tile,
617
+ band=f"I{i}"
618
+ )
619
+
620
+ granule.add_layer(f"NBAR_I{i}", BRDF_parameters.NBAR)
621
+
622
+ if diagnostics:
623
+ granule.add_layer(f"NBARSE_I{i}", BRDF_parameters.NBAR_SE)
624
+ granule.add_layer(f"WSA_I{i}", BRDF_parameters.WSA)
625
+ granule.add_layer(f"WSASE_I{i}", BRDF_parameters.WSA_SE)
626
+ granule.add_layer(f"BSA_I{i}", BRDF_parameters.BSA)
627
+ granule.add_layer(f"BSASE_I{i}", BRDF_parameters.BSA_SE)
628
+ granule.add_layer(f"BRDFSE_I{i}", BRDF_parameters.BRDF_SE)
629
+ granule.add_layer(f"count_I{i}", BRDF_parameters.count)
630
+
631
+ NIR = granule.variable("NBAR_I2")
632
+ red = granule.variable("NBAR_I1")
633
+ NDVI = rasters.clip((NIR - red) / (NIR + red), -1, 1)
634
+ granule.add_layer("NDVI", NDVI)
635
+
636
+ time_UTC = datetime(date_UTC.year, date_UTC.month, date_UTC.day, 10, 30)
637
+ geometry = generate_modland_grid(*parsehv(tile), 1200)
638
+ AOT = self.AOT(time_UTC=time_UTC, geometry=geometry, resampling="cubic")
639
+
640
+ if diagnostics:
641
+ granule.add_layer("AOT", AOT)
642
+
643
+ doy = date_UTC.timetuple().tm_yday
644
+ SZA = calculate_SZA(doy, 10.5, geometry)
645
+
646
+ if diagnostics:
647
+ granule.add_layer("SZA", SZA)
648
+
649
+ b = {}
650
+
651
+ for m in (1, 2, 3, 4, 5, 7, 8, 10, 11):
652
+ BRDF_parameters = self.BRDF_parameters(
653
+ date_UTC=date_UTC,
654
+ tile=tile,
655
+ band=f"M{m}"
656
+ )
657
+ WSA = BRDF_parameters.WSA
658
+ granule.add_layer(f"WSA_M{m}", WSA)
659
+ BSA = BRDF_parameters.BSA
660
+ granule.add_layer(f"BSA_M{m}", BSA)
661
+
662
+ band_albedo = bidirectional_reflectance(
663
+ white_sky_albedo=WSA,
664
+ black_sky_albedo=BSA,
665
+ SZA=SZA,
666
+ AOT=AOT
667
+ )
668
+
669
+ b[m] = rasters.clip(band_albedo, 0, 1)
670
+
671
+ if diagnostics:
672
+ granule.add_layer(f"WSASE_M{m}", BRDF_parameters.WSA_SE)
673
+ granule.add_layer(f"BSASE_M{m}", BRDF_parameters.BSA_SE)
674
+ granule.add_layer(f"BRDFSE_M{m}", BRDF_parameters.BRDF_SE)
675
+ granule.add_layer(f"NBAR_M{m}", BRDF_parameters.NBAR)
676
+ granule.add_layer(f"NBARSE_M{m}", BRDF_parameters.NBAR_SE)
677
+ granule.add_layer(f"count_M{m}", BRDF_parameters.count)
678
+
679
+ albedo = 0.2418 * b[1] \
680
+ - 0.201 * b[2] \
681
+ + 0.2093 * b[3] \
682
+ + 0.1146 * b[4] \
683
+ + 0.1348 * b[5] \
684
+ + 0.2251 * b[7] \
685
+ + 0.1123 * b[8] \
686
+ + 0.0860 * b[10] \
687
+ + 0.0803 * b[11] \
688
+ - 0.0131
689
+
690
+ albedo = rasters.clip(albedo, 0, 1)
691
+ granule.add_layer("albedo", albedo)
692
+ logger.info(f"finished processing VNP43NRT at {cl.place(tile)} on {cl.time(date_UTC)} ({cl.time(timer)})")
693
+
694
+ return granule
695
+
696
+ def granule(
697
+ self,
698
+ date_UTC: Union[date, str],
699
+ tile: str) -> VNP43NRTGranule:
700
+ return self.VNP43NRT(
701
+ date_UTC=date_UTC,
702
+ tile=tile
703
+ )
704
+
705
+ def albedo(
706
+ self,
707
+ date_UTC: date or str,
708
+ geometry: RasterGeometry,
709
+ filename: str = None,
710
+ save_preview: bool = True) -> Raster:
711
+ if isinstance(date_UTC, str):
712
+ date_UTC = parser.parse(date_UTC).date()
713
+
714
+ if filename is not None and exists(filename):
715
+ return Raster.open(filename, cmap=ALBEDO_COLORMAP)
716
+
717
+ tiles = sorted(find_modland_tiles(geometry.boundary_latlon.geometry))
718
+ albedo = None
719
+
720
+ for tile in tiles:
721
+ granule = self.granule(date_UTC=date_UTC, tile=tile)
722
+ granule_albedo = granule.albedo
723
+ source_cell_size = granule_albedo.geometry.cell_size
724
+ dest_cell_size = geometry.cell_size
725
+ logger.info(f"projecting VIIRS albedo from {cl.val(f'{source_cell_size} m')} to {cl.val(f'{dest_cell_size} m')}")
726
+ projected_albedo = granule_albedo.to_geometry(geometry)
727
+
728
+ if albedo is None:
729
+ albedo = projected_albedo
730
+ else:
731
+ albedo = rasters.where(np.isnan(albedo), projected_albedo, albedo)
732
+
733
+ albedo.cmap = ALBEDO_COLORMAP
734
+
735
+ if filename is not None:
736
+ logger.info(f"writing albedo mosaic: {cl.file(filename)}")
737
+ albedo.to_geotiff(filename)
738
+
739
+ if save_preview:
740
+ albedo.percentilecut.to_geojpeg(filename.replace(".tif", ".jpeg"))
741
+
742
+ return albedo
743
+
744
+ def NDVI(
745
+ self,
746
+ date_UTC: Union[date, str],
747
+ geometry: RasterGeometry,
748
+ filename: str = None,
749
+ resampling: str = None) -> Raster:
750
+ if isinstance(date_UTC, str):
751
+ date_UTC = parser.parse(date_UTC).date()
752
+
753
+ if filename is not None and exists(filename):
754
+ return Raster.open(filename, cmap=NDVI_COLORMAP)
755
+
756
+ if resampling is None:
757
+ resampling = self.vnp09ga.resampling
758
+
759
+ tiles = sorted(find_modland_tiles(geometry.boundary_latlon.geometry))
760
+
761
+ if len(tiles) == 0:
762
+ raise ValueError("no VIIRS tiles found covering target geometry")
763
+
764
+ NDVI = None
765
+
766
+ for tile in tiles:
767
+ granule = self.granule(date_UTC=date_UTC, tile=tile)
768
+ granule_NDVI = granule.NDVI
769
+ projected_NDVI = granule_NDVI.to_geometry(geometry, resampling=resampling)
770
+
771
+ if NDVI is None:
772
+ NDVI = projected_NDVI
773
+ else:
774
+ NDVI = rasters.where(np.isnan(NDVI), projected_NDVI, NDVI)
775
+
776
+ if NDVI is None:
777
+ raise ValueError("VIIRS NDVI did not generate")
778
+
779
+ NDVI.cmap = NDVI_COLORMAP
780
+
781
+ if filename is not None:
782
+ logger.info(f"writing NDVI mosaic: {cl.file(filename)}")
783
+ NDVI.to_geotiff(filename)
784
+
785
+ return NDVI
786
+
787
+
788
+ def main(argv=sys.argv):
789
+ cl.configure()
790
+
791
+ parser = argparse.ArgumentParser(description="run the VNP43NRT BRDF/NDVI/albedo product")
792
+
793
+ parser.add_argument(
794
+ "--date",
795
+ dest="date"
796
+ )
797
+
798
+ parser.add_argument(
799
+ "--start",
800
+ dest="start"
801
+ )
802
+
803
+ parser.add_argument(
804
+ "--end",
805
+ dest="end"
806
+ )
807
+
808
+ parser.add_argument(
809
+ "--tile",
810
+ dest="tile"
811
+ )
812
+
813
+ parser.add_argument(
814
+ "--working-directory",
815
+ dest="working_directory"
816
+ )
817
+
818
+ parser.add_argument(
819
+ "--diagnostics",
820
+ action="store_true"
821
+ )
822
+
823
+ args = parser.parse_args(args=argv[1:])
824
+
825
+ if args.start is not None:
826
+ start = args.start
827
+ elif args.start is None and args.date is not None:
828
+ start = args.date
829
+ else:
830
+ raise ValueError("no dates given")
831
+
832
+ end = args.end
833
+
834
+ if end is None:
835
+ end = start
836
+
837
+ if args.tile is None:
838
+ raise ValueError("no tile given")
839
+ else:
840
+ tile = args.tile
841
+
842
+ working_directory = args.working_directory
843
+ diagnostics = args.diagnostics
844
+
845
+ date_message = f"on {cl.time(start)}" if start == end else f"from {cl.time(start)} to {cl.time(end)}"
846
+ message = f"running VNP43NRT BRDF/NDVI/albedo at {cl.place(tile)} {date_message}"
847
+ logger.info(message)
848
+
849
+ vnp43nrt = VNP43NRT(working_directory=working_directory)
850
+
851
+ start = dateutil.parser.parse(start).date()
852
+ end = dateutil.parser.parse(end).date()
853
+
854
+ for date_UTC in date_range(start, end):
855
+ granule = vnp43nrt.VNP43NRT(
856
+ date_UTC=date_UTC,
857
+ tile=tile,
858
+ diagnostics=diagnostics
859
+ )
860
+
861
+
862
+ if __name__ == "__main__":
863
+ sys.exit(main(argv=sys.argv))