Skip to content

Commit 1364d2f

Browse files
committed
wip with query
1 parent ab58444 commit 1364d2f

File tree

9 files changed

+150
-16
lines changed

9 files changed

+150
-16
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ catalogs:
2525
username: youruser
2626
password: yourpass
2727
```
28+
# load from the environment variable - SUPERSTAC_CATALOG_CONFIG
2829

2930
# Todo - auth configuration documentation.
3031

superstac/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
from superstac.catalog_registry import (
2+
register_catalog,
3+
get_catalog_registry,
4+
load_catalogs_from_config,
5+
)
16

7+
__all__ = ["register_catalog", "get_catalog_registry", "load_catalogs_from_config"]

superstac/catalog.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ def get_available_catalogs(
9898
return available
9999

100100
def load_catalogs_from_config(
101-
self, file: Union[str, Path, None] = None
101+
self, config: Union[str, Path, None] = None
102102
) -> Dict[str, CatalogEntry]:
103103
"""Load catalogs from configuration file.
104104
105105
Args:
106-
file (Union[str, Path, None], optional): Path to the configuration file. Defaults to None.
106+
config (Union[str, Path, None], optional): Path to the configuration file. Defaults to None.
107107
108108
Raises:
109109
CatalogConfigFileNotFound: Raised when the catalog config file is not founds.
@@ -114,11 +114,12 @@ def load_catalogs_from_config(
114114
Dict[str, CatalogEntry]: The registered catalogs.
115115
"""
116116
logger.info("Loading catalogs from configuration file.")
117-
if file is None:
117+
if config is None:
118+
# todo. - make public and environment variable
118119
base_dir = Path(__file__).parent
119-
file = base_dir / ".superstac.yml"
120+
config = base_dir / ".superstac.yml"
120121

121-
path = Path(file).expanduser().resolve()
122+
path = Path(config).expanduser().resolve()
122123
logger.debug(f"Resolved config path: {path}")
123124

124125
if not path.exists():
@@ -161,14 +162,3 @@ def load_catalogs_from_config(
161162
logger.warning(f"Failed to register catalog '{name}': {e}")
162163
logger.info("All catalogs loaded and registered.")
163164
return self.catalogs
164-
165-
166-
## TEST
167-
168-
169-
if __name__ == "__main__":
170-
# cm = CatalogManager()
171-
# cm.register_catalog(name="My Catalog", url="https://example.com/stac")
172-
# print(cm.load_catalogs_from_config())
173-
# print(cm.get_available_catalogs())
174-
...

superstac/catalog_registry.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""SuperSTAC Catalog Registry"""
2+
3+
from typing import Union
4+
from superstac.catalog import CatalogManager
5+
6+
7+
_catalog_registry = CatalogManager()
8+
9+
10+
def get_catalog_registry() -> CatalogManager:
11+
"""
12+
Returns the singleton CatalogManager instance.
13+
"""
14+
return _catalog_registry
15+
16+
17+
def register_catalog(*args, **kwargs):
18+
"""
19+
Shortcut to register a catalog globally.
20+
"""
21+
return _catalog_registry.register_catalog(*args, **kwargs)
22+
23+
24+
def load_catalogs_from_config(file: Union[str, None] = None):
25+
"""
26+
Loads catalogs from YAML into the global registry.
27+
"""
28+
return _catalog_registry.load_catalogs_from_config(file)
29+
30+
31+
def clear_registry():
32+
"""
33+
Optional: Reset the registry, mainly for testing.
34+
"""
35+
_catalog_registry.catalogs.clear()

superstac/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""SuperSTAC Enums"""
2+
13
from enum import Enum
24

35

superstac/exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
"""SuperSTAC Exceptions"""
2+
3+
14
class InvalidCatalogSchemaError(Exception):
25
"""Raised when an invalid catalog schema is provided."""
36

superstac/query.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""SuperSTAC Query Manager"""
2+
3+
from typing import List, Optional, Dict, Any
4+
from pystac_client import Client
5+
from pystac import Item
6+
from superstac._logging import logger
7+
from superstac.utils import BAND_MAP
8+
9+
10+
# todo - support auth and headers for client.open() in query_catalog_with_pystac param.
11+
# test resolving band alias
12+
13+
14+
def query_catalog_with_pystac(
15+
name: str,
16+
url: str,
17+
required_assets: Optional[List[str]] = None,
18+
max_items: int = 100,
19+
**search_kwargs: Any,
20+
) -> List[Dict[str, Any]]:
21+
"""
22+
Search a single catalog using pystac-client and normalize the assets.
23+
24+
Args:
25+
name: Unique catalog name.
26+
url: STAC API URL.
27+
required_assets: Band aliases to filter and rename assets.
28+
max_items: Maximum number of results.
29+
**search_kwargs: All valid parameters supported by pystac-client's search().
30+
31+
Returns:
32+
List of STAC items (as dicts), normalized with filtered assets and catalog_name.
33+
"""
34+
try:
35+
client = Client.open(url)
36+
logger.debug(f"Querying STAC with: {search_kwargs.items()}")
37+
search = client.search(max_items=max_items, **search_kwargs)
38+
items: List[Item] = list(search.items())
39+
40+
collection_id = None
41+
collections = search_kwargs.get("collections")
42+
if isinstance(collections, list) and collections:
43+
collection_id = collections[0]
44+
elif isinstance(collections, str):
45+
collection_id = collections
46+
47+
band_map = BAND_MAP.get(collection_id, {})
48+
normalized = []
49+
50+
for item in items:
51+
item_assets = item.assets
52+
normalized_assets = {}
53+
54+
for alias, actual in band_map.items():
55+
if not required_assets or alias in required_assets:
56+
if actual in item_assets:
57+
normalized_assets[alias] = item_assets[actual].to_dict()
58+
59+
if normalized_assets:
60+
item_dict = item.to_dict()
61+
item_dict["assets"] = normalized_assets
62+
item_dict["catalog_name"] = name
63+
normalized.append(item_dict)
64+
65+
logger.info(
66+
f"[{name}] Returned {len(normalized)} items for collection '{collection_id}'"
67+
)
68+
return normalized
69+
70+
except Exception as e:
71+
logger.warning(f"[{name}] Catalog query failed: {e}")
72+
return []
73+
74+
75+
if __name__ == "__main__":
76+
results = query_catalog_with_pystac(
77+
name="aws",
78+
url="https://earth-search.aws.element84.com/v1",
79+
collections=["sentinel-2-l2a"],
80+
bbox=[6.0, 49.0, 7.0, 50.0],
81+
datetime="2024-01-01/2024-01-31",
82+
query={"eo:cloud_cover": {"lt": 20}},
83+
required_assets=["red", "nir"],
84+
sortby=[{"field": "properties.datetime", "direction": "desc"}],
85+
)
86+
print(results)

superstac/search.py

Whitespace-only changes.

superstac/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""SuperSTAC utils"""
2+
13
import uuid
24

35

@@ -13,3 +15,12 @@ def compute_catalog_id(name: str, url: str) -> str:
1315
"""
1416
uid = uuid.uuid4().hex[:10]
1517
return f"{name.lower().replace(' ', '_')}_{uid}"
18+
19+
20+
# Band map between Element 84's STAC and Planetary Computer
21+
# allow users to provide band map ?
22+
BAND_MAP = {
23+
"sentinel-2-l2a": {"red": "B04", "green": "B03", "blue": "B02", "nir": "B08"},
24+
"landsat-8": {"red": "SR_B4", "green": "SR_B3", "blue": "SR_B2", "nir": "SR_B5"},
25+
"modis": {"red": "sur_refl_b01", "nir": "sur_refl_b02"},
26+
}

0 commit comments

Comments
 (0)