geotiff (DuckDB Community Extension)
August 20, 2025 · View on GitHub
geotiff lets DuckDB read GeoTIFF rasters via GDAL and expose them as a table function.
Install
INSTALL geotiff FROM community;
LOAD geotiff;
(If you installed an older copy locally and want to refresh:)
FORCE INSTALL geotiff FROM community;
LOAD geotiff;
Usage
Single band (long form)
Returns two columns:
-
cell_id BIGINT — 0-based linear index in row-major order (row * width + col)
-
value DOUBLE — pixel value (NULL for NoData)
SELECT * FROM read_geotiff('cea.tif', band := 1) LIMIT 5;
Multiple bands (wide form)
Returns one row per cell with one column per requested band:
SELECT * FROM read_geotiff('cea.tif', band := [1,2,3]) LIMIT 5;
-- schema: (cell_id BIGINT, band1 DOUBLE, band2 DOUBLE, band3 DOUBLE)
Typical patterns
Create a wide table from a multi-band raster:
CREATE TABLE r_chelsa AS
SELECT * FROM read_geotiff('cea.tif', band := [1,2,3]);
CREATE INDEX idx_r_chelsa_cell ON r_chelsa(cell_id);
Add one more band as a new column:
ALTER TABLE r_chelsa ADD COLUMN IF NOT EXISTS band4 DOUBLE;
UPDATE r_chelsa t
SET band4 = g.band4
FROM read_geotiff('cea.tif', band := [4]) g
WHERE t.cell_id = g.cell_id;
Filter/aggregate:
-- mean of band2 over all cells
SELECT avg(band2) FROM r_chelsa;
-- spatial subset: pick a range of cell_ids
SELECT * FROM r_chelsa WHERE cell_id BETWEEN 1e6 AND 1e6 + 999;
Arguments:
-
band LIST
– read a single or multiple band(s) and return a wide table -
target_mb INTEGER – approximate in-memory window size (MB) used to batch raster I/O and reduce GDAL call overhead. Defaults to 64; increase (e.g. 256–1024) on big machines to reduce passes over the file. The extension chooses a block-aligned number of rows.
Notes:
The function streams; it does not load the full raster in memory.
NoData values are returned as NA.
R example:
library(duckdb)
con <- dbConnect(duckdb::duckdb())
dbExecute(con, "INSTALL geotiff FROM community;")
dbExecute(con, "LOAD geotiff;")
# Single band
dbGetQuery(con, "SELECT * FROM read_geotiff('cea.tif', band := 1) LIMIT 5;")
# Multiple bands
dbGetQuery(con, "SELECT * FROM read_geotiff('cea.tif', band := [1,2,3]) LIMIT 5;")
dbDisconnect(con, shutdown = TRUE)
Performance tips:
Tune target_mb upward if you have RAM and want fewer GDAL calls.
Create an index on cell_id after your final load for faster random access.