Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Documentation

Learn the architecture, run the examples, integrate the library, and look up exact behavior.

Choose the path that matches what you need:

  • Overview explains the project and its boundaries.
  • Get started takes you from prerequisites to a rendered tile.
  • Guides cover styling, mosaics, and production operation.
  • Reference records exact HTTP parameters, features, and configuration.

1 - Overview

Understand what async-geotiff owns and how data moves through it.

Start here for the project model, architecture, and deliberate boundaries.

1.1 - What is async-geotiff?

The project’s purpose, supported data, and deliberate boundaries.

async-geotiff is a Rust library for turning Cloud-Optimized GeoTIFFs (COGs) into styled XYZ map tiles. It reads only the byte ranges needed for a tile, decodes native TIFF blocks, optionally reprojects them, applies a rendering style, and encodes the result.

Core capabilities

  • Async COG access over HTTPS or any caller-configured object_store backend.
  • Shared, byte-bounded caching of decoded native TIFF blocks.
  • WebMercatorQuad (EPSG:3857) and WorldCRS84Quad (EPSG:4326) tile grids.
  • U8, U16, I16, U32, I32, F32, and F64 source pixels.
  • Nearest, bilinear, cubic, cubic-spline, Lanczos, and average resampling.
  • Rescaling, colormaps, RGB composition, color formulas, and arithmetic band math.
  • PNG output and feature-gated lossless WebP output.
  • Multi-asset mosaics backed by MosaicJSON, STAC, or a custom source.

What the library deliberately does not own

No GDAL runtime. TIFF I/O uses async-tiff and object_store. The default proj feature uses the system libproj library only for coordinate transformations that do not have a built-in fast path.

No web framework in the library. The Axum HTTP layer and MapLibre, OpenLayers, and Leaflet viewers live under examples/. Applications can use CogReader, Tiler, or MosaicTiler without inheriting Axum.

No credentials manager. When opening S3, GCS, MinIO, or another object store, the application configures authentication on the store and passes the ready object to the library.

No telemetry backend. The crate emits tracing spans and exposes cache statistics. The application decides whether to export them to OpenTelemetry, Prometheus, logs, or another system.

Note

A tile fully outside the dataset or projection domain returns Ok(None). The example HTTP server maps that result to 204 No Content.

1.2 - Architecture

Follow a tile from an HTTP request or library call to encoded bytes.

The library separates asynchronous range I/O from CPU-heavy decode, warp, render, and encode work. Applications compose the public layers they need.

flowchart LR
    Client[HTTP client or Rust caller] --> Style[TileStyle]
    Client --> Tiler[Tiler]
    Tiler --> Grid[TileMatrixSet]
    Tiler --> Reader[CogReader]
    Reader --> Store[object_store / HTTP range reads]
    Reader <--> Cache[Shared BlockCache]
    Reader --> CPU[CpuLimiter + spawn_blocking]
    CPU --> Warp[Warp and resampling]
    Warp --> Render[Rescale / colormap / composite]
    Style --> Render
    Render --> Encode[PNG / lossless WebP]
    Encode --> Result[Option&lt;Bytes&gt;]
    Mosaic[MosaicTiler] --> ReaderPool[ReaderPool]
    ReaderPool --> Reader
    Mosaic --> Render

Main components

ComponentResponsibility
CogReaderOpen a COG, read metadata and overviews, select and stitch native blocks, and sample points or windows.
BlockCacheShare decoded native blocks across readers under one byte budget; coalesce concurrent cold fetches.
TilerPlan a tile, choose an overview, read the source window, warp when required, and render encoded bytes.
TileStyleValidate Raster Style JSON/Q3 and bind supported rendering capabilities.
MosaicTilerFind assets, open readers through a pool, warp each asset, reduce overlapping pixels, and render once.
CpuLimiterBound CPU-heavy blocking work across readers and tilers.

Tile data flow

  1. TileCoord identifies a tile in a supported TileMatrixSet.
  2. Tiler computes the destination bounds and checks whether they intersect the source dataset.
  3. It selects an overview whose ground resolution is appropriate for the requested tile.
  4. CogReader loads missing native blocks through range requests and reuses cached blocks where possible.
  5. Same-CRS tiles use an affine fast path. Cross-CRS tiles use an inverse warp; EPSG:4326 ↔ EPSG:3857 has a closed-form fast path, while other pairs require the proj feature.
  6. Source nodata is excluded before interpolation. The Raster Style pipeline evaluates channels, applies fixed-range stretching, Gamma, color mapping and opacity, then encodes PNG or lossless WebP. Metadata scale/offset calibration is currently rejected.

Concurrency model

Tokio drives asynchronous metadata and byte-range I/O. Decode, stitch, warp, reduction, render, and encode jobs run through spawn_blocking behind a semaphore. Applications can inject one Arc<BlockCache> across single-COG and mosaic readers to avoid multiplying decoded-cache budgets. They can also inject one CpuLimiter across non-mosaic CogReader/Tiler instances. Current MosaicTiler uses its own internal limiter and cannot join that permit budget, so mixed traffic has no automatic process-wide CPU cap.

2 - Get started

Install dependencies, run the example server, and render a tile through the library.

Follow the shortest path from a fresh checkout to a visible map tile.

2.1 - Requirements

Rust, system libraries, and optional documentation tooling.

Library and examples

  • Rust 1.92 or newer, matching Cargo.toml.
  • The default feature set requires system libproj 9.x.
  • Git is required when working from the repository.

On macOS:

brew install proj

On Debian or Ubuntu:

sudo apt install libproj-dev libsqlite3-dev libtiff-dev clang

On other platforms, install libproj with the platform’s supported package manager or build without the default features. The project does not currently provide platform-specific Windows installation commands.

If you only need same-CRS tiles or the built-in EPSG:4326 ↔ EPSG:3857 fast path, build without the default features:

cargo build --no-default-features

Documentation site

The OINK site under website/ requires Go 1.27 or newer and Hugo Extended 0.165.0 or newer. Hugo must report extended in its version string.

go version
hugo version

The library itself does not depend on Hugo or Go.

2.2 - Quick start

Run the example server and request your first rendered tile.

Clone and run

git clone https://github.com/mapseekai/async-geotiff-rs.git
cd async-geotiff-rs
COG_URL='https://raw.githubusercontent.com/cogeotiff/rio-tiler/0b08b7f35a8b639cee2f35a0cb565034f7b55bfd/tests/fixtures/cog.tif'
cargo run --release --example serve -- "$COG_URL"

The pinned 800 KiB rio-tiler fixture is a public, range-capable COG intended for tests and first-run verification. Replace it with your own COG after the smoke test. The server listens on 127.0.0.1:8080 by default and prints the available viewer and tile URLs when startup completes.

Open one of the included viewers:

The viewer automatically centers on the opened dataset and is the most reliable visual smoke test. You can also request the world tile directly:

curl --fail --output tile.png \
  'http://127.0.0.1:8080/tiles/WebMercatorQuad/0/0/0.png'

Success produces a PNG file that you can open in any image viewer. A valid tile coordinate outside the source extent returns HTTP 204 instead of a synthetic blank image; if that happens, use one of the included viewers to request an in-bounds tile at the dataset’s calculated center and zoom.

Browse local files

Run the server without a URL to discover data/*.tif and data/*.tiff files:

mkdir -p data
cp /path/to/example.tif data/
cargo run --release --example serve

The viewers then display a dataset switcher.

Optional formats

PNG is always available. Enable lossless WebP explicitly:

cargo run --release --features webp --example serve -- \
  https://example.com/cog.tif

The /formats endpoint reports only the encoders compiled into the running binary.

2.3 - Library usage

Open a COG and render a tile directly from Rust.

Add the crates.io dependency to your application:

cargo new async-geotiff-demo
cd async-geotiff-demo
[dependencies]
async-geotiff = "0.13.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

To avoid the system libproj dependency while retaining the mosaic APIs used by the example server, set default-features = false, features = ["mosaic"] on the dependency. General CRS transformations will then be unavailable. For local development, replace the version with path = "../async-geotiff-rs", adjusting the path to your checkout.

Render one tile

use std::sync::Arc;

use async_geotiff::io::cog::CogReader;
use async_geotiff::style::TileStyle;
use async_geotiff::tiler::Tiler;
use async_geotiff::tms::{TileCoord, TileMatrixSet};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://raw.githubusercontent.com/cogeotiff/rio-tiler/0b08b7f35a8b639cee2f35a0cb565034f7b55bfd/tests/fixtures/cog.tif";
    let reader = Arc::new(
        CogReader::builder()
            .source_id(url)
            .open_http(url)
            .await?,
    );
    let tiler = Tiler::new(reader);
    let style = TileStyle::from_raster_query("version=3.0&type=pseudocolor&bidx=1&colormap_name=viridis&method=linear&rescale=0,3000")?;
    let coord = TileCoord {
        tms: TileMatrixSet::WebMercatorQuad,
        z: 0,
        x: 0,
        y: 0,
    };

    if let Some(bytes) = tiler.tile(coord, &style).await? {
        std::fs::write("tile.png", bytes)?;
    } else {
        eprintln!("tile is outside the dataset or projection domain");
    }
    Ok(())
}

TileStyle::from_raster_query accepts the same query string used by the example HTTP server, without the leading ?. Save the example as src/main.rs, then run cargo run. The pinned URL is a small, range-capable test COG; replace it after verifying the integration.

Point queries and geographic bounds

CogReader::point(x, y, &coord_crs, bidx) returns a nearest-pixel value without rendering. Coordinates use coord_crs; a point outside the image returns None. Band indices are one-based and refer to the bands returned by read_window. An empty selection returns all of those bands in their existing order.

When no independent TIFF mask is present, the first Alpha sample is promoted to the validity mask and removed from the returned bands. Thus ordinary RGBA data exposes RGB to point queries. With an independent mask, the reader keeps its native bands and uses that mask instead. A masked pixel returns Some(PointValue { valid: false, .. }), rather than a band-selection error. Raster Style’s separate restriction on Alpha sources still applies to rendering.

wgs84_bounds() gives the geographic envelope used for dataset statistics. When a projected footprint contains a pole, the envelope includes that pole and spans [-180, 180] longitude. An envelope can include space outside the actual image; query pixels or use their validity mask to determine coverage.

The low-level io::stats::Histogram::quantile API accepts public u64 bin counts and accumulates them without u64 overflow, including directly constructed histograms. The 100th percentile is the upper edge of the last nonempty bin, excluding trailing empty bins even when their preceding counts are very large. These embedded histograms are not the statistics used by Raster Style.

Share memory and CPU budgets

For multiple COGs, create one block cache and give every reader a stable, distinct source ID:

use std::sync::Arc;

use async_geotiff::io::block_cache::BlockCache;
use async_geotiff::io::cache::CacheConfig;
use async_geotiff::io::cog::CogReader;

let cache = Arc::new(BlockCache::new(CacheConfig::default()));

let first = CogReader::builder()
    .source_id("scene-a")
    .block_cache(Arc::clone(&cache))
    .open_http("https://example.com/a.tif")
    .await?;

let second = CogReader::builder()
    .source_id("scene-b")
    .block_cache(Arc::clone(&cache))
    .open_http("https://example.com/b.tif")
    .await?;

Readers with the same cache share its default 512 MiB budget. The source ID is part of the cache key: reuse it only for the same logical COG.

3 - Guides

Apply styling, compose mosaics, and operate async-geotiff in production.

Task-oriented guides for the capabilities most applications compose.

3.1 - Styling and band math

Configure raster styles with Raster Style 3.0 JSON and Q3 inputs.

Inputs use SDK raster-style = "=0.4.0", protocol 3.0, and Rust 1.92. The SDK validates structure and semantics; the adapter rejects unsupported capabilities. Unknown fields and duplicate singleton parameters are errors. The subset below targets Tiler::tile and MosaicTiler::tile, not full backend certification.

Rust inputs

use async_geotiff::style::TileStyle;

let style = TileStyle::from_json(r#"{
  "version": "3.0",
  "extensions": ["https://mapseekai.github.io/raster-style/extensions/raster/tile/v1.0.0/schema.json"],
  "renderer": {"type": "pseudocolor", "bidx": [1], "colormap_name": "viridis"},
  "stretch": {"method": "linear", "rescale": [[0, 3000]]},
  "raster:tile": {"format": "png", "tilesize": 256}
}"#)?;

Use TileStyle::from_raster_query for an original Q3 string, or TileStyle::from_raster_style(&RasterStyle) for an SDK-validated document. RasterStyle is re-exported from async_geotiff::style. style.raster_style_json() returns normalized JSON. Reparse the document to change a style. The parsed style is immutable; format() and tilesize() expose read-only output settings.

Tile and Mosaic are versioned extensions: list their exact Schema URLs in extensions, and place settings in raster:tile / raster:mosaic. Q3 carries the URL array in extensions and the fields object in extension_fields. Use raster_style::encode_query to encode a validated SDK document. Incomplete validation and third-party extension declarations are rejected before execution.

HTTP Q3

Single-band pseudocolor:

/tiles/WebMercatorQuad/0/0/0.png?version=3.0&type=pseudocolor&bidx=1&method=linear&rescale=0,3000&colormap_name=viridis

RGB with one range broadcast to all three outputs:

version=3.0&type=rgb&bidx=1&bidx=2&bidx=3&method=linear&rescale=0,3000

dataset, mode, and _v belong to the example server, not the style document. TMS and tile coordinates remain in the path. The SDK decodes raw query fragments once: encode + as %2B and spaces as %20. Never pre-decode Q3. JSON is a Rust API entrypoint; the example HTTP service uses Q3 GET requests. PNG is the default. WebP requires the webp feature, a declared Tile 1.0.0 extension with raster:tile: {format: "webp", lossless: true}, and a .webp path. The path extension must match the style format.

Stretch and color

  • Renderers: gray, rgb, pseudocolor, categorized, single_color.
  • method=none, including omission, uses [0,255] on display paths and never triggers automatic statistics.
  • method=linear requires rescale; RGB accepts one broadcast or three channel ranges.
  • Display paths support range_policy=clamp|transparent, checked before color operations.
  • Named ramps use the project’s pinned colorous 256-entry tables: viridis, magma, inferno, plasma, cividis, turbo, greys.
  • Native colormap objects use exact data values; interval arrays use half-open [lo,hi) bounds, including the final interval. With explicit stretch or Gamma, pseudocolor uses 0–255 lookup indices; categorized always uses exact data values.
  • Gray supports renderer_invert; valid zero and negative inputs work with single_color.
  • opacity multiplies output Alpha. Quantized zero Alpha produces transparent black.

Categorized supports explicit exact-value tables and nearest only. It reads the base level because source overviews lack category-preservation evidence. Numeric paths support the existing nearest, bilinear, cubic, cubic_spline, lanczos, and average kernels. Dependencies are resampled independently, preserving validity. Source Alpha bands are currently rejected before pixel reads.

cubic_spline smooths even at integer pixel centers. It therefore still runs its kernel when the requested tile grid exactly matches the source grid; direct tile rendering and the warp path preserve the same resampling semantics. Cubic, CubicSpline and Lanczos also preserve finite constant F64 values near the numeric limits without making them transparent through intermediate overflow. Truly non-finite samples or unrepresentable results remain invalid.

Statistical stretch and curves

All seven methods are supported: none, linear, minmax, percentile, stddev, histogram_equalization, and curve. The examples expose their parameters:

method=percentile&percentiles=2,98
method=stddev&stddev=2
method=histogram_equalization
method=curve&curves=[[[0,0],[128,0.5],[255,1]]]

Percentiles use linear interpolation of sorted samples; standard deviation is population deviation. Histogram equalization uses the empirical CDF, without bins. Curve points are [data value, normalized output]; provide one broadcast curve or three RGB curves. Percent-encode the JSON in an actual Q3 URL.

Tiler and MosaicTiler bind a fixed 1024 × 1024 EPSG:4326 grid over the complete source footprint or union of the fixed mosaic collection. The grid has north-up rows, equally spaced longitude/latitude centers, and coverage/validity masks inherited from each source. Overview choice is fixed from each source’s full extent at this grid size, independent of tile zoom. The same upstream resampling, NoData, expressions, tuple validity and mosaic stages run on that grid. Statistics never come from the requested tile or unverified embedded GDAL summaries.

Omitted statistics means dataset/mosaic scope, sample accuracy and a maximum of 1,000,000 row-major evenly spaced grid positions. statistics={"scope":"dataset", "accuracy":"exact"} visits all cells of this bound grid, not every native resolution source pixel. NoData samples are discarded without replacement. Empty statistics give transparent output; a constant range maps its value to 0.5.

Statistics are shared across tiles and cached with reader instance/order and the full normalized style. CPU work, source reads and retained median inputs use the existing resource limits. Complete reads of the selected overview can be rejected when they exceed the work budget. Two cached plans bound retained CDF storage.

MosaicJSON with quadkey_zoom=0 provides a complete fixed collection for statistics. Partitioned MosaicJSON statistical stretches fail explicitly: flattening its index would lose coverage masks and regional paint order. Tile rendering always uses the index-selected assets. Custom MosaicSource implementations must provide statistics_assets() whose full-footprint composite preserves tile selection semantics. Unbound dynamic STAC collections, viewport scope and external statistics.ref snapshots fail explicitly. Low-level render(ImageData, ...) requires a resolved plan for statistical methods; use the tilers to bind it.

Expressions

Decimal literals, parentheses, unary minus, and arithmetic + - * / are supported. Band numbers are one-based; RGB expressions separate three outputs with ;.

version=3.0&type=gray&expression=(b5-b4)%2F(b5%2Bb4)&method=linear&rescale=-1,1

This NDVI example requires band 5 to be near-infrared and band 4 red; check the asset metadata first. Expressions cannot coexist with bidx, but may combine with supported Gamma operations. Invalid inputs, division by zero, and non-finite results become transparent. Functions, conditionals, and scientific notation are unsupported. Both the SDK and existing arithmetic parser bound input size.

Color formulas

effects.color_formula supports Gamma, evaluated in array order. Q3 example:

version=3.0&type=gray&bidx=1&color_formula=gamma%20rgb%202

Unsupported requests fail explicitly: external statistics snapshots, calibration other than none, index, color_mapping, source palettes, mode resampling, terrain, third-party extensions, other color operations, JPEG, and lossy WebP.

Mosaic

With the default mosaic feature enabled, declare this JSON member:

"raster:mosaic": {"pixel_selection": "highest", "stage": "after_channels", "rank_channel": 1}

Declare the official Mosaic 1.0.0 schema URL in extensions. Q3 encodes the declaration array in extensions and the object containing raster:mosaic in extension_fields. The old mosaic short parameters are rejected. All five methods (first, highest, lowest, mean, median) support before_channels and after_channels. Call mosaic.tile(coord, &style).await? to take the selection from the document. tile_with_options(coord, &style, &options) adds request cancellation and deadlines.

Before channels, ranking positions refer to deduplicated source dependencies sorted by source band number; after channels, positions refer to output order. rank_channel is one-based and only applies to highest/lowest. Only complete, valid tuples from one source participate; channels are never stitched across sources. Gamma, colormaps, and opacity apply once after merging. The source Alpha limitation still applies.

Assets remain outside the style, supplied by MosaicJSON, STAC, or a custom MosaicSource. See the mosaic guide for configuration and limits.

Migration

Removed: from_query / from_pairs, ValidatedTileStyle, compile_validated, mutable style fields, Stretch / ColourDefinition / Colour / lut_from_stops, and the separate mosaic selection argument. Use from_json / from_raster_query / from_raster_style. TileStyle::default() is a protocol gray-band-1 style with fixed 0–255 mapping. HTTP requires version=3.0&type=...; old parameters and unknown fields fail. All viewers emit Q3 only. Opacity is applied once by the server; MapLibre offers mosaic stage and rank-channel controls.

Legacy inputRaster Style input
bidx=1&colormap_name=viridis&rescale=0,3000Add version=3.0&type=pseudocolor&method=linear
Three identical RGB rangesOne broadcast range; explicitly set type=rgb and three bidx values
Omitted stretch auto-selects stddev for non-U8Fixed [0,255]; explicitly supply a range when needed
Continuous stops in colormapDo not reuse: native arrays are interval tables; continuous color_mapping is not yet implemented
stretch=percent&pc=2,98method=percentile&percentiles=2,98

3.2 - Mosaics and STAC

Render overlapping COG assets from MosaicJSON, STAC, or a custom source.

The default-enabled mosaic Cargo feature adds MosaicTiler and the MosaicSource trait; applications that do not need mosaics can disable default features. Because that also disables proj, use --no-default-features --features proj when general CRS transformation is still required. The source maps a tile coordinate to assets; the tiler opens and warps those assets, reduces overlapping pixels, and renders the composite once.

MosaicJSON

Run the embedded example or pass a MosaicJSON file:

cargo run --example mosaic_json
cargo run --example mosaic_json -- /path/to/mosaic.json

The parser accepts MosaicJSON 0.0.3. Its quadkey index is defined in WebMercatorQuad; using a MosaicJSON source with WorldCRS84Quad returns a query error.

Connect the example server to a file at startup:

ASYNC_GEOTIFF_MOSAIC=/path/to/mosaic.json \
  cargo run --release --example serve

Tiles are then available under /mosaic/tiles/....

Raster Style 3.0

let style = TileStyle::from_json(r#"{
  "version": "3.0",
  "extensions": ["https://mapseekai.github.io/raster-style/extensions/raster/mosaic/v1.0.0/schema.json"],
  "renderer": {"type": "rgb", "bidx": [3, 2, 1]},
  "stretch": {"method": "linear", "rescale": [[0, 3000]]},
  "raster:mosaic": {"pixel_selection": "highest", "stage": "before_channels", "rank_channel": 1}
}"#)?;
let tile = mosaic.tile(coord, &style).await?;

before_channels merges dependencies before evaluating expressions; after_channels evaluates each source first, then merges outputs. The example’s dependency table is [1,2,3], so rank_channel=1 compares source band 1; after_channels would compare the first output, source band 3. Ties preserve the first source’s complete tuple. Mean/median use the same complete valid tuples for every channel and avoid intermediate overflow for extreme finite values.

Q3 example:

/mosaic/tiles/WebMercatorQuad/0/0/0.png?version=3.0&type=rgb&bidx=3&bidx=2&bidx=1&method=linear&rescale=0,3000&extensions=%5B%22https%3A%2F%2Fmapseekai.github.io%2Fraster-style%2Fextensions%2Fraster%2Fmosaic%2Fv1.0.0%2Fschema.json%22%5D&extension_fields=%7B%22raster%3Amosaic%22%3A%7B%22pixel_selection%22%3A%22mean%22%2C%22stage%22%3A%22after_channels%22%7D%7D

In MapLibre, select mosaic under Source to configure stage and ranking. Asset locations remain server-bound; JSON/Q3 does not contain the asset list. Protocol mosaics require explicit configuration. Source Alpha bands remain unsupported and return a capability error. See the styling guide.

Pixel selection

StrategyBehavior
FirstFirst valid pixel wins; stops early after the tile is fully covered.
HighestSelect the complete tuple with maximum rank_channel.
LowestSelect the complete tuple with minimum rank_channel.
MeanStreaming mean of valid samples; constant memory in asset count.
MedianMedian of valid samples; retains every contributing image until completion.

Both bidx and rank_channel are one-based; the latter is a position at the merge stage.

MosaicConfig defaults to 8 assets processed concurrently per chunk, an opened-reader capacity of 256 in each MosaicTiler reader pool, and a hard cap of 64 assets per requested tile. Exceeding the cap returns an error, never a truncated partial result. Configure all three limits through the builder:

use std::sync::Arc;

use async_geotiff::io::block_cache::BlockCache;
use async_geotiff::io::cache::CacheConfig;
use async_geotiff::mosaic::{HttpAccessPolicy, MosaicConfig, MosaicTiler};

let cache = Arc::new(BlockCache::new(CacheConfig::default()));
let config = MosaicConfig {
    chunk_size: 4,
    reader_capacity: 64,
    max_assets_per_tile: 32,
    ..Default::default()
};
let mosaic = MosaicTiler::builder(source)
    .config(config)
    .block_cache(Arc::clone(&cache))
    .http_policy(HttpAccessPolicy::strict())
    .build();

Keep the asset cap enabled for untrusted or low-zoom indexes to prevent fetch amplification. Choose deterministic source order for first and extremum ties. Median additionally limits retained inputs to 256 MiB and observes the shared work budget.

STAC demo

Enable stac and provide all three startup settings:

ASYNC_GEOTIFF_STAC_URL=https://earth-search.aws.element84.com/v1 \
ASYNC_GEOTIFF_STAC_COLLECTION=sentinel-2-l2a \
ASYNC_GEOTIFF_STAC_ASSET_KEY=visual \
  cargo run --release --example serve --features stac

The API, collection, and asset key are fixed at process startup. HTTP query parameters cannot turn the demo into an arbitrary STAC or COG proxy.

After startup, open http://127.0.0.1:8080/maplibre. When STAC is configured, the MapLibre viewer selects mosaic mode by default and lets you verify the configured collection without guessing a tile coordinate.

For custom authentication, proxies or timeouts, pass a configured reqwest 0.11 ClientBuilder to StacSource::with_http_client_builder before building it. The source retains those settings and enforces at most ten same-origin redirect hops. Cross-origin redirects are rejected before sending credentials, and transport errors omit full URLs. The deprecated with_http_client now fails before I/O because an already built client’s redirect policy cannot be replaced. StacSource::new keeps its existing signature.

HTTP asset policy

The default policy allows public and private HTTP(S) assets but blocks loopback, link-local, and cloud-metadata destinations. Use HttpAccessPolicy::strict() for fully untrusted documents: it requires HTTPS and also blocks private address ranges. permissive() should be reserved for operator-controlled asset lists.

The example server explicitly allows its own HTTP hostname so configured mosaics can read its /data assets. Other hosts use the default policy; there is no environment override. A service that accepts untrusted mosaic or STAC documents should construct MosaicTiler itself and pass strict() through the builder as shown above.

Presigned query strings are redacted from mosaic errors and log messages. The access policy is a URL-resolution guard, not a complete outbound proxy sandbox. Applications still own client timeouts, redirect policy, egress firewalling, document-size limits, and any logging they add around the library.

3.3 - Production operation

Size caches, bound CPU work, secure remote assets, and export observability.

Share one cache

The default decoded-block cache budget is 512 MiB. A server with many readers should inject the same Arc<BlockCache> into every reader and mosaic. Give each distinct COG a stable source ID so cache entries cannot collide.

This budget covers resident decoded blocks in the cache, not total process RSS. In-flight compressed bytes, decode and warp buffers, encoded responses, reader metadata, and mosaic reducer state are additional memory. In particular, Median retains every contributing image until reduction completes.

Default tile entrypoints share a RequestLimiter admitting 64 requests and a 512 MiB WorkBudget for active work, separate from decoded-cache residency. Custom limiters and budgets can be injected. These accounting limits are not a hard RSS ceiling: keep per-tile asset fan-out and retained output buffers bounded, and size containers using representative load tests. See runtime limits and source consistency for defaults, bounded memory-pressure retries, cancellation and pinned source snapshots.

Use BlockCache::stats() to observe:

  • hits and misses
  • decoded and fetched
  • entries
  • resident_bytes

Useful derived signals include hit ratio, fetched-to-decoded amplification, and resident bytes divided by the configured budget.

Bound CPU work

Decode, stitch, warp, mosaic reduction, render, and encode are CPU-heavy. They run in spawn_blocking behind CpuLimiter. Non-mosaic CogReader and Tiler instances accept a caller-owned limiter. MosaicTiler shares one internal limiter across its assets and final render, but does not accept the non-mosaic limiter. Defaults follow the host’s available parallelism and clamp zero to one permit.

Tiler::new reuses the limiter carried by its reader:

use std::sync::Arc;

use async_geotiff::CpuLimiter;
use async_geotiff::io::cog::CogReader;
use async_geotiff::tiler::Tiler;

let cpu = CpuLimiter::new(8);
let reader = Arc::new(
    CogReader::builder()
        .source_id("scene-a")
        .cpu_limiter(cpu.clone())
        .open_http("https://example.com/a.tif")
        .await?,
);
let tiler = Tiler::new(reader);

The example server’s CPU accounting depends on the path. A command-line default COG uses separate reader-decode and tiler-render limiters, so those phases can overlap. Lazily selected local datasets reuse one AppState limiter sized to the larger configured value. MosaicTiler owns another shared limiter. Treat the two environment values as component tuning, not as a universal process-wide CPU cap. For non-mosaic readers and tilers, wire one library-level CpuLimiter through every component that supports injection.

Current MosaicTiler owns an internal shared limiter and does not expose a builder hook for injecting the non-mosaic limiter. A strict numeric CPU-job cap across mixed mosaic and single-COG traffic is therefore unsupported today. External request concurrency provides useful coarse backpressure, but is not equivalent to one shared permit budget.

Do not lower tile concurrency preemptively. Measure P95 latency and CPU contention first; an unnecessarily small limit adds queueing to every tile.

Tune access patterns

  • Increase cache capacity for multi-dataset servers or wide pan/zoom sessions.
  • Prefer ZSTD-compressed COGs over DEFLATE when read latency matters.
  • The example server prefetches a one-block ring by default. Set ASYNC_GEOTIFF_PREFETCH_RING=0 when extra object-store GETs are undesirable.
  • Keep MosaicConfig::max_assets_per_tile bounded for untrusted indexes.

Export traces and metrics

Tiler::tile and CogReader::read_window emit tracing spans. Connect a tracing-opentelemetry layer in the application when OTLP traces are needed. The library intentionally takes no OpenTelemetry dependency.

Record request count, latency, error count, and the Ok(None) rate at the HTTP or application boundary. Poll cache statistics separately for memory and fetch behavior.

Protect remote access

Use a strict HttpAccessPolicy when mosaic or STAC documents are not trusted. Configure credentials on an object_store instance rather than embedding them in asset identifiers. Avoid logging raw presigned URLs at application boundaries outside the library.

3.4 - Runtime limits and source consistency

Working memory, request admission, cancellation and pinned object snapshots.

Active work and memory ownership

Default readers, tilers and mosaic pools share WorkBudget::global(), a 512 MiB allowance. WorkBudget::new(bytes) creates an independent allowance; clone and inject the same budget through ReaderConfig::work_budget, Tiler::with_work_budget, and MosaicTiler::builder(...).work_budget(...) when these objects must share an application limit.

Low-level reservations fail immediately with AgError::ResourceLimit. Public tile/warp/mosaic requests release the failed attempt, wait for exclusive admission, and retry once under the original deadline. Direct CogReader calls remain fail-fast; individually oversized requests still fail. used_bytes(), peak_bytes() and limit_bytes() expose the accounting. Cloned reservations stay live until their last owner is dropped, including an already-running blocking job after its caller cancels. Managed response buffers retain their byte reservation for the buffer lifetime.

This is an active-work allowance, not an OS RSS ceiling. Completed caller-owned images, resident decoded caches, allocator overhead, runtime/TLS state and unrelated application allocations have different ownership. Set CacheConfig::max_bytes for decoded-cache residency, share caches deliberately, and bound how many completed outputs the application retains.

Reader defaults add per-operation validation:

SettingDefault
Source-window pixels16 × 1024 × 1024
Window bytes / compressed fetch batch256 MiB
Blocks per window4096
Declared decoded bytes per block64 MiB
Cumulative metadata bytes while opening8 MiB
IFDs256
Entries per IFD4096
Concurrent foreground windows8

Use ReaderConfig::default() and override fields explicitly. Oversized ranges, metadata and arithmetic overflow fail before the corresponding fetch/allocation. Background prefetch uses immediate budget acquisition and skips when foreground work owns the allowance. These guards and a small workload are not a proof of every codec/backend’s behavior on arbitrary input.

The default decoder registration now bounds the output of uncompressed, Deflate/OldDeflate, LZW, ZSTD and JPEG blocks against the declared block shape. Typed/planar byte arithmetic and JPEG MCU padding are included in validation and work estimates. This covers the registered implementations; it does not make an arbitrary caller-supplied codec implementation safe.

Request admission, deadlines and cancellation

The default RequestLimiter is shared across public tile calls and admits 64 requests. Excess calls return AgError::Overloaded immediately. Custom limiters can be shared using Tiler::with_request_limiter and MosaicTilerBuilder::request_limiter. Internal mosaic child work does not consume a second request admission.

The default request timeout is 30 seconds, including I/O and permit waits. TilerConfig and MosaicConfig expose max_in_flight_requests and request_timeout; use ..Default::default() when constructing these configs. tile_with_options (and single-source warp_tile_with_options) accepts a RequestOptions containing a per-call timeout and a cloneable RequestCancellation. Calling cancel() on any clone ends the request with AgError::Cancelled; expiration returns AgError::DeadlineExceeded.

Cancellation is cooperative. It stops later pipeline stages and drops pending I/O, but does not preempt an arbitrary running CPU instruction or remote server handler. Blocking work retains its admission/accounting until it finishes. HTTP client/TLS initialization runs off async workers under a separate bounded limit; local workload telemetry distinguishes request return from later activity.

The example server exposes:

  • ASYNC_GEOTIFF_MAX_IN_FLIGHT_REQUESTS (positive integer).
  • ASYNC_GEOTIFF_REQUEST_TIMEOUT_MS (positive integer milliseconds).

HTTP admission and timeout include dataset lookup/opening. Library callers that open a CogReader directly outside a tile request should apply their own outer timeout when an application deadline must include that initial open.

The server passes the configured timeout and a shared library request limiter to default, lazy local, MosaicJSON and STAC tile paths. Raising the HTTP values does not leave an additional fixed 30-second total request deadline or 64-request admission cap. Transport connection/read timeouts remain independent. Running CPU work retains its library admission until it drains after HTTP cancellation. ASYNC_GEOTIFF_PREFETCH_RING applies to default and lazy local single-COG readers; mosaic asset readers retain their zero-prefetch default.

HTTP mosaic asset error labels remove URL userinfo, query strings and fragments, including when opening is rejected by the access policy. Store-backed asset keys retain their existing semantics; they are not interpreted as HTTP URLs.

Mosaic ordering and selected bands

MosaicJSON assets retain their listed paint order during deduplication. Prefix lookups traverse matching quadkeys in lexical order and retain the first occurrence of each asset, preserving each key’s list order.

Mosaic selection comes from the Raster Style document, including pixel_selection, stage, and the one-based rank_channel for Highest/Lowest. Before channels, the input tuple contains deduplicated source dependencies sorted by band number; after channels, it contains evaluated output channels. Every component must be valid for the asset’s tuple to participate. Highest/Lowest keep a complete tuple from the winning asset, with source order breaking ties.

Dependencies are warped independently so unrelated bands’ nodata cannot affect interpolation. Gamma, color mapping and opacity run once after reduction. Calibration other than none currently returns an error; the old unscale parameter is removed. Public Tiler::warp_tile returns compact dependencies or, for after_channels, evaluated channels. It no longer returns every source band.

Pinned object snapshots

Managed open_http and open_object_store use SnapshotPolicy::RequireValidator by default. A strong ETag or immutable object version must be available. Conditional reads and decoded-cache identity pin metadata and pixels to that snapshot; a changed source produces an error instead of a mixed-version tile. Version-aware stores may continue serving the pinned old version. LocalFileSystem managed opens supply validators, so the checked-in fixtures do not need to waive this contract.

An opaque ObjectReader does not expose its underlying store for conditional requests. Passing it to .open(reader) therefore requires explicit .snapshot_policy(SnapshotPolicy::AssumeImmutable). This is a caller guarantee, not verified snapshot consistency. Use it only for a source the caller keeps immutable, or migrate to a managed store open.

Validator consistency depends on truthful validators and the backend’s atomic write contract. It cannot detect a server that changes bytes while reusing the same validator, or raw in-place filesystem writes that violate that contract.

There is no automatic source refresh. Reopen a direct reader to discover the current validator. For mosaics, call invalidate_source(key, source_id) so the next lookup opens a fresh snapshot, or use a new source version identity. Invalidation does not mutate an existing reader Arc: it retains its original snapshot, and uncached reads fail if the old version is no longer available. New reader/cache identities include the validator and size, preventing stale decoded blocks from being reused as new imagery.

3.5 - Local workload and benchmarks

Reproduce bounded latency and peak RSS measurements without external services.

Run these tools from the Git checkout on macOS or Linux. They are development tools and do not run when an application depends on this crate.

Build the optimized binary separately from measurement:

cargo build --release --locked --example load_audit --features mosaic
python3 scripts/run_load_audit.py --samples 32 --concurrency 4 --output target/load-audit/results.json

Repeat with concurrency 1 or 8 and a different output filename. The wrapper rejects more than 128 samples or concurrency above 16 and kills a run after 120 seconds. All network traffic goes to an ephemeral loopback server in the workload process. No credentials or public datasets are required. The driver calls the library tile APIs directly; the HTTP server supplies TIFF bytes. This is not an end-to-end load test of the example server’s tile routes.

The 64×64 synthetic Deflate TIFF has a 32×32 overview, three bands, four base blocks and a nodata corner. The server adds 2 ms to each GET. Cold samples include reader/client creation and metadata; warm samples use an already rendered tile’s reader and decoded blocks. Mosaic samples use three separately identified URLs with identical fixture pixels and the Mean reducer. Outputs are 64×64 PNGs. Samples run in bounded batches; throughput includes batch barriers and is not an open-loop arrival-rate test.

Each latency table reports nearest-rank P50/P95/P99. With 32 samples, P99 is the maximum sample; do not interpret it as a stable production tail estimate. The JSON includes cache counters before/after, individual cold cache snapshots, GET counts, prepared response bytes, maximum active server handlers, and the global work-budget estimate (its peak is cumulative across scenarios). “Response bytes” means payloads prepared by the server, not bytes proven delivered to a cancelled client.

Cancellation cases use a 100 ms server delay. One case aborts task futures; others use an explicit cancellation handle (20 ms) or a deadline (10 ms). The latter launch eight requests against an admission limit of two and record all rejection/termination outcomes. Single-source readers are opened first so these cases exercise in-flight tile reads. Cold mosaic cases additionally cover client/metadata setup. Counters are sampled at return and after a 250 ms settling period; admitted-request permits and work-budget bytes are recorded. Remote server handlers may finish after client cancellation. These counters do not measure every background TLS initialization job or prove OS thread preemption of a running decoder.

A memory-admission case submits eight 1024×1024 renders to a 64 KiB work budget. It requires eight resource-limit errors, no pixel GETs, and zero retained work bytes afterward. This checks early rejection, not peak memory under a full budget.

The wrapper records UTC time, OS/architecture, logical CPU count, Rust version, commit and dirty status, executable SHA256, fixture SHA256, build-profile label, and child user/system CPU time. Peak RSS is the workload process’s wait4 ru_maxrss (bytes on macOS; KiB converted on Linux); compiler memory is excluded. It is a whole-run peak, not a per-scenario value or a process-tree total.

These are bounded synthetic measurements on one machine. They do not establish production capacity, an adversarial codec memory bound, or behavior under an unbounded request stream. Fixture regeneration uses Python’s standard library; Deflate bytes also depend on the installed zlib implementation.

Resampler microbenchmark

cargo bench --locked --bench resampler_bench

The six cases compare contiguous and strided bilinear sampling, each with and without a mask, plus masked and unmasked Average downsampling. This measures kernel cost, not HTTP latency or cross-CRS pipeline throughput. Criterion is a development dependency and is not a dependency of library consumers.

4 - Reference

Exact routes, parameters, features, defaults, and operational settings.

Use this section when you need exact supported values and defaults.

4.1 - Example HTTP API

Routes, tile path format, style parameters, and response behavior.

The HTTP API belongs to examples/serve.rs; it is not part of the library’s dependency surface.

Routes

Method and pathPurpose
GET /tiles/{tms}/{z}/{x}/{y}.{ext}Render a tile from the selected single COG.
GET /mosaic/tiles/{tms}/{z}/{x}/{y}.{ext}Render a tile from the configured mosaic or STAC source.
GET /datasetsList local TIFF files discovered under data/.
GET /cache/statsReturn decoded-block cache statistics.
GET /formatsReturn output formats compiled into the server.
GET /boundsReturn bounds used by the example viewers.
GET, HEAD /data/{name}Serve a discovered local TIFF with range support.

Supported TMS names are WebMercatorQuad and WorldCRS84Quad. Supported style extensions are png and feature-gated lossless webp.

Raster Style Q3

Both tile routes require complete Q3: version=3.0&type=.... Unknown fields, old parameters, and unimplemented capabilities fail explicitly. dataset, mode, and _v are separate singleton context keys; asset URLs are not styles.

ParameterRepeatMeaning
version / typeno3.0 and gray/rgb/pseudocolor/categorized/single_color.
bidxyesOne-based source bands; RGB requires exactly three.
expressionnoOne expression for gray, three separated by semicolons for RGB; excludes bidx.
method / rescaleranges onlynone, linear, minmax, percentile, stddev, histogram_equalization, curve; see the styling guide for parameters and the shared statistics grid.
colormap_name / colormapnoPseudocolor maps; native tables use exact values or half-open intervals.
nodatanoFinite number or nan.
color_formulanoSupported Gamma operations, applied in order.
opacityno0–1; default 1.
resampling / reprojectnonearest, bilinear, cubic, cubic_spline, lanczos, average.
extensionsnoJSON array of exact versioned Schema URLs; percent-encode the value.
extension_fieldsnoJSON object containing raster:tile and/or raster:mosaic; percent-encode the value.

raster:tile supports tilesize (64, 128, 256, 512, 1024), format (png, or webp with lossless: true); the format must match the path. raster:mosaic requires pixel_selection and stage, with optional rank_channel for highest/lowest. Declare the Tile 1.0.0 and/or Mosaic 1.0.0 Schema URLs. The former tile/mosaic short parameters are rejected.

Encode literal + as %2B in raw Q3. Do not decode before concatenating query fragments. See the styling guide for semantics and limits.

Responses

  • 200 OK returns encoded image bytes.
  • 204 No Content means the coordinate is valid but fully outside the source dataset or projection domain.
  • Invalid style values and malformed coordinates return a client error with a structured library error behind it.

The default output size is 256×256, chosen for broad XYZ-client compatibility.

4.2 - Cargo features

Compile-time capabilities and their dependency effects.
FeatureDefaultEffect
projyesEnables general CRS transformation through system libproj.
mosaicyesEnables MosaicTiler, MosaicSource, MosaicJSON, and async asset fan-out.
webpnoEnables lossless WebP encoding through image.
jpegnoEnables the low-level JPEG encoder; Raster Style requests currently reject JPEG.
stacnoAdds the STAC client and implies mosaic.
perf-tracingnoEmits detailed internal timing events.
tokio-consolenoEnables Tokio Console integration and implies perf-tracing.

Examples:

# Default: projection + mosaics + PNG
cargo build --release

# Minimal library without general PROJ or mosaics
cargo build --release --no-default-features

# Example server with all image formats
cargo run --release --example serve --features webp

# STAC-backed mosaic demo
cargo run --release --example serve --features stac

# Full verification surface
cargo test --all-features --locked

The serve, mosaic_source, and mosaic_json examples require the mosaic feature. PNG support is always compiled.

4.3 - Example server configuration

Environment variables accepted by the included Axum server.

These variables configure cargo run --example serve. They are example-server contracts, not global library configuration.

VariableDefaultMeaning
ASYNC_GEOTIFF_BIND127.0.0.1:8080Socket address for the HTTP server.
ASYNC_GEOTIFF_BLOCK_CACHE_MB512Shared decoded-block cache capacity in MiB; must be positive.
ASYNC_GEOTIFF_MAX_DECODE_TASKSavailable parallelismMaximum concurrent decode/stitch CPU jobs; must be positive.
ASYNC_GEOTIFF_MAX_TILE_TASKSavailable parallelismMaximum concurrent tile warp/render CPU jobs; must be positive.
ASYNC_GEOTIFF_MAX_IN_FLIGHT_REQUESTS64Shared tile request admission limit; must be positive.
ASYNC_GEOTIFF_REQUEST_TIMEOUT_MS30000Tile request timeout, including dataset lookup/opening; must be positive.
ASYNC_GEOTIFF_PREFETCH_RING1Native-block rings prefetched for default/local single-COG readers; 0 disables it.
ASYNC_GEOTIFF_SLOW_TILE_MSunsetEmit a slow-tile warning above this positive millisecond threshold.
ASYNC_GEOTIFF_MOSAICunsetPath to a MosaicJSON file loaded at startup.
ASYNC_GEOTIFF_STAC_URLunsetFixed STAC API URL; requires the stac feature and the next two variables.
ASYNC_GEOTIFF_STAC_COLLECTIONunsetFixed STAC collection ID.
ASYNC_GEOTIFF_STAC_ASSET_KEYunsetFixed STAC COG asset key.
ASYNC_GEOTIFF_TOKIO_CONSOLEoffEnable Tokio Console with 1, true, yes, or on when compiled with tokio-console.
RUST_LOGasync_geotiff=debug,warnStandard tracing filter for the example server.

ASYNC_GEOTIFF_MOSAIC and the STAC configuration are mutually exclusive. All three STAC variables must be supplied together.

The decode-task and tile-task values tune single-COG work, but are not a single process-wide CPU cap. The command-line default COG uses separate limiters; lazily selected local datasets share one limiter sized to the larger value; mosaics own their own limiter. The example server does not expose environment variables for MosaicConfig fields such as max_assets_per_tile.

The request admission limit and timeout apply to the command-line default COG, lazy ?dataset= selections, MosaicJSON and STAC tiles. These paths share the configured library admission limiter, including when running CPU work outlives a cancelled HTTP request. Increasing the HTTP settings also increases the effective library settings; no additional fixed 64-request admission cap or 30-second total request deadline is retained. Transport connection/read timeouts remain independent. Excess requests return 503 and expired requests return 504.

Deadline, cancellation and overload classifications are preserved through mosaic asset opening and wrapped TIFF errors: they return 504, 408 and 503 respectively, while ordinary HTTP asset errors still use redacted diagnostics.

Prefetch tuning applies to both single-COG entrypoints. Mosaic asset readers keep their existing prefetch default of zero; the server does not expose a Mosaic prefetch setting.

HTTP mosaic asset errors omit URL userinfo, query strings and fragments from their diagnostic labels. Store-backed object keys retain their existing key semantics. Keep source credentials in server-side configuration; tile callers do not need the original asset URL.

Example

ASYNC_GEOTIFF_BIND=0.0.0.0:8080 \
ASYNC_GEOTIFF_BLOCK_CACHE_MB=1024 \
ASYNC_GEOTIFF_MAX_DECODE_TASKS=8 \
ASYNC_GEOTIFF_MAX_TILE_TASKS=8 \
ASYNC_GEOTIFF_PREFETCH_RING=0 \
ASYNC_GEOTIFF_SLOW_TILE_MS=500 \
RUST_LOG=async_geotiff=info \
  cargo run --release --example serve -- https://example.com/cog.tif

Invalid numeric values fail at process startup instead of being silently clamped.

4.4 - Releases and crates.io

Package contents, verification and publication of async-geotiff.

The package name is async-geotiff; Rust imports use async_geotiff. Version 0.13.0 prepares the first crates.io distribution. Older GitHub releases remain available as source archives. Making the GitHub repository public does not itself publish a crate; Cargo uploads a separate .crate archive.

Package contents

Cargo.toml uses an explicit include list: library sources, examples and their HTML viewers, tests and small reproducible fixtures, the resampler benchmark, manifests, READMEs, changelog and the MIT license text. Tests and fixtures stay together because unit tests and examples embed fixture bytes. website/, scripts, agent instructions, local imagery and generated reports are not in the registry package.

benches/resampler_bench.rs measures six useful kernel cases and is retained. scripts/run_load_audit.py records bounded local latency and peak RSS; it is retained in Git, with output under ignored target/load-audit/. Historical reviews and machine-specific results are available in Git history through 7170204, rather than in the current source tree. See the workload guide for reproducible measurements.

Verify a version

Update Cargo.toml, Cargo.lock, the changelog and both website languages. Run the five feature configurations documented in AGENTS.md, including all-target and documentation tests, plus the viewer and strict Hugo builds. Then inspect and compile the actual registry archive:

cargo +stable package --list --locked
cargo +stable publish --dry-run --locked

The .crate file is generated under target/package/. For local review before committing, add --allow-dirty; omit it for the final publication. The docs.rs build disables default features to avoid requiring native PROJ while documenting mosaics, STAC and image encoders. Applications still get the normal default features.

Publish

After verification, commit the release and push the matching version tag:

git tag v0.13.0
git push origin main v0.13.0

The Release workflow validates the tag against Cargo.toml, installs native build dependencies, runs tests, verifies the archive and attaches it to a GitHub Release. A successful release workflow deploys the exact commit’s documentation to Cloudflare Pages. Pushing a tag does not upload to crates.io.

For the registry upload, use a crates.io account with a verified email and a token allowed to publish this crate:

cargo login
cargo +stable publish --locked

Alternatively, configure the repository secret CARGO_REGISTRY_TOKEN, open the Release workflow in GitHub Actions, select the matching version tag, and enable publish_to_crates_io. The workflow repeats verification before uploading. Do not paste tokens into commands, logs or committed files.

Published versions cannot be overwritten; verify the contents and version before uploading. See the Cargo publishing guide.

Upgrade from 0.12

Raster Style remains SDK 0.4.0 / protocol 3.0/Q3. For custom STAC clients, replace the built-client with_http_client constructor with with_http_client_builder, passing a configured reqwest 0.11 ClientBuilder. The old constructor returns an error. See mosaics for the authentication and redirect contract. The release also fixes independent mask grids, polar coverage, Alpha point queries, exact-grid CubicSpline, extreme finite resampling, histogram counts and server configuration propagation.