This release is a pre-release and may not be stable for production use.
gdalcubes.py 
Python bindings for gdalcubes - On-demand Earth observation data cubes from GDAL image collections.
Installation
You need development files for GDAL, netCDF (C library), SQLite3, libcurl, with CMake ≥ 3.18 and a C++17 compiler.
On macOS:
brew install gdal netcdf
On Debian/Ubuntu:
apt install libgdal-dev libnetcdf-dev libsqlite3-dev libcurl4-openssl-dev
Then install the package:
pip install .
Pre-built wheels bundle GDAL and friends, so most users will not need any of the system packages above once those are published.
Optional extras
gdalcubes.py provides some integrations with the Python ecosystem. These integrations require extra Python dependencies. The table below lists these extra dependencies and when they are used:
| Extra | Installs | For |
|---|---|---|
gdalcubes[xarray] |
xarray, dask, netCDF4 | reading results with to_xarray() and extract_geom() |
gdalcubes[tif] |
rioxarray | reading GeoTIFF collections |
gdalcubes[stac] |
pystac-client, planetary-computer | building collections from STAC |
After selecting the extra dependencies you want, you can install them with the following command:
pip install 'gdalcubes[xarray,tif,stac]'
Getting started
This is the same walkthrough as the R package README, using a public Landsat 8 surface reflectance dataset over the Amazon (~2 GB). The full script is in examples/local-files.py. Below we take it one step at a time.
1. Configure gdalcubes
Set how many threads to use and turn on a progress bar for the long-running steps.
import os
import gdalcubes as gc
gc.set_threads(os.cpu_count() or 1)
gc.set_progress(True)
2. Download the example imagery
import urllib.request
import zipfile
urllib.request.urlretrieve(
"https://hs-bochum.sciebo.de/s/8XcKAmPfPGp2CYh/download", "L8_Amazon.zip"
)
with zipfile.ZipFile("L8_Amazon.zip") as archive:
archive.extractall("L8_Amazon")
3. Build an image collection
Scanning the images once extracts their spatial extent, acquisition time, and band layout into an image collection, a small SQLite database that references the files (no second copy of the data).
How metadata is read from file names is described by a collection format. Presets for several Sentinel, Landsat, and MODIS products ship with the package. You can list them with gc.list_collection_formats().
import glob
files = glob.glob("L8_Amazon/**/*.tif", recursive=True)
col = gc.create_image_collection(files, fmt="L8_SR", out="L8.db")
4. Define a data cube view
A data cube view defines the geometry of the target cube independently of the imagery: spatial reference system, extent, pixel size, and temporal resolution.
Here we ask for a coarse overview, the full collection extent at 1 km pixels, one cell per year, and let gdalcubes reproject, crop, resample, and aggregate on the fly.
view = gc.cube_view(
srs="EPSG:3857",
extent=col,
dx=1000,
dy=1000,
dt="P1Y",
aggregation="median",
resampling="bilinear",
)
5. Create the cube
raster_cube() returns immediately: it is a lazy proxy, nothing is computed yet.
cube = gc.raster_cube(col, view)
print(cube)
# <gdalcubes.Cube 10 band(s) [AEROSOL, B01, ..., RADSAT_QA],
# t: 2013-01-01..2019-12-31 (n=7), y: ... (n=559), x: ... (n=783), srs: EPSG:3857>
6. Compute a median RGB composite
Operations compose as plain functions and stay lazy. First pick the three bands, then reduce the time axis to a single median per band.
rgb_median = gc.reduce_time(
gc.select_bands(cube, ["B02", "B03", "B04"]),
["median(B02)", "median(B03)", "median(B04)"],
)
7. Compute an NDVI composite
ndvi = gc.apply_pixel(
gc.select_bands(cube, ["B04", "B05"]),
"(B05-B04)/(B05+B04)",
names=["NDVI"],
)
8. Export the result
Evaluation starts here. Write a single netCDF file:
gc.write_netcdf(ndvi, "ndvi.nc")
Or one (optionally cloud-optimized) GeoTIFF per time slice, which any GDAL-based tool (rasterio, QGIS, ...) reads directly:
gc.write_tif(ndvi, "ndvi_tifs", prefix="NDVI_", cog=True)
9. Use the result with xarray
Step 8 already evaluated the cube and wrote it to disk, so reading it back costs nothing more. The function to_xarray() reads those files rather than evaluating again, and returns an xarray.Dataset backed by dask:
ds = gc.to_xarray(ndvi)
ds["NDVI"].isel(time=0).mean().compute()
It does not matter which format you wrote, netCDF or the GeoTIFF collection both work.
For a cube that has not been written yet, you can use the to_xarray() giving a path. It evaluates once and reads from there:
ds = gc.to_xarray(rgb_median, "rgb_median.nc")
The resulting xarray.Dataset has one variable per band, dimensions time, y, x, and a crs coordinate, so rioxarray picks up the projection directly.
Load from disk
To load a cube, netCDF or GeoTIFF, as an xarray.Dataset you can use the specialized open functions from gdalcubes.py. For example, to load an existing cube saved as netCDF you can use:
ds = gc.open_xarray("ndvi.nc")
To read a GeoTIFF collection from a directory, you can use:
ds = gc.open_tif_collection("ndvi_tifs", prefix="NDVI_")
Once you load it, you can continue the operations using the xarray / dask infrastructure.
10. Extract time series of feature locations
extract_geom() evaluates a cube and extracts values from feature locations specified in a vector data source (any format GDAL reads, or a geopandas.GeoDataFrame). The function returns a pandas.DataFrame with one row per feature, time step, and pixel. The fid column is the position of the feature in the input, so the rows join back to their features directly:
df = gc.extract_geom(ndvi, "samples.gpkg")
Pass time_column="date" to extract each feature at its own time step only, as recorded in that attribute.
11. Read chunks in memory
A cube is evaluated one chunk at a time, in the chunk size it was created with. The chunks_iter() hands those chunks to Python as the core computes them, so a cube can be processed without writing it to disk first:
for chunk in gc.chunks_iter(ndvi):
if chunk.status is gc.ChunkStatus.ERROR:
raise RuntimeError(f"chunk {chunk.info.id} failed to read")
if chunk.values is None:
continue
print(chunk.info.start, chunk.values.shape)
The chunk.values is a C-contiguous float64 array of shape (bands, t, y, x), with NaN for no data and row 0 at the northern edge. It shares the core's own buffer, which it keeps alive, so nothing is copied.
Next, the chunk.info says where the chunk is: start and shape in cube coordinates, left, right, bottom and top in the cube's spatial reference system, and t0 and t1 for its first and last time step. The info.slices gives the same position as slices, which is all it takes to put the chunks back together:
import numpy as np
values = np.full(tuple(ndvi.size), np.nan)
for chunk in gc.chunks_iter(ndvi):
if chunk.values is not None:
values[(slice(None), *chunk.info.slices)] = chunk.values
To drive the loop yourself, or to read several chunks at once, use chunks_count() and chunks_read().
Reading releases the GIL while the core works, so Python threads read in parallel, exactly as the core does internally when it evaluates a whole cube:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=8) as pool:
chunks = pool.map(lambda i: gc.chunks_read(ndvi, i), range(gc.chunks_count(ndvi)))
This is not faster than writing the cube, it is just a different way to access the cube data.
Data cubes from STAC
Instead of indexing local files, an image collection can be built directly from STAC search results. No image file is opened, the metadata comes from the STAC items, so indexing thousands of remote COGs is instant. The full example script is in examples/stac-mpc.py.
Install the extra client libraries:
pip install gdalcubes[stac]
1. Search a catalog
Search with any STAC client (here pystac-client). URL signing happens at search time.
import planetary_computer
import pystac_client
# create catalog client
catalog = pystac_client.Client.open(
"https://planetarycomputer.microsoft.com/api/stac/v1",
modifier=planetary_computer.sign_inplace,
)
# search
items = catalog.search(
collections=["sentinel-2-l2a"],
bbox=[6.1, 50.2, 6.9, 50.8],
datetime="2023-06/2023-08",
)
items = items.item_collection()
2. Build the collection from the items
from gdalcubes import stac
col = gc.stac_image_collection(
items, source = "MPC", assets=["B04", "B08", "SCL"]
)
3. Build a cloud-masked cube
The mask argument drops pixels whose SCL class is one of the listed values (cloud, shadow, ...).
view = gc.cube_view(
srs="EPSG:32632",
extent=col,
dx=100,
dy=100,
dt="P1M",
aggregation="median",
resampling="bilinear",
)
cube = gc.raster_cube(
col, view, mask={
"band": "SCL",
"values": [3, 8, 9, 10]
}
)
Adapters
Because providers implement STAC in different ways, provider behavior is factored into an explicit adapter, a frozen bundle of pure functions (asset selection and band naming, href → GDAL URI, datetime extraction, item filtering, GDAL configuration) with spec-compliant defaults.
Presets are plain constants (stac.PLANETARY_COMPUTER, stac.EARTH_SEARCH).
You can customize the Default adapter by subclassing or changing attributes:
adapter = stac.DEFAULT.replace(resolve_uri=lambda href: "/vsicurl/" + href)
Note that signed URLs (e.g. Planetary Computer SAS tokens) expire, so STAC-built collections are best treated as ephemeral, the default is a temporary database, rebuilt from a fresh search when needed.
Development
Using uv, from the repository root:
uv pip install -e '.[test,xarray,tif]' # build and install (re-run after C++ changes)
uv run pytest # run the tests
uvx ruff format gdalcubes examples # format
uvx ruff check gdalcubes # lint
uvx mypy gdalcubes # type-check
Metadata
Release files for gdalcubes 0.1.0.dev1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gdalcubes-0.1.0.dev1.tar.gz | 511.7 kB | Without provenance | Details for gdalcubes-0.1.0.dev1.tar.gz |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gdalcubes-0.1.0.dev1-cp314-cp314-pyemscripten_2026_0_wasm32.whl | CPython 3.14 | CPython 3.14 | PyEmscripten 2026.0+ WebAssembly | Details for gdalcubes-0.1.0.dev1-cp314-cp314-pyemscripten_2026_0_wasm32.whl |
| gdalcubes-0.1.0.dev1-cp313-cp313-macosx_26_0_arm64.whl | CPython 3.13 | CPython 3.13 | macOS 26.0+ ARM64 | Details for gdalcubes-0.1.0.dev1-cp313-cp313-macosx_26_0_arm64.whl |
Total release size: 12.2 MB
Release files / gdalcubes-0.1.0.dev1.tar.gz
| Download URL | gdalcubes-0.1.0.dev1.tar.gz |
|---|---|
| Size | 511.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8b58a390cb9ec457bd1503bd4c813b301b41ac68ab83058e09dbf5e22cb23ef2
|
|
BLAKE2b-256 checksum How to use checksums |
a72cd4822af4691d993ebb2703927f588deb5f11b8c514200dbf084e7d863687
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / gdalcubes-0.1.0.dev1-cp314-cp314-pyemscripten_2026_0_wasm32.whl
| Download URL | gdalcubes-0.1.0.dev1-cp314-cp314-pyemscripten_2026_0_wasm32.whl |
|---|---|
| Size | 11.1 MB |
| Tags | CPython 3.14 PyEmscripten 2026.0+ WebAssembly |
|
SHA-256 checksum How to use checksums |
c9c913b2c642a4b25d81efdbc09e377922786059b38fc647f5a4276771172bdf
|
|
BLAKE2b-256 checksum How to use checksums |
5d6b23a4f905446048f94e42cc5842fe6a277077b69c842b5afd73840b429555
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / gdalcubes-0.1.0.dev1-cp313-cp313-macosx_26_0_arm64.whl
| Download URL | gdalcubes-0.1.0.dev1-cp313-cp313-macosx_26_0_arm64.whl |
|---|---|
| Size | 652.6 kB |
| Tags | CPython 3.13 macOS 26.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
779e37f0df52b117b5fba9bfc8567d427735ab0b29c9006be77cadd7c3c3013b
|
|
BLAKE2b-256 checksum How to use checksums |
cfe44650b7b686352da2e567b57d479d76fc9612f14d956e952684c68f1efa44
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for gdalcubes 0.1.0.dev1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.