1
+ from typing import Union , List
2
+
3
+ import numpy as np
4
+
5
+ from rasters import Raster , RasterGeometry
6
+
1
7
from VIIRS_swath_granules import VIIRSSwathGranule
2
8
9
+ from .constants import SWATH_NAME
10
+
3
11
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 )
0 commit comments