Metadata-Version: 2.4
Name: tern-stac
Version: 0.4.4
Summary: Python helpers for working with TERN STAC data
Author: TERN STAC Team
License: MIT
Project-URL: Homepage, https://github.com/TERN-Open-Data/tern-stac
Project-URL: Issues, https://github.com/TERN-Open-Data/tern-stac/issues
Keywords: stac,tern,rasterio,xarray,geopandas,pystac
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pystac-client>=0.9.0
Requires-Dist: pystac>=1.12.0
Provides-Extra: rasterio
Requires-Dist: rasterio>=1.3.0; extra == "rasterio"
Provides-Extra: xarray
Requires-Dist: xarray>=2024.1.0; extra == "xarray"
Requires-Dist: rioxarray>=0.16.0; extra == "xarray"
Provides-Extra: plot
Requires-Dist: matplotlib>=3.8.0; extra == "plot"
Requires-Dist: xarray>=2024.1.0; extra == "plot"
Provides-Extra: stackstac
Requires-Dist: stackstac>=0.5.1; extra == "stackstac"
Requires-Dist: dask[array]>=2024.1.0; extra == "stackstac"
Requires-Dist: xarray>=2024.1.0; extra == "stackstac"
Requires-Dist: rasterio>=1.3.0; extra == "stackstac"
Provides-Extra: odc
Requires-Dist: odc-stac>=0.3.0; extra == "odc"
Requires-Dist: odc-geo>=0.4.0; extra == "odc"
Requires-Dist: xarray>=2024.1.0; extra == "odc"
Requires-Dist: rioxarray>=0.16.0; extra == "odc"
Provides-Extra: lidar
Requires-Dist: laspy>=2.5.0; extra == "lidar"
Requires-Dist: lazrs>=0.6.0; extra == "lidar"
Requires-Dist: odc-geo>=0.4.0; extra == "lidar"
Requires-Dist: numpy>=1.24.0; extra == "lidar"
Requires-Dist: xarray>=2024.1.0; extra == "lidar"
Provides-Extra: geopandas
Requires-Dist: geopandas>=0.14.0; extra == "geopandas"
Provides-Extra: all
Requires-Dist: rasterio>=1.3.0; extra == "all"
Requires-Dist: xarray>=2024.1.0; extra == "all"
Requires-Dist: rioxarray>=0.16.0; extra == "all"
Requires-Dist: geopandas>=0.14.0; extra == "all"
Requires-Dist: odc-stac>=0.3.0; extra == "all"
Requires-Dist: odc-geo>=0.4.0; extra == "all"
Requires-Dist: laspy>=2.5.0; extra == "all"
Requires-Dist: lazrs>=0.6.0; extra == "all"
Requires-Dist: matplotlib>=3.8.0; extra == "all"
Requires-Dist: stackstac>=0.5.1; extra == "all"
Requires-Dist: dask[array]>=2024.1.0; extra == "all"
Dynamic: license-file

# TERN STAC

Find and read TERN environmental data from Python. `tern-stac` connects to the
[TERN STAC API](https://stac-api.tern.org.au/) and provides helpers for opening
raster data (gridded maps and imagery), vector data (points, lines and polygons),
and time series.

You can explore datasets in a web browser before writing any Python. Start by
[finding a collection ID](#2-find-a-collection-id), then follow the small search
example below. Searching returns descriptions and file links; reading the data
is a separate step.

## Contents

- [Install](#1-install)
- [Find a collection ID](#2-find-a-collection-id)
- [Search for data](#3-search-for-data)
- [Set up a TERN API key](#4-set-up-a-tern-api-key-to-read-data)
- [Choose and read a file](#5-choose-and-read-a-file)
- [Example notebooks](#example-notebooks)
- [More helpers](#more-helpers)
- [Troubleshooting](#troubleshooting)
- [For maintainers](#for-maintainers)

## 1. Install

The package declares Python 3.9 or later; optional data libraries may require a
newer version. Run this in a terminal (Command Prompt or PowerShell on Windows):

```bash
python -m pip install tern-stac
```

If your system uses `python3`, replace `python` with `python3` in terminal commands.
In a Jupyter notebook, use `%pip install tern-stac` in a code cell instead.
To upgrade an existing installation:

```bash
python -m pip install --upgrade tern-stac
```

The basic installation is enough to search collections and inspect file links.
Install additional tools when you are ready to read or plot data:

| What you want to do | Terminal command |
| --- | --- |
| Read a raster file with Rasterio | `python -m pip install "tern-stac[rasterio]"` |
| Work with labelled raster arrays in xarray | `python -m pip install "tern-stac[xarray]"` |
| Read vector files with GeoPandas | `python -m pip install "tern-stac[geopandas]"` |
| Load multiple imagery items with ODC | `python -m pip install "tern-stac[odc]"` |
| Plot raster maps and time series | `python -m pip install "tern-stac[plot]"` |
| Read LiDAR point clouds with laspy | `python -m pip install "tern-stac[lidar]"` |
| Stack compatible raster items with stackstac | `python -m pip install "tern-stac[stackstac]"` |
| Install all of the above | `python -m pip install "tern-stac[all]"` |

Extras can be combined, for example `"tern-stac[xarray,plot]"`. Workflows using
`chunks=True` also need Dask: `python -m pip install "dask[array]"`.

## 2. Find a collection ID

STAC (SpatioTemporal Asset Catalog) describes data using three main terms:

| Term | Meaning |
| --- | --- |
| **Collection** | A group of related data, such as a soil carbon product. Its **collection ID** is the exact text used to select it in an API query. |
| **Item** | One record within a collection, often describing a location, date or survey. An item ID selects that record. |
| **Asset** | A file linked from a collection or item, such as a GeoTIFF, point cloud or preview image. Its **asset key** selects that file within the record. |

There are two browser-based ways to find the collection ID. No Python is needed
for either approach. Start with the API browser search (Option A).

### Option A: Search the TERN API browser (preferred)

1. Open the [TERN STAC API browser](https://stac-api.tern.org.au/stac-browser).
2. Search for collections and open the collection you need.
3. Look at the URL in your browser's address bar. Copy the text immediately after
   `collections/`, stopping at the next `/`, `?` or `#`, if present.

For example, this [soil carbon collection](https://stac-api.tern.org.au/stac-browser/collections/curtin_curtin__soil_carbon_sequestration_collection)
has this URL:

```text
https://stac-api.tern.org.au/stac-browser/collections/curtin_curtin__soil_carbon_sequestration_collection
```

Its collection ID is:

```text
curtin_curtin__soil_carbon_sequestration_collection
```

### Option B: Browse the TERN data catalogue

1. Open the [TERN data STAC browser](https://data.tern.org.au/stac-browser).
2. Browse to the dataset you need and open its **collection** page.
3. Find the collection-level metadata field **`tern:api_id`** and copy its value.
   If the field is hard to find, use your browser's Find command (`Ctrl+F` or
   `Cmd+F`) to look for `tern:api_id`.

Use the value of `tern:api_id` for API queries. The collection title and the
catalogue's own ID may be different. If you cannot find this field, try Option A.

Copy the ID exactly, including repeated underscores. In Python, use it as a
quoted string inside a list: `collections=[collection_id]`.

The browser URLs are for people. The Python client connects to
`https://stac-api.tern.org.au/` by default; you do not need to pass a browser URL.

## 3. Search for data

### Start with a small search

Run this Python code in a notebook cell or a `.py` file. It uses the soil carbon
collection above and retrieves at most five item descriptions. It does not read
the linked data files or require a data API key.

```python
from tern_stac import TernStacClient

client = TernStacClient()
collection_id = "curtin_curtin__soil_carbon_sequestration_collection"

collection = client.get_collection(collection_id)
print("Collection:", collection.title or collection.id)
print("Description:", collection.description)

search = client.search(collections=[collection_id], max_items=5)
items = list(search.items())
print(f"Retrieved {len(items)} item(s), capped at 5.")

for item in items:
    print(item.id)

if not items:
    print("No items found. Check the collection ID and any search filters.")
```

You should see the collection title, its description and up to five item IDs.
Replace `collection_id` with the ID you found to explore a different collection.
`get_collection()` retrieves collection details; `search()` searches **items**
inside the selected collection. Results are fetched when you iterate over
`search.items()` or convert it to a list.

### Narrow the search by place and date

After the first example, you can add filters. These coordinates describe a small
area around Brisbane; replace them and the dates with values within your chosen
collection's coverage.

```python
search = client.search(
    collections=[collection_id],
    bbox=[152.91, -27.56, 153.15, -27.36],
    datetime="2010-01-01/2020-12-31",
    max_items=10,
)
items = list(search.items())
print(f"Retrieved {len(items)} item(s), capped at 10.")
```

- `bbox` is a rectangle in **[west, south, east, north]** order, using longitude
  and latitude in degrees (WGS84 / EPSG:4326). Australian latitudes are negative.
- `datetime` uses `start/end` dates. You can also use a single year such as
  `"2020"`, or `"2020-01-01/.."` for an open-ended range.
- Filters are combined: an item must match the collection, place **and** date.
  Remove the place and date filters first if nothing is returned.
- A search finds items whose footprints intersect the rectangle. It does not
  crop the files to that rectangle.
- `max_items` caps the total number retrieved. `limit` controls the requested
  page size, so `limit=10` alone does **not** cap the total at ten. Start small
  before removing `max_items` to retrieve all matches.

For more search options, see the
[PySTAC Client search reference](https://pystac-client.readthedocs.io/en/stable/api.html#pystac_client.Client.search).

### Optional: find collections from Python

You can also search collection descriptions using a keyword:

```python
from tern_stac import TernStacClient

client = TernStacClient()
results = client.collection_search(q="soil", max_collections=10)
for collection in results.collections():
    print(collection.id, "—", collection.title)
```

Copy a returned `collection.id` into your item search. Collection searches fetch
at most five collections per page in this wrapper; `max_collections` controls
the total retrieved across pages.

## 4. Set up a TERN API key to read data

You can browse and search public metadata without a key. Reading protected files
on `data.tern.org.au` requires a TERN API key, even if the search succeeded.
Set this up before using the loaders below.

### Get a key

1. Visit the [TERN Account portal](https://account.tern.org.au) and sign in.
2. Select **Create API key** in the left menu.
3. Enter a name so you can recognise the key later.
4. Select **Request API Key** and copy the generated key.

![TERN Account portal showing the steps to create and copy an API key](apikey.png)

### Recommended: save the key in a .netrc file

Create a plain-text file named **`.netrc`** in the home folder of the user running
Python. To find that folder, run:

```python
from pathlib import Path

print(Path.home())
```

Typical locations are:

| System | File location |
| --- | --- |
| Windows | `C:\Users\your-username\.netrc` |
| macOS | `/Users/your-username/.netrc` |
| Linux | `/home/your-username/.netrc` |

In a remote notebook or WSL, use that environment's home folder. On Windows,
ensure your editor saves `.netrc`, not `.netrc.txt`.

Add the following, replacing `YOUR_TERN_API_KEY` with your actual key. Keep
`login apikey` exactly as shown; it is not your account username.

```text
machine data.tern.org.au
  login apikey
  password YOUR_TERN_API_KEY
```

If `.netrc` already exists, edit or add the entry for `data.tern.org.au` and keep
any entries for other services. On macOS/Linux, restrict access to the file:

```bash
chmod 600 ~/.netrc
```

Treat the key like a password: keep it out of notebooks, screenshots and Git.
Restart Python or your notebook kernel after changing authentication settings.

### Alternative: GDAL configuration

For GDAL-based readers such as Rasterio and rioxarray, you can instead create
`~/.gdal/gdalrc` (`C:\Users\your-username\.gdal\gdalrc` on Windows). Create the
`.gdal` folder if needed, and add:

```ini
[credentials]

[.tern_data]
path=/vsicurl/https://data.tern.org.au
GDAL_HTTP_USERPWD=apikey:YOUR_TERN_API_KEY
```

This path-specific configuration requires GDAL 3.5 or later and applies only to
GDAL-based readers. See the [GDAL configuration documentation](https://gdal.org/en/stable/user/configoptions.html#gdal-configuration-file).

## 5. Choose and read a file

### Inspect the available assets first

Run this after an item search from section 3. Asset keys vary by dataset; names
such as `rgb` or `geometry` are not universal.

```python
if not items:
    raise RuntimeError("No items found. Broaden your search before reading data.")

item = items[0]
print("Item:", item.id)
for key, asset in item.assets.items():
    print("Key:", key)
    print("  Title:", asset.title)
    print("  Type:", asset.media_type, "Roles:", asset.roles)
    print("  URL:", asset.href)
```

Choose a file suitable for your task. A `data` role can help identify data files;
other assets may be thumbnails or documentation. Check the title, type and URL
as well. Some collections also link files directly in `collection.assets`.

### Read a raster with Rasterio

Install `"tern-stac[rasterio]"` and configure your key first. Replace the
placeholder below with a **raster asset key printed above**, such as the key for
a GeoTIFF or a GDAL-readable VRT (a file linking raster tiles).

```python
asset_key = "REPLACE_WITH_A_RASTER_ASSET_KEY"
reader = client.load_rasterio(item, asset_key=asset_key)

if reader is None:
    raise RuntimeError("Could not open the data. Check your TERN API key setup.")

with reader as dataset:
    print("Coordinate system:", dataset.crs)
    print("Size:", dataset.width, "x", dataset.height)
    # Read a small preview of the first band to limit memory use.
    preview = dataset.read(
        1,
        out_shape=(min(256, dataset.height), min(256, dataset.width)),
        masked=True,
    )
    print("Preview shape:", preview.shape)
```

Opening a remote asset may read data over the network. A reduced preview limits
array size, but the amount transferred depends on the file layout.

### Other ways to open data

These alternatives use the `client`, `item` and selected `asset_key` above.
Install the corresponding extra and choose an asset of the correct file type.

```python
# Raster as an xarray DataArray; requires tern-stac[xarray].
xda = client.load_xarray(item, asset_key=asset_key)
if xda is not None:
    print(xda)
    xda.close()
```

```python
# Vector file as a GeoDataFrame; requires tern-stac[geopandas].
vector_asset_key = "REPLACE_WITH_A_VECTOR_ASSET_KEY"
gdf = client.load_geodataframe(item, asset_key=vector_asset_key)
if gdf is not None:
    print(gdf.head())
```

An item's footprint describes its coverage; it does not necessarily have a
vector file attached. A raster loader cannot read every asset type.

If you already have a file URL, `load_from_tern(url, backend="rasterio")` opens it
with the chosen backend (`rasterio`, `xarray` or `geopandas`). You can also pass an
item and `asset_key`. Import the helper with `from tern_stac import load_from_tern`.
Authentication and optional dependencies are still required.

## Example notebooks

The [examples folder](examples) contains Jupyter notebooks (`.ipynb` files).
Download or clone this repository to run them locally; installing the package
with pip does not place the notebooks in your working folder.

From the repository folder, run these terminal commands:

```bash
python -m pip install jupyterlab "tern-stac[all]"
python -m jupyterlab
```

In the JupyterLab page that opens, select a notebook from `examples/`. Check the
collection ID, dates, coordinates and asset choices near the top, configure your
API key, then run the cells from top to bottom. You can also open notebooks in
VS Code with its Jupyter extension. Use a Python environment with `tern-stac`
installed. Notebook files are opened in Jupyter, not run with `python file.ipynb`.

| Notebook | What it demonstrates |
| --- | --- |
| [Fractional cover](examples/fractional_cover_api_workflow.ipynb) | Mean raster values over a region through time. |
| [SMIPS soil moisture](examples/smips_point_timeseries_api_workflow.ipynb) | A time series sampled at one location. |
| [Drone imagery](examples/imagery_api_workflow.ipynb) | Load multiple bands with ODC, select a region and preview imagery. |
| [LiDAR](examples/lidar_api_workflow.ipynb) | Find a point-cloud file and read it with laspy. |
| [stackstac](examples/stackstac_api_workflow.ipynb) | Stack and plot Planetary Computer imagery; uses a separate data source and needs `python -m pip install planetary-computer`. |
| [AET evapotranspiration](examples/aet_timeseries_api_workflow.ipynb) | A draft time-series workflow; currently needs the `load_items_as_time_series` import and an `assets` variable supplied before all cells can run. |

`laz_to_canopy_height()` is currently unavailable and raises `NotImplementedError`.
The LiDAR notebook demonstrates point-cloud access, not canopy-height generation.

## More helpers

These tools support more involved analyses. Use the notebooks for dataset-specific
asset choices and coordinate examples.

| Helper | Purpose |
| --- | --- |
| `get_item_asset_href(item, asset_key=...)` | Return one asset URL. Filtering by `media_type` or `role` instead returns a string for one match, a list of strings for multiple matches, and raises `KeyError` for no matches. |
| `load_items_as_time_series(items, ...)` | Load raster assets through time, optionally sampling a point or calculating a region mean. Requires the `xarray` extra and Dask when using `chunks=True`. |
| `load_assets_as_time_series(assets, ...)` | Load collection-level raster assets supplied as a list of STAC Asset objects or dictionaries, with `href` and `datetime` metadata. Optional `geometry` metadata supports spatial filtering. |
| `load_items_odc(items, bands=..., ...)` | Load compatible raster items into an xarray Dataset with ODC. Requires the `odc` extra. |
| `load_items_stackstac(items, assets=..., ...)` | Stack compatible raster items into an xarray DataArray. Requires the `stackstac` extra. |
| `mosaic_time`, `get_array_bounds`, `get_array_epsg` | Combine a stackstac array across time or inspect its spatial reference. |
| `bounds_from_geodataframe`, `spatial_slice`, `mean_over_dims` | Get region bounds, select part of an array and calculate means. |
| `preview_raster`, `plot_time_series`, `explore_odc` | Preview maps, plot time series or explore data on a map. Interactive maps may need additional packages reported by the underlying ODC tools. |

For `load_items_as_time_series`, `point=(longitude, latitude)` defaults to
`point_crs="EPSG:4326"`. Alternatively, pass `clip_bounds=(west, south, east, north)`
with `clip_bounds_crs="EPSG:4326"` to calculate a region mean. Choose either
`point` or `clip_bounds`, not both. With default processing, multiple matching
assets at the same timestamp are combined into a point or region mean. Select
assets representing the quantity you intend to average.

Time-series helpers expect a `datetime` value. For records describing an interval,
inspect their metadata and choose an appropriate field such as
`time_key="start_datetime"` if that represents the time you need. Missing-data
values can be converted to NaN with `to_numpy_nodata=True` before calculating means.

For `spatial_slice`, supply `bounds_crs` when your bounds use a different
coordinate system from the data. `bounds_from_geodataframe` returns bounds in
the GeoDataFrame's existing coordinate system, which may differ from the
longitude/latitude coordinates required by an API search.

For compatible imagery, `load_items_odc` accepts dataset-specific `bands`, while
`load_items_stackstac` accepts `assets`. Check actual asset keys before reusing
band names from another dataset. Chunked arrays defer work: plotting, saving or
calling `.compute()` can trigger network reads and require substantial memory.

### Use another STAC API

```python
from tern_stac import TernStacClient

client = TernStacClient(api_url="https://your.stac.endpoint/")
```

Replace the example URL with the other API's root URL. Alternatively, set the
`TERN_STAC_URL` environment variable before creating the client. An explicit
`api_url` takes precedence. Other providers may need their own authentication.

For a quick connection check from a terminal, run `tern-stac`. It prints the
connected catalogue's basic metadata; `tern-stac --url URL` overrides the endpoint.

## Troubleshooting

| Problem | What to try |
| --- | --- |
| No items returned | Copy the exact API collection ID again, remove date/place filters, then add them back one at a time. Check the collection's spatial and temporal coverage in the browser. |
| Collection not found | Use `tern:api_id` from the data catalogue or the ID after `collections/` in the API browser. Keep repeated underscores; do not pass the full browser URL. |
| `401 Unauthorized`, an authentication warning, or a loader returning `None` | Check the `.netrc` location, hostname, `login apikey` and key value. Restart the kernel. Some loaders warn and return `None` on a detected HTTP 401, so fix authentication before using the result. |
| GDAL does not find `.netrc` | With GDAL 3.7+, set `GDAL_HTTP_NETRC_FILE` to the full file path before opening data, or use the GDAL configuration alternative above. |
| `ModuleNotFoundError` or an optional library is missing | Install the extra for your task in the same Python environment as your script or notebook. In notebooks, use `%pip install "tern-stac[xarray]"` (adjust the extra as needed), then restart the kernel. |
| Missing Dask / unrecognised chunk manager | Install `"dask[array]"`, or use `chunks=None` for small reads with the xarray/time-series helpers. |
| Asset key not found / multiple assets available | Print `item.assets` keys as shown above, then pass one explicitly using `asset_key`. |
| Unsupported file format | Check the asset's type and URL. Select a data file supported by your loader, rather than metadata or a thumbnail. |
| Slow query or excessive memory use | Start with `max_items=5`, narrow the date and area, and read a small region or point before loading a full dataset. |
| Search works but reading a file fails | The metadata API and file host are separate services. Check the file URL and authentication; for assets hosted elsewhere, follow that provider's access instructions. |

The explicit netrc path setting is documented in
[GDAL's HTTP options](https://gdal.org/en/stable/user/configoptions.html#config-GDAL_HTTP_NETRC_FILE).
For unresolved library problems, use the
[issue tracker](https://github.com/TERN-Open-Data/tern-stac/issues) with a small code
example, the error message and your package version. Never include an API key.
Get the installed version with `python -m pip show tern-stac`.

## For maintainers

Versions are derived from Git tags by `setuptools_scm`; do not edit
`src/tern_stac/_version.py` manually. The [publishing workflow](.github/workflows/publish.yml)
builds and publishes on pushed tags using OpenID Connect:

- `vX.Y.Z` publishes to PyPI.
- `test-X.Y.Z` publishes to TestPyPI.

To prepare a distribution locally:

```bash
python -m pip install --upgrade build twine
python -m build
python -m twine check dist/*
```

For a manual release, upload the checked distributions with
`python -m twine upload dist/*`. For automated releases, tag the intended commit
with the appropriate version prefix and push that tag.

## License

The library is licensed under [MIT](LICENSE). For a dataset's usage conditions
and citation information, consult its collection metadata.
