@@ -367,6 +367,7 @@ def get_data(self, lat, lon):
367
367
ret_lat , ret_lon = cpc_lat_lon_to_conventional (self .snapped_lat , self .snapped_lon )
368
368
return (float (ret_lat ), float (ret_lon )), pd .Series (ret_dict )
369
369
370
+
370
371
class Era5LandWind (SimpleGriddedDataset ):
371
372
"""
372
373
Abstract class from which ERA5 Land Wind datasets inherit. Sets formatting that these datasets use
@@ -375,6 +376,84 @@ class Era5LandWind(SimpleGriddedDataset):
375
376
def zero_padding (self ):
376
377
return 8
377
378
379
+ class Vhi (IpfsDataset ):
380
+ """
381
+ Instantiable gridded vegetative health dataset. Due to some metadata differences with other sets, doesn't
382
+ inherit from GriddedDataset
383
+ """
384
+ dataset = "vhi"
385
+ NUM_NAS_AT_START_OF_DATA = 34
386
+
387
+ def get_data (self , lat , lon ):
388
+ super ().get_data ()
389
+ first_metadata = self .get_metadata (self .head )
390
+ snapped_lat , snapped_lon = self .snap_to_grid (float (lat ), float (lon ), first_metadata )
391
+ self .zip_file_name = f"{ snapped_lat :.3f} .zip"
392
+ self .gzip_name = f"{ snapped_lat :.3f} _{ snapped_lon :.3f} .gz"
393
+ hashes = self .traverse_ll (self .head )
394
+
395
+ ret_dict = {}
396
+ for h in hashes :
397
+ date_range = self .get_date_range_from_metadata (h )
398
+ weather_dict = self .get_weather_dict (date_range , h )
399
+ ret_dict = {** ret_dict , ** weather_dict }
400
+
401
+ return (snapped_lat , snapped_lon ), pd .Series (ret_dict ).iloc [self .NUM_NAS_AT_START_OF_DATA :]
402
+
403
+ def get_weather_dict (self , date_range , ipfs_hash ):
404
+ """
405
+ Uses a weekly time span, so logic is a little different from other datasets
406
+ """
407
+ with zipfile .ZipFile (self .get_file_object (f"{ ipfs_hash } /{ self .zip_file_name } " )) as zi :
408
+ with gzip .open (zi .open (self .gzip_name )) as gz :
409
+ cell_text = gz .read ().decode ('utf-8' )
410
+ vhi_dict = {}
411
+ year = date_range [0 ].year
412
+ for year_data in cell_text .split ('\n ' ):
413
+ if year == date_range [0 ].year :
414
+ date_itr = date_range [0 ]
415
+ else :
416
+ date_itr = datetime .date (year , 1 , 1 )
417
+ for week_data in year_data .split (',' ):
418
+ vhi_dict [date_itr ] = "-999" if week_data == "-999.00" else week_data
419
+ date_itr += datetime .timedelta (days = 7 )
420
+ year += 1
421
+ return vhi_dict
422
+
423
+ @classmethod
424
+ def snap_to_grid (cls , lat , lon , metadata ):
425
+ """
426
+ Find the nearest (lat,lon) on IPFS for a VHI metadata file.
427
+ args:
428
+ :lat: = -90 < lat < 90, float
429
+ :lon: = -180 < lon < 180, float
430
+ :metadata: a dWeather metadata file
431
+ return: lat, lon
432
+ Necessary to rewrite because the files' coordinates correspond to the center of each gridcell, not the northwest corner
433
+ """
434
+
435
+ # metadata measures from center, not left edge
436
+ resolution = metadata ['resolution' ]
437
+ min_lat = metadata ['latitude range' ][0 ] + resolution / 2 # start [lat, lon]
438
+ min_lon = metadata ['longitude range' ][0 ] + resolution / 2 # end [lat, lon]
439
+
440
+ # check that the lat lon is in the bounding box
441
+ snap_lat = round (round ((lat - min_lat ) / resolution ) * resolution + min_lat , 3 )
442
+ snap_lon = round (round ((lon - min_lon ) / resolution ) * resolution + min_lon , 3 )
443
+ return snap_lat , snap_lon
444
+
445
+ def get_date_range_from_metadata (self , h ):
446
+ """
447
+ args:
448
+ :h: hash for ipfs directory containing metadata
449
+ return: list of [start_time, end_time]
450
+ """
451
+ metadata = self .get_metadata (h )
452
+ # first year is filled with -999s, so have to start from 1981-01-01
453
+ start_date = "1981-01-01" if metadata ["date range" ][0 ] == "1981-08-28" else metadata ["date range" ][0 ]
454
+ end_date = metadata ["date range" ][1 ]
455
+ return [datetime .date .fromisoformat (dt ) for dt in [start_date , end_date ]]
456
+
378
457
class StationDataset (IpfsDataset ):
379
458
"""
380
459
Instantiable class used for pulling in "ghcnd" or "ghcnd-imputed-daily" station data
0 commit comments