Docs add async geotiff tutorial#528
Conversation
Adds a tutorial walking through pixel-level Cloud Optimized GeoTIFF reads with async-geotiff — no GDAL dependency, async-first, Rust core. Companion notebook lives in PlanetaryComputerExamples at quickstarts/async-geotiff.ipynb and is wired in via external_docs_config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nshots Replace nonexistent APIs (obstore async_=True, GeoTIFF.open(full_href), generate_tms, lonboard RasterLayer.from_stac) with the shipping equivalents: from_asset(asset) + empty open() path, the Sentinel-2 visual RGB asset, and a BitmapTileLayer fed by the Planetary Computer tiler. Add the window-preview and Lonboard-scene screenshots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| @@ -0,0 +1,164 @@ | |||
| # Reading Planetary Computer COGs with async-geotiff | |||
|
|
|||
| [async-geotiff](https://github.com/developmentseed/async-geotiff) is a Python Cloud Optimized GeoTIFF reader with no GDAL dependency. The core is Rust, image decoding runs in a thread pool, buffers are zero-copy, and every API is fully type-hinted. Use it when you want async I/O for pixel-level analysis without putting GDAL on the system. | |||
There was a problem hiding this comment.
| [async-geotiff](https://github.com/developmentseed/async-geotiff) is a Python Cloud Optimized GeoTIFF reader with no GDAL dependency. The core is Rust, image decoding runs in a thread pool, buffers are zero-copy, and every API is fully type-hinted. Use it when you want async I/O for pixel-level analysis without putting GDAL on the system. | |
| [async-geotiff](https://github.com/developmentseed/async-geotiff) is a Python [Cloud Optimized GeoTIFF](https://cogeo.org/) reader with no GDAL dependency. The core is Rust, image decoding runs in a thread pool, buffers are zero-copy, and every API is fully type-hinted. Use it when you want async I/O for pixel-level analysis without putting GDAL on the system. |
| uv add async-geotiff obstore planetary-computer pystac-client lonboard matplotlib | ||
| ``` | ||
|
|
||
| `async-geotiff` is the user-facing library. `async-tiff` is the lower-level Rust core. Use it directly only if you're building library infrastructure on top. |
There was a problem hiding this comment.
| `async-geotiff` is the user-facing library. `async-tiff` is the lower-level Rust core. Use it directly only if you're building library infrastructure on top. | |
| `async-geotiff` is the high-level library for reading GeoTIFF and COG files. `async-tiff` is the lower-level Rust core for generically reading TIFF files. It shouldn't be necessary to touch for most users. |
| print(geotiff.overviews) # finest → coarsest | ||
| ``` | ||
|
|
||
| The header read is a single range request. This is the same pattern used by [obstore.](./obstore.md) |
There was a problem hiding this comment.
| The header read is a single range request. This is the same pattern used by [obstore.](./obstore.md) | |
| The header read usually fits in one or two range requests, facilitated by [obstore](./obstore.md). |
|
|
||
| ## Pick an overview | ||
|
|
||
| `geotiff.overviews` is ordered finest-to-coarsest. Index `0` is the full-resolution image. A coarser overview is the right choice for previews or zoomed-out work: |
There was a problem hiding this comment.
No, the geotiff itself is the full-resolution image. Index 0 of the overviews is the finest resolution after the full-resolution data.
| array = await full_res.read(window=window) | ||
| ``` | ||
|
|
||
| The returned `Array` has: |
There was a problem hiding this comment.
The returned class is named RasterArray https://developmentseed.org/async-geotiff/latest/api/raster-array/
| import numpy as np | ||
| import matplotlib.pyplot as plt | ||
|
|
||
| plt.imshow(np.transpose(array.data, (1, 2, 0))) |
There was a problem hiding this comment.
In case it's clearer, this is shipped as reshape_as_image so that users don't have to remember the band ordering (1, 2, 0) is easy to forget or get wrong IMO
|
|
||
| ## Visualize the scene with Lonboard | ||
|
|
||
| For an interactive map view of the same Sentinel-2 item, stream its COG tiles through the Planetary Computer tiler into a [Lonboard](https://developmentseed.org/lonboard/) `BitmapTileLayer`: |
There was a problem hiding this comment.
As a note, the BitmapTileLayer uses titiler on the backend to send formatted PNG tiles, this doesn't read the COG data directly. That's fine if you'd like to point out how this integrates with the planetary computer titiler server!
In case you want an example of how to read the COG data directly through async-geotiff, you'd want to use RasterLayer.from_geotiff which natively integrates with async-geotiff.
| Each `read()` is independent. Fire many at once with `asyncio.gather` | ||
|
|
||
| async-geotiff issues range requests in parallel and decodes them on the Rust thread pool: | ||
|
|
||
| ```python | ||
| import asyncio | ||
|
|
||
| windows = [ | ||
| Window(c, r, 256, 256) | ||
| for c in range(0, 2048, 256) for r in range(0, 2048, 256) | ||
| ] | ||
| arrays = await asyncio.gather( | ||
| *[full_res.read(window=w) for w in windows] | ||
| ) | ||
| ``` |
There was a problem hiding this comment.
Similar to #527 (comment), this isn't an ideal example to suggest to people, because we're instructing them to make entirely independent requests for each window of the file.
But this is something that read handles automatically, and it'll be faster because it can minimize the total number of requests that need to be made.
So here, a single
window = Window(0, 0, 2048, 2048)
full_res.read(window=window)would be a lot better than making many independent window reads
|
|
||
| ## When to use something else | ||
|
|
||
| - For resampling, reprojection, or warping, hand the array to [rasterio](https://rasterio.readthedocs.io/). |
There was a problem hiding this comment.
| - For resampling, reprojection, or warping, hand the array to [rasterio](https://rasterio.readthedocs.io/). | |
| - For resampling, reprojection, or warping, use [rasterio](https://rasterio.readthedocs.io/), either alone or in combination with async-geotiff. |
| - For resampling, reprojection, or warping, hand the array to [rasterio](https://rasterio.readthedocs.io/). | ||
| - For interactive visualization, see [Lonboard](https://developmentseed.org/lonboard/). | ||
| - For the raw-bytes layer beneath async-geotiff, see [obstore](https://developmentseed.org/obstore/). | ||
| - For library authors building on the Rust core, drop to [async-tiff](https://github.com/developmentseed/async-tiff). |
There was a problem hiding this comment.
Python library authors who want to build on top of GeoTIFF should still be using async-geotiff, not async-tiff. It's really only people who want generic TIFF support, and don't want to specialize their code to support only GeoTIFF, who should be using async-tiff.
Based on the outline here
Related notebook: microsoft/PlanetaryComputerExamples#315