GOES-R fixed grid projection y-coordinate center of image
standard_name :
projection_y_coordinate
units :
rad
axis :
Y
array(0.08624, dtype=float32)
x_image
()
float32
-0.03136
long_name :
GOES-R fixed grid projection x-coordinate center of image
standard_name :
projection_x_coordinate
units :
rad
axis :
X
array(-0.03136, dtype=float32)
retrieval_local_zenith_angle
()
float32
80.0
long_name :
threshold angle between the line of sight to the satellite and the local zenith at the observation target for good or degraded quality total precipitable water, vertical moisture profile, vertical temperature profile and derived stability indices data production
standard_name :
platform_zenith_angle
units :
degree
bounds :
retrieval_local_zenith_angle_bounds
array(80., dtype=float32)
quantitative_local_zenith_angle
()
float32
70.0
long_name :
threshold angle between the line of sight to the satellite and the local zenith at the observation target for good quality total precipitable water, vertical moisture profile, vertical temperature profile and derived stability indices data production
standard_name :
platform_zenith_angle
units :
degree
bounds :
quantitative_local_zenith_angle_bounds
array(70., dtype=float32)
solar_zenith_angle
()
float32
180.0
long_name :
threshold angle between the line of sight to the sun and the local zenith at the observation target for good quality total precipitable water, vertical moisture profile, vertical temperature profile and derived stability indices data production
standard_name :
solar_zenith_angle
units :
degree
bounds :
solar_zenith_angle_bounds
array(180., dtype=float32)
latitude
()
float32
70.0
long_name :
threshold latitude for assigning overall quality flag of good to product data
standard_name :
latitude
units :
degrees_north
bounds :
latitude_bounds
array(70., dtype=float32)
sounding_emissive_wavelengths
(sounding_emissive_bands)
float32
dask.array<chunksize=(7,), meta=np.ndarray>
long_name :
ABI band central emissive wavelengths used to generate this Sounding product
standard_name :
sensor_band_central_radiation_wavelength
units :
um
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Array
\n",
+ "
Chunk
\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bytes
\n",
+ "
28 B
\n",
+ "
28 B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
Shape
\n",
+ "
(7,)
\n",
+ "
(7,)
\n",
+ "
\n",
+ "
\n",
+ "
Dask graph
\n",
+ "
1 chunks in 55 graph layers
\n",
+ "
\n",
+ "
\n",
+ "
Data type
\n",
+ "
float32 numpy.ndarray
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
sounding_emissive_band_ids
(sounding_emissive_bands)
int8
dask.array<chunksize=(7,), meta=np.ndarray>
long_name :
ABI band identifiers used to generate this Sounding product
latitude: point (good quality pixel produced) retrieval_local_zenith_angle: point (good or degraded quality pixel produced) quantitative_local_zenith_angle: point (good quality pixel produced) solar_zenith_angle: point (good quality pixel produced) t: point area: point
ancillary_variables :
DQF_Overall DQF_Retrieval DQF_SkinTemp
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Array
\n",
+ "
Chunk
\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bytes
\n",
+ "
48 B
\n",
+ "
4 B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
Shape
\n",
+ "
(12,)
\n",
+ "
(1,)
\n",
+ "
\n",
+ "
\n",
+ "
Dask graph
\n",
+ "
12 chunks in 49 graph layers
\n",
+ "
\n",
+ "
\n",
+ "
Data type
\n",
+ "
float32 numpy.ndarray
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
DQF_Overall
(t)
float32
dask.array<chunksize=(1,), meta=np.ndarray>
long_name :
ABI L2+ Total Precipitable Water data overall quality flags
standard_name :
status_flag
valid_range :
[ 0 10]
units :
1
grid_mapping :
goes_imager_projection
cell_methods :
latitude: point retrieval_local_zenith_angle: point quantitative_local_zenith_angle: point solar_zenith_angle: point t: point area: point
number of total precipitable water pixels whose value is outside valid measurement range
units :
count
grid_mapping :
goes_imager_projection
cell_methods :
latitude: sum retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: sum (interval: 0.000280 rad comment: good and degraded due to quantitative LZA threshold exceeded quality pixels whose values are outside valid measurement range only)
latitude: sum retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: minimum (interval: 0.000280 rad comment: good and degraded due to quantitative LZA threshold exceeded quality pixels only)
latitude: sum retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: maximum (interval: 0.000280 rad comment: good and degraded due to quantitative LZA threshold exceeded quality pixels only)
latitude: sum retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: mean (interval: 0.000280 rad comment: good and degraded due to quantitative LZA threshold exceeded quality pixels only)
latitude: sum retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: standard_deviation (interval: 0.000280 rad comment: good and degraded due to quantitative LZA threshold exceeded quality pixels only)
local zenith angle degree range where good or degraded quality total precipitable water, vertical moisture profile, vertical temperature profile and derived stability indices data is produced
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Array
\n",
+ "
Chunk
\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bytes
\n",
+ "
96 B
\n",
+ "
8 B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
Shape
\n",
+ "
(12, 2)
\n",
+ "
(1, 2)
\n",
+ "
\n",
+ "
\n",
+ "
Dask graph
\n",
+ "
12 chunks in 37 graph layers
\n",
+ "
\n",
+ "
\n",
+ "
Data type
\n",
+ "
float32 numpy.ndarray
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
quantitative_local_zenith_angle_bounds
(t, number_of_LZA_bounds)
float32
dask.array<chunksize=(1, 2), meta=np.ndarray>
long_name :
local zenith angle degree range where good quality total precipitable water, vertical moisture profile, vertical temperature profile and derived stability indices data is produced
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Array
\n",
+ "
Chunk
\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bytes
\n",
+ "
96 B
\n",
+ "
8 B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
Shape
\n",
+ "
(12, 2)
\n",
+ "
(1, 2)
\n",
+ "
\n",
+ "
\n",
+ "
Dask graph
\n",
+ "
12 chunks in 37 graph layers
\n",
+ "
\n",
+ "
\n",
+ "
Data type
\n",
+ "
float32 numpy.ndarray
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
solar_zenith_angle_bounds
(t, number_of_SZA_bounds)
float32
dask.array<chunksize=(1, 2), meta=np.ndarray>
long_name :
solar zenith angle degree range where good quality total precipitable water, vertical moisture profile, vertical temperature profile and derived stability indices data is produced
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Array
\n",
+ "
Chunk
\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bytes
\n",
+ "
96 B
\n",
+ "
8 B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
Shape
\n",
+ "
(12, 2)
\n",
+ "
(1, 2)
\n",
+ "
\n",
+ "
\n",
+ "
Dask graph
\n",
+ "
12 chunks in 37 graph layers
\n",
+ "
\n",
+ "
\n",
+ "
Data type
\n",
+ "
float32 numpy.ndarray
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
latitude_bounds
(t, number_of_lat_bounds)
float32
dask.array<chunksize=(1, 2), meta=np.ndarray>
long_name :
latitude range for assigning overall quality flag of good to product data
mean difference of the observed and modeled brightness temperature (Joint Center for Satellite Data Assimilation Community Radiative Transfer Model using temporally interpolated NWP data as input) for the emissive band central wavelengths used in the generation of this Sounding product
units :
K
grid_mapping :
goes_imager_projection
cell_methods :
retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: mean (interval: 0.000280 rad comment: geolocated/not missing pixels only)
\n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
Array
\n",
+ "
Chunk
\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
Bytes
\n",
+ "
336 B
\n",
+ "
28 B
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
Shape
\n",
+ "
(12, 7)
\n",
+ "
(1, 7)
\n",
+ "
\n",
+ "
\n",
+ "
Dask graph
\n",
+ "
12 chunks in 37 graph layers
\n",
+ "
\n",
+ "
\n",
+ "
Data type
\n",
+ "
float32 numpy.ndarray
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
\n",
+ "
std_dev_obs_modeled_diff_sounding_emissive_bands
(t, sounding_emissive_bands)
float32
dask.array<chunksize=(1, 7), meta=np.ndarray>
long_name :
standard deviation of the difference of the observed and modeled brightness temperature values (Joint Center for Satellite Data Assimilation Community Radiative Transfer Model using temporally interpolated NWP data as input) for the emissive band central wavelengths used in the generation of this Sounding product
units :
K
grid_mapping :
goes_imager_projection
cell_methods :
retrieval_local_zenith_angle: sum solar_zenith_angle: sum t: sum area: standard_deviation (interval: 0.000280 rad comment: geolocated/not missing pixels only)
DOC/NOAA/NESDIS > U.S. Department of Commerce, National Oceanic and Atmospheric Administration, National Environmental Satellite, Data, and Information Services
The Total Precipitable Water product consists of the water depth if it were condensed in the atmospheric column between approximately 300 hPa and the surface. The product is generated using a regression retrieval followed by an iterative physical retrieval that makes use of a radiative transfer model. Product data is generated both day and night.
keywords :
ATMOSPHERE > ATMOSPHERIC WATER VAPOR > PRECIPITABLE WATER
keywords_vocabulary :
NASA Global Change Master Directory (GCMD) Earth Science Keywords, Version 7.0.0.0.0
license :
Unclassified data. Access is restricted to approved users only.
processing_level :
National Aeronautics and Space Administration (NASA) L2
date_created :
2024-04-08T10:05:43.5Z
cdm_data_type :
Image
time_coverage_start :
2024-04-08T10:01:17.2Z
time_coverage_end :
2024-04-08T10:03:54.4Z
timeline_id :
ABI Mode 6
production_data_source :
Realtime
id :
58e3d05e-c48b-44b0-8171-c6641c4923c3
"
+ ],
+ "text/plain": [
+ "\n",
+ "Dimensions: (t: 12,\n",
+ " number_of_time_bounds: 2,\n",
+ " number_of_image_bounds: 2,\n",
+ " number_of_LZA_bounds: 2,\n",
+ " number_of_SZA_bounds: 2,\n",
+ " number_of_lat_bounds: 2,\n",
+ " sounding_emissive_bands: 7)\n",
+ "Coordinates:\n",
+ " * t (t) datetime64[ns] 2024...\n",
+ " y float64 0.1068\n",
+ " x float64 -0.00462\n",
+ " y_image float32 0.08624\n",
+ " x_image float32 -0.03136\n",
+ " retrieval_local_zenith_angle float32 80.0\n",
+ " quantitative_local_zenith_angle float32 70.0\n",
+ " solar_zenith_angle float32 180.0\n",
+ " latitude float32 70.0\n",
+ " sounding_emissive_wavelengths (sounding_emissive_bands) float32 dask.array\n",
+ " sounding_emissive_band_ids (sounding_emissive_bands) int8 dask.array\n",
+ "Dimensions without coordinates: number_of_time_bounds, number_of_image_bounds,\n",
+ " number_of_LZA_bounds, number_of_SZA_bounds,\n",
+ " number_of_lat_bounds, sounding_emissive_bands\n",
+ "Data variables: (12/29)\n",
+ " TPW (t) float32 dask.array\n",
+ " DQF_Overall (t) float32 dask.array\n",
+ " DQF_Retrieval (t) float32 dask.array\n",
+ " DQF_SkinTemp (t) float32 dask.array\n",
+ " time_bounds (t, number_of_time_bounds) datetime64[ns] dask.array\n",
+ " goes_imager_projection (t) int32 -2147483647 ....\n",
+ " ... ...\n",
+ " latitude_bounds (t, number_of_lat_bounds) float32 dask.array\n",
+ " percent_uncorrectable_L0_errors (t) float32 0.0 ... 0.0\n",
+ " percent_uncorrectable_GRB_errors (t) float32 0.0 ... 0.0\n",
+ " total_attempted_retrievals (t) float64 7.855e+04 ....\n",
+ " mean_obs_modeled_diff_sounding_emissive_bands (t, sounding_emissive_bands) float32 dask.array\n",
+ " std_dev_obs_modeled_diff_sounding_emissive_bands (t, sounding_emissive_bands) float32 dask.array\n",
+ "Attributes: (12/29)\n",
+ " naming_authority: gov.nesdis.noaa\n",
+ " Conventions: CF-1.7\n",
+ " Metadata_Conventions: Unidata Dataset Discovery v1.0\n",
+ " standard_name_vocabulary: CF Standard Name Table (v35, 20 July 2016)\n",
+ " institution: DOC/NOAA/NESDIS > U.S. Department of Commerce,...\n",
+ " project: GOES\n",
+ " ... ...\n",
+ " cdm_data_type: Image\n",
+ " time_coverage_start: 2024-04-08T10:01:17.2Z\n",
+ " time_coverage_end: 2024-04-08T10:03:54.4Z\n",
+ " timeline_id: ABI Mode 6\n",
+ " production_data_source: Realtime\n",
+ " id: 58e3d05e-c48b-44b0-8171-c6641c4923c3"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "g2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([9.366711, 9.349924, 9.433856, 9.395705, 9.404861, 9.508631,\n",
+ " 9.476584, 9.494897, 9.565093, 9.546782, 9.517787, 9.667336],\n",
+ " dtype=float32)"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "g2.TPW.values"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/goes2go/NEW.py b/goes2go/NEW.py
index a8c2554..ea00adc 100644
--- a/goes2go/NEW.py
+++ b/goes2go/NEW.py
@@ -18,7 +18,7 @@
import toml
from goes2go import config
-from goes2go.data import _goes_file_df, goes_latest, goes_nearesttime, goes_timerange
+from goes2go.data import _goes_file_df, goes_latest, goes_nearesttime, goes_timerange, goes_single_point_timerange
log = logging.getLogger(__name__)
@@ -185,7 +185,7 @@ def latest(self, **kwargs):
def nearesttime(
self,
attime,
- within=pd.to_timedelta(config["nearesttime"].get("within", "1H")),
+ within=pd.to_timedelta(config["nearesttime"].get("within", "1h")),
**kwargs,
):
"""Get the GOES data nearest a specified time.
@@ -230,6 +230,36 @@ def timerange(self, start=None, end=None, recent=None, **kwargs):
**kwargs,
)
+ def single_point_timerange(self, latitude, longitude, start=None, end=None, recent=None, decimal_coordinates=True, **kwargs):
+ """Get GOES data for a time range at the scan point nearest to a defined single latitude/longitude point.
+
+ Parameters
+ ----------
+ latitude, longitude : float
+ Location where you wish to extract the point values from
+ start, end : datetime
+ Required if recent is None.
+ recent : timedelta or pandas-parsable timedelta str
+ Required if start and end are None. If timedelta(hours=1), will
+ get the most recent files for the past hour.
+ decimal_coordinates: bool
+ If latitude/longitude are specified in decimal or radian coordinates.
+ """
+
+ return goes_single_point_timerange(
+ latitude,
+ longitude,
+ start,
+ end,
+ recent,
+ decimal_coordinates,
+ satellite=self.satellite,
+ product=self.product,
+ domain=self.domain,
+ bands=self.bands,
+ **kwargs,
+ )
+
def df(self, start, end, refresh=True):
"""Get list of requested GOES files as pandas.DataFrame.
diff --git a/goes2go/data.py b/goes2go/data.py
index 407685c..5cd228f 100644
--- a/goes2go/data.py
+++ b/goes2go/data.py
@@ -18,13 +18,17 @@
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, as_completed, wait
from datetime import datetime, timedelta
+from functools import partial
from pathlib import Path
+import sys
import numpy as np
import pandas as pd
import s3fs
import xarray as xr
+from goes2go.tools import lat_lon_to_scan_angles
+
# NOTE: These config dict values are retrieved from __init__ and read
# from the file ${HOME}/.config/goes2go/config.toml
from . import config
@@ -133,7 +137,7 @@ def _goes_file_df(satellite, product, start, end, bands=None, refresh=True):
start = pd.to_datetime(start)
end = pd.to_datetime(end)
- DATES = pd.date_range(f"{start:%Y-%m-%d %H:00}", f"{end:%Y-%m-%d %H:00}", freq="1H")
+ DATES = pd.date_range(f"{start:%Y-%m-%d %H:00}", f"{end:%Y-%m-%d %H:00}", freq="1h")
# List all files for each date
# ----------------------------
@@ -423,6 +427,148 @@ def goes_timerange(
elif return_as == "xarray":
return _as_xarray(df, **params)
+def _preprocess_single_point(ds, target_lat, target_lon, decimal_coordinates=True):
+ """
+ Preprocessing function to select only the single relevant data subset
+
+ Parameters
+ ----------
+ ds: xarray Dataset
+ The dataset to look through and choose the particular location
+ target_lat, target_lon : float
+ Location where you wish to extract the point values from
+ decimal_coordinates: bool
+ If latitude/longitude are specified in decimal or radian coordinates.
+ """
+ x_target, y_target = lat_lon_to_scan_angles(target_lat, target_lon, ds["goes_imager_projection"], decimal_coordinates)
+ return ds.sel(x=x_target, y=y_target, method="nearest")
+
+def goes_single_point_timerange(
+ latitude,
+ longitude,
+ start=None,
+ end=None,
+ recent=None,
+ decimal_coordinates=True,
+ *,
+ satellite=config["timerange"].get("satellite"),
+ product=config["timerange"].get("product"),
+ domain=config["timerange"].get("domain"),
+ return_as=config["timerange"].get("return_as"),
+ download=config["timerange"].get("download"),
+ overwrite=config["timerange"].get("overwrite"),
+ save_dir=config["timerange"].get("save_dir"),
+ max_cpus=config["timerange"].get("max_cpus"),
+ bands=None,
+ s3_refresh=config["timerange"].get("s3_refresh"),
+ verbose=config["timerange"].get("verbose", True),
+):
+ """
+ Get GOES data for a time range at the scan point nearest to a defined single latitude/longitude point.
+
+ Parameters
+ ----------
+ latitude, longitude : float
+ Location where you wish to extract the point values from
+ start, end : datetime
+ Required if recent is None.
+ recent : timedelta or pandas-parsable timedelta str
+ Required if start and end are None. If timedelta(hours=1), will
+ get the most recent files for the past hour.
+ decimal_coordinates: bool
+ If latitude/longitude are specified in decimal or radian coordinates.
+ satellite : {'goes16', 'goes17', 'goes18'}
+ Specify which GOES satellite.
+ The following alias may also be used:
+
+ - ``'goes16'``: 16, 'G16', or 'EAST'
+ - ``'goes17'``: 17, 'G17', or 'WEST'
+ - ``'goes18'``: 18, 'G18', or 'WEST'
+
+ product : {'ABI', 'GLM', other GOES product}
+ Specify the product name.
+
+ - 'ABI' is an alias for ABI-L2-MCMIP Multichannel Cloud and Moisture Imagery
+ - 'GLM' is an alias for GLM-L2-LCFA Geostationary Lightning Mapper
+
+ Others may include ``'ABI-L1b-Rad'``, ``'ABI-L2-DMW'``, etc.
+ For more available products, look at this `README
+ `_
+ domain : {'C', 'F', 'M'}
+ ABI scan region indicator. Only required for ABI products if the
+ given product does not end with C, F, or M.
+
+ - C: Contiguous United States (alias 'CONUS')
+ - F: Full Disk (alias 'FULL')
+ - M: Mesoscale (alias 'MESOSCALE')
+
+ return_as : {'xarray', 'filelist'}
+ Return the data as an xarray.Dataset or as a list of files
+ download : bool
+ - True: Download the data to disk to the location set by :guilabel:`save_dir`
+ - False: Just load the data into memory.
+ save_dir : pathlib.Path or str
+ Path to save the data.
+ overwrite : bool
+ - True: Download the file even if it exists.
+ - False Do not download the file if it already exists
+ max_cpus : int
+ bands : None, int, or list
+ ONLY FOR L1b-Rad products; specify the bands you want
+ s3_refresh : bool
+ Refresh the s3fs.S3FileSystem object when files are listed.
+
+ """
+ # If `start`, or `end` is a string, parse with Pandas
+ if isinstance(start, str):
+ start = pd.to_datetime(start)
+ if isinstance(end, str):
+ end = pd.to_datetime(end)
+ # If `recent` is a string (like recent='1H'), parse with Pandas
+ if isinstance(recent, str):
+ recent = pd.to_timedelta(recent)
+
+ params = locals()
+ satellite, product, domain = _check_param_inputs(**params)
+ params["satellite"] = satellite
+ params["product"] = product
+ params["domain"] = domain
+
+ check1 = start is not None and end is not None
+ check2 = recent is not None
+ assert check1 or check2, "🤔 `start` and `end` *or* `recent` is required"
+
+ if check1:
+ assert hasattr(start, "second") and hasattr(
+ end, "second"
+ ), "`start` and `end` must be a datetime object"
+ elif check2:
+ assert hasattr(recent, "seconds"), "`recent` must be a timedelta object"
+
+ # Parameter Setup
+ # ---------------
+ # Create a range of directories to check. The GOES S3 bucket is
+ # organized by hour of day.
+ if recent is not None:
+ start = datetime.utcnow() - recent
+ end = datetime.utcnow()
+
+ df = _goes_file_df(satellite, product, start, end, bands=bands, refresh=s3_refresh)
+
+ if download:
+ _download(df, save_dir=save_dir, overwrite=overwrite, verbose=verbose)
+
+ if return_as == "filelist":
+ df.attrs["filePath"] = save_dir
+ return df
+ elif return_as == "xarray":
+ partial_func = partial(_preprocess_single_point, target_lat=latitude, target_lon=longitude, decimal_coordinates=decimal_coordinates)
+ preprocessed_ds = xr.open_mfdataset([str(config['timerange']['save_dir']) + "/" + f for f in df['file'].to_list()],
+ concat_dim='t',
+ combine='nested',
+ preprocess=partial_func)
+ return preprocessed_ds
+
def goes_latest(
*,
@@ -516,7 +662,7 @@ def goes_latest(
def goes_nearesttime(
attime,
- within=pd.to_timedelta(config["nearesttime"].get("within", "1H")),
+ within=pd.to_timedelta(config["nearesttime"].get("within", "1h")),
*,
satellite=config["nearesttime"].get("satellite"),
product=config["nearesttime"].get("product"),
diff --git a/goes2go/tools.py b/goes2go/tools.py
index d2253a8..3c7da07 100644
--- a/goes2go/tools.py
+++ b/goes2go/tools.py
@@ -190,3 +190,111 @@ def glm_crs(G, reference_variable="flash_lat"):
dat = G.metpy.parse_cf("flash_lat")
crs = dat.metpy.cartopy_crs
return crs
+
+
+def scan_angles_to_lat_lon(x, y, goes_imager_projection, decimal_coordinates=True):
+ """
+ Convert ABI scan angle coordinates to geodetic latitude and longitude.
+
+ Scan angles are projected onto the GRS80 elliopsoid.
+ https://www.goes-r.gov/users/docs/PUG-L1b-vol3.pdf (section 5.1.2.8.1)
+
+ Parameters
+ ----------
+ x : float
+ A scan angle coordinate in the x direction.
+ y : float
+ A scan angle coordinate in the y direction.
+ goes_imager_projection : xarray.Dataset
+ An xarray.Dataset with the projection information in it's attributes.
+ decimal_coordinates : bool
+ A boolean designating if latitude and longitude should be returned in decimal degrees (returns decimal radians if false).
+
+ Returns
+ -------
+ Two objects are returned
+ 1. latitude on the GSR80 ellipsoid
+ 2. longitude on the GSR80 ellipsoid
+ """
+ r_pol = goes_imager_projection.semi_minor_axis
+ r_eq = goes_imager_projection.semi_major_axis
+ H = goes_imager_projection.perspective_point_height + r_eq
+ lambda_0 = np.radians(
+ goes_imager_projection.longitude_of_projection_origin
+ ) # Always in degrees from the GOES data
+
+ a = np.sin(x) ** 2 + np.cos(x) ** 2 * (
+ np.cos(y) ** 2 + r_eq**2 / r_pol**2 * np.sin(y) ** 2
+ )
+ b = -2 * H * np.cos(x) * np.cos(y)
+ c = H**2 - r_eq**2
+
+ r_s = (-b - np.sqrt(b**2 - 4 * a * c)) / (
+ 2 * a
+ ) # Distance from the satellite to point P
+
+ s_x = r_s * np.cos(x) * np.cos(y)
+ s_y = -r_s * np.sin(x)
+ s_z = r_s * np.cos(x) * np.sin(y)
+
+ latitude = np.arctan(
+ (r_eq**2 / r_pol**2) * (s_z / np.sqrt((H - s_x) ** 2 + s_y**2))
+ )
+ longitude = lambda_0 - np.arctan(s_y / (H - s_x))
+
+ if decimal_coordinates:
+ latitude = np.degrees(latitude)
+ longitude = np.degrees(longitude)
+
+ return latitude, longitude
+
+
+def lat_lon_to_scan_angles(latitude, longitude, goes_imager_projection, decimal_coordinates=True):
+ """
+ Convert geodetic latitude and longitude to ABI scan angle coordinates.
+
+ Latitude and Longitude inputs are assumed to be located on the GRS80 elliopsoid.
+ https://www.goes-r.gov/users/docs/PUG-L1b-vol3.pdf (section 5.1.2.8.1)
+
+ Parameters
+ ----------
+ latitude : float
+ Latitude on the GSR80 ellipsoid.
+ longitude : float
+ Longitude on the GSR80 ellipsoid.
+ goes_imager_projection : xarray.Dataset
+ An xarray.Dataset with the projection information in it's attributes.
+ decimal_coordinates : bool
+ A boolean designating if latitude and longitude inputs are in decimal degrees (returns decimal radians if false).
+
+ Returns
+ -------
+ Two objects are returned
+ 1. A scan angle coordinate in the x direction measured in radians
+ 2. A scan angle coordinate in the y direction measured in radians
+ """
+ if decimal_coordinates:
+ latitude = np.radians(latitude)
+ longitude = np.radians(longitude)
+
+ r_pol = goes_imager_projection.semi_minor_axis
+ r_eq = goes_imager_projection.semi_major_axis
+ H = goes_imager_projection.perspective_point_height + r_eq
+ e = 0.0818191910435 # eccentricity of GRD 1980 ellipsoid
+ lambda_0 = np.radians(goes_imager_projection.longitude_of_projection_origin) # Always in degrees from the GOES data
+
+ phi_c = np.arctan((r_pol**2/r_eq**2)*np.tan(latitude)) # Geocentric latitude
+ r_c = r_pol/(np.sqrt(1-(e**2*np.cos(phi_c)**2))) # Geocentric distance to the point on the ellipsoid
+
+ s_x = H - r_c*np.cos(phi_c)*np.cos(longitude-lambda_0)
+ s_y = -r_c*np.cos(phi_c)*np.sin(longitude-lambda_0)
+ s_z = r_c*np.sin(phi_c)
+
+ # Confirm location is visible from the satellite
+ if (H*(H-s_x) < (s_y**2 + (r_eq**2/r_pol**2)*s_z**2)).any():
+ raise ValueError("One or more points not visible by satellite")
+
+ y = np.arctan(s_z/s_x)
+ x = np.arcsin(-s_y / (np.sqrt(s_x**2+s_y**2+s_z**2)))
+
+ return x, y
\ No newline at end of file
diff --git a/tests/test_GOES.py b/tests/test_GOES.py
index 486e95c..2b5d02a 100644
--- a/tests/test_GOES.py
+++ b/tests/test_GOES.py
@@ -19,10 +19,12 @@ def test_GOES16_nearesttime():
ds = GOES(satellite=16).nearesttime("2022-01-01")
+def test_GOES16_single_point_timerange():
+ ds = GOES(satellite=16).single_point_timerange(38.897957, -77.036560, "2022-01-01 00:00", "2022-01-01 01:00")
+
def test_GOES16_timerange():
ds = GOES(satellite=16).timerange("2022-01-01 00:00", "2022-01-01 01:00")
-
def test_GOES16_df():
df = GOES(satellite=16).df("2022-01-01 00:00", "2022-01-01 01:00")