| Title: | Create GDAL Multidimensional VRTs from 2D Raster Collections |
|---|---|
| Description: | Stack 2D rasters (COGs, GeoTIFFs, etc.) along a new dimension, producing GDAL Multidimensional VRT files. This enables treating a collection of 2D rasters as a single multidimensional array, readable by GDAL's multidim API, xarray backends (gdx, xgdal), and other tools. Supports regular axes (start + increment), explicit values, and automatic extraction of dimension values from filenames using regex patterns with optional date/time parsing. |
| Authors: | Michael Sumner [aut, cre] (ORCID: <https://orcid.org/0000-0002-2471-7511>) |
| Maintainer: | Michael Sumner <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-10-27 23:59:35 UTC |
| Source: | https://github.com/hypertidy/vrtstack |
Writes a GDAL multidimensional VRT for one variable with dimensions
(time, Y, X). Y/X are <RegularlySpacedValues> from the signature; the
concat coordinate is written as inline string values from the sources table.
One <Source> is emitted per file: a multi-step file contributes a single
source whose full extent lands at the DestSlab offset of its first step.
emit_mdim_vrt(sources, sig, path)emit_mdim_vrt(sources, sig, path)
sources |
A sources table from |
sig |
A signature from |
path |
Output VRT path. |
path, invisibly.
Stack 2D rasters along a new dimension, producing a GDAL Multidimensional VRT. This enables treating a collection of 2D rasters (COGs, GeoTIFFs, etc.) as a single multidimensional array, readable by GDAL's multidim API and xarray backends.
gdal_mdim_stack( files, dim_name, dim_values = NULL, dim_start = NULL, dim_increment = NULL, dim_type = NULL, dim_direction = NULL, dim_unit = NULL, time_origin = NULL, time_unit = "days", parse_format = NULL, array_name = NULL, output = NULL )gdal_mdim_stack( files, dim_name, dim_values = NULL, dim_start = NULL, dim_increment = NULL, dim_type = NULL, dim_direction = NULL, dim_unit = NULL, time_origin = NULL, time_unit = "days", parse_format = NULL, array_name = NULL, output = NULL )
files |
Character vector of file paths (COGs, GeoTIFFs, /vsicurl/ URIs, etc.) |
dim_name |
Name for the new dimension (e.g., "time", "depth", "level") |
dim_values |
Dimension values. One of:
|
dim_start, dim_increment
|
For regular axes, specify start and increment instead of explicit values. Files are assumed to be in the correct order. |
dim_type |
Optional dimension type: |
dim_direction |
Optional direction: |
dim_unit |
Unit string for the dimension (e.g., |
time_origin |
For temporal dimensions, origin date as |
time_unit |
For temporal dimensions: |
parse_format |
For date/time extraction, a |
array_name |
Name for the data array in the VRT. Default: derived from
common filename prefix, or |
output |
Output VRT path. If NULL, returns XML string without writing. |
The function generates a GDAL Multidimensional VRT that virtually stacks 2D rasters along a new dimension. Key features:
Regular axes: When dimension values are evenly spaced (or specified via
dim_start/dim_increment), the VRT uses <RegularlySpacedValues> for
compact representation.
Auto-detection: Even with explicit dim_values, regular spacing is
detected automatically and converted to the compact form.
Filename parsing: Extract dimension values from filenames using regex,
with optional date/time parsing. Useful for time series like
"temperature_20230101.tif".
Cloud-native: Supports /vsicurl/ URIs for remote files. HTTP(S) URLs
are automatically prefixed with /vsicurl/.
The resulting VRT can be read by:
GDAL's multidim API: gdalmdiminfo, gdalmdimtranslate
Python xarray backends: gdx, xgdal
Any tool supporting GDAL multidimensional rasters
Path to VRT file (invisibly), or XML string if output is NULL.
## Not run: # Stack soil layers by depth gdal_mdim_stack( files = c("soil_0cm.tif", "soil_30cm.tif", "soil_100cm.tif"), dim_name = "depth", dim_values = c(0, 30, 100), dim_type = "VERTICAL", dim_direction = "DOWN", dim_unit = "cm", output = "soil_stack.vrt" ) # Time series with regex extraction gdal_mdim_stack( files = list.files(pattern = "temp_\\d{8}\\.tif$"), dim_name = "time", dim_values = "(\\d{8})", parse_format = "%Y%m%d", dim_type = "TEMPORAL", time_origin = "1970-01-01", time_unit = "days", output = "temperature.vrt" ) # Regular axis - monthly data gdal_mdim_stack( files = sprintf("precip_%02d.tif", 1:12), dim_name = "month", dim_start = 1, dim_increment = 1, output = "monthly.vrt" ) # Cloud-hosted COGs gdal_mdim_stack( files = paste0( "https://storage.googleapis.com/solus100pub/claytotal_", c(0, 5, 15, 30, 60, 100, 150), "_cm_p.tif" ), dim_name = "depth", dim_values = c(0, 5, 15, 30, 60, 100, 150), dim_type = "VERTICAL", dim_unit = "cm", array_name = "claytotal", output = "solus_clay.vrt" ) ## End(Not run)## Not run: # Stack soil layers by depth gdal_mdim_stack( files = c("soil_0cm.tif", "soil_30cm.tif", "soil_100cm.tif"), dim_name = "depth", dim_values = c(0, 30, 100), dim_type = "VERTICAL", dim_direction = "DOWN", dim_unit = "cm", output = "soil_stack.vrt" ) # Time series with regex extraction gdal_mdim_stack( files = list.files(pattern = "temp_\\d{8}\\.tif$"), dim_name = "time", dim_values = "(\\d{8})", parse_format = "%Y%m%d", dim_type = "TEMPORAL", time_origin = "1970-01-01", time_unit = "days", output = "temperature.vrt" ) # Regular axis - monthly data gdal_mdim_stack( files = sprintf("precip_%02d.tif", 1:12), dim_name = "month", dim_start = 1, dim_increment = 1, output = "monthly.vrt" ) # Cloud-hosted COGs gdal_mdim_stack( files = paste0( "https://storage.googleapis.com/solus100pub/claytotal_", c(0, 5, 15, 30, 60, 100, 150), "_cm_p.tif" ), dim_name = "depth", dim_values = c(0, 5, 15, 30, 60, 100, 150), dim_type = "VERTICAL", dim_unit = "cm", array_name = "claytotal", output = "solus_clay.vrt" ) ## End(Not run)
<Unit> on coordinate arrays that lost it in gdal mdim mosaic
A coordinate array is any <Array> with no <Source>. Units are read from
one source via gdal mdim info (or supplied directly via units). Arrays
that already carry a <Unit> are left untouched, so this is idempotent and a
no-op once GDAL propagates the unit itself.
graft_coord_units(x, source = NULL, units = NULL)graft_coord_units(x, source = NULL, units = NULL)
x |
Mosaic recipe: xml2 document or XML string. |
source |
One source path to read units from (via |
units |
Optional named character vector (array name -> unit); bypasses
|
Enriched x as an xml_document.
Inject invariant root-group provenance attributes dropped by mosaic
graft_group_attrs( x, source = NULL, attrs = NULL, skip = c("Conventions", .MDIM_PER_FILE_GLOBALS) )graft_group_attrs( x, source = NULL, attrs = NULL, skip = c("Conventions", .MDIM_PER_FILE_GLOBALS) )
x |
Mosaic recipe: xml2 document or XML string. |
source |
One source path to read globals from (via |
attrs |
Optional named character vector (name -> value); bypasses
|
skip |
Attribute names to exclude (default: |
Enriched x as an xml_document.
One row (as a list) per collection: the grid geometry and array metadata the emitter needs, derived from catalog metadata or a single cached probe. The emitter trusts these values, so a collection with non-aligned assets is a signature-gate refusal, never a resample.
mdim_signature( collection, variable, nx, ny, x0, dx, y0, dy, srs, dtype, nodata = NA, scale = NA, offset = NA, unit = NA_character_, steps_per_file = 1L, chunk_nx = NA_integer_, chunk_ny = NA_integer_, provenance = "probe" )mdim_signature( collection, variable, nx, ny, x0, dx, y0, dy, srs, dtype, nodata = NA, scale = NA, offset = NA, unit = NA_character_, steps_per_file = 1L, chunk_nx = NA_integer_, chunk_ny = NA_integer_, provenance = "probe" )
collection, variable
|
Identifiers for the collection and data array. |
nx, ny
|
Grid dimensions in X and Y. |
x0, dx, y0, dy
|
Grid origin and spacing as pixel-centre coordinates in the
array's own orientation ( |
srs |
Spatial reference (e.g. |
dtype |
VRT data type of the array (e.g. |
nodata, scale, offset, unit
|
Optional array metadata; |
steps_per_file |
Number of concat-axis steps stored in each source file. |
chunk_nx, chunk_ny
|
Optional source chunk (block) shape, recorded for later byte-range harvesting. |
provenance |
Free text recording where the grid facts came from (catalog, probe, or a published grid). |
A named list describing one collection's grid and array metadata.
mdim_sources(), emit_mdim_vrt()
One row per output step along the concat (time) axis. This is the interface
contract produced by adapters and consumed by emit_mdim_vrt().
mdim_sources( time, dsn, source_ref, kind, step_in_file = 0L, dest_index = NULL, source_view = NA_character_ )mdim_sources( time, dsn, source_ref, kind, step_in_file = 0L, dest_index = NULL, source_view = NA_character_ )
time |
Vector of per-step coordinate values (e.g. a |
dsn |
Source data source names (file paths or |
source_ref |
Array path (e.g. |
kind |
Source flavour: |
step_in_file |
Zero-based step index within a multi-step file. |
dest_index |
Zero-based destination position along the concat axis;
defaults to the rank order of |
source_view |
Optional GetView expression that brings each source to
exactly the destination rank (e.g. |
A data frame ordered by dest_index, one row per output step.
mdim_signature(), emit_mdim_vrt()
Pure XML-tree rewrite (no GDAL needed). Sets the concat dimension size,
rewrites the concat coordinate array's values, and regenerates the
<Source> list of every data array (an <Array> that owns a <Source>).
Per-array SourceArray path and slab geometry are read from the array's
first source, never assumed from the array name.
mdim_template_extend(xml, files, concat_values = NULL, concat_dim = "time")mdim_template_extend(xml, files, concat_values = NULL, concat_dim = "time")
xml |
Template VRT as an XML string (or anything |
files |
Full character vector of sources. |
concat_values |
Values for the concat coordinate array, or |
concat_dim |
Concat dimension name (default |
An xml_document (serialise with as.character() / xml2::write_xml()).
The emitter's requirements expressed as a checklist over one already-parsed
STAC item (as from jsonlite::fromJSON(..., simplifyVector = FALSE)). No
HTTP is performed here; fetching items is an adapter's job.
stac_sufficiency(item, asset = NULL)stac_sufficiency(item, asset = NULL)
item |
A parsed STAC item (a list). |
asset |
Optional asset key; when supplied, asset-level fields take precedence over item-level properties. |
proj:epsg versus proj:code is a proj-extension version marker,
not a gap: either satisfies the CRS requirement. datetime may be null for
interval items, with start_datetime/end_datetime carrying the extent.
A data frame with one row per checked field: field, found_at
(one of "item-properties", "asset", "absent"), and a short
value_preview.
gdal mdim info
Opens path through gdalraster's mdim info algorithm as a round-trip check
that the emitted VRT parses and can be interpreted by GDAL.
verify_vrt(path)verify_vrt(path)
path |
Path to a VRT. |
The gdal mdim info output as a character string, invisibly.
A convenience wrapper for gdal_mdim_stack() for vertical profile data
(soil layers, atmospheric levels, ocean depths, etc.)
vrt_depth_stack( files, values, direction = "DOWN", unit = NULL, dim_name = NULL, array_name = NULL, output = NULL )vrt_depth_stack( files, values, direction = "DOWN", unit = NULL, dim_name = NULL, array_name = NULL, output = NULL )
files |
Character vector of file paths |
values |
Numeric vector of depth/level values, or regex pattern to extract |
direction |
Vertical direction: |
unit |
Unit string (e.g., |
dim_name |
Dimension name. Default: |
array_name |
Name for the data array |
output |
Output VRT path |
Path to VRT file (invisibly), or XML string if output is NULL.
## Not run: # Soil layers at specific depths vrt_depth_stack( files = c("soil_0cm.tif", "soil_30cm.tif", "soil_100cm.tif"), values = c(0, 30, 100), unit = "cm", output = "soil_profile.vrt" ) # Extract depth from filename vrt_depth_stack( files = list.files(pattern = "temp_\\d+m\\.tif$"), values = "(\\d+)m", # extracts "50" from "temp_50m.tif" unit = "m", output = "ocean_profile.vrt" ) ## End(Not run)## Not run: # Soil layers at specific depths vrt_depth_stack( files = c("soil_0cm.tif", "soil_30cm.tif", "soil_100cm.tif"), values = c(0, 30, 100), unit = "cm", output = "soil_profile.vrt" ) # Extract depth from filename vrt_depth_stack( files = list.files(pattern = "temp_\\d+m\\.tif$"), values = "(\\d+)m", # extracts "50" from "temp_50m.tif" unit = "m", output = "ocean_profile.vrt" ) ## End(Not run)
A convenience wrapper for gdal_mdim_stack() when files represent regularly
spaced values (e.g., monthly data, hourly snapshots, uniform vertical levels).
vrt_regular_stack( files, dim_name, start, increment, unit = NULL, array_name = NULL, output = NULL )vrt_regular_stack( files, dim_name, start, increment, unit = NULL, array_name = NULL, output = NULL )
files |
Character vector of file paths, in order |
dim_name |
Name for the new dimension |
start |
Starting value |
increment |
Spacing between values |
unit |
Unit string |
array_name |
Name for the data array |
output |
Output VRT path |
Path to VRT file (invisibly), or XML string if output is NULL.
## Not run: # 12 monthly files vrt_regular_stack( files = sprintf("precip_%02d.tif", 1:12), dim_name = "month", start = 1, increment = 1, output = "monthly_precip.vrt" ) # Pressure levels at 50 hPa intervals vrt_regular_stack( files = level_files, dim_name = "pressure", start = 1000, increment = -50, unit = "hPa", output = "pressure_levels.vrt" ) ## End(Not run)## Not run: # 12 monthly files vrt_regular_stack( files = sprintf("precip_%02d.tif", 1:12), dim_name = "month", start = 1, increment = 1, output = "monthly_precip.vrt" ) # Pressure levels at 50 hPa intervals vrt_regular_stack( files = level_files, dim_name = "pressure", start = 1000, increment = -50, unit = "hPa", output = "pressure_levels.vrt" ) ## End(Not run)
A convenience wrapper for gdal_mdim_stack() tailored for time series data.
Extracts dates from filenames and encodes them as numeric offsets from an origin.
vrt_time_stack( files, pattern = "(\\d{8})", format = "%Y%m%d", origin = "1970-01-01", unit = "days", array_name = NULL, output = NULL )vrt_time_stack( files, pattern = "(\\d{8})", format = "%Y%m%d", origin = "1970-01-01", unit = "days", array_name = NULL, output = NULL )
files |
Character vector of file paths |
pattern |
Regex pattern with one capturing group for the date portion.
Default: |
format |
strptime format for parsing. Default: |
origin |
Time origin as |
unit |
Time unit: |
array_name |
Name for the data array |
output |
Output VRT path |
Path to VRT file (invisibly), or XML string if output is NULL.
## Not run: # Daily temperature files: temp_20230101.tif, temp_20230102.tif, ... vrt_time_stack( files = list.files(pattern = "temp_\\d{8}\\.tif$"), output = "temperature_timeseries.vrt" ) # Custom pattern for ISO dates: data_2023-01-15.tif vrt_time_stack( files = my_files, pattern = "(\\d{4}-\\d{2}-\\d{2})", format = "%Y-%m-%d", output = "timeseries.vrt" ) ## End(Not run)## Not run: # Daily temperature files: temp_20230101.tif, temp_20230102.tif, ... vrt_time_stack( files = list.files(pattern = "temp_\\d{8}\\.tif$"), output = "temperature_timeseries.vrt" ) # Custom pattern for ISO dates: data_2023-01-15.tif vrt_time_stack( files = my_files, pattern = "(\\d{4}-\\d{2}-\\d{2})", format = "%Y-%m-%d", output = "timeseries.vrt" ) ## End(Not run)
vrtstack() builds a GDAL multidimensional VRT that concatenates a set of
sources along one axis. Two construction paths share a single concat-axis
resolver:
vrtstack( files, concat = NULL, template = NULL, concat_dim = "time", n_template = 10, parse_format = NULL, origin = NULL, unit = "days", metadata_from = "first", output = NULL, ... )vrtstack( files, concat = NULL, template = NULL, concat_dim = "time", n_template = 10, parse_format = NULL, origin = NULL, unit = "days", metadata_from = "first", output = NULL, ... )
files |
Character vector of source paths/URIs (full set). |
concat |
Concat-axis spec, resolved by |
template |
One of: |
concat_dim |
Name of the concatenation dimension. Default |
n_template |
Files used to build the auto-generated recipe when
|
parse_format, origin, unit
|
Passed to |
metadata_from |
Which source donates the coordinate units and root-group
provenance restored onto an auto-generated recipe ( |
output |
Output VRT path, or |
... |
Passed to the from-scratch builder ( |
from-scratch (template = NULL) - the original GeoTIFF/COG path. Each
2D source contributes one slab of a single new 3D array. Sources use the
classic <SourceBand> + <SourceTranspose> form.
template-extend (template = <vrt> or template = TRUE) - for
mdim-recognised sources (NetCDF/Zarr/HDF5) that may carry several arrays
(OISST: anom/err/ice/sst). A small "first-n" mdim VRT is taken as the
recipe, and the full file set is fleshed out by re-parameterising only the
concat dimension and the per-array <Source> lists. Everything else
(coordinate arrays, slab geometry, scale/offset/unit/attributes) is carried
verbatim from the template.
The template is the GDAL-native analogue of the "recipe-not-payload"
principle: GDAL's own gdal mdim mosaic writes the recipe for the first n
files; we only template the axis that varies with the file set.
Path to the VRT (invisibly) or the XML string if output is NULL.
#vrt <- vrtstack(<files>, concat = "(\d{8})", parse_format = "%Y%m%d", #origin = "1978-01-01", unit = "days", # must match the template #template = TRUE, concat_dim = "time")#vrt <- vrtstack(<files>, concat = "(\d{8})", parse_format = "%Y%m%d", #origin = "1978-01-01", unit = "days", # must match the template #template = TRUE, concat_dim = "time")