Skip to content

Commit 7f58a7a

Browse files
committed
refactor use of earthaccess
1 parent 4f4d2d8 commit 7f58a7a

7 files changed

Lines changed: 189 additions & 122 deletions

File tree

HydroEO/satellites/swot/__init__.py

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

33
import datetime
44

5-
import earthaccess
6-
75
from HydroEO.satellites.swot import download as _download
86
from HydroEO.satellites.swot import preprocess as _preprocess
97

@@ -21,15 +19,13 @@ def query(
2119
startdate=startdate,
2220
enddate=enddate,
2321
product=product,
24-
earthaccess_client=earthaccess,
2522
)
2623

2724

2825
def download(results, download_directory: str):
2926
return _download.download(
3027
results=results,
3128
download_directory=download_directory,
32-
earthaccess_client=earthaccess,
3329
)
3430

3531

HydroEO/satellites/swot/download.py

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
import datetime
23
import logging
34
import os
@@ -15,18 +16,123 @@
1516
SWOT_LAKE_SHORT_NAME = "SWOT_L2_HR_LakeSP_D"
1617

1718

19+
@contextlib.contextmanager
20+
def _suppress_granule_size_warning():
21+
"""Suppress known earthaccess DataGranule.size deprecation warning."""
22+
with warnings.catch_warnings():
23+
warnings.filterwarnings(
24+
"ignore",
25+
message=r"As of version 1\.0, `DataGranule\.size` will be accessed as an attribute",
26+
category=FutureWarning,
27+
module=r"earthaccess\.(results|store)",
28+
)
29+
yield
30+
31+
32+
def _login(credentials=None):
33+
"""Authenticate with earthaccess, handling credentials if provided.
34+
35+
Parameters
36+
----------
37+
credentials : tuple(str, str) | None
38+
(username, password) tuple, or None for anonymous login.
39+
"""
40+
if credentials:
41+
username, password = credentials
42+
if username and password:
43+
try:
44+
earthaccess.login(strategy="environment", persist=True)
45+
except Exception:
46+
earthaccess.login()
47+
else:
48+
logger.warning(
49+
"No Earthdata credentials provided, attempting anonymous login"
50+
)
51+
earthaccess.login()
52+
else:
53+
earthaccess.login()
54+
55+
56+
def _search(**params):
57+
"""Search earthaccess for granules matching query parameters.
58+
59+
Parameters
60+
----------
61+
**params
62+
Query parameters (short_name, temporal, bounding_box, granule_name, etc.).
63+
64+
Returns
65+
-------
66+
list
67+
Matching granule results, or empty list on error.
68+
"""
69+
try:
70+
with _suppress_granule_size_warning():
71+
results = earthaccess.search_data(**params)
72+
return results
73+
except Exception as e:
74+
logger.error("Error searching for data: %s", e)
75+
return []
76+
77+
78+
def _filter_new(results, processed_granules):
79+
"""Filter results to keep only granules not yet processed.
80+
81+
Parameters
82+
----------
83+
results : list
84+
Granule result objects from earthaccess.search_data().
85+
processed_granules : set[str]
86+
GranuleUR values already processed, to exclude.
87+
88+
Returns
89+
-------
90+
list
91+
Granule result objects not in processed_granules.
92+
"""
93+
granule_ids = [result["umm"]["GranuleUR"] for result in results]
94+
new_granule_ids = [gid for gid in granule_ids if gid not in processed_granules]
95+
return [r for r in results if r["umm"]["GranuleUR"] in new_granule_ids]
96+
97+
98+
def _download_files(results, directory):
99+
"""Download granule files to a local directory.
100+
101+
Parameters
102+
----------
103+
results : list
104+
Granule result objects to download.
105+
directory : str
106+
Local directory to write files to.
107+
108+
Returns
109+
-------
110+
list
111+
Paths to successfully downloaded files, or empty list on error.
112+
"""
113+
if not results:
114+
return []
115+
116+
try:
117+
with _suppress_granule_size_warning():
118+
files = earthaccess.download(results, directory, show_progress=True)
119+
return files or []
120+
except Exception as e:
121+
logger.error("Error downloading files: %s", e)
122+
return []
123+
124+
18125
def query(
19126
aoi: list,
20127
startdate: datetime.date,
21128
enddate: datetime.date,
22129
product: str = SWOT_LAKE_SHORT_NAME,
23-
earthaccess_client=earthaccess,
24130
) -> object:
25131
# format coordinates and extract bounds
26132
aoi = geometry.format_coord_list(aoi)
27133

28-
# login and authenticate earthacess
29-
earthaccess_client.login()
134+
# login and authenticate earthaccess
135+
_login()
30136

31137
# define query parameters
32138
params = {
@@ -35,21 +141,11 @@ def query(
35141
"bounding_box": shapely.Polygon(aoi).bounds,
36142
}
37143

38-
# Silence a known earthaccess deprecation warning until upstream migrates
39-
# DataGranule.size() to DataGranule.size attribute access.
40-
with warnings.catch_warnings():
41-
warnings.filterwarnings(
42-
"ignore",
43-
message=r"As of version 1\.0, `DataGranule\.size` will be accessed as an attribute",
44-
category=FutureWarning,
45-
module=r"earthaccess\.results",
46-
)
47-
results = earthaccess_client.search_data(**params)
48-
144+
results = _search(**params)
49145
return results
50146

51147

52-
def download(results, download_directory: str, earthaccess_client=earthaccess):
148+
def download(results, download_directory: str):
53149
# Check if we have a progress log file in this directory, if not make it
54150
log_path = os.path.join(download_directory, "downloaded.log")
55151
if not os.path.exists(log_path):
@@ -71,16 +167,7 @@ def download(results, download_directory: str, earthaccess_client=earthaccess):
71167
logger.info("%s files shown as downloaded in log", len(results) - len(to_download))
72168
logger.info("%s files will be downloaded", len(to_download))
73169
if to_download:
74-
with warnings.catch_warnings():
75-
warnings.filterwarnings(
76-
"ignore",
77-
message=r"As of version 1\.0, `DataGranule\.size` will be accessed as an attribute",
78-
category=FutureWarning,
79-
module=r"earthaccess\.(results|store)",
80-
)
81-
files = earthaccess_client.download(
82-
to_download, download_directory, show_progress=True
83-
)
170+
files = _download_files(to_download, download_directory)
84171

85172
with open(log_path, "a") as log:
86173
for file in files:

HydroEO/satellites/swot/pixc.py

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from pathlib import Path
1212
from typing import Any
1313

14-
import earthaccess
1514
import geopandas as gpd
1615
import numpy as np
1716
import pandas as pd
@@ -23,6 +22,13 @@
2322
from shapely.geometry import box
2423
from tqdm import tqdm
2524

25+
from HydroEO.satellites.swot.download import (
26+
_download_files,
27+
_filter_new,
28+
_login,
29+
_search,
30+
)
31+
2632
logger = logging.getLogger(__name__)
2733

2834
# Classification enum for SWOT PIXC data
@@ -116,15 +122,7 @@ def _download_granules(
116122
"""Download SWOT PIXC granules matching query, skipping already processed ones."""
117123
logger.debug("=== SWOT Pixel Cloud Download Phase ===")
118124

119-
username, password = credentials
120-
if username and password:
121-
try:
122-
earthaccess.login(strategy="environment", persist=True)
123-
except Exception:
124-
earthaccess.login()
125-
else:
126-
logger.warning("No Earthdata credentials provided, attempting anonymous login")
127-
earthaccess.login()
125+
_login(credentials)
128126

129127
aoi_config = config["aoi"]
130128
if aoi_config["type"] == "bbox":
@@ -147,44 +145,34 @@ def _download_granules(
147145
logger.info(
148146
"Searching for SWOT PIXC product '%s' in AOI %s", product, aoi_config["name"]
149147
)
150-
try:
151-
swot_results = earthaccess.search_data(
152-
short_name=product,
153-
bounding_box=bounds,
154-
temporal=(time_start, time_end),
155-
)
156-
logger.debug("Found %d granules matching query", len(swot_results))
157-
except Exception as e:
158-
logger.error("Error searching for SWOT data: %s", e)
159-
return []
148+
149+
search_params = {
150+
"short_name": product,
151+
"bounding_box": bounds,
152+
"temporal": (time_start, time_end),
153+
}
154+
swot_results = _search(**search_params)
155+
logger.debug("Found %d granules matching query", len(swot_results))
160156

161157
if not swot_results:
162158
logger.warning("No SWOT PIXC granules found matching the query")
163159
return []
164160

165-
granule_ids = [result["umm"]["GranuleUR"] for result in swot_results]
166-
new_granules = [gid for gid in granule_ids if gid not in processed_granules]
161+
new_results = _filter_new(swot_results, processed_granules)
167162
logger.debug(
168163
"%d new granules to download (already processed: %d)",
169-
len(new_granules),
164+
len(new_results),
170165
len(processed_granules),
171166
)
172167

173-
if not new_granules:
168+
if not new_results:
174169
logger.info("All granules already processed, skipping download")
175170
return []
176171

177-
new_results = [r for r in swot_results if r["umm"]["GranuleUR"] in new_granules]
178172
logger.debug("Downloading %d granules to %s", len(new_results), raw_dir)
179-
try:
180-
downloaded_files = earthaccess.download(
181-
new_results, str(raw_dir), show_progress=True
182-
)
183-
logger.debug("Successfully downloaded %d files", len(downloaded_files))
184-
return downloaded_files or []
185-
except Exception as e:
186-
logger.error("Error downloading SWOT data: %s", e)
187-
return []
173+
downloaded_files = _download_files(new_results, str(raw_dir))
174+
logger.debug("Successfully downloaded %d files", len(downloaded_files))
175+
return downloaded_files or []
188176

189177

190178
def _preprocess_granules(

HydroEO/satellites/swot/raster.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from pathlib import Path
1313
from typing import Any
1414

15-
import earthaccess
1615
import geopandas as gpd
1716
import numpy as np
1817
import rasterio
@@ -25,6 +24,12 @@
2524
from tqdm import tqdm
2625

2726
from HydroEO import FLOAT32_NODATA_VALUE
27+
from HydroEO.satellites.swot.download import (
28+
_download_files,
29+
_filter_new,
30+
_login,
31+
_search,
32+
)
2833

2934
logger = logging.getLogger(__name__)
3035

@@ -121,15 +126,7 @@ def _download_granules(
121126
"""Download SWOT granules matching query, skipping already processed ones."""
122127
logger.debug("=== SWOT Raster Download Phase ===")
123128

124-
username, password = credentials
125-
if username and password:
126-
try:
127-
earthaccess.login(strategy="environment", persist=True)
128-
except Exception:
129-
earthaccess.login()
130-
else:
131-
logger.warning("No Earthdata credentials provided, attempting anonymous login")
132-
earthaccess.login()
129+
_login(credentials)
133130

134131
aoi_config = config["aoi"]
135132
if aoi_config["type"] == "bbox":
@@ -156,45 +153,35 @@ def _download_granules(
156153
logger.info(
157154
"Searching for SWOT product '%s' in AOI %s", product, aoi_config["name"]
158155
)
159-
try:
160-
swot_results = earthaccess.search_data(
161-
short_name=product,
162-
bounding_box=bounds,
163-
temporal=(time_start, time_end),
164-
granule_name=granule_filter,
165-
)
166-
logger.debug("Found %d granules matching query", len(swot_results))
167-
except Exception as e:
168-
logger.error("Error searching for SWOT data: %s", e)
169-
return []
156+
157+
search_params = {
158+
"short_name": product,
159+
"bounding_box": bounds,
160+
"temporal": (time_start, time_end),
161+
"granule_name": granule_filter,
162+
}
163+
swot_results = _search(**search_params)
164+
logger.debug("Found %d granules matching query", len(swot_results))
170165

171166
if not swot_results:
172167
logger.warning("No SWOT granules found matching the query")
173168
return []
174169

175-
granule_ids = [result["umm"]["GranuleUR"] for result in swot_results]
176-
new_granules = [gid for gid in granule_ids if gid not in processed_granules]
170+
new_results = _filter_new(swot_results, processed_granules)
177171
logger.debug(
178172
"%d new granules to download (already processed: %d)",
179-
len(new_granules),
173+
len(new_results),
180174
len(processed_granules),
181175
)
182176

183-
if not new_granules:
177+
if not new_results:
184178
logger.info("All granules already processed, skipping download")
185179
return []
186180

187-
new_results = [r for r in swot_results if r["umm"]["GranuleUR"] in new_granules]
188181
logger.debug("Downloading %d granules to %s", len(new_results), raw_dir)
189-
try:
190-
downloaded_files = earthaccess.download(
191-
new_results, str(raw_dir), show_progress=True
192-
)
193-
logger.debug("Successfully downloaded %d files", len(downloaded_files))
194-
return downloaded_files or []
195-
except Exception as e:
196-
logger.error("Error downloading SWOT data: %s", e)
197-
return []
182+
downloaded_files = _download_files(new_results, str(raw_dir))
183+
logger.debug("Successfully downloaded %d files", len(downloaded_files))
184+
return downloaded_files or []
198185

199186

200187
def _preprocess_granules(

0 commit comments

Comments
 (0)