appeears module¶
Utilities for submitting and downloading NASA AppEEARS tasks.
AppEEARSClient
¶
Client for the NASA AppEEARS API.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
api_url |
str |
Base URL for the AppEEARS API. |
'https://appeears.earthdatacloud.nasa.gov/api/' |
token |
Optional[str] |
Existing AppEEARS bearer token. |
None |
session |
Optional[Any] |
Optional requests-compatible session. |
None |
Source code in hypercoast/appeears.py
class AppEEARSClient:
"""Client for the NASA AppEEARS API.
Args:
api_url: Base URL for the AppEEARS API.
token: Existing AppEEARS bearer token.
session: Optional requests-compatible session.
"""
def __init__(
self,
api_url: str = APPEEARS_API_URL,
token: Optional[str] = None,
session: Optional[Any] = None,
) -> None:
import requests
self.api_url = api_url.rstrip("/") + "/"
self.token = token
self.session = session or requests.Session()
@property
def headers(self) -> Dict[str, str]:
"""Return authorization headers for authenticated requests.
Returns:
Dictionary containing the bearer token header when a token exists.
"""
if self.token is None:
return {}
return {"Authorization": f"Bearer {self.token}"}
def login(
self,
username: Optional[str] = None,
password: Optional[str] = None,
token: Optional[str] = None,
) -> Dict[str, Any]:
"""Authenticate with AppEEARS.
Args:
username: NASA Earthdata username. If omitted, environment variables
or a netrc entry for urs.earthdata.nasa.gov are used.
password: NASA Earthdata password.
token: Existing AppEEARS token. When provided, no login request is
sent.
Returns:
AppEEARS login response containing the bearer token.
"""
if token is not None:
self.token = token
return {"token": token}
if username is None:
username = os.environ.get("EARTHDATA_USERNAME")
if password is None:
password = os.environ.get("EARTHDATA_PASSWORD")
if username is None or password is None:
try:
from netrc import netrc
auth = netrc().authenticators("urs.earthdata.nasa.gov")
except (FileNotFoundError, OSError):
auth = None
if auth is not None:
username = username or auth[0]
password = password or auth[2]
if username is None or password is None:
raise ValueError(
"username and password are required unless EARTHDATA_USERNAME "
"and EARTHDATA_PASSWORD or a netrc entry are available."
)
response = self._request(
"post", "login", auth=(username, password), auth_header=False
)
self.token = response["token"]
return response
def products(
self,
keyword: Optional[str] = None,
platform: Optional[str] = None,
available: Optional[bool] = True,
) -> List[Dict[str, Any]]:
"""List AppEEARS products.
Args:
keyword: Case-insensitive search text matched against product fields.
platform: Platform name, such as ``"EMIT"``.
available: If set, filter by the AppEEARS availability flag.
Returns:
List of product metadata dictionaries.
"""
products = self._request("get", "product", auth_header=False)
if keyword is not None:
query = keyword.lower()
products = [p for p in products if query in json.dumps(p).lower()]
if platform is not None:
products = [
p
for p in products
if str(p.get("Platform", "")).lower() == platform.lower()
]
if available is not None:
products = [p for p in products if p.get("Available") is available]
return products
def layers(self, product: str) -> Dict[str, Dict[str, Any]]:
"""List layers for an AppEEARS product.
Args:
product: Product and version string, such as ``"EMIT_L2A_RFL.001"``.
Returns:
Dictionary keyed by layer name.
"""
return self._request("get", f"product/{product}", auth_header=False)
def spectral_layers(
self,
product: str = EMIT_REFLECTANCE_PRODUCT,
wavelengths: Optional[Sequence[float]] = None,
include_flags: bool = False,
) -> List[Dict[str, Any]]:
"""Return spectral layer metadata, optionally nearest to wavelengths.
Args:
product: AppEEARS product and version.
wavelengths: Target wavelengths in nanometers. If omitted, all
spectral bands are returned.
include_flags: Whether to include good wavelength flag layers.
Returns:
List of layer records containing product, layer, wavelength, fwhm,
units, description, and data type.
"""
records = []
for layer, metadata in self.layers(product).items():
if not include_flags and layer.endswith("_GW"):
continue
pattern = r"B\d{3}(?:_GW)?" if include_flags else r"B\d{3}"
if not re.fullmatch(pattern, layer):
continue
record = _layer_record(product, layer, metadata)
if record["wavelength"] is not None:
records.append(record)
records.sort(key=lambda item: item["wavelength"])
if wavelengths is None:
return records
selected = []
used_layers = set()
for wavelength in wavelengths:
nearest = min(
records,
key=lambda item: abs(float(item["wavelength"]) - float(wavelength)),
)
if nearest["layer"] not in used_layers:
selected.append(nearest)
used_layers.add(nearest["layer"])
return selected
def projections(self) -> Dict[str, Any]:
"""List AppEEARS output projections.
Returns:
Dictionary of AppEEARS projection metadata.
"""
return self._request("get", "spatial/proj", auth_header=False)
def submit_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""Submit an AppEEARS task.
Args:
task: AppEEARS task payload.
Returns:
AppEEARS task submission response.
"""
self._require_token()
return self._request("post", "task", json=task)
def tasks(
self,
limit: Optional[int] = None,
offset: Optional[int] = None,
pretty: bool = False,
) -> Dict[str, Any]:
"""List submitted AppEEARS tasks for the authenticated user.
Args:
limit: Maximum number of tasks to return.
offset: Result offset for pagination.
pretty: Whether AppEEARS should pretty-print the JSON response.
Returns:
Task list response from AppEEARS.
"""
self._require_token()
params = {"pretty": pretty}
if limit is not None:
params["limit"] = limit
if offset is not None:
params["offset"] = offset
return self._request("get", "task", params=params)
def task(self, task_id: str) -> Dict[str, Any]:
"""Get an AppEEARS task.
Args:
task_id: AppEEARS task identifier.
Returns:
Task metadata response.
"""
self._require_token()
return self._request("get", f"task/{task_id}")
def status(self, task_id: str) -> Dict[str, Any]:
"""Get an AppEEARS task status.
Args:
task_id: AppEEARS task identifier.
Returns:
Task status response.
"""
self._require_token()
return self._request("get", f"status/{task_id}")
def wait_for_task(
self,
task_id: str,
interval: int = 20,
timeout: Optional[int] = None,
verbose: bool = True,
) -> Dict[str, Any]:
"""Wait for an AppEEARS task to finish.
Args:
task_id: AppEEARS task identifier.
interval: Polling interval in seconds.
timeout: Optional timeout in seconds.
verbose: Whether to print status changes.
Returns:
Final task metadata response.
"""
start = time.time()
last_status = None
while True:
response = self.task(task_id)
status = response.get("status")
if verbose and status != last_status:
print(status)
if status == "done":
return response
if status in {"error", "failed"}:
raise RuntimeError(f"AppEEARS task {task_id} failed: {response}")
if timeout is not None and time.time() - start > timeout:
raise TimeoutError(f"Timed out waiting for AppEEARS task {task_id}.")
last_status = status
time.sleep(interval)
def bundle(self, task_id: str) -> Dict[str, Any]:
"""Get bundle metadata for a completed AppEEARS task.
Args:
task_id: AppEEARS task identifier.
Returns:
Bundle metadata response.
"""
self._require_token()
return self._request("get", f"bundle/{task_id}")
def download_bundle(
self,
task_id: str,
out_dir: Union[str, os.PathLike[str]] = ".",
file_ids: Optional[Iterable[str]] = None,
file_types: Optional[Iterable[str]] = None,
overwrite: bool = False,
) -> List[str]:
"""Download files from an AppEEARS task bundle.
Args:
task_id: AppEEARS task identifier.
out_dir: Output directory.
file_ids: Optional subset of AppEEARS file identifiers to download.
file_types: Optional file type filter, such as ``["tif", "csv"]``.
overwrite: Whether to overwrite existing files.
Returns:
List of downloaded file paths.
"""
bundle = self.bundle(task_id)
selected_file_ids = set(file_ids) if file_ids is not None else None
selected_file_types = set(file_types) if file_types is not None else None
paths = []
for item in bundle.get("files", []):
file_id = item["file_id"]
if selected_file_ids is not None and file_id not in selected_file_ids:
continue
if (
selected_file_types is not None
and item.get("file_type") not in selected_file_types
):
continue
path = self.download_file(
task_id=task_id,
file_id=file_id,
file_name=item["file_name"],
out_dir=out_dir,
overwrite=overwrite,
)
paths.append(path)
return paths
def download_file(
self,
task_id: str,
file_id: str,
file_name: str,
out_dir: Union[str, os.PathLike[str]] = ".",
overwrite: bool = False,
chunk_size: int = 8192,
) -> str:
"""Download one AppEEARS bundle file.
Args:
task_id: AppEEARS task identifier.
file_id: Bundle file identifier.
file_name: Bundle file name.
out_dir: Output directory.
overwrite: Whether to overwrite an existing file.
chunk_size: Response chunk size in bytes.
Returns:
Path to the downloaded file.
"""
self._require_token()
out_path = _bundle_output_path(out_dir, file_name)
if out_path.exists() and not overwrite:
return str(out_path)
response = self._raw_request("get", f"bundle/{task_id}/{file_id}", stream=True)
out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("wb") as dst:
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
dst.write(chunk)
return str(out_path)
def s3credentials(self) -> Dict[str, Any]:
"""Request temporary S3 credentials from AppEEARS.
Returns:
Temporary credential response from AppEEARS.
"""
self._require_token()
return self._request("get", "s3credentials")
def _request(
self,
method: str,
path: str,
auth_header: bool = True,
**kwargs: Any,
) -> Any:
"""Send a JSON request to AppEEARS.
Args:
method: HTTP method name.
path: API path relative to the base URL.
auth_header: Whether to include the bearer token header.
**kwargs: Additional request keyword arguments.
Returns:
Decoded JSON response.
"""
response = self._raw_request(method, path, auth_header=auth_header, **kwargs)
try:
return response.json()
except ValueError:
return response.text
def _raw_request(
self,
method: str,
path: str,
auth_header: bool = True,
**kwargs: Any,
) -> Any:
"""Send a raw request to AppEEARS.
Args:
method: HTTP method name.
path: API path relative to the base URL.
auth_header: Whether to include the bearer token header.
**kwargs: Additional request keyword arguments.
Returns:
Requests response object.
"""
headers = kwargs.pop("headers", {})
if auth_header:
headers = {**self.headers, **headers}
kwargs.setdefault("timeout", 120)
response = self.session.request(
method.upper(), self.api_url + path.lstrip("/"), headers=headers, **kwargs
)
try:
response.raise_for_status()
except Exception as error:
message = _response_error_message(response)
if message:
error.args = (*error.args, message)
raise
return response
def _require_token(self) -> None:
"""Raise an error when an authenticated request lacks a token."""
if self.token is None:
raise ValueError("Call login() or provide a token before this request.")
headers: Dict[str, str]
property
readonly
¶
Return authorization headers for authenticated requests.
Returns:
| Type | Description |
|---|---|
Dict[str, str] |
Dictionary containing the bearer token header when a token exists. |
bundle(self, task_id)
¶
Get bundle metadata for a completed AppEEARS task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Bundle metadata response. |
Source code in hypercoast/appeears.py
def bundle(self, task_id: str) -> Dict[str, Any]:
"""Get bundle metadata for a completed AppEEARS task.
Args:
task_id: AppEEARS task identifier.
Returns:
Bundle metadata response.
"""
self._require_token()
return self._request("get", f"bundle/{task_id}")
download_bundle(self, task_id, out_dir='.', file_ids=None, file_types=None, overwrite=False)
¶
Download files from an AppEEARS task bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
out_dir |
Union[str, os.PathLike[str]] |
Output directory. |
'.' |
file_ids |
Optional[Iterable[str]] |
Optional subset of AppEEARS file identifiers to download. |
None |
file_types |
Optional[Iterable[str]] |
Optional file type filter, such as |
None |
overwrite |
bool |
Whether to overwrite existing files. |
False |
Returns:
| Type | Description |
|---|---|
List[str] |
List of downloaded file paths. |
Source code in hypercoast/appeears.py
def download_bundle(
self,
task_id: str,
out_dir: Union[str, os.PathLike[str]] = ".",
file_ids: Optional[Iterable[str]] = None,
file_types: Optional[Iterable[str]] = None,
overwrite: bool = False,
) -> List[str]:
"""Download files from an AppEEARS task bundle.
Args:
task_id: AppEEARS task identifier.
out_dir: Output directory.
file_ids: Optional subset of AppEEARS file identifiers to download.
file_types: Optional file type filter, such as ``["tif", "csv"]``.
overwrite: Whether to overwrite existing files.
Returns:
List of downloaded file paths.
"""
bundle = self.bundle(task_id)
selected_file_ids = set(file_ids) if file_ids is not None else None
selected_file_types = set(file_types) if file_types is not None else None
paths = []
for item in bundle.get("files", []):
file_id = item["file_id"]
if selected_file_ids is not None and file_id not in selected_file_ids:
continue
if (
selected_file_types is not None
and item.get("file_type") not in selected_file_types
):
continue
path = self.download_file(
task_id=task_id,
file_id=file_id,
file_name=item["file_name"],
out_dir=out_dir,
overwrite=overwrite,
)
paths.append(path)
return paths
download_file(self, task_id, file_id, file_name, out_dir='.', overwrite=False, chunk_size=8192)
¶
Download one AppEEARS bundle file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
file_id |
str |
Bundle file identifier. |
required |
file_name |
str |
Bundle file name. |
required |
out_dir |
Union[str, os.PathLike[str]] |
Output directory. |
'.' |
overwrite |
bool |
Whether to overwrite an existing file. |
False |
chunk_size |
int |
Response chunk size in bytes. |
8192 |
Returns:
| Type | Description |
|---|---|
str |
Path to the downloaded file. |
Source code in hypercoast/appeears.py
def download_file(
self,
task_id: str,
file_id: str,
file_name: str,
out_dir: Union[str, os.PathLike[str]] = ".",
overwrite: bool = False,
chunk_size: int = 8192,
) -> str:
"""Download one AppEEARS bundle file.
Args:
task_id: AppEEARS task identifier.
file_id: Bundle file identifier.
file_name: Bundle file name.
out_dir: Output directory.
overwrite: Whether to overwrite an existing file.
chunk_size: Response chunk size in bytes.
Returns:
Path to the downloaded file.
"""
self._require_token()
out_path = _bundle_output_path(out_dir, file_name)
if out_path.exists() and not overwrite:
return str(out_path)
response = self._raw_request("get", f"bundle/{task_id}/{file_id}", stream=True)
out_path.parent.mkdir(parents=True, exist_ok=True)
with out_path.open("wb") as dst:
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
dst.write(chunk)
return str(out_path)
layers(self, product)
¶
List layers for an AppEEARS product.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
product |
str |
Product and version string, such as |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Dict[str, Any]] |
Dictionary keyed by layer name. |
Source code in hypercoast/appeears.py
def layers(self, product: str) -> Dict[str, Dict[str, Any]]:
"""List layers for an AppEEARS product.
Args:
product: Product and version string, such as ``"EMIT_L2A_RFL.001"``.
Returns:
Dictionary keyed by layer name.
"""
return self._request("get", f"product/{product}", auth_header=False)
login(self, username=None, password=None, token=None)
¶
Authenticate with AppEEARS.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
username |
Optional[str] |
NASA Earthdata username. If omitted, environment variables or a netrc entry for urs.earthdata.nasa.gov are used. |
None |
password |
Optional[str] |
NASA Earthdata password. |
None |
token |
Optional[str] |
Existing AppEEARS token. When provided, no login request is sent. |
None |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
AppEEARS login response containing the bearer token. |
Source code in hypercoast/appeears.py
def login(
self,
username: Optional[str] = None,
password: Optional[str] = None,
token: Optional[str] = None,
) -> Dict[str, Any]:
"""Authenticate with AppEEARS.
Args:
username: NASA Earthdata username. If omitted, environment variables
or a netrc entry for urs.earthdata.nasa.gov are used.
password: NASA Earthdata password.
token: Existing AppEEARS token. When provided, no login request is
sent.
Returns:
AppEEARS login response containing the bearer token.
"""
if token is not None:
self.token = token
return {"token": token}
if username is None:
username = os.environ.get("EARTHDATA_USERNAME")
if password is None:
password = os.environ.get("EARTHDATA_PASSWORD")
if username is None or password is None:
try:
from netrc import netrc
auth = netrc().authenticators("urs.earthdata.nasa.gov")
except (FileNotFoundError, OSError):
auth = None
if auth is not None:
username = username or auth[0]
password = password or auth[2]
if username is None or password is None:
raise ValueError(
"username and password are required unless EARTHDATA_USERNAME "
"and EARTHDATA_PASSWORD or a netrc entry are available."
)
response = self._request(
"post", "login", auth=(username, password), auth_header=False
)
self.token = response["token"]
return response
products(self, keyword=None, platform=None, available=True)
¶
List AppEEARS products.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keyword |
Optional[str] |
Case-insensitive search text matched against product fields. |
None |
platform |
Optional[str] |
Platform name, such as |
None |
available |
Optional[bool] |
If set, filter by the AppEEARS availability flag. |
True |
Returns:
| Type | Description |
|---|---|
List[Dict[str, Any]] |
List of product metadata dictionaries. |
Source code in hypercoast/appeears.py
def products(
self,
keyword: Optional[str] = None,
platform: Optional[str] = None,
available: Optional[bool] = True,
) -> List[Dict[str, Any]]:
"""List AppEEARS products.
Args:
keyword: Case-insensitive search text matched against product fields.
platform: Platform name, such as ``"EMIT"``.
available: If set, filter by the AppEEARS availability flag.
Returns:
List of product metadata dictionaries.
"""
products = self._request("get", "product", auth_header=False)
if keyword is not None:
query = keyword.lower()
products = [p for p in products if query in json.dumps(p).lower()]
if platform is not None:
products = [
p
for p in products
if str(p.get("Platform", "")).lower() == platform.lower()
]
if available is not None:
products = [p for p in products if p.get("Available") is available]
return products
projections(self)
¶
List AppEEARS output projections.
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Dictionary of AppEEARS projection metadata. |
Source code in hypercoast/appeears.py
def projections(self) -> Dict[str, Any]:
"""List AppEEARS output projections.
Returns:
Dictionary of AppEEARS projection metadata.
"""
return self._request("get", "spatial/proj", auth_header=False)
s3credentials(self)
¶
Request temporary S3 credentials from AppEEARS.
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Temporary credential response from AppEEARS. |
Source code in hypercoast/appeears.py
def s3credentials(self) -> Dict[str, Any]:
"""Request temporary S3 credentials from AppEEARS.
Returns:
Temporary credential response from AppEEARS.
"""
self._require_token()
return self._request("get", "s3credentials")
spectral_layers(self, product='EMIT_L2A_RFL.001', wavelengths=None, include_flags=False)
¶
Return spectral layer metadata, optionally nearest to wavelengths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
product |
str |
AppEEARS product and version. |
'EMIT_L2A_RFL.001' |
wavelengths |
Optional[Sequence[float]] |
Target wavelengths in nanometers. If omitted, all spectral bands are returned. |
None |
include_flags |
bool |
Whether to include good wavelength flag layers. |
False |
Returns:
| Type | Description |
|---|---|
List[Dict[str, Any]] |
List of layer records containing product, layer, wavelength, fwhm, units, description, and data type. |
Source code in hypercoast/appeears.py
def spectral_layers(
self,
product: str = EMIT_REFLECTANCE_PRODUCT,
wavelengths: Optional[Sequence[float]] = None,
include_flags: bool = False,
) -> List[Dict[str, Any]]:
"""Return spectral layer metadata, optionally nearest to wavelengths.
Args:
product: AppEEARS product and version.
wavelengths: Target wavelengths in nanometers. If omitted, all
spectral bands are returned.
include_flags: Whether to include good wavelength flag layers.
Returns:
List of layer records containing product, layer, wavelength, fwhm,
units, description, and data type.
"""
records = []
for layer, metadata in self.layers(product).items():
if not include_flags and layer.endswith("_GW"):
continue
pattern = r"B\d{3}(?:_GW)?" if include_flags else r"B\d{3}"
if not re.fullmatch(pattern, layer):
continue
record = _layer_record(product, layer, metadata)
if record["wavelength"] is not None:
records.append(record)
records.sort(key=lambda item: item["wavelength"])
if wavelengths is None:
return records
selected = []
used_layers = set()
for wavelength in wavelengths:
nearest = min(
records,
key=lambda item: abs(float(item["wavelength"]) - float(wavelength)),
)
if nearest["layer"] not in used_layers:
selected.append(nearest)
used_layers.add(nearest["layer"])
return selected
status(self, task_id)
¶
Get an AppEEARS task status.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Task status response. |
Source code in hypercoast/appeears.py
def status(self, task_id: str) -> Dict[str, Any]:
"""Get an AppEEARS task status.
Args:
task_id: AppEEARS task identifier.
Returns:
Task status response.
"""
self._require_token()
return self._request("get", f"status/{task_id}")
submit_task(self, task)
¶
Submit an AppEEARS task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task |
Dict[str, Any] |
AppEEARS task payload. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
AppEEARS task submission response. |
Source code in hypercoast/appeears.py
def submit_task(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""Submit an AppEEARS task.
Args:
task: AppEEARS task payload.
Returns:
AppEEARS task submission response.
"""
self._require_token()
return self._request("post", "task", json=task)
task(self, task_id)
¶
Get an AppEEARS task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Task metadata response. |
Source code in hypercoast/appeears.py
def task(self, task_id: str) -> Dict[str, Any]:
"""Get an AppEEARS task.
Args:
task_id: AppEEARS task identifier.
Returns:
Task metadata response.
"""
self._require_token()
return self._request("get", f"task/{task_id}")
tasks(self, limit=None, offset=None, pretty=False)
¶
List submitted AppEEARS tasks for the authenticated user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit |
Optional[int] |
Maximum number of tasks to return. |
None |
offset |
Optional[int] |
Result offset for pagination. |
None |
pretty |
bool |
Whether AppEEARS should pretty-print the JSON response. |
False |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Task list response from AppEEARS. |
Source code in hypercoast/appeears.py
def tasks(
self,
limit: Optional[int] = None,
offset: Optional[int] = None,
pretty: bool = False,
) -> Dict[str, Any]:
"""List submitted AppEEARS tasks for the authenticated user.
Args:
limit: Maximum number of tasks to return.
offset: Result offset for pagination.
pretty: Whether AppEEARS should pretty-print the JSON response.
Returns:
Task list response from AppEEARS.
"""
self._require_token()
params = {"pretty": pretty}
if limit is not None:
params["limit"] = limit
if offset is not None:
params["offset"] = offset
return self._request("get", "task", params=params)
wait_for_task(self, task_id, interval=20, timeout=None, verbose=True)
¶
Wait for an AppEEARS task to finish.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
interval |
int |
Polling interval in seconds. |
20 |
timeout |
Optional[int] |
Optional timeout in seconds. |
None |
verbose |
bool |
Whether to print status changes. |
True |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Final task metadata response. |
Source code in hypercoast/appeears.py
def wait_for_task(
self,
task_id: str,
interval: int = 20,
timeout: Optional[int] = None,
verbose: bool = True,
) -> Dict[str, Any]:
"""Wait for an AppEEARS task to finish.
Args:
task_id: AppEEARS task identifier.
interval: Polling interval in seconds.
timeout: Optional timeout in seconds.
verbose: Whether to print status changes.
Returns:
Final task metadata response.
"""
start = time.time()
last_status = None
while True:
response = self.task(task_id)
status = response.get("status")
if verbose and status != last_status:
print(status)
if status == "done":
return response
if status in {"error", "failed"}:
raise RuntimeError(f"AppEEARS task {task_id} failed: {response}")
if timeout is not None and time.time() - start > timeout:
raise TimeoutError(f"Timed out waiting for AppEEARS task {task_id}.")
last_status = status
time.sleep(interval)
appeears_area_task(task_name, geometry, layers, start_date, end_date, output_format=None, projection='geographic', recurring=False, year_range=None, orthorectify=True, additional_options=None)
¶
Build an AppEEARS area task payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_name |
str |
AppEEARS task name. |
required |
geometry |
Any |
GeoJSON geometry, feature, feature collection, GeoDataFrame,
vector file path, or bounding box |
required |
layers |
Sequence[Dict[str, str]] |
Task-ready AppEEARS layers. |
required |
start_date |
str |
Start date as |
required |
end_date |
str |
End date as |
required |
output_format |
Optional[str] |
AppEEARS output format, such as |
None |
projection |
str |
AppEEARS output projection name. |
'geographic' |
recurring |
bool |
Whether the task uses recurring dates. |
False |
year_range |
Optional[Sequence[int]] |
Two-year range for recurring tasks. |
None |
orthorectify |
Optional[bool] |
Whether AppEEARS should orthorectify EMIT area outputs. AppEEARS requires this option for EMIT area tasks. Set to None to omit it. |
True |
additional_options |
Optional[Dict[str, Any]] |
Additional AppEEARS output options. |
None |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
AppEEARS area task payload. |
Source code in hypercoast/appeears.py
def appeears_area_task(
task_name: str,
geometry: Any,
layers: Sequence[Dict[str, str]],
start_date: str,
end_date: str,
output_format: Optional[str] = None,
projection: str = "geographic",
recurring: bool = False,
year_range: Optional[Sequence[int]] = None,
orthorectify: Optional[bool] = True,
additional_options: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Build an AppEEARS area task payload.
Args:
task_name: AppEEARS task name.
geometry: GeoJSON geometry, feature, feature collection, GeoDataFrame,
vector file path, or bounding box ``[xmin, ymin, xmax, ymax]``.
layers: Task-ready AppEEARS layers.
start_date: Start date as ``YYYY-MM-DD`` or ``MM-DD-YYYY``.
end_date: End date as ``YYYY-MM-DD`` or ``MM-DD-YYYY``.
output_format: AppEEARS output format, such as ``"geotiff"`` or
``"netcdf4"``. If None, EMIT tasks use ``"netcdf4"`` and other
tasks use ``"geotiff"``.
projection: AppEEARS output projection name.
recurring: Whether the task uses recurring dates.
year_range: Two-year range for recurring tasks.
orthorectify: Whether AppEEARS should orthorectify EMIT area outputs.
AppEEARS requires this option for EMIT area tasks. Set to None to
omit it.
additional_options: Additional AppEEARS output options.
Returns:
AppEEARS area task payload.
"""
has_emit = _has_emit_layers(layers)
if output_format is None:
output_format = "netcdf4" if has_emit else "geotiff"
output = {"format": {"type": output_format}, "projection": projection}
options = dict(additional_options or {})
if orthorectify is not None and has_emit:
options.setdefault("orthorectify", bool(orthorectify))
if options:
output["additionalOptions"] = options
params = {
"dates": [
_date_range(
start_date=start_date,
end_date=end_date,
recurring=recurring,
year_range=year_range,
)
],
"layers": list(layers),
"output": output,
"geo": _normalize_geojson(geometry),
}
return {"task_type": "area", "task_name": task_name, "params": params}
appeears_download(task_id, out_dir='.', client=None, file_types=None, overwrite=False)
¶
Download an AppEEARS task bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
out_dir |
Union[str, os.PathLike[str]] |
Output directory. |
'.' |
client |
Optional[AppEEARSClient] |
Authenticated AppEEARS client. |
None |
file_types |
Optional[Iterable[str]] |
Optional file type filter, such as |
None |
overwrite |
bool |
Whether to overwrite existing files. |
False |
Returns:
| Type | Description |
|---|---|
List[str] |
List of downloaded file paths. |
Source code in hypercoast/appeears.py
def appeears_download(
task_id: str,
out_dir: Union[str, os.PathLike[str]] = ".",
client: Optional[AppEEARSClient] = None,
file_types: Optional[Iterable[str]] = None,
overwrite: bool = False,
) -> List[str]:
"""Download an AppEEARS task bundle.
Args:
task_id: AppEEARS task identifier.
out_dir: Output directory.
client: Authenticated AppEEARS client.
file_types: Optional file type filter, such as ``["tif"]``.
overwrite: Whether to overwrite existing files.
Returns:
List of downloaded file paths.
"""
if client is None:
client = appeears_login()
return client.download_bundle(
task_id=task_id,
out_dir=out_dir,
file_types=file_types,
overwrite=overwrite,
)
appeears_emit_layers(wavelengths=None, product='EMIT_L2A_RFL.001', include_flags=False, return_metadata=False, client=None)
¶
Select EMIT AppEEARS layers by wavelength.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wavelengths |
Optional[Sequence[float]] |
Target wavelengths in nanometers. If omitted, all spectral EMIT bands are returned. |
None |
product |
str |
EMIT product and version. |
'EMIT_L2A_RFL.001' |
include_flags |
bool |
Whether to include good wavelength flag layers. |
False |
return_metadata |
bool |
Whether to return wavelength metadata. If False,
task-ready |
False |
client |
Optional[AppEEARSClient] |
Optional AppEEARS client. |
None |
Returns:
| Type | Description |
|---|---|
List[Dict[str, Any]] |
Selected EMIT layer records or task-ready layer dictionaries. |
Source code in hypercoast/appeears.py
def appeears_emit_layers(
wavelengths: Optional[Sequence[float]] = None,
product: str = EMIT_REFLECTANCE_PRODUCT,
include_flags: bool = False,
return_metadata: bool = False,
client: Optional[AppEEARSClient] = None,
) -> List[Dict[str, Any]]:
"""Select EMIT AppEEARS layers by wavelength.
Args:
wavelengths: Target wavelengths in nanometers. If omitted, all spectral
EMIT bands are returned.
product: EMIT product and version.
include_flags: Whether to include good wavelength flag layers.
return_metadata: Whether to return wavelength metadata. If False,
task-ready ``{"product": product, "layer": layer}`` dictionaries
are returned.
client: Optional AppEEARS client.
Returns:
Selected EMIT layer records or task-ready layer dictionaries.
"""
client = client or AppEEARSClient()
layers = client.spectral_layers(
product=product, wavelengths=wavelengths, include_flags=include_flags
)
if return_metadata:
return layers
return [{"product": item["product"], "layer": item["layer"]} for item in layers]
appeears_layers(product, client=None)
¶
List AppEEARS layers for a product.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
product |
str |
Product and version string. |
required |
client |
Optional[AppEEARSClient] |
Optional AppEEARS client. |
None |
Returns:
| Type | Description |
|---|---|
Dict[str, Dict[str, Any]] |
Dictionary keyed by layer name. |
Source code in hypercoast/appeears.py
def appeears_layers(
product: str,
client: Optional[AppEEARSClient] = None,
) -> Dict[str, Dict[str, Any]]:
"""List AppEEARS layers for a product.
Args:
product: Product and version string.
client: Optional AppEEARS client.
Returns:
Dictionary keyed by layer name.
"""
client = client or AppEEARSClient()
return client.layers(product)
appeears_login(username=None, password=None, token=None, api_url='https://appeears.earthdatacloud.nasa.gov/api/')
¶
Create and authenticate an AppEEARS client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
username |
Optional[str] |
NASA Earthdata username. |
None |
password |
Optional[str] |
NASA Earthdata password. |
None |
token |
Optional[str] |
Existing AppEEARS token. |
None |
api_url |
str |
Base URL for the AppEEARS API. |
'https://appeears.earthdatacloud.nasa.gov/api/' |
Returns:
| Type | Description |
|---|---|
AppEEARSClient |
Authenticated AppEEARS client. |
Source code in hypercoast/appeears.py
def appeears_login(
username: Optional[str] = None,
password: Optional[str] = None,
token: Optional[str] = None,
api_url: str = APPEEARS_API_URL,
) -> AppEEARSClient:
"""Create and authenticate an AppEEARS client.
Args:
username: NASA Earthdata username.
password: NASA Earthdata password.
token: Existing AppEEARS token.
api_url: Base URL for the AppEEARS API.
Returns:
Authenticated AppEEARS client.
"""
client = AppEEARSClient(api_url=api_url)
client.login(username=username, password=password, token=token)
return client
appeears_point_task(task_name, coordinates, layers, start_date, end_date, recurring=False, year_range=None)
¶
Build an AppEEARS point task payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_name |
str |
AppEEARS task name. |
required |
coordinates |
Union[Sequence[float], Sequence[Dict[str, Any]]] |
One |
required |
layers |
Sequence[Dict[str, str]] |
Task-ready AppEEARS layers. |
required |
start_date |
str |
Start date as |
required |
end_date |
str |
End date as |
required |
recurring |
bool |
Whether the task uses recurring dates. |
False |
year_range |
Optional[Sequence[int]] |
Two-year range for recurring tasks. |
None |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
AppEEARS point task payload. |
Source code in hypercoast/appeears.py
def appeears_point_task(
task_name: str,
coordinates: Union[Sequence[float], Sequence[Dict[str, Any]]],
layers: Sequence[Dict[str, str]],
start_date: str,
end_date: str,
recurring: bool = False,
year_range: Optional[Sequence[int]] = None,
) -> Dict[str, Any]:
"""Build an AppEEARS point task payload.
Args:
task_name: AppEEARS task name.
coordinates: One ``(longitude, latitude)`` pair or AppEEARS coordinate
dictionaries containing longitude and latitude.
layers: Task-ready AppEEARS layers.
start_date: Start date as ``YYYY-MM-DD`` or ``MM-DD-YYYY``.
end_date: End date as ``YYYY-MM-DD`` or ``MM-DD-YYYY``.
recurring: Whether the task uses recurring dates.
year_range: Two-year range for recurring tasks.
Returns:
AppEEARS point task payload.
"""
params = {
"dates": [
_date_range(
start_date=start_date,
end_date=end_date,
recurring=recurring,
year_range=year_range,
)
],
"layers": list(layers),
"coordinates": _normalize_coordinates(coordinates),
}
return {"task_type": "point", "task_name": task_name, "params": params}
appeears_products(keyword=None, platform=None, available=True, client=None)
¶
List AppEEARS products.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keyword |
Optional[str] |
Case-insensitive search text matched against product fields. |
None |
platform |
Optional[str] |
Platform name, such as |
None |
available |
Optional[bool] |
If set, filter by the AppEEARS availability flag. |
True |
client |
Optional[AppEEARSClient] |
Optional AppEEARS client. |
None |
Returns:
| Type | Description |
|---|---|
List[Dict[str, Any]] |
List of product metadata dictionaries. |
Source code in hypercoast/appeears.py
def appeears_products(
keyword: Optional[str] = None,
platform: Optional[str] = None,
available: Optional[bool] = True,
client: Optional[AppEEARSClient] = None,
) -> List[Dict[str, Any]]:
"""List AppEEARS products.
Args:
keyword: Case-insensitive search text matched against product fields.
platform: Platform name, such as ``"EMIT"``.
available: If set, filter by the AppEEARS availability flag.
client: Optional AppEEARS client.
Returns:
List of product metadata dictionaries.
"""
client = client or AppEEARSClient()
return client.products(keyword=keyword, platform=platform, available=available)
appeears_submit_task(task, client=None)
¶
Submit an AppEEARS task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task |
Dict[str, Any] |
AppEEARS task payload. |
required |
client |
Optional[AppEEARSClient] |
Authenticated AppEEARS client. |
None |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
AppEEARS task submission response. |
Source code in hypercoast/appeears.py
def appeears_submit_task(
task: Dict[str, Any],
client: Optional[AppEEARSClient] = None,
) -> Dict[str, Any]:
"""Submit an AppEEARS task.
Args:
task: AppEEARS task payload.
client: Authenticated AppEEARS client.
Returns:
AppEEARS task submission response.
"""
if client is None:
client = appeears_login()
return client.submit_task(task)
appeears_wait(task_id, client=None, interval=20, timeout=None, verbose=True)
¶
Wait for an AppEEARS task to finish.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id |
str |
AppEEARS task identifier. |
required |
client |
Optional[AppEEARSClient] |
Authenticated AppEEARS client. |
None |
interval |
int |
Polling interval in seconds. |
20 |
timeout |
Optional[int] |
Optional timeout in seconds. |
None |
verbose |
bool |
Whether to print status changes. |
True |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Final task metadata response. |
Source code in hypercoast/appeears.py
def appeears_wait(
task_id: str,
client: Optional[AppEEARSClient] = None,
interval: int = 20,
timeout: Optional[int] = None,
verbose: bool = True,
) -> Dict[str, Any]:
"""Wait for an AppEEARS task to finish.
Args:
task_id: AppEEARS task identifier.
client: Authenticated AppEEARS client.
interval: Polling interval in seconds.
timeout: Optional timeout in seconds.
verbose: Whether to print status changes.
Returns:
Final task metadata response.
"""
if client is None:
client = appeears_login()
return client.wait_for_task(
task_id=task_id, interval=interval, timeout=timeout, verbose=verbose
)
read_appeears(files, layers=None, product='EMIT_L2A_RFL.001', variable='reflectance', client=None, **kwargs)
¶
Read AppEEARS raster output as an xarray hyperspectral cube.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
files |
Union[str, os.PathLike[str], Sequence[Union[str, os.PathLike[str]]]] |
GeoTIFF or NetCDF path, directory, or sequence of paths. |
required |
layers |
Optional[Union[Dict[str, Dict[str, Any]], Sequence[Dict[str, Any]]]] |
Optional AppEEARS layer metadata from |
None |
product |
str |
AppEEARS product used to infer wavelength metadata when layers are omitted. |
'EMIT_L2A_RFL.001' |
variable |
str |
Output data variable name. |
'reflectance' |
client |
Optional[AppEEARSClient] |
Optional AppEEARS client used to fetch layer metadata. |
None |
**kwargs |
Any |
Additional keyword arguments passed to
|
{} |
Returns:
| Type | Description |
|---|---|
Any |
xarray.Dataset containing the stacked raster data. |
Source code in hypercoast/appeears.py
def read_appeears(
files: Union[str, os.PathLike[str], Sequence[Union[str, os.PathLike[str]]]],
layers: Optional[Union[Dict[str, Dict[str, Any]], Sequence[Dict[str, Any]]]] = None,
product: str = EMIT_REFLECTANCE_PRODUCT,
variable: str = "reflectance",
client: Optional[AppEEARSClient] = None,
**kwargs: Any,
) -> Any:
"""Read AppEEARS raster output as an xarray hyperspectral cube.
Args:
files: GeoTIFF or NetCDF path, directory, or sequence of paths.
layers: Optional AppEEARS layer metadata from ``appeears_layers`` or
``appeears_emit_layers(..., return_metadata=True)``.
product: AppEEARS product used to infer wavelength metadata when layers
are omitted.
variable: Output data variable name.
client: Optional AppEEARS client used to fetch layer metadata.
**kwargs: Additional keyword arguments passed to
``rioxarray.open_rasterio``.
Returns:
xarray.Dataset containing the stacked raster data.
"""
paths = _coerce_paths(files)
if layers is None:
client = client or AppEEARSClient()
layers = client.layers(product)
metadata = _metadata_by_layer(product, layers)
if paths and all(Path(path).suffix.lower() in {".nc", ".nc4"} for path in paths):
return _read_appeears_netcdf(paths, metadata, product, variable, **kwargs)
import rioxarray
import xarray as xr
arrays = []
wavelengths = []
band_names = []
for path in paths:
layer_name = _layer_from_filename(path)
if layer_name is None:
continue
layer_info = metadata.get(layer_name)
wavelength = None if layer_info is None else layer_info.get("wavelength")
if wavelength is None:
continue
data = rioxarray.open_rasterio(path, masked=True, **kwargs)
if "band" in data.dims and data.sizes.get("band") == 1:
data = data.squeeze("band", drop=True)
arrays.append(data)
wavelengths.append(float(wavelength))
band_names.append(layer_name)
if not arrays:
raise ValueError("No AppEEARS spectral GeoTIFF files could be read.")
order = sorted(range(len(wavelengths)), key=lambda index: wavelengths[index])
arrays = [arrays[index] for index in order]
wavelengths = [wavelengths[index] for index in order]
band_names = [band_names[index] for index in order]
data = xr.concat(
arrays,
dim=xr.DataArray(wavelengths, dims="wavelength", name="wavelength"),
)
dataset = data.to_dataset(name=variable)
dataset[variable].attrs["band_names"] = band_names
dataset.attrs["source"] = "NASA AppEEARS"
dataset.attrs["product"] = product
return dataset