Reading COGs from Azure Blob and GCS
The read code is identical across providers; only the URL prefix and the credentials change:
import rasterio
# Azure Blob Storage
with rasterio.Env(AZURE_STORAGE_ACCOUNT="myaccount", AZURE_STORAGE_SAS_TOKEN=sas):
with rasterio.open("/vsiaz/mycontainer/scenes/T36MYF_B04.tif") as src:
print(src.profile["tiled"], src.block_shapes[0], src.overviews(1))
# Google Cloud Storage
with rasterio.Env(GS_NO_SIGN_REQUEST="YES"): # public bucket
with rasterio.open("/vsigs/mybucket/scenes/T36MYF_B04.tif") as src:
window = rasterio.windows.Window(2048, 2048, 512, 512)
arr = src.read(1, window=window)
Everything about the byte layout described in Understanding Cloud-Optimized GeoTIFF Structure applies unchanged — only the transport differs.
Why This Arises in Remote Sensing Workflows
Public satellite archives are not on one cloud. Sentinel and Landsat collections are mirrored across providers, national agencies publish to whichever platform they procured, and a project that starts on one provider frequently acquires a second when a collaborator brings their own data. A pipeline that only knows how to read s3:// paths becomes the reason a dataset cannot be used.
The good news is that GDAL’s virtual file system abstracts all of this: /vsis3/, /vsiaz/, /vsigs/ and /vsicurl/ all present the same interface, and rasterio sees a file. Windowed reads, overview selection and header-only opens work identically, so the analysis code needs no provider awareness at all.
What does differ is credentials and, more importantly, cost geometry. Reading an Azure-hosted archive from a Google region pays egress on one side and ingress latency on the other, and no amount of COG structure compensates for a cross-cloud read path.
Environment & Setup
| Package | Version | Why |
|---|---|---|
rasterio |
≥1.3.0 | Env configuration and the VSI-backed reads |
GDAL |
≥3.4.0 | /vsiaz/ and /vsigs/ drivers |
planetary-computer |
≥1.0 | Only if you need SAS signing for that catalogue |
pystac-client |
≥0.7 | Resolving asset URLs from a catalogue |
pip install "rasterio>=1.3.0" "pystac-client>=0.7"
Complete Working Example
This helper resolves a provider-agnostic URI into a VSI path, applies the right GDAL configuration, and reads a window — the shape a pipeline needs when its inputs come from several archives.
from urllib.parse import urlparse
import rasterio
from rasterio.windows import Window
# Settings that matter on every provider: no sibling listing, header in one request
COMMON = {
"GDAL_DISABLE_READDIR_ON_OPEN": "EMPTY_DIR",
"GDAL_INGESTED_BYTES_AT_OPEN": "65536",
"GDAL_HTTP_MULTIRANGE": "YES",
"GDAL_HTTP_MERGE_CONSECUTIVE_RANGES": "YES",
"VSI_CACHE": "TRUE",
"VSI_CACHE_SIZE": "50000000",
}
def vsi_path(uri: str) -> str:
"""Map an s3://, az:// or gs:// URI onto the matching GDAL VSI path."""
parsed = urlparse(uri)
scheme, container, key = parsed.scheme, parsed.netloc, parsed.path.lstrip("/")
prefixes = {"s3": "/vsis3/", "az": "/vsiaz/", "abfs": "/vsiaz/",
"gs": "/vsigs/", "https": "/vsicurl/"}
if scheme == "https":
return f"/vsicurl/{uri}"
if scheme not in prefixes:
return uri # a local path
return f"{prefixes[scheme]}{container}/{key}"
def provider_config(uri: str, *, sas_token: str | None = None,
azure_account: str | None = None) -> dict:
"""GDAL options for the provider behind `uri`."""
scheme = urlparse(uri).scheme
cfg = dict(COMMON)
if scheme in ("az", "abfs"):
cfg["AZURE_STORAGE_ACCOUNT"] = azure_account or ""
if sas_token:
cfg["AZURE_STORAGE_SAS_TOKEN"] = sas_token.lstrip("?")
elif scheme == "gs":
cfg["GS_NO_SIGN_REQUEST"] = "YES" if sas_token is None else "NO"
elif scheme == "s3":
cfg.setdefault("AWS_NO_SIGN_REQUEST", "YES")
return cfg
def read_window(uri: str, window: Window, *, band: int = 1, **creds):
"""Read one window from any supported provider with sane HTTP settings."""
with rasterio.Env(**provider_config(uri, **creds)):
with rasterio.open(vsi_path(uri)) as src:
if not src.profile.get("tiled"):
raise ValueError(f"{uri} is not tiled — windowed reads will be expensive")
return src.read(band, window=window, masked=True)
if __name__ == "__main__":
arr = read_window("gs://gcp-public-data-landsat/…/LC08_B4.TIF", Window(2048, 2048, 512, 512))
print(arr.shape, arr.dtype, arr.mask.mean())
Two of the common settings do most of the work. GDAL_DISABLE_READDIR_ON_OPEN stops GDAL listing the containing prefix on every open — the single largest source of surprise requests on all three providers. GDAL_INGESTED_BYTES_AT_OPEN sized to the header turns a two-request open into a one-request open, and both are explained further in How to Read COG Headers Without Downloading Full Files.
Variant Patterns
1. Short-lived signed URLs
Several catalogues hand out assets that must be signed, with tokens valid for minutes. The rule that follows is not about signing but about when you sign.
import planetary_computer as pc
import pystac_client
import rasterio
catalog = pystac_client.Client.open("https://planetarycomputer.microsoft.com/api/stac/v1")
item = next(catalog.search(collections=["sentinel-2-l2a"], max_items=1).items())
# Sign at read time, not at search time: a URL captured into a manifest will have expired
href = pc.sign(item.assets["B04"].href)
with rasterio.Env(GDAL_DISABLE_READDIR_ON_OPEN="EMPTY_DIR"):
with rasterio.open(href) as src:
print(src.profile["compress"], src.block_shapes[0])
A pipeline that stores item identifiers and re-resolves hrefs at read time survives token expiry, storage reorganisation and asset renaming; one that stores signed URLs breaks on all three.
2. Credentials without secrets in code
Every provider supports ambient credentials, and using them is both safer and simpler than threading secrets through function arguments.
import rasterio
# Azure: managed identity or the az CLI session, no key in the process
with rasterio.Env(AZURE_STORAGE_ACCOUNT="myaccount", AZURE_NO_SIGN_REQUEST="NO"):
with rasterio.open("/vsiaz/container/scene.tif") as src:
profile = src.profile
# GCS: application default credentials from the environment
with rasterio.Env(CPL_MACHINE_IS_GCE="YES"):
with rasterio.open("/vsigs/bucket/scene.tif") as src:
profile = src.profile
Keep the configuration inside a rasterio.Env block rather than setting process-wide environment variables: it scopes the credentials to the read, and it makes concurrent access to two providers straightforward.
3. Cross-cloud reads, and why to avoid them
Common Errors
RasterioIOError: … does not exist in the file system, and is not recognized as a supported dataset name
Usually a missing VSI prefix — an az:// or gs:// URI passed straight to rasterio.open. Map it to /vsiaz/ or /vsigs/ first, as in vsi_path above.
403 from a URL that worked minutes ago
A SAS or signed URL expired. Store item identifiers, not signed URLs, and sign at read time.
Reads are correct but far slower than on S3
Sibling listing is enabled, or the object is in a different region from the compute. Set GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR first, then check the region — the cost of getting that wrong is quantified in Reducing S3 Egress Costs in Raster Pipelines.
Frequently Asked Questions
Q: Do I need different code for each cloud provider? No. Only the URL prefix and the credential configuration differ; every read, window and overview call is identical because GDAL abstracts the storage behind its virtual file system.
Q: Can I read a Planetary Computer asset without a token? Some collections are public, but most assets need a short-lived SAS token appended to the URL. Because the token expires in minutes, resolve it at read time rather than storing signed URLs in a manifest.
Q: Why is the first read much slower than later ones?
The first open resolves credentials and may list the containing prefix. Setting GDAL_DISABLE_READDIR_ON_OPEN to EMPTY_DIR removes the listing, and reusing one rasterio.Env keeps the credential resolution warm.
Related
- Understanding Cloud-Optimized GeoTIFF Structure — the parent topic; the layout that makes any of this worthwhile.
- Reading a COG over S3 Without Downloading — the same read pattern on the third provider.
- How to Read COG Headers Without Downloading Full Files — the GDAL settings referenced above, in detail.
- Querying STAC Catalogs Programmatically — where these asset URLs come from.