|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# Building an analysis-ready data cube from Planetary Computer STAC", |
| 8 | + "", |
| 9 | + "A STAC search returns item metadata: hrefs, dates, cloud cover. Analysis usually wants something else, a single aligned array indexed by time, band, and space, that you can run math across. Two libraries build that cube from STAC items: [odc-stac](https://odc-stac.readthedocs.io/) and [stackstac](https://stackstac.readthedocs.io/). They take the same inputs and produce lazy, Dask-backed xarray objects, but they differ in ways that matter on Planetary Computer data.", |
| 10 | + "", |
| 11 | + "This notebook loads a few low-cloud [Sentinel-2 L2A](https://planetarycomputer.microsoft.com/dataset/sentinel-2-l2a) scenes over Portland with both libraries and compares them. The companion [data cube tutorial](../overview/odc-stac.md) has the full narrative." |
| 12 | + ] |
| 13 | + }, |
| 14 | + { |
| 15 | + "cell_type": "markdown", |
| 16 | + "metadata": {}, |
| 17 | + "source": [ |
| 18 | + "## Install" |
| 19 | + ] |
| 20 | + }, |
| 21 | + { |
| 22 | + "cell_type": "code", |
| 23 | + "execution_count": null, |
| 24 | + "metadata": {}, |
| 25 | + "outputs": [], |
| 26 | + "source": [ |
| 27 | + "%pip install --quiet odc-stac stackstac pystac-client planetary-computer" |
| 28 | + ] |
| 29 | + }, |
| 30 | + { |
| 31 | + "cell_type": "markdown", |
| 32 | + "metadata": {}, |
| 33 | + "source": [ |
| 34 | + "## Search the catalog", |
| 35 | + "", |
| 36 | + "Open the catalog with the Planetary Computer signer so every returned asset href is signed, then search for a few low-cloud Sentinel-2 scenes over Portland.", |
| 37 | + "", |
| 38 | + "**Expected result:** up to four signed Sentinel-2 items." |
| 39 | + ] |
| 40 | + }, |
| 41 | + { |
| 42 | + "cell_type": "code", |
| 43 | + "execution_count": null, |
| 44 | + "metadata": {}, |
| 45 | + "outputs": [], |
| 46 | + "source": [ |
| 47 | + "import pystac_client\n", |
| 48 | + "import planetary_computer\n", |
| 49 | + "\n", |
| 50 | + "catalog = pystac_client.Client.open(\n", |
| 51 | + " \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n", |
| 52 | + " modifier=planetary_computer.sign_inplace,\n", |
| 53 | + ")\n", |
| 54 | + "items = list(catalog.search(\n", |
| 55 | + " collections=[\"sentinel-2-l2a\"],\n", |
| 56 | + " bbox=[-122.7, 45.5, -122.6, 45.6],\n", |
| 57 | + " datetime=\"2024-07-01/2024-08-01\",\n", |
| 58 | + " query={\"eo:cloud_cover\": {\"lt\": 20}},\n", |
| 59 | + " max_items=4,\n", |
| 60 | + ").items())\n", |
| 61 | + "len(items)" |
| 62 | + ] |
| 63 | + }, |
| 64 | + { |
| 65 | + "cell_type": "markdown", |
| 66 | + "metadata": {}, |
| 67 | + "source": [ |
| 68 | + "## Load with odc-stac", |
| 69 | + "", |
| 70 | + "`odc.stac.load` reads the items into an `xarray.Dataset`, one named variable per band. It infers the coordinate reference system and resolution from the STAC metadata, so you only specify what you want to change. The bands arrive as separate `data_vars` (reference them by name, `cube.B04`) and values come back as `float32`.", |
| 71 | + "", |
| 72 | + "**Expected result:** an `xarray.Dataset`, dims `(time, y, x)`, bands `B04`/`B03`/`B02` as `float32`, CRS `EPSG:32610` inferred automatically." |
| 73 | + ] |
| 74 | + }, |
| 75 | + { |
| 76 | + "cell_type": "code", |
| 77 | + "execution_count": null, |
| 78 | + "metadata": {}, |
| 79 | + "outputs": [], |
| 80 | + "source": [ |
| 81 | + "import odc.stac\n", |
| 82 | + "\n", |
| 83 | + "cube = odc.stac.load(\n", |
| 84 | + " items,\n", |
| 85 | + " bands=[\"B04\", \"B03\", \"B02\"],\n", |
| 86 | + " bbox=[-122.7, 45.5, -122.6, 45.6],\n", |
| 87 | + " resolution=10,\n", |
| 88 | + " chunks={\"time\": 1, \"x\": 1024, \"y\": 1024},\n", |
| 89 | + ")\n", |
| 90 | + "cube" |
| 91 | + ] |
| 92 | + }, |
| 93 | + { |
| 94 | + "cell_type": "markdown", |
| 95 | + "metadata": {}, |
| 96 | + "source": [ |
| 97 | + "## Load with stackstac", |
| 98 | + "", |
| 99 | + "`stackstac.stack` produces a single `xarray.DataArray` with a `band` dimension instead of named variables. On Planetary Computer Sentinel-2 it needs one thing odc-stac did not: an explicit CRS. Without `epsg=`, the call raises `Cannot pick a common CRS, since asset 'B04' ... does not have one`, because the Planetary Computer's Sentinel-2 items do not expose a per-asset CRS that stackstac can infer. In exchange, stackstac attaches every STAC item property to the cube as a coordinate.", |
| 100 | + "", |
| 101 | + "**Expected result:** an `xarray.DataArray`, dims `(time, band, y, x)`, `float64`, with item properties attached as coordinates." |
| 102 | + ] |
| 103 | + }, |
| 104 | + { |
| 105 | + "cell_type": "code", |
| 106 | + "execution_count": null, |
| 107 | + "metadata": {}, |
| 108 | + "outputs": [], |
| 109 | + "source": [ |
| 110 | + "import stackstac\n", |
| 111 | + "\n", |
| 112 | + "cube = stackstac.stack(\n", |
| 113 | + " items,\n", |
| 114 | + " assets=[\"B04\", \"B03\", \"B02\"],\n", |
| 115 | + " bounds_latlon=[-122.7, 45.5, -122.6, 45.6],\n", |
| 116 | + " resolution=10,\n", |
| 117 | + " epsg=32610, # required: see above\n", |
| 118 | + " chunksize=1024,\n", |
| 119 | + ")\n", |
| 120 | + "cube" |
| 121 | + ] |
| 122 | + }, |
| 123 | + { |
| 124 | + "cell_type": "markdown", |
| 125 | + "metadata": {}, |
| 126 | + "source": [ |
| 127 | + "## How they compare", |
| 128 | + "", |
| 129 | + "| | odc-stac | stackstac |", |
| 130 | + "|---|---|---|", |
| 131 | + "| CRS on PC Sentinel-2 | inferred automatically | must pass `epsg=` |", |
| 132 | + "| Shape | `Dataset`, named bands, dims `(time, y, x)` | `DataArray`, `band` dim, dims `(time, band, y, x)` |", |
| 133 | + "| dtype | `float32` | `float64` (twice the memory) |", |
| 134 | + "| STAC metadata | not attached | every item property as a coordinate |" |
| 135 | + ] |
| 136 | + }, |
| 137 | + { |
| 138 | + "cell_type": "markdown", |
| 139 | + "metadata": {}, |
| 140 | + "source": [ |
| 141 | + "## A recommendation", |
| 142 | + "", |
| 143 | + "For Planetary Computer work, odc-stac is the smoother default. It infers the CRS, returns named bands, and uses `float32`, which halves memory before you have done anything. stackstac earns its place when you want the full STAC metadata riding along on the cube as coordinates, or when you already have stackstac code and a single `DataArray` fits your pipeline.", |
| 144 | + "", |
| 145 | + "Migrating a stackstac call to odc-stac is mostly renaming: `assets` becomes `bands`, `bounds_latlon` becomes `bbox`, and you can drop the `epsg=` argument. Reference bands by name afterward rather than selecting along a `band` dimension." |
| 146 | + ] |
| 147 | + }, |
| 148 | + { |
| 149 | + "cell_type": "markdown", |
| 150 | + "metadata": {}, |
| 151 | + "source": [ |
| 152 | + "## When to use something else", |
| 153 | + "", |
| 154 | + "A cube is the right shape for time-series and multi-band analysis across an area. When you only need pixels from a single scene, the cube machinery is overhead; read the window directly with [async-geotiff](../overview/async-geotiff.md) instead." |
| 155 | + ] |
| 156 | + } |
| 157 | + ], |
| 158 | + "metadata": { |
| 159 | + "kernelspec": { |
| 160 | + "display_name": "Python 3", |
| 161 | + "language": "python", |
| 162 | + "name": "python3" |
| 163 | + }, |
| 164 | + "language_info": { |
| 165 | + "name": "python", |
| 166 | + "version": "3.12.12" |
| 167 | + } |
| 168 | + }, |
| 169 | + "nbformat": 4, |
| 170 | + "nbformat_minor": 5 |
| 171 | +} |
0 commit comments