Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 57 additions & 14 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,31 +242,74 @@ auth <- gdal_auth_gcs() # GOOGLE_APPLICATION_CREDENTIALS
job |> gdal_with_env(auth) |> gdal_run()

## GDAL Version Compatibility
GDAL 3.13.0+ introduced parameter naming standardization. Known changes include:
- `dst_crs` → `output_crs` (reproject and related CRS output functions)
- `dataset` → `input` (functions that previously used `dataset` parameter)

GDAL's API continues to evolve; consult GDAL release notes for version-specific parameter changes.
### Cross-Version Support
gdalcli 0.7.1+ provides **automatic, transparent compatibility** across GDAL versions 3.11.4 through 3.13+.

Use `gdal_check_version()` for version-aware code:
**RFC 104 Parameter Standardization** introduced parameter naming changes starting in GDAL 3.12.0:
- CRS parameters: `dst_crs` → `output_crs`, `src_crs` → `input_crs`
- Dataset parameters: `dataset` → `input`
- Override parameters: `a_srs` → `override_crs` (for CRS override without reprojection)
- Legacy aliases: `s_srs` (old gdal_translate), `t_srs` (old gdalwarp) automatically converted
- Backward compatibility: Old names accepted throughout the CLI

### How It Works
All 83 auto-generated functions use **version-aware parameter alias routing** internally:
- Code detects the installed GDAL version at runtime
- Parameters are automatically mapped to the correct names for that version (GDAL 3.12+: use new names; older: use old names)
- Users can pass either old (`dst_crs`) or new (`output_crs`) parameter names
- System automatically selects correct name for installed GDAL version

**Complete parameter alias support** (RFC 104):
- `dst_crs`, `t_srs` → `output_crs` (standardized in GDAL 3.12.0+)
- `src_crs`, `s_srs` → `input_crs` (standardized in GDAL 3.12.0+)
- `a_srs` → `override_crs` (standardized in GDAL 3.12.0+)
- `dataset` → `input` (standardized in GDAL 3.12.0+)

### Using Version-Specific Parameters
For code that needs to explicitly handle GDAL version differences, use `gdal_check_version()`:

```r
# Example: Reproject function (dst_crs → output_crs)
if (gdal_check_version("3.13", op = ">=")) {
# Example: Using either old or new parameter names (both work)
# On GDAL 3.12+: uses output_crs, on older: uses dst_crs
job <- gdal_raster_reproject(input = "in.tif", output_crs = "EPSG:4326")
job <- gdal_raster_reproject(input = "in.tif", dst_crs = "EPSG:4326") # Also works!

# Example: Legacy parameter names (e.g., from old gdal_translate/gdalwarp scripts)
job <- gdal_raster_reproject(input = "in.tif", t_srs = "EPSG:4326") # Old gdalwarp name
job <- gdal_vector_reproject(input = "in.gpkg", s_srs = "EPSG:4326") # Old source CRS name

# Example: Explicit version checks for complex logic
if (gdal_check_version("3.12", op = ">=")) {
job <- gdal_raster_reproject(input = "in.tif", output_crs = "EPSG:4326")
} else {
job <- gdal_raster_reproject(input = "in.tif", dst_crs = "EPSG:4326")
}

# Example: Overview function (dataset → input)
if (gdal_check_version("3.13", op = ">=")) {
job <- gdal_raster_overview_add(input = "in.tif", levels = c(2, 4, 8))
} else {
job <- gdal_raster_overview_add(dataset = "in.tif", levels = c(2, 4, 8))
}
```

### Parameter Synonyms

Beyond version-aware aliases, gdalcli supports **parameter synonyms** — alternative names for the same parameter that are always valid. This provides flexibility when migrating code or using familiar parameter names:

```r
# Example: output_format synonyms (all equivalent)
job <- gdal_raster_convert(input = "in.tif", output = "out.tif", output_format = "COG")
job <- gdal_raster_convert(input = "in.tif", output = "out.tif", format = "COG") # Shorter alias
job <- gdal_raster_convert(input = "in.tif", output = "out.tif", of = "COG") # Common abbreviation

# Example: input_layer synonyms (all equivalent)
job <- gdal_vector_convert(input = "in.gpkg", output = "out.gpkg", input_layer = "my_layer")
job <- gdal_vector_convert(input = "in.gpkg", output = "out.gpkg", layer = "my_layer") # Shorter
job <- gdal_vector_convert(input = "in.gpkg", output = "out.gpkg", input-layer = "my_layer") # Dash variant
```

All synonyms are automatically normalized to their canonical parameter names, ensuring compatibility across different coding styles and legacy scripts.

### Recommended Approach
**In most cases, just use the modern parameter names** (`output_crs`, `input_crs`, `input`, `override_crs`) — gdalcli's version routing handles the rest automatically regardless of installed GDAL version. The system works seamlessly whether users are on GDAL 3.11.4, 3.12.0, 3.13.0, or newer.

For parameter synonyms, use whichever name you find most readable or natural — all alternatives work identically. The system normalizes them behind the scenes.

### Programmatic Command Invocation

`gdal_call()` enables dynamic command invocation by name or function reference, supporting metaprogramming and serialization patterns:
Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: gdalcli
Title: Frontend for the GDAL (>= 3.11) CLI
Version: 0.7.0
Version: 0.7.1
Authors@R:
person("Andrew", "Brown", role = c("aut", "cre"),
email = "brown.andrewg@gmail.com",
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# gdalcli 0.7.1 (2026-05-10)

- All generated functions gain an ellipsis and support parameter synonyms (e.g., `format`/`of` for `output_format`)

# gdalcli 0.7.0 (2026-05-09)

- Added update intent feature for pipeline classification. Now code generation auto-detects commands that open files for update (e.g., `gdal_raster_edit`, `gdal_vector_edit`) via `GDAL_INTENT_MAPPINGS.json` with customizable overrides
Expand Down
44 changes: 44 additions & 0 deletions R/core-gdal_parameter_aliases.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#' GDAL Parameter Aliases and Synonyms
#'
#' Documentation of parameter aliases and synonyms supported by gdalcli functions
#' for backward compatibility across GDAL versions.
#'
#' @details
#' All gdalcli wrapper functions support flexible parameter naming through two mechanisms:
#'
#' **Version-aware aliases**: Old GDAL parameter names (introduced before RFC 104)
#' automatically route to modern names on GDAL 3.12+, where parameter standardization
#' (RFC 104) introduced the new naming scheme. Backward compatibility is maintained
#' throughout: pass either old or new names; routing is automatic.
#'
#' Common aliases (RFC 104 - GDAL 3.12+):
#' - `dst_crs` or `t_srs` -> `output_crs` (standardized CRS destination parameter)
#' - `src_crs` or `s_srs` -> `input_crs` (standardized CRS source parameter)
#' - `dataset` -> `input` (unified input parameter naming)
#' - `a_srs` -> `override_crs` (CRS override without reprojection)
#'
#' **Parameter synonyms**: Alternative names for the same parameter that are always
#' valid, regardless of GDAL version. Useful for shorter or more familiar parameter names.
#'
#' Common synonyms:
#' - `output_format`: also accepts `format`, `of`, `output-format`
#' - `input_layer`: also accepts `layer`, `input-layer`
#'
#' Users can pass parameters using either old or new names; the system automatically
#' handles routing to the correct names for the installed GDAL version.
#'
#' @examples
#' \dontrun{
#' # All of these are equivalent across GDAL versions
#' job1 <- gdal_raster_reproject(input = "in.tif", output_crs = "EPSG:4326")
#' job2 <- gdal_raster_reproject(input = "in.tif", dst_crs = "EPSG:4326") # Old name
#' job3 <- gdal_raster_reproject(input = "in.tif", t_srs = "EPSG:4326") # Legacy gdalwarp name
#'
#' # Parameter synonyms
#' conv1 <- gdal_raster_convert(input = "in.tif", output_format = "COG")
#' conv2 <- gdal_raster_convert(input = "in.tif", format = "COG") # Shorter
#' conv3 <- gdal_raster_convert(input = "in.tif", of = "COG") # Abbreviation
#' }
#' @seealso [gdal_call()] for dynamic command invocation, [gdal_check_version()] for version-specific logic
#' @name gdal_parameter_aliases
NULL
2 changes: 1 addition & 1 deletion R/core-optional-features.R
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ gdal_capabilities <- function() {
current = version,
is_3_11 = gdal_check_version("3.11", op = ">="),
is_3_12 = gdal_check_version("3.12", op = ">="),
is_3_13 = gdal_check_version("3.13", op = ">=")
is_3_13 = gdal_check_version("3.12.2", op = ">=")
),
features = list(
explicit_args = .gdal_has_feature("explicit_args"),
Expand Down
136 changes: 136 additions & 0 deletions R/core-parameter-aliases.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Parameter alias and synonym handling
# Supports: (1) version-aware renames (RFC 104), (2) parameter synonyms
# Routes old parameter names (dst_crs > output_crs, dataset > input, etc.) based on GDAL version
# Normalizes synonyms (format/of > output_format, layer/input-layer > input_layer) always

#' Merge Parameter Aliases and Synonyms
#'
#' Handle parameter aliases (version-aware) and synonyms (always valid).
#' Routes old parameter names based on GDAL version; normalizes synonyms to canonical names.
#'
#' @param dots List from `...` containing potential aliases/synonyms
#' @param named_args Named parameters from function signature
#' @param gdal_version GDAL version string (optional, auto-detected if NULL)
#'
#' @return List of merged parameters with aliases and synonyms resolved
#' @keywords internal
.merge_alias_parameters <- function(dots, named_args, gdal_version = NULL) {
# Canonical parameter names with version-aware aliases and synonyms
canonical_params <- list(
# CRS parameters (old names: dst_crs, t_srs, src_crs, s_srs, a_srs)
output_crs = list(
aliases = c("dst_crs", "t_srs"),
old_name = "dst_crs",
synonyms = c()
),
input_crs = list(
aliases = c("src_crs", "s_srs"),
old_name = "src_crs",
synonyms = c()
),
override_crs = list(
aliases = c("a_srs"),
old_name = "a_srs",
synonyms = c()
),
# Dataset/input parameters (old name: dataset)
input = list(
aliases = c("dataset"),
old_name = "dataset",
synonyms = c()
),
# Output format (synonyms only)
output_format = list(
aliases = c(),
old_name = NULL,
synonyms = c("format", "of", "output-format")
),
# Layer parameters (synonyms only)
input_layer = list(
aliases = c(),
old_name = NULL,
synonyms = c("layer", "input-layer")
),
output_layer = list(
aliases = c(),
old_name = NULL,
synonyms = c()
)
)

# Check if we're on GDAL 3.12+
# Parameter renames (RFC 104) introduced in GDAL 3.12.0, standardized in 3.13.0
# Backward compatibility: old names accepted in CLI, routed to new names
is_new_version <- gdal_check_version("3.12", op = ">=")

# Start with existing named arguments
result <- named_args

if (length(dots) > 0) {
for (arg_name in names(dots)) {
canonical_name <- arg_name
found_mapping <- FALSE
mapping_type <- NULL

# Check if already canonical
if (arg_name %in% names(canonical_params)) {
canonical_name <- arg_name
found_mapping <- TRUE
mapping_type <- "canonical"
} else {
# Look for version-aware aliases or synonyms
for (can_name in names(canonical_params)) {
param_config <- canonical_params[[can_name]]

if (arg_name %in% param_config$aliases) {
canonical_name <- can_name
found_mapping <- TRUE
mapping_type <- "version_alias"
break
}

if (arg_name %in% param_config$synonyms) {
canonical_name <- can_name
found_mapping <- TRUE
mapping_type <- "synonym"
break
}
}
}

if (found_mapping) {
if (mapping_type == "version_alias") {
# Route based on GDAL version
if (is_new_version) {
if (is.null(result[[canonical_name]])) {
result[[canonical_name]] <- dots[[arg_name]]
}
} else {
old_name <- canonical_params[[canonical_name]]$old_name
if (!is.null(old_name) && is.null(result[[old_name]])) {
result[[old_name]] <- dots[[arg_name]]
}
}
} else if (mapping_type == "synonym") {
# Normalize to canonical name
if (is.null(result[[canonical_name]])) {
result[[canonical_name]] <- dots[[arg_name]]
}
} else if (mapping_type == "canonical") {
# Already canonical
if (is.null(result[[arg_name]])) {
result[[arg_name]] <- dots[[arg_name]]
}
}
} else {
# No mapping found
if (is.null(result[[arg_name]])) {
result[[arg_name]] <- dots[[arg_name]]
}
}
}
}

result
}

5 changes: 4 additions & 1 deletion R/gdal.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#' See \url{https://gdal.org/en/release-3.11/programs/gdal.html} for detailed GDAL documentation.
#' @param x A filename (for 'gdal info'), a pipeline string (for 'gdal pipeline'), a command vector, or a gdal_job object from a piped operation
#' @param drivers Display driver list as JSON document (Logical)
#' @param ... Parameter aliases and synonyms for backward compatibility. See `?gdal_parameter_aliases` for details.
#' @return A [gdal_job] object.
#' @family gdal_utilities
#' @examples
Expand All @@ -23,7 +24,8 @@
#' }
#' @export
gdal <- function(x = NULL,
drivers = FALSE) {
drivers = FALSE,
...) {

if (!is.null(x)) {
# Check if x is a piped gdal_job
Expand Down Expand Up @@ -93,6 +95,7 @@ gdal <- function(x = NULL,

merged_args <- list()
if (!missing(drivers)) merged_args[["drivers"]] <- drivers
merged_args <- .merge_alias_parameters(list(...), merged_args)
.update_intent <- "SAFE"

.arg_mapping <- list(
Expand Down
6 changes: 4 additions & 2 deletions R/gdal_driver_gpkg_repack.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#'
#' See \url{https://gdal.org/en/release-3.11/programs/gdal_driver_gpkg_repack.html} for detailed GDAL documentation.
#' @param dataset GeoPackage dataset (Dataset path) (required). Can also be a [gdal_job] object to extend a pipeline
#' @param ... Parameter aliases and synonyms for backward compatibility. See `?gdal_parameter_aliases` for details.
#' @return A [gdal_job] object.
#' @family gdal_driver_utilities
#' @examples
Expand All @@ -19,7 +20,8 @@
#' result <- gdal_job_run(job)
#' }
#' @export
gdal_driver_gpkg_repack <- function(dataset) {
gdal_driver_gpkg_repack <- function(dataset,
...) {
new_args <- list()
if (!missing(dataset)) new_args[["dataset"]] <- dataset

Expand All @@ -34,7 +36,7 @@ gdal_driver_gpkg_repack <- function(dataset) {
}


merged_args <- new_args
merged_args <- .merge_alias_parameters(list(...), new_args)
.update_intent_mapping <- NULL
.update_intent <- infer_update_intent("gdal_driver_gpkg_repack", merged_args, .update_intent_mapping)

Expand Down
6 changes: 4 additions & 2 deletions R/gdal_driver_gti_create.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#' @param color_interpretation Color interpretation(s) of the bands of the virtual mosaic (Character vector). Choices: "red", "green", "blue", "alpha", "gray", ...
#' @param mask Defines that the virtual mosaic has a mask band (Logical)
#' @param fetch_metadata Fetch a metadata item from source rasters and write it as a field in the index. (Character vector). Format: `<gdal-metadata-name>,<field-name>,<field-type>`
#' @param ... Parameter aliases and synonyms for backward compatibility. See `?gdal_parameter_aliases` for details.
#' @return A [gdal_job] object.
#' @family gdal_driver_utilities
#' @examples
Expand Down Expand Up @@ -72,7 +73,8 @@ gdal_driver_gti_create <- function(input,
nodata = NULL,
color_interpretation = NULL,
mask = FALSE,
fetch_metadata = NULL) {
fetch_metadata = NULL,
...) {
new_args <- list()
if (!missing(input)) new_args[["input"]] <- input
if (!missing(output)) new_args[["output"]] <- output
Expand Down Expand Up @@ -113,7 +115,7 @@ gdal_driver_gti_create <- function(input,
}


merged_args <- new_args
merged_args <- .merge_alias_parameters(list(...), new_args)
.update_intent_mapping <- NULL
.update_intent <- infer_update_intent("gdal_driver_gti_create", merged_args, .update_intent_mapping)

Expand Down
Loading