Skip to content

Commit fa01e6f

Browse files
Merge pull request ET-Toolbox#6 from gregory-halverson/main
searching, downloading, loading
2 parents 03026e0 + 8ef5379 commit fa01e6f

File tree

6 files changed

+684
-1
lines changed

6 files changed

+684
-1
lines changed

Search and Download.ipynb

Lines changed: 298 additions & 0 deletions
Large diffs are not rendered by default.

VNP21IMGNRT_002/VNP21IMGNRT_002.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
from .constants import *
22
from .VNP21IMGNRT_granule import *
3+
from .search_granules import *
4+
from .retrieve_granule import *
Lines changed: 303 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,306 @@
1+
from typing import Union, List
2+
3+
import numpy as np
4+
5+
from rasters import Raster, RasterGeometry
6+
17
from VIIRS_swath_granules import VIIRSSwathGranule
28

9+
from .constants import SWATH_NAME
10+
311
class VNP21IMGNRTGranule(VIIRSSwathGranule):
4-
pass
12+
def __init__(self, filename: Union[str, VIIRSSwathGranule]):
13+
"""
14+
Initialize the VNP21IMGNRTGranule object.
15+
16+
:param filename: Path to the VIIRS granule file.
17+
"""
18+
if isinstance(filename, VIIRSSwathGranule):
19+
filename = filename.filename
20+
elif isinstance(filename, str):
21+
filename = filename
22+
else:
23+
raise TypeError("filename must be a string or VIIRSSwathGranule object")
24+
25+
super().__init__(filename)
26+
27+
def variables(self, swath: str = SWATH_NAME) -> List[str]:
28+
"""
29+
Return the list of variables in a specific swath.
30+
31+
:param swath: The swath name.
32+
"""
33+
return super().variables(swath=swath)
34+
35+
@property
36+
def latitude(self) -> np.ndarray:
37+
return self.read_latitude(swath=SWATH_NAME)
38+
39+
def get_QC(self, geometry: RasterGeometry = None, resampling: str = "nearest") -> Raster:
40+
"""
41+
Get the Quality Control (QC) data as a Raster object.
42+
43+
:param geometry: The target geometry for resampling.
44+
:param resampling: The resampling method.
45+
"""
46+
with h5py.File(self.filename_absolute, "r") as f:
47+
dataset_name = f"{SWATH_NAME}/QC"
48+
QC = np.array(f[dataset_name])
49+
h, v = self.hv
50+
grid = generate_modland_grid(h, v, QC.shape[0])
51+
52+
logger.info("opening VIIRS file: " + colored_logging.file(self.filename))
53+
54+
logger.info(
55+
f"loading {colored_logging.val(dataset_name)} " +
56+
"at " + colored_logging.val(f"{grid.cell_size:0.2f} m") + " resolution"
57+
)
58+
59+
QC = Raster(QC, geometry=grid)
60+
61+
if geometry is not None:
62+
QC = QC.to_geometry(geometry, resampling=resampling)
63+
64+
return QC
65+
66+
QC = property(get_QC)
67+
68+
def get_cloud_mask(self, target_shape: tuple = None) -> Raster:
69+
"""
70+
Get the cloud mask as a Raster object.
71+
72+
:param target_shape: The target shape for resizing.
73+
"""
74+
h, v = self.hv
75+
76+
if self._cloud_mask is None:
77+
QC = self.QC
78+
cloud_mask = ((QC >> 4) & 3) > 0
79+
self._cloud_mask = cloud_mask
80+
else:
81+
cloud_mask = self._cloud_mask
82+
83+
if target_shape is not None:
84+
cloud_mask = resize(cloud_mask, target_shape, order=0).astype(bool)
85+
shape = target_shape
86+
else:
87+
shape = cloud_mask.shape
88+
89+
geometry = generate_modland_grid(h, v, shape[0])
90+
cloud_mask = Raster(cloud_mask, geometry=geometry)
91+
92+
return cloud_mask
93+
94+
cloud_mask = property(get_cloud_mask)
95+
96+
def dataset(
97+
self,
98+
filename: str,
99+
dataset_name: str,
100+
scale_factor: float = 1,
101+
offset: float = 0,
102+
fill: float = None,
103+
lower_range: float = None,
104+
upper_range: float = None,
105+
cloud_mask: Raster = None,
106+
apply_cloud_mask: bool = True,
107+
geometry: RasterGeometry = None,
108+
resampling: str = None) -> Raster:
109+
"""
110+
Get a dataset as a Raster object.
111+
112+
:param filename: The filename of the dataset.
113+
:param dataset_name: The name of the dataset.
114+
:param scale_factor: The scale factor to apply.
115+
:param offset: The offset to apply.
116+
:param fill: The fill value to replace with NaN.
117+
:param lower_range: The lower range for valid data.
118+
:param upper_range: The upper range for valid data.
119+
:param cloud_mask: The cloud mask to apply.
120+
:param apply_cloud_mask: Whether to apply the cloud mask.
121+
:param geometry: The target geometry for resampling.
122+
:param resampling: The resampling method.
123+
"""
124+
filename = abspath(expanduser(filename))
125+
126+
with h5py.File(filename, "r") as f:
127+
DN = np.array(f[dataset_name])
128+
h, v = self.hv
129+
grid = generate_modland_grid(h, v, DN.shape[0])
130+
131+
logger.info("opening VIIRS file: " + colored_logging.file(self.filename))
132+
133+
logger.info(
134+
f"loading {colored_logging.val(dataset_name)} " +
135+
"at " + colored_logging.val(f"{grid.cell_size:0.2f} m") + " resolution"
136+
)
137+
138+
DN = Raster(DN, geometry=grid)
139+
140+
data = DN
141+
142+
if fill is not None:
143+
data = np.where(data == fill, np.nan, data)
144+
145+
if lower_range is not None:
146+
data = np.where(data < lower_range, np.nan, data)
147+
148+
if upper_range is not None:
149+
data = np.where(data > upper_range, np.nan, data)
150+
151+
data = data * scale_factor + offset
152+
153+
if apply_cloud_mask:
154+
if cloud_mask is None:
155+
cloud_mask = self.get_cloud_mask(target_shape=data.shape)
156+
157+
data = rt.where(cloud_mask, np.nan, data)
158+
159+
if geometry is not None:
160+
data = data.to_geometry(geometry, resampling=resampling)
161+
162+
return data
163+
164+
@property
165+
def geometry(self) -> RasterGrid:
166+
"""
167+
Return the geometry of the granule.
168+
"""
169+
return generate_modland_grid(*self.hv, 1200)
170+
171+
def get_Emis_14(self, geometry: RasterGeometry = None) -> Raster:
172+
"""
173+
Get the Emissivity Band 14 data as a Raster object.
174+
175+
:param geometry: The target geometry for resampling.
176+
"""
177+
image = self.dataset(
178+
self.filename_absolute,
179+
f"HDFEOS/GRIDS/VIIRS_Grid_Daily_1km_LST21/Data Fields/Emis_14",
180+
scale_factor=0.002,
181+
offset=0.49,
182+
cloud_mask=None,
183+
apply_cloud_mask=False
184+
)
185+
186+
if np.all(np.isnan(image)):
187+
raise ValueError("blank emissivity band 14 image")
188+
189+
if geometry is not None:
190+
image = image.to_geometry(geometry)
191+
192+
return image
193+
194+
Emis_14 = property(get_Emis_14)
195+
196+
def get_Emis_15(self, geometry: RasterGeometry = None) -> Raster:
197+
"""
198+
Get the Emissivity Band 15 data as a Raster object.
199+
200+
:param geometry: The target geometry for resampling.
201+
"""
202+
image = self.dataset(
203+
self.filename_absolute,
204+
f"HDFEOS/GRIDS/VIIRS_Grid_Daily_1km_LST21/Data Fields/Emis_15",
205+
scale_factor=0.002,
206+
offset=0.49,
207+
cloud_mask=None,
208+
apply_cloud_mask=False
209+
)
210+
211+
if np.all(np.isnan(image)):
212+
raise ValueError("blank emissivity band 15 image")
213+
214+
if geometry is not None:
215+
image = image.to_geometry(geometry)
216+
217+
return image
218+
219+
Emis_15 = property(get_Emis_15)
220+
221+
def get_Emis_16(self, geometry: RasterGeometry = None) -> Raster:
222+
"""
223+
Get the Emissivity Band 16 data as a Raster object.
224+
225+
:param geometry: The target geometry for resampling.
226+
"""
227+
image = self.dataset(
228+
self.filename_absolute,
229+
f"HDFEOS/GRIDS/VIIRS_Grid_Daily_1km_LST21/Data Fields/Emis_16",
230+
scale_factor=0.002,
231+
offset=0.49,
232+
cloud_mask=None,
233+
apply_cloud_mask=False
234+
)
235+
236+
if np.all(np.isnan(image)):
237+
raise ValueError("blank emissivity band 16 image")
238+
239+
if geometry is not None:
240+
image = image.to_geometry(geometry)
241+
242+
return image
243+
244+
Emis_16 = property(get_Emis_16)
245+
246+
def get_LST_1KM(self, geometry: RasterGeometry = None) -> Raster:
247+
"""
248+
Get the Land Surface Temperature (LST) 1KM data as a Raster object.
249+
250+
:param geometry: The target geometry for resampling.
251+
"""
252+
image = self.dataset(
253+
self.filename_absolute,
254+
f"HDFEOS/GRIDS/VIIRS_Grid_Daily_1km_LST21/Data Fields/LST_1KM",
255+
scale_factor=0.02,
256+
offset=0.0,
257+
fill=0,
258+
lower_range=7500,
259+
upper_range=65535,
260+
cloud_mask=None,
261+
apply_cloud_mask=True
262+
)
263+
264+
if np.all(np.isnan(image)):
265+
raise ValueError("blank LST 1km image")
266+
267+
if geometry is not None:
268+
image = image.to_geometry(geometry)
269+
270+
return image
271+
272+
LST_1KM = property(get_LST_1KM)
273+
274+
ST_K = LST_1KM
275+
276+
@property
277+
def ST_C(self):
278+
"""
279+
Return the Land Surface Temperature in Celsius.
280+
"""
281+
return self.ST_K - 273.15
282+
283+
def get_View_Angle(self, geometry: RasterGeometry = None) -> Raster:
284+
"""
285+
Get the View Angle data as a Raster object.
286+
287+
:param geometry: The target geometry for resampling.
288+
"""
289+
image = self.dataset(
290+
self.filename_absolute,
291+
f"HDFEOS/GRIDS/VIIRS_Grid_Daily_1km_LST21/Data Fields/View_Angle",
292+
scale_factor=1.0,
293+
offset=-65.0,
294+
cloud_mask=None,
295+
apply_cloud_mask=False
296+
)
297+
298+
if np.all(np.isnan(image)):
299+
raise ValueError("blank view angle image")
300+
301+
if geometry is not None:
302+
image = image.to_geometry(geometry)
303+
304+
return image
305+
306+
View_Angle = property(get_View_Angle)

VNP21IMGNRT_002/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22

33
VNP21IMGNRT_002_DOI = "10.5067/VIIRS/VNP21IMG_NRT.002"
44
VNP21IMGNRT_002_CONCEPT_ID = "C2788950354-LANCEMODIS"
5+
6+
DOWNLOAD_DIRECTORY = "~/data/VNP21IMGNRT"
7+
SWATH_NAME = "VIIRS_I5_LST"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import os
2+
from os.path import join, abspath, expanduser, splitext
3+
import posixpath
4+
import earthaccess
5+
6+
import VIIRS_swath_granules
7+
8+
from .constants import *
9+
from .VNP21IMGNRT_granule import VNP21IMGNRTGranule
10+
11+
def retrieve_granule(
12+
remote_granule: earthaccess.results.DataGranule,
13+
download_directory: str = DOWNLOAD_DIRECTORY,
14+
parent_directory: str = None) -> VNP21IMGNRTGranule:
15+
"""
16+
Retrieve and download a VIIRS granule from a remote source.
17+
18+
Args:
19+
remote_granule (earthaccess.results.DataGranule): The remote granule to be downloaded.
20+
download_directory (str): The directory where the granule will be downloaded.
21+
22+
Returns:
23+
VNP21IMGNRTGranule: The downloaded and processed VIIRS tiled granule.
24+
"""
25+
return VNP21IMGNRTGranule(VIIRS_swath_granules.retrieve_granule(
26+
remote_granule=remote_granule,
27+
download_directory=download_directory,
28+
parent_directory=parent_directory
29+
))

0 commit comments

Comments
 (0)