Skip to content

Create STAC Package

This tutorial walks through a small stacpkg packaging workflow. You will query the OpenAerialMap STAC API over Austria, derive object metadata facts for the imagery assets, validate those assets, create a package, enrich the item metadata, and write an inspection summary.

The goal is not to cover every option. The goal is to see the shape of the workflow once, end to end. Start with the short path when you only need the package artifact. Use the step-by-step flow when you want reusable intermediate tables for inspection, validation, or later commands.

Short Path

The example queries the OpenAerialMap STAC API at https://api.imagery.hotosm.org/stac for the openaerialmap collection. It uses a small Austria-only bounding box, sorts by start_datetime oldest first, and limits the response to two Items.

OpenAerialMap items can include metadata, thumbnail, and visual assets. stacpkg build skips metadata assets by default, so the package asset lock contains the thumbnail and visual assets used in this sample.

This shortest flow streams the selected STAC Items into a package directory without writing an items checkpoint first. Run the commands in the same shell so $tmpdir remains available:

Note

We recommend the rustac CLI for this short path because it is fast and stream-oriented. You can also use pystac-client or direct HTTP calls; the step-by-step checkpoints below demonstrate the direct HTTP path with curl.

tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/stacpkg-openaerialmap-austria.XXXXXX")
export STACPKG_TUTORIAL_DIR="$tmpdir"
bbox='16.415,47.1705,16.431,47.734' # subset of Austria

rustac --output-format ndjson search \
    https://api.imagery.hotosm.org/stac \
    - \
    --collections openaerialmap \
    --bbox "$bbox" \
    --sortby start_datetime \
    --max-items 2 \
  | stacpkg items from-ndjson \
  | stacpkg build "$tmpdir/openaerialmap-austria.pkg"

echo "created $tmpdir/openaerialmap-austria.pkg"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.pkg

The command creates this package layout:

/tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.pkg/
  items.parquet
  assets.lock.parquet

This package can now be pushed to any OCI-compliant registry; see Distribute The Package below.

Validate the package's asset lock whenever you want to check the recorded object metadata against the current live assets:

stacpkg asset-lock from-parquet "$tmpdir/openaerialmap-austria.pkg/assets.lock.parquet" \
  | stacpkg asset-lock validate

Step-by-Step Checkpoints

The rest of this tutorial expands the same workflow into materialized checkpoints under $tmpdir. This is useful when you want to inspect the selected Items, validate the asset lock before building, or reuse an intermediate table in another command.

Materialize The Items

Create a temporary directory, then use a plain STAC API curl request to fetch one small ItemCollection response. items from-json adapts that STAC JSON into the items Arrow stream, and items to-parquet writes a reusable STAC GeoParquet-style checkpoint:

tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/stacpkg-openaerialmap-austria.XXXXXX")
export STACPKG_TUTORIAL_DIR="$tmpdir"
bbox='16.415,47.1705,16.431,47.734' # subset of Austria

curl -fsS https://api.imagery.hotosm.org/stac/search \
  --header "Accept: application/geo+json" \
  --header "Content-Type: application/json" \
  --data-binary "{
    \"collections\": [\"openaerialmap\"],
    \"bbox\": [$bbox],
    \"sortby\": [{\"field\": \"start_datetime\", \"direction\": \"asc\"}],
    \"limit\": 2
  }" \
  | stacpkg items from-json \
  | stacpkg items to-parquet "$tmpdir/openaerialmap-austria.items.parquet"

echo "created $tmpdir/openaerialmap-austria.items.parquet"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.items.parquet

This curl request asks the STAC API for one two-item response. It does not page through all matches. For larger searches, prefer the NDJSON rustac path from the short flow so stacpkg items from-ndjson can stream item batches into Arrow IPC.

Rendered as selected columns, the source selection now looks like this.

item_id collection start_datetime title asset keys
631ee6653cdf1c0006b63c5b openaerialmap 2022-07-28T13:20:34Z Winzerstrasse - 28.7.2022 metadata, thumbnail, visual
631ee8593cdf1c0006b63c5f openaerialmap 2022-07-28T16:11:00Z Brundlgfangen - 28.7.2022 metadata, thumbnail, visual

Lock Asset Metadata

Create an asset lock with probed metadata for all non-metadata assets:

stacpkg items from-parquet "$tmpdir/openaerialmap-austria.items.parquet" \
  | stacpkg asset-lock derive \
  | stacpkg asset-lock to-parquet "$tmpdir/openaerialmap-austria.assets.lock.parquet"

echo "created $tmpdir/openaerialmap-austria.assets.lock.parquet"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.assets.lock.parquet

The lock has one row per non-metadata asset. For this sample that is two Items times the thumbnail and visual asset keys, or four asset lock rows.

Rendered as selected columns, the metadata lock looks like this:

item_id asset_key store_type store_container key size_bytes etag last_modified
631ee6653cdf1c0006b63c5b thumbnail https https://oin-hotosm-temp.s3.amazonaws.com 631ee60c.../631ee60c...png 793014 "81acb9dabba94aa59ab376948bf46259" 2025-01-16T18:35:01+00:00
631ee6653cdf1c0006b63c5b visual https https://oin-hotosm-temp.s3.amazonaws.com 631ee60c.../631ee60c...tif 9913534 "7d1bfba6a65db543052fa6d7b4187e64-2" 2025-01-16T18:35:01+00:00
631ee8593cdf1c0006b63c5f thumbnail https https://oin-hotosm-temp.s3.amazonaws.com 631ee840.../631ee840...png 609308 "c1797f27c862a7ece7a05dbb4f505171" 2025-01-16T18:35:02+00:00
631ee8593cdf1c0006b63c5f visual https https://oin-hotosm-temp.s3.amazonaws.com 631ee840.../631ee840...tif 5434212 "552f232cc25c681bf4457572522cefbd" 2025-01-16T18:35:02+00:00

Validate The Assets

Use the metadata-probed asset lock to validate the recorded provenance facts against the current live assets:

stacpkg asset-lock from-parquet "$tmpdir/openaerialmap-austria.assets.lock.parquet" \
  | stacpkg asset-lock validate

Sample output:

{"asset_key":"thumbnail","errors":[],"item_id":"631ee6653cdf1c0006b63c5b","key":"631ee60c3cdf1c0006b63c56/0/631ee60c3cdf1c0006b63c57.png","store_container":"https://oin-hotosm-temp.s3.amazonaws.com","store_type":"https","valid":true}
{"asset_key":"visual","errors":[],"item_id":"631ee6653cdf1c0006b63c5b","key":"631ee60c3cdf1c0006b63c56/0/631ee60c3cdf1c0006b63c57.tif","store_container":"https://oin-hotosm-temp.s3.amazonaws.com","store_type":"https","valid":true}
{"asset_key":"thumbnail","errors":[],"item_id":"631ee8593cdf1c0006b63c5f","key":"631ee8403cdf1c0006b63c5d/0/631ee8403cdf1c0006b63c5e.png","store_container":"https://oin-hotosm-temp.s3.amazonaws.com","store_type":"https","valid":true}
{"asset_key":"visual","errors":[],"item_id":"631ee8593cdf1c0006b63c5f","key":"631ee8403cdf1c0006b63c5d/0/631ee8403cdf1c0006b63c5e.tif","store_container":"https://oin-hotosm-temp.s3.amazonaws.com","store_type":"https","valid":true}

Validation prints dynamic JSON lines and exits non-zero if any asset no longer matches the lock.

Build A Package

Create a package directory from the selected Items and the validated asset lock:

stacpkg items from-parquet "$tmpdir/openaerialmap-austria.items.parquet" \
  | stacpkg build "$tmpdir/openaerialmap-austria.pkg" \
      --asset-lock <(stacpkg asset-lock from-parquet \
        "$tmpdir/openaerialmap-austria.assets.lock.parquet")

echo "created $tmpdir/openaerialmap-austria.pkg/items.parquet"
echo "created $tmpdir/openaerialmap-austria.pkg/assets.lock.parquet"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.pkg/items.parquet
created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.pkg/assets.lock.parquet

The package contains:

/tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.pkg/
  items.parquet
  assets.lock.parquet

At this point the package has snapshotted the selected STAC Items and the validated asset lock rows.

Inspect The Asset Lock

Read the package's asset lock when you want to see referenced assets as rows:

stacpkg asset-lock from-parquet "$tmpdir/openaerialmap-austria.pkg/assets.lock.parquet"

Sample terminal output:

item_id                  | asset_key | store_type | size_bytes
-------------------------+-----------+------------+-----------
631ee6653cdf1c0006b63c5b | thumbnail | https      | 793014
631ee6653cdf1c0006b63c5b | visual    | https      | 9913534
4 rows x 9 columns

This matches the four rows from the earlier "Lock Asset Metadata" section: stacpkg build placed that validated asset lock into the package as assets.lock.parquet.

The items table answers which STAC records were selected. The asset lock answers which asset files those records reference and what object metadata was observed.

Enrich The STAC Items

The OpenAerialMap objects are also available through public S3. Write asset metadata and S3 alternate hrefs back into STAC GeoParquet. First create an S3-shaped asset lock from the package lock. This maps HTTPS locations to equivalent s3:// hrefs without copying bytes:

stacpkg asset-lock from-parquet "$tmpdir/openaerialmap-austria.pkg/assets.lock.parquet" \
  | stacpkg asset-lock relocate \
      --dry-run \
      --source-prefix https://oin-hotosm-temp.s3.amazonaws.com/ \
      --store-type s3 \
      --store-container oin-hotosm-temp \
      --store-endpoint-url https://s3.amazonaws.com \
      --layout source-key \
  | stacpkg asset-lock to-parquet "$tmpdir/openaerialmap-austria.s3.assets.lock.parquet"

echo "created $tmpdir/openaerialmap-austria.s3.assets.lock.parquet"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.s3.assets.lock.parquet

Then enrich the items from that mapped lock:

stacpkg items from-parquet "$tmpdir/openaerialmap-austria.pkg/items.parquet" \
  | stacpkg items enrich \
      --asset-lock <(stacpkg asset-lock from-parquet \
        "$tmpdir/openaerialmap-austria.s3.assets.lock.parquet") \
      --alternate-key s3 \
  | stacpkg items to-parquet "$tmpdir/openaerialmap-austria.enriched.items.parquet"

echo "created $tmpdir/openaerialmap-austria.enriched.items.parquet"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.enriched.items.parquet

This keeps the asset lock as the package contract while making selected fields and alternate.s3.href values available to STAC clients.

For the first selected thumbnail, the enriched STAC asset now includes:

{
  "href": "https://oin-hotosm-temp.s3.amazonaws.com/631ee60c3cdf1c0006b63c56/0/631ee60c3cdf1c0006b63c57.png",
  "alternate": {
    "s3": {
      "href": "s3://oin-hotosm-temp/631ee60c3cdf1c0006b63c56/0/631ee60c3cdf1c0006b63c57.png"
    }
  }
}

Inspect The Package

Create a package summary:

stacpkg inspect "$tmpdir/openaerialmap-austria.pkg" > "$tmpdir/inspect.yaml"

echo "created $tmpdir/inspect.yaml"

Sample output:

created /tmp/stacpkg-openaerialmap-austria.ABC123/inspect.yaml

Sample $tmpdir/inspect.yaml contents:

package: "/tmp/stacpkg-openaerialmap-austria.ABC123/openaerialmap-austria.pkg"
items:
  count: 2
  collections:
    - "openaerialmap"
assets:
  count: 4
  asset_keys:
    - "thumbnail"
    - "visual"
  known_size_bytes: 16750068
  known_size_count: 4

Open $tmpdir/inspect.yaml and check the item count, asset count, and package files.

Distribute The Package

You can now push this package to any OCI-compliant registry and pull it back in another environment. Use $tmpdir/openaerialmap-austria.pkg as the package directory for stacpkg push; stacpkg pull reconstructs the same items.parquet and assets.lock.parquet files.

See the package commands in the CLI reference for the push and pull syntax, including local HTTP registry options.

What You Learned

You streamed an OpenAerialMap Austria STAC search directly into a package, then expanded the same workflow with materialized checkpoints. The package now has:

  • the exact selected Items in items.parquet;
  • validated object metadata in assets.lock.parquet;
  • package file metadata derived from the package directory and OCI descriptors;
  • dynamic validation results for current assets;
  • an enriched items table and a package inspection summary;
  • an OCI-distributable package that can be pushed and pulled.

Related executable coverage for OpenAerialMap packaging lives in tests/e2e/test_usecase_openaerialmap_s3_alternate_package.py.