Skip to main content

GeoRange IO

An access-pattern-aware sparse reader for large remote Cloud Optimized GeoTIFF (COG) archives. GeoRange IO plans a batch of point or window reads before fetching, reuses each compressed block, stops DEFLATE decoding at the last needed row, and coalesces ranges using an explicit bandwidth/latency model.

The implementation grew out of a measured headroom study. See AUDIT.md for the current correctness and performance audit, and REPORT.md for the underlying experiments.

Running it

make baseline

That brings up the infrastructure, stages the corpus, indexes every COG's block layout, generates the five workload specs, runs the whole sweep, computes the W5 oracle, and regenerates results/, data/MANIFEST.md and the tables in REPORT.md from scratch.

The corpus is about 9.5 GB fetched from public AWS buckets, so the first make stage is the slow step. It is resumable and idempotent; re-running skips anything already in MinIO.

Useful subsets:

make up                 # infrastructure only
make stage index specs  # corpus and workloads, no measurement
make sweep RTTS="50"    # one latency instead of the full sweep
make sweep-bwcap        # the bandwidth-capped comparison on its own
make analyze report     # regenerate tables and plots from existing runs

Layout

Path What it is
data/ pinned source list, staging, manifest generation
infra/ MinIO, the logging proxy, the pinned GDAL bench container
baseline/ block index, theoretical minimum, workloads, harness, analysis
results/ specs, raw proxy logs, per-run JSON, summary CSV, plots
REPORT.md the headroom analysis and the recommendation

How measurement works

Every byte and every request reported in Phase 0 is counted at the proxy (infra/proxy/app.py), which sits between GDAL and MinIO and logs the exact Range header, response byte count and duration of each request. GDAL's own CPL_DEBUG output is useful for cross-checking and is not the source of truth.

The denominator is computed with no reader in the loop: baseline/cog_index.py parses every COG's TileOffsets and TileByteCounts, and baseline/theoretical.py maps each workload's query geometry onto the exact set of internal blocks it requires and sums their compressed sizes.

amplification = bytes_actually_fetched / bytes_theoretically_required

baseline/diagnose.py then maps every fetched byte range back onto the real tile layout, so waste is attributed to a cause rather than just counted, and baseline/granularity.py measures how much of the minimum is forced by the format's block size rather than by the query.

baseline/tile_index.py builds a checkpoint index into a tile's DEFLATE stream so a reader can start in the middle of it instead of at byte zero. It drives libz through ctypes, because Python's zlib does not expose inflatePrime, which the bit-position restore needs. baseline/gate_checkpoint.py measures what that is worth against reading whole tiles, and against merely stopping early.

baseline/prefix_decode.py asks a different question: how much of a tile a sparse read actually needs. A COG tile is one DEFLATE stream, so reading an early row never requires the tail of it. It measures the byte and decode saving available to a reader that stops early, and verifies the recovered pixels against GDAL.

Two assumptions the design rests on are checked rather than asserted. baseline/check_invariance.py confirms that what GDAL fetches does not depend on injected latency, which is what licenses running the chunk-size comparison at a single RTT. baseline/crosscheck.py reconciles the proxy's accounting against GDAL's own CPL_DEBUG record of the ranges it pulled.

Installing

git clone https://github.com/thomaslin312/georange-io.git
cd georange-io
python -m pip install .

The distribution name is georange-io; the Python import is georange_io. The supported API and compatibility policy are documented in API.md.

The reader

georange_io/ is the thing the measurements argued for, and it is an installable package rather than a harness:

from georange_io import SparseReader

with SparseReader(
    base_url="https://sentinel-cogs.s3.us-west-2.amazonaws.com",
    bucket="sentinel-s2-l2a-cogs",
    timeout_s=30,
    retries=3,
    workers=8,
) as rd:
    values = rd.sample([("path/B04.tif", 5000, 3000), ...])
    windows = rd.read([("path/B04.tif", 0, 100, 100, 512, 512), ...])

Nothing has to be indexed in advance. Each file is described from its own header on first use, which for a cloud-optimised file is one request. It groups requests by block so each is fetched once, fetches and inflates each block only as far as the deepest request in it needs, merges nearby blocks when the extra bytes cost less than the round trips saved, and where a .sbx sidecar exists enters a block at a DEFLATE restart point instead of at byte zero.

read_any delegates anything the fast path declines to GDAL, so a caller gets one uniform result.

make test            # windows, overview levels, and every refusal path
make verify          # every value must match GDAL, on three workloads

Correctness is the gate, not a nicety: the claim is identical values for fewer bytes, so experiments/verify.py compares every single value against GDAL and fails on one mismatch. It currently passes on 14,933 reads spanning all three predictors, both block sizes and both dtypes in the corpus, plus window reads at every overview level.

The reader refuses rather than guessing. An encoding it cannot decode, a window running off the edge of a level, an index that does not describe the object being read, or a sidecar built for a different file are all errors, because the failure mode of each is plausible-looking wrong pixels rather than a crash.

For private stores, pass static HTTP headers or a headers_for(path, start, end) callback that returns per-request authentication headers. timeout_s, retries, and backoff_s control bounded exponential retry behavior. Output, compressed-range, and uncompressed-block limits protect services from accidental or hostile allocations.

Sidecars use the identity-bound SBX3 format. They are accepted only when their content SHA-256, object version, or ETag matches the source record. Older identity-free sidecars fail closed and the reader continues without them.

Current measured result

A three-repetition live AWS benchmark shuffles engine order on each repetition and compares SHA-256 digests of every returned value. For 60 sparse reads over 12 Sentinel-2 acquisitions, all 12 engine runs agreed exactly:

Engine Requests Bytes Median wall time
tuned GDAL 72 91.3 MB 46.57 s
GeoRange IO, 1 worker 72 56.7 MB 30.01 s
GeoRange IO, 8 workers 72 56.7 MB 11.96 s
GeoRange IO, 8 workers + sidecar 72 31.7 MB 8.24 s

That is a 1.61× byte reduction and 3.89× median wall-time gain without a sidecar; sidecars increase the byte reduction to 2.88×. On first access the request counts tie because both readers must fetch 12 COG headers. The full limits and methodology are in AUDIT.md.

Reproducibility

  • Every workload has a fixed seed, persisted in its spec (baseline/workloads/common.py).
  • GDAL, PROJ, libcurl and rasterio versions are recorded in every result row.
  • Raw proxy logs are committed gzipped under results/raw/, not just summaries.
  • The corpus is pinned by URL, byte count and SHA-256 in data/sources.yaml and data/staged.json.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

georange_io-0.2.0.tar.gz (38.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

georange_io-0.2.0-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

Details for the file georange_io-0.2.0.tar.gz.

File metadata

  • Download URL: georange_io-0.2.0.tar.gz
  • Upload date:
  • Size: 38.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for georange_io-0.2.0.tar.gz
Algorithm Hash digest
SHA256 94e9aff7590a9acc0a73b0e7df5cca7a1e8b33f3facfa7b94be44d9d535f12db
MD5 72e26f1107b4da1e6e863c5050245e7c
BLAKE2b-256 38d89eb3ce0a45d878a8bcca4f4ae336046dc9002ccbe0b1333b2069a6af4d12

See more details on using hashes here.

Provenance

The following attestation bundles were made for georange_io-0.2.0.tar.gz:

Publisher: release.yml on thomaslin312/georange-io

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file georange_io-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: georange_io-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 29.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for georange_io-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 23f10d004e77de780b4204cc347a87144a1489057d266be7834b43f7eaa402e7
MD5 4f175b5efa2879ae7af6cb6b10dcc4e9
BLAKE2b-256 45f6cbb9fb0331acf910a1d9bce2656c1247329deecf3aff673d0aeac15b6c52

See more details on using hashes here.

Provenance

The following attestation bundles were made for georange_io-0.2.0-py3-none-any.whl:

Publisher: release.yml on thomaslin312/georange-io

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.2

2 files

0.2.1

2 files

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page