DuckDB Raster Extension
May 19, 2026 · View on GitHub
A DuckDB extension for reading and writing geospatial raster data using SQL.
It exposes raster files (e.g. GeoTIFF, COG, VRT) as standard DuckDB tables, with one row per tile and one datacube column per band. You can filter, transform, and aggregate raster data using plain SQL, and write results back to any GDAL-supported raster format.
-- Compute NDVI from red (band 1) and NIR (band 2) directly in SQL
SELECT
geometry,
(databand_2 - databand_1) / (databand_2 + databand_1) AS ndvi
FROM
RT_Read('path/to/raster/file.tif')
;
How do I get it?
Loading from community
The DuckDB Raster Extension is available as a signed community extension. See more details on its DuckDB CE web page.
To install and load it, you can run the following SQL commands in DuckDB:
INSTALL raster FROM community;
LOAD raster;
Function Reference
| Function | Summary |
|---|---|
RT_Drivers | Returns the list of supported GDAL raster drivers and file formats. |
RT_Read | Reads a raster file (or a mosaic of raster files) and returns a table with the raster data. |
RT_ReadCells | Reads a raster file (or a mosaic of raster files) and returns a table with one row per value cell in the raster. |
RT_Write | (COPY TO) Exports a data table to a new raster file. |
| Function | Summary |
|---|---|
RT_Array2Cube | Packages a plain SQL array into a datacube column. |
RT_Cube2Array | Extracts pixel values from a datacube column into a plain SQL array. |
RT_Cube2Type | Changes the pixel data type of a datacube. |
RT_Cube<UnaryOp> | Applies a unary operation to the datacube element-wise (RT_CubeNeg, RT_CubeAbs, …). |
RT_Cube<BinaryOp> | Applies a binary operation between two datacubes or a datacube and a scalar. Operators +, -, *, /, ^, % are also supported. |
RT_CubeStats | Calculates statistics for a specific band (0-based index) of a datacube. |
RT_GdalConfig | Sets a GDAL configuration option (e.g. for S3 authentication). |
| Function | Summary |
|---|---|
RT_RasterValue | Returns the value in a datacube at the specified pixel coordinates. |
RT_RasterValues | Returns the values in a band of a datacube at the specified array of pixel coordinates. |
RT_CoordValue | Returns the value in a datacube at the specified world coordinates. |
RT_CoordValues | Returns the values in a band of a datacube at the specified array of world coordinates. |
RT_Envelope | Computes the bounding box of the valid (non-no-data) cells in the input datacube for a specific band and returns it as a geometry. |
RT_Polygon | Creates a polygon geometry for each contiguous region of non-no-data values for a specific band in the datacube. |
RT_CubeClip | Returns a datacube where cells outside the given geometry are replaced by the specified value. |
RT_CubeBurn | Returns a datacube where cells inside the given geometry are replaced by the specified value. |
Aggregate functions operate on groups of rows (e.g. from a GROUP BY query) and return a single value per group.
| Function | Summary |
|---|---|
RT_CubeStats_Agg | Calculates statistics for a specific band (0-based index) in a set of datacubes. |
RT_RasterValue_Agg | Returns the value in a set of datacubes at the specified pixel coordinates. |
RT_CoordValue_Agg | Returns the value in a set of datacubes at the specified world coordinates. |
Examples
More examples are available in the examples guide and in the SQL tests used by the CI pipeline.
Listing available drivers
SELECT short_name, long_name, help_url FROM RT_Drivers();
┌────────────────┬──────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────┐
│ short_name │ long_name │ help_url │
│ varchar │ varchar │ varchar │
├────────────────┼──────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
│ VRT │ Virtual Raster │ https://gdal.org/drivers/raster/vrt.html │
│ GTiff │ GeoTIFF │ https://gdal.org/drivers/raster/gtiff.html │
│ COG │ Cloud optimized GeoTIFF generator │ https://gdal.org/drivers/raster/cog.html │
│ · │ · │ · │
│ · │ · │ · │
│ · │ · │ · │
│ ENVI │ ENVI .hdr Labelled │ https://gdal.org/drivers/raster/envi.html │
│ Zarr │ Zarr │ NULL │
│ HTTP │ HTTP Fetching Wrapper │ NULL │
└────────────────┴──────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘
Reading a raster file (or a mosaic of raster files)
SELECT * FROM RT_Read('path/to/raster/file.tif');
┌───────┬───────────┬────────────┬────────────────────────────────┬─────────────────────────┬───────┬────────┬────────┬───────┬───────┬────────────┬────────────┐
│ id │ x │ y │ bbox │ geometry │ level │ tile_x │ tile_y │ cols │ rows │ metadata │ databand_1 │
│ int64 │ double │ double │ struct(xmin, ymin, xmax, ymax) │ geometry('epsg:25830') │ int32 │ int32 │ int32 │ int32 │ int32 │ JSON │ BLOB │
├───────┼───────────┼────────────┼────────────────────────────────┼─────────────────────────┼───────┼────────┼────────┼───────┼───────┼────────────┼────────────┤
│ 0 │ 545619.75 │ 4724508.25 │ {xmin: 545539.75, │ POLYGON ((...)) │ 0 │ 0 │ 0 │ 320 │ 8 │ {...} │ ... │
│ │ │ │ ymin: 4724506.25, │ │ │ │ │ │ │ │ │
│ │ │ │ xmax: 545699.75, │ │ │ │ │ │ │ │ │
│ │ │ │ ymax: 4724510.25} │ │ │ │ │ │ │ │ │
│ 1 │ 545619.75 │ 4724504.25 │ {xmin: 545539.75, │ POLYGON ((...)) │ 0 │ 0 │ 1 │ 320 │ 8 │ {...} │ ... │
│ │ │ │ ymin: 4724502.25, │ │ │ │ │ │ │ │ │
│ │ │ │ xmax: 545699.75, │ │ │ │ │ │ │ │ │
│ │ │ │ ymax: 4724506.25} │ │ │ │ │ │ │ │ │
└───────┴───────────┴────────────┴────────────────────────────────┴─────────────────────────┴───────┴────────┴────────┴───────┴───────┴────────────┴────────────┘
Function accepts a string or a list of strings as input. In case of a list of strings, the function creates a virtual raster (VRT) mosaic of the input files, which allows you to read multiple raster files as if they were one. This is especially useful when working with large rasters that are split into multiple files.
-- Read multiple raster files as a mosaic using a VRT dataset
SELECT
geometry, databand_1
FROM
RT_Read([
'path/to/mosaic/raster-clip00.tif',
'path/to/mosaic/raster-clip01.tif',
'path/to/mosaic/raster-clip10.tif',
'path/to/mosaic/raster-clip11.tif'
])
;
RT_Read accepts pattern-based file paths with wildcards (*) and recursive globbing (**) to read multiple files without having to list them all explicitly.
-- Use a wildcard pattern to read multiple files
SELECT
geometry, databand_1
FROM
RT_Read('path/to/mosaic/raster-*.tif')
;
Spatial manipulation is supported, so you can filter tiles by their spatial location or use the geometry or bbox columns to perform spatial operations and analyses.
LOAD spatial;
-- Filter tiles by spatial location
SELECT
x, y, bbox, geometry
FROM
RT_Read('path/to/raster/file.tif')
WHERE
ST_Intersects(geometry, ST_GeomFromText('POLYGON((...)))'))
;
LOAD spatial;
-- Vectorize valid (non-nodata) pixel regions into polygon geometries
SELECT
RT_Polygon(databand_1, tile_x, tile_y, metadata) AS geometry
FROM
RT_Read('path/to/raster/file.tif')
;
You can also read raster data at the pixel level using the RT_ReadCells function, which returns one row per value cell in the raster, along with its pixels and spatial coordinates.
-- Read raster file at the pixel level, one row per value cell with one column per band
SELECT
id, x, y, geometry, pixel_x, pixel_y, band_1, band_2, band_3
FROM
RT_ReadCells('path/to/raster/file.tif')
;
Writing a raster file
You can write a new raster file from any SQL query that produces a geometry column and one or more datacube columns. The geometry column is used to determine the spatial location and extent of each tile, while the datacube columns are used to populate the pixel values for each band.
COPY (
SELECT
geometry, databand_1, databand_2, databand_3
FROM
RT_Read('./input.tiff')
)
TO './output.tiff'
WITH (
FORMAT RASTER,
DRIVER 'COG',
CREATION_OPTIONS ('COMPRESS=LZW'),
RESAMPLING 'nearest',
-- ENVELOPE [545539.750, 4724420.250, 545699.750, 4724510.250], -- explicit extent (optional)
COMPUTE_VALID_ENVELOPE true, -- derive extent from valid pixels
SRS 'EPSG:25830',
GEOMETRY_COLUMN 'geometry',
DATABAND_COLUMNS ['databand_3', 'databand_2', 'databand_1']
);
Band algebra
You can use the scalar functions to perform pixel-wise operations and transformations on the datacubes, such as computing indices, applying mathematical functions, or changing data types.
WITH __input AS (
SELECT
RT_Cube2ArrayFloat(databand_1, true) AS band
FROM
RT_Read('path/to/raster/file.tif', blocksize_x := 512, blocksize_y := 512)
)
SELECT
list_min(band.values) AS band_min,
list_stddev_pop(band.values) AS band_stddev,
list_max(band.values) AS band_max
FROM
__input
;
Algebraic operations between datacubes (bands) or between datacubes and scalars are supported directly in SQL using the RT_Cube<BinaryOp> functions or standard arithmetic operators (+, -, *, /, ^, %). For example, you can compute the NDVI index from the red and NIR bands of a raster file like this:
WITH __input AS (
SELECT
databand_1 AS red,
databand_3 AS nir
FROM
RT_Read('path/to/raster/file.tif', blocksize_x := 512, blocksize_y := 512)
)
SELECT
RT_Cube2TypeFloat((nir - red) / (nir + red)) AS ndvi
FROM
__input
;
For the full function reference and all available options, see docs/functions.md.
How do I build it?
This extension is based on the DuckDB extension template.
Dependencies
You need CMake ≥ 3.5 and a C++14-compatible compiler. Ninja is recommended and can be selected by setting GEN=ninja.
git clone --recurse-submodules https://github.com/ahuarte47/duckdb-raster
cd duckdb-raster
make release
Invoke the built DuckDB (with the extension statically linked):
./build/release/duckdb
See the Makefile or the extension template documentation for additional options.
Running the tests
SQL tests live in ./test/sql and are the primary test suite for the extension:
make test
Installing a locally built binary
To load an unsigned local build, launch DuckDB with allow_unsigned_extensions enabled:
CLI:
duckdb -unsigned
Python:
con = duckdb.connect(':memory:', config={'allow_unsigned_extensions': 'true'})
NodeJS:
db = new duckdb.Database(':memory:', { allow_unsigned_extensions: 'true' });
Then load the extension from its local path:
LOAD 'build/release/extension/raster/raster.duckdb_extension';