Skip to content

Docs add async geotiff tutorial#528

Open
aboydnw wants to merge 5 commits into
microsoft:developfrom
aboydnw:docs-add-async-geotiff-tutorial
Open

Docs add async geotiff tutorial#528
aboydnw wants to merge 5 commits into
microsoft:developfrom
aboydnw:docs-add-async-geotiff-tutorial

Conversation

@aboydnw

@aboydnw aboydnw commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Based on the outline here

Related notebook: microsoft/PlanetaryComputerExamples#315

aboydnw and others added 4 commits May 30, 2026 00:12
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>
@aboydnw aboydnw marked this pull request as ready for review June 2, 2026 20:34

@kylebarron kylebarron left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is a good start!

Comment thread docs/overview/async-geotiff.md Outdated
@@ -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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[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.

Comment thread docs/overview/async-geotiff.md Outdated
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`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.

Comment thread docs/overview/async-geotiff.md Outdated
print(geotiff.overviews) # finest → coarsest
```

The header read is a single range request. This is the same pattern used by [obstore.](./obstore.md)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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).

Comment thread docs/overview/async-geotiff.md Outdated

## 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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the geotiff itself is the full-resolution image. Index 0 of the overviews is the finest resolution after the full-resolution data.

Comment thread docs/overview/async-geotiff.md Outdated
array = await full_res.read(window=window)
```

The returned `Array` has:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread docs/overview/async-geotiff.md Outdated
import numpy as np
import matplotlib.pyplot as plt

plt.imshow(np.transpose(array.data, (1, 2, 0)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread docs/overview/async-geotiff.md Outdated

## 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`:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/overview/async-geotiff.md Outdated
Comment on lines +141 to +155
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]
)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread docs/overview/async-geotiff.md Outdated

## When to use something else

- For resampling, reprojection, or warping, hand the array to [rasterio](https://rasterio.readthedocs.io/).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- 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.

Comment thread docs/overview/async-geotiff.md Outdated
- 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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Apply Kyle Barron's review feedback:

- Link to cogeo.org in intro for "Cloud Optimized GeoTIFF".
- Clarify async-tiff is generic TIFF, async-geotiff is the right
  high-level entry for COG/GeoTIFF work.
- Simplify header-read description and reference obstore.
- Fix overviews framing: geotiff is full-res; overviews[0] is the
  finest overview below it, not full-res itself.
- Rename returned class Array -> RasterArray; rename example variable
  array -> raster_array.
- Swap manual (1,2,0) transpose for the reshape_as_image helper.
- Replace the BitmapTileLayer + titiler-backed visualization with
  RasterLayer.from_geotiff + a render_tile callback so the example
  actually showcases async-geotiff.
- Replace the asyncio.gather fan-out with a single Window(0,0,2048,
  2048) read, because read() coalesces range requests itself.
- Rephrase rasterio and async-tiff guidance in the closing list.
- Add pillow to install (used by the render_tile callback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@kylebarron kylebarron left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one question about the example but overall looks good!

## Pick a resolution

`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:
`geotiff` itself is the full-resolution image. `geotiff.overviews` is the resolution pyramid *below* the full-resolution data, ordered finest-to-coarsest — index `0` is the finest overview, index `-1` is the coarsest. For zoomed-out previews or quick checks, read from a coarser overview; for analysis, read from `geotiff` directly:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: geotiff.overviews are

return EncodedImage(data=buf.getvalue(), media_type="image/png")


Map(RasterLayer.from_geotiff(geotiff, render_tile=render_visual_tile), height=800)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously the example was end-to-end, because tilejson loaded from a URL. Should we make this example end-to-end too? geotiff isn't created anywhere

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants