Metadata-Version: 2.4
Name: linz-stac-utils
Version: 0.1.0
Summary: Load public LINZ elevation data from the nz-elevation STAC catalog.
Keywords: elevation,geospatial,linz,stac
Author: Quinn Hornblow
Author-email: Quinn Hornblow <quinn.hornblow@protonmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Dist: numpy>=1.24
Requires-Dist: odc-geo>=0.5.1
Requires-Dist: odc-stac>=0.5.2
Requires-Dist: platformdirs>=4.9.4
Requires-Dist: pystac>=1.14.3
Requires-Dist: pystac-client>=0.9.0
Requires-Dist: requests-cache>=1.3.1
Requires-Dist: shapely>=2.1.2
Requires-Dist: xarray>=2023.1
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/quinnhornblow/linz-stac-utils
Project-URL: Issues, https://github.com/quinnhornblow/linz-stac-utils/issues
Project-URL: Repository, https://github.com/quinnhornblow/linz-stac-utils
Description-Content-Type: text/markdown

# linz-stac-utils

`linz-stac-utils` is a small Python library for querying and loading public LINZ elevation datasets from the `nz-elevation` STAC catalog hosted in S3.

It exists to make LINZ elevation access simpler in Python scripts and notebooks. Instead of repeatedly wiring together `pystac-client`, cached catalog requests, and `odc.stac` loading logic, this package provides a thin reusable wrapper around that workflow.

## What It Does

- opens the public LINZ elevation STAC catalog
- fetches collection and item metadata
- loads STAC results into `xarray` objects with `odc.stac`
- loads a New Zealand elevation surface, preferring the LiDAR 1 m DEM and using the contour-interpolated 8 m DEM to fill LiDAR gaps
- optionally exports the loaded surface as a Cloud Optimized GeoTIFF

## Current Scope

- Python API only
- focused on the public `nz-elevation` catalog
- aimed at data access, loading, and optional DEM export rather than a general CLI workflow

This is an independent project and is not affiliated with or endorsed by Land
Information New Zealand (LINZ).

## Installation

Requirements:

- Python 3.11+

Install with `uv`:

```bash
uv add linz-stac-utils
```

Or install with `pip`:

```bash
python -m pip install linz-stac-utils
```

## Usage

Load an elevation surface for a bounding box at a chosen output resolution. Valid
LiDAR pixels take precedence; the contour-interpolated 8 m DEM fills locations
without LiDAR coverage:

```python
from linz_stac_utils import load_elevation

elevation = load_elevation(
    bbox=(172.6300, -43.5350, 172.6400, -43.5250),
    resolution=10,
    output_path="christchurch-dem.tif",
)
```

`load_elevation()` follows the spatial portion of `odc.stac.load`:

- Provide exactly one of `bbox` or `intersects`; calls with neither or both are rejected.
- `bbox` is `(min_longitude, min_latitude, max_longitude, max_latitude)` in `EPSG:4326`.
- `intersects` accepts an ODC geometry, Shapely geometry, GeoJSON mapping, or an object with `__geo_interface__`; Shapely and GeoJSON inputs are interpreted as `EPSG:4326`.
- `crs` defaults to `EPSG:2193`; `resolution` is required and is in the output CRS units.
- `intersects` crops and masks the output polygon with all touched pixels retained.
- The latest valid LiDAR value takes precedence for each pixel. The contour
  surface fills only pixels with no valid LiDAR value.
- Supplying `chunks` returns a Dask-backed surface and preserves spatial chunk
  boundaries.
- Set `overwrite=True` to replace an existing output file.

Use a polygon when the rectangular `bbox` is not precise enough:

```python
from shapely.geometry import Polygon

elevation = load_elevation(
    intersects=Polygon(
        [
            (172.6300, -43.5350),
            (172.6400, -43.5350),
            (172.6350, -43.5250),
        ]
    ),
    resolution=10,
)
```

For lower-level catalog access, use `StacCatalogClient` directly:

```python
from linz_stac_utils import StacCatalogClient

client = StacCatalogClient()
dataset = client.load(
    collections=["01JE4ZZWAG19KPKRHYJJP02HC9"],
    bbox=(172.6300, -43.5350, 172.6400, -43.5250),
    resolution=1000,
)
```

`load()` filters static-catalog items locally before loading them. It supports
`bbox`, `intersects`, item IDs, and a positive result limit. It defaults to
`EPSG:2193`, and resolutions are specified in the output CRS units.

See the [regional elevation example](https://github.com/quinnhornblow/linz-stac-utils/blob/main/src/examples/elevation.ipynb)
for an interactive workflow.

## Notes

- network access is required to read remote catalog and raster data
- STAC API responses are cached locally with `requests-cache` for one day by default. The cache is created when a client is initialized in your platform's user cache directory, rather than in the installed package directory.
- LINZ describes the contour-interpolated 8 m DEM as suitable for cartographic visualization and not suitable for terrain analysis. Its use as a fallback does not make the output a LiDAR-quality terrain model.

Configure caching by creating and injecting a STAC IO instance:

```python
from pathlib import Path

from linz_stac_utils import StacCatalogClient, build_stac_io

client = StacCatalogClient(
    stac_io=build_stac_io(
        cache_path=Path("data/stac.sqlite"),
        expire_after=3600,
    )
)
```

Pass `cache=False` to `build_stac_io()` to use an uncached STAC session.

## Development

Install the project and development dependencies:

```bash
uv sync
```

Run tests:

```bash
uv run pytest
```

Run linting:

```bash
uv run ruff check .
```
