1818
1919import multiprocessing
2020from concurrent .futures import ThreadPoolExecutor , as_completed
21- from datetime import datetime , timedelta
21+ from datetime import datetime , timedelta , timezone
2222from functools import partial
2323from pathlib import Path
2424
@@ -83,7 +83,9 @@ def _check_param_inputs(**params):
8383 if satellite in aliases :
8484 satellite = key
8585 if satellite not in _satellite :
86- raise ValueError (f"satellite must be one of { list (_satellite .keys ())} or an alias { list (_satellite .values ())} " )
86+ raise ValueError (
87+ f"satellite must be one of { list (_satellite .keys ())} or an alias { list (_satellite .values ())} "
88+ )
8789
8890 ## Determine the Domain (only needed for ABI product)
8991 if product .upper ().startswith ("ABI" ):
@@ -100,7 +102,9 @@ def _check_param_inputs(**params):
100102 domain = key
101103 product = product + domain
102104 if (domain not in _domain ) and (domain not in ["M1" , "M2" ]):
103- raise ValueError (f"domain must be one of { list (_domain .keys ())} or an alias { list (_domain .values ())} " )
105+ raise ValueError (
106+ f"domain must be one of { list (_domain .keys ())} or an alias { list (_domain .values ())} "
107+ )
104108 else :
105109 domain = None
106110
@@ -110,12 +114,16 @@ def _check_param_inputs(**params):
110114 if product .upper () in aliases :
111115 product = key
112116 if product not in _product :
113- raise ValueError (f"product must be one of { list (_product .keys ())} or an alias { list (_product .values ())} " )
117+ raise ValueError (
118+ f"product must be one of { list (_product .keys ())} or an alias { list (_product .values ())} "
119+ )
114120
115121 return satellite , product , domain
116122
117123
118- def _goes_file_df (satellite , product , start , end , bands = None , refresh = True , ignore_missing = False ):
124+ def _goes_file_df (
125+ satellite , product , start , end , bands = None , refresh = True , ignore_missing = False
126+ ):
119127 """Get list of requested GOES files as pandas.DataFrame.
120128
121129 Parameters
@@ -150,11 +158,10 @@ def _goes_file_df(satellite, product, start, end, bands=None, refresh=True, igno
150158 else :
151159 files += fs .ls (path , refresh = refresh )
152160
153-
154161 # Build a table of the files
155162 # --------------------------
156163 df = pd .DataFrame (files , columns = ["file" ])
157- df .drop (index = df .index [~ df ["file" ].str .contains (".nc" )],inplace = True )
164+ df .drop (index = df .index [~ df ["file" ].str .contains (".nc" )], inplace = True )
158165 df [["product_mode" , "satellite" , "start" , "end" , "creation" ]] = (
159166 df ["file" ].str .rsplit ("_" , expand = True , n = 5 ).loc [:, 1 :]
160167 )
@@ -197,6 +204,10 @@ def _goes_file_df(satellite, product, start, end, bands=None, refresh=True, igno
197204
198205def _download (df , save_dir , overwrite , max_threads = 10 , verbose = False ):
199206 """Download the files from a DataFrame listing with multithreading."""
207+ if len (df ) == 0 :
208+ if verbose :
209+ print ("🛸 No files to download....🌌" )
210+ return
200211
201212 def do_download (src ):
202213 dst = Path (save_dir ) / src
@@ -221,7 +232,7 @@ def do_download(src):
221232 this_list = [future .result () for future in as_completed (futures )]
222233
223234 print (
224- f"📦 Finished downloading [{ len (df )} ] files to [{ save_dir / Path (df .file [0 ]).parents [3 ]} ]."
235+ f"📦 Finished downloading [{ len (df )} ] files to [{ save_dir / Path (df .file [0 ]).parents [3 ]} ]."
225236 )
226237
227238
@@ -285,6 +296,7 @@ def _as_xarray(df, **params):
285296 n = len (df .file )
286297 if n == 0 :
287298 print ("🛸 No data....🌌" )
299+ return None
288300 elif n == 1 :
289301 # If we only have one file, we don't need multiprocessing
290302 ds = _as_xarray_MP (df .iloc [0 ].file , save_dir , 1 , 1 , verbose )
@@ -410,7 +422,7 @@ def goes_timerange(
410422 raise ValueError ("🤔 `start` and `end` *or* `recent` is required" )
411423 if check1 :
412424 if not (hasattr (start , "second" ) and hasattr (end , "second" )):
413- raise ValueError ( "`start` and `end` must be a datetime object" )
425+ raise ValueError ("`start` and `end` must be a datetime object" )
414426 elif check2 :
415427 if not hasattr (recent , "seconds" ):
416428 raise ValueError ("`recent` must be a timedelta object" )
@@ -420,10 +432,18 @@ def goes_timerange(
420432 # Create a range of directories to check. The GOES S3 bucket is
421433 # organized by hour of day.
422434 if recent is not None :
423- start = datetime .utcnow () - recent
424- end = datetime .utcnow ()
425-
426- df = _goes_file_df (satellite , product , start , end , bands = bands , refresh = s3_refresh , ignore_missing = ignore_missing )
435+ start = datetime .now (timezone .utc ).replace (tzinfo = None ) - recent
436+ end = datetime .now (timezone .utc ).replace (tzinfo = None )
437+
438+ df = _goes_file_df (
439+ satellite ,
440+ product ,
441+ start ,
442+ end ,
443+ bands = bands ,
444+ refresh = s3_refresh ,
445+ ignore_missing = ignore_missing ,
446+ )
427447
428448 if download :
429449 _download (df , save_dir = save_dir , overwrite = overwrite , verbose = verbose )
@@ -434,6 +454,7 @@ def goes_timerange(
434454 elif return_as == "xarray" :
435455 return _as_xarray (df , ** params )
436456
457+
437458def _preprocess_single_point (ds , target_lat , target_lon , decimal_coordinates = True ):
438459 """
439460 Preprocessing function to select only the single relevant data subset.
@@ -447,9 +468,12 @@ def _preprocess_single_point(ds, target_lat, target_lon, decimal_coordinates=Tru
447468 decimal_coordinates: bool
448469 If latitude/longitude are specified in decimal or radian coordinates.
449470 """
450- x_target , y_target = lat_lon_to_scan_angles (target_lat , target_lon , ds ["goes_imager_projection" ], decimal_coordinates )
471+ x_target , y_target = lat_lon_to_scan_angles (
472+ target_lat , target_lon , ds ["goes_imager_projection" ], decimal_coordinates
473+ )
451474 return ds .sel (x = x_target , y = y_target , method = "nearest" )
452475
476+
453477def goes_single_point_timerange (
454478 latitude ,
455479 longitude ,
@@ -548,7 +572,7 @@ def goes_single_point_timerange(
548572 raise ValueError ("🤔 `start` and `end` *or* `recent` is required" )
549573 if check1 :
550574 if not (hasattr (start , "second" ) and hasattr (end , "second" )):
551- raise ValueError ( "`start` and `end` must be a datetime object" )
575+ raise ValueError ("`start` and `end` must be a datetime object" )
552576 elif check2 :
553577 if not hasattr (recent , "seconds" ):
554578 raise ValueError ("`recent` must be a timedelta object" )
@@ -558,10 +582,18 @@ def goes_single_point_timerange(
558582 # Create a range of directories to check. The GOES S3 bucket is
559583 # organized by hour of day.
560584 if recent is not None :
561- start = datetime .utcnow () - recent
562- end = datetime .utcnow ()
563-
564- df = _goes_file_df (satellite , product , start , end , bands = bands , refresh = s3_refresh , ignore_missing = ignore_missing )
585+ start = datetime .now (timezone .utc ).replace (tzinfo = None ) - recent
586+ end = datetime .now (timezone .utc ).replace (tzinfo = None )
587+
588+ df = _goes_file_df (
589+ satellite ,
590+ product ,
591+ start ,
592+ end ,
593+ bands = bands ,
594+ refresh = s3_refresh ,
595+ ignore_missing = ignore_missing ,
596+ )
565597
566598 if download :
567599 _download (df , save_dir = save_dir , overwrite = overwrite , verbose = verbose )
@@ -570,11 +602,18 @@ def goes_single_point_timerange(
570602 df .attrs ["filePath" ] = save_dir
571603 return df
572604 elif return_as == "xarray" :
573- partial_func = partial (_preprocess_single_point , target_lat = latitude , target_lon = longitude , decimal_coordinates = decimal_coordinates )
574- preprocessed_ds = xr .open_mfdataset ([str (config ['timerange' ]['save_dir' ]) + "/" + f for f in df ['file' ].to_list ()],
575- concat_dim = 't' ,
576- combine = 'nested' ,
577- preprocess = partial_func )
605+ partial_func = partial (
606+ _preprocess_single_point ,
607+ target_lat = latitude ,
608+ target_lon = longitude ,
609+ decimal_coordinates = decimal_coordinates ,
610+ )
611+ preprocessed_ds = xr .open_mfdataset (
612+ [str (save_dir ) + "/" + f for f in df ["file" ].to_list ()],
613+ concat_dim = "t" ,
614+ combine = "nested" ,
615+ preprocess = partial_func ,
616+ )
578617 return preprocessed_ds
579618
580619
@@ -647,10 +686,18 @@ def goes_latest(
647686 # ---------------
648687 # Create a range of directories to check. The GOES S3 bucket is
649688 # organized by hour of day. Look in the current hour and last hour.
650- start = datetime .utcnow () - timedelta (hours = 1 )
651- end = datetime .utcnow ()
652-
653- df = _goes_file_df (satellite , product , start , end , bands = bands , refresh = s3_refresh , ignore_missing = ignore_missing )
689+ start = datetime .now (timezone .utc ).replace (tzinfo = None ) - timedelta (hours = 1 )
690+ end = datetime .now (timezone .utc ).replace (tzinfo = None )
691+
692+ df = _goes_file_df (
693+ satellite ,
694+ product ,
695+ start ,
696+ end ,
697+ bands = bands ,
698+ refresh = s3_refresh ,
699+ ignore_missing = ignore_missing ,
700+ )
654701
655702 # Filter for specific mesoscale domain
656703 if domain is not None and domain .upper () in ["M1" , "M2" ]:
@@ -744,6 +791,7 @@ def goes_nearesttime(
744791 satellite , product , _ = _check_param_inputs (** params )
745792 params ["satellite" ] = satellite
746793 params ["product" ] = product
794+ params ["domain" ] = domain
747795
748796 # Parameter Setup
749797 # ---------------
@@ -752,7 +800,15 @@ def goes_nearesttime(
752800 start = attime - within
753801 end = attime + within
754802
755- df = _goes_file_df (satellite , product , start , end , bands = bands , refresh = s3_refresh , ignore_missing = ignore_missing )
803+ df = _goes_file_df (
804+ satellite ,
805+ product ,
806+ start ,
807+ end ,
808+ bands = bands ,
809+ refresh = s3_refresh ,
810+ ignore_missing = ignore_missing ,
811+ )
756812
757813 # return df, start, end, attime
758814
0 commit comments