# Generated by using Rcpp::compileAttributes() -> do not edit by hand
# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393

#' Helper functions for GDAL raster data types
#'
#' These are convenience functions that return information about a raster
#' data type, return the smallest data type that can fully express two input
#' data types, or find the smallest data type able to support specified
#' requirements.
#'
#' @name data_type_helpers
#'
#' @details
#' `dt_size()` returns the data type size in bytes by default, optionally in
#' bits (returns zero if `dt` is not recognized).
#'
#' `dt_is_complex()` returns `TRUE` if the passed type is complex (one of
#' CInt16, CInt32, CFloat32 or CFloat64), i.e., if it consists of a real and
#' imaginary component.
#'
#' `dt_is_integer()` returns `TRUE` if the passed type is integer (one of
#' Byte, UInt8 (GDAL >= 3.13), Int16, UInt16, Int32, UInt32, CInt16, CInt32).
#'
#' `dt_is_floating()` returns `TRUE` if the passed type is floating (one of
#' Float32, Float16, Float64, CFloat16, CFloat32, CFloat64).
#'
#' `dt_is_signed()` returns `TRUE` if the passed type is signed.
#'
#' `dt_union()` returns the smallest data type that can fully express both
#' input data types (returns a data type name as character string).
#'
#' `dt_union_with_value()` unions a data type with the data type found for a
#' given value, and returns the resulting data type name as character string.
#'
#' `dt_find()` finds the smallest data type able to support the given
#' requirements (returns a data type name as character string).
#'
#' `dt_find_for_value()` finds the smallest data type able to support the
#' given `value` (returns a data type name as character string).
#'
#' @param dt Character string containing a GDAL data type name (e.g.,
#' `"Byte"`, `"UInt8"` (GDAL >= 3.13), `"Int16"`, `"UInt16"`, `"Int32"`,
#' `"UInt32"`, `"Float32"`, `"Float64"`, etc.)
#' @param as_bytes Logical value, `TRUE` to return data type size in bytes
#' (the default), `FALSE` to return the size in bits.
#' @param dt_other Character string containing a GDAL data type name.
#' @param value Numeric value for which to find a data type (passing the real
#' part if `is_complex = TRUE`).
#' @param is_complex Logical value, `TRUE` if `value` is complex (default is
#' `FALSE`), or if complex values are necessary in `dt_find()`.
#' @param bits Integer value specifying the number of bits necessary.
#' @param is_signed Logical value, `TRUE` if negative values are necessary.
#' @param is_floating Logical value, `TRUE` if non-integer values are
#' necessary.
#'
#' @seealso
#' [`GDALRaster$getDataTypeName()`][GDALRaster]
#'
#' @examples
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' ds <- new(GDALRaster, elev_file)
#'
#' ds$getDataTypeName(band = 1) |> dt_size()
#' ds$getDataTypeName(band = 1) |> dt_size(as_bytes = FALSE)
#' ds$getDataTypeName(band = 1) |> dt_is_complex()
#' ds$getDataTypeName(band = 1) |> dt_is_integer()
#' ds$getDataTypeName(band = 1) |> dt_is_floating()
#' ds$getDataTypeName(band = 1) |> dt_is_signed()
#'
#' ds$close()
#'
#' f <- system.file("extdata/complex.tif", package="gdalraster")
#' ds <- new(GDALRaster, f)
#'
#' ds$getDataTypeName(band = 1) |> dt_size()
#' ds$getDataTypeName(band = 1) |> dt_size(as_bytes = FALSE)
#' ds$getDataTypeName(band = 1) |> dt_is_complex()
#' ds$getDataTypeName(band = 1) |> dt_is_integer()
#' ds$getDataTypeName(band = 1) |> dt_is_floating()
#' ds$getDataTypeName(band = 1) |> dt_is_signed()
#'
#' ds$close()
#'
#' dt_union("Byte", "Int16")
#' dt_union_with_value("Byte", -1)
#' dt_union_with_value("Byte", 256)
#'
#' dt_find(bits = 32, is_signed = FALSE, is_floating = FALSE)
#' dt_find_for_value(0)
#' dt_find_for_value(-1)
#' dt_find_for_value(NaN)
#' dt_find_for_value(.Machine$integer.max)
dt_size <- function(dt, as_bytes = TRUE) {
    .Call(`_gdalraster_dt_size`, dt, as_bytes)
}

#' @rdname data_type_helpers
dt_is_complex <- function(dt) {
    .Call(`_gdalraster_dt_is_complex`, dt)
}

#' @rdname data_type_helpers
dt_is_integer <- function(dt) {
    .Call(`_gdalraster_dt_is_integer`, dt)
}

#' @rdname data_type_helpers
dt_is_floating <- function(dt) {
    .Call(`_gdalraster_dt_is_floating`, dt)
}

#' @rdname data_type_helpers
dt_is_signed <- function(dt) {
    .Call(`_gdalraster_dt_is_signed`, dt)
}

#' @rdname data_type_helpers
dt_union <- function(dt, dt_other) {
    .Call(`_gdalraster_dt_union`, dt, dt_other)
}

#' @rdname data_type_helpers
dt_union_with_value <- function(dt, value, is_complex = FALSE) {
    .Call(`_gdalraster_dt_union_with_value`, dt, value, is_complex)
}

#' @rdname data_type_helpers
dt_find <- function(bits, is_signed, is_floating, is_complex = FALSE) {
    .Call(`_gdalraster_dt_find`, bits, is_signed, is_floating, is_complex)
}

#' @rdname data_type_helpers
dt_find_for_value <- function(value, is_complex = FALSE) {
    .Call(`_gdalraster_dt_find_for_value`, value, is_complex)
}

#' Get GDAL version
#'
#' `gdal_version()` returns a character vector of GDAL runtime version
#' information. `gdal_version_num()` returns only the full version number
#' (`gdal_version()[2]`) as an integer value.
#'
#' @name gdal_version
#'
#' @returns
#' `gdal_version()` returns a character vector of length four containing:
#'   * "-version" - one line version message, e.g., "GDAL 3.6.3, released
#'   2023/03/12"
#'   * "GDAL_VERSION_NUM" - formatted as a string, e.g., "3060300" for
#'   GDAL 3.6.3.0
#'   * "GDAL_RELEASE_DATE" - formatted as a string, e.g., "20230312"
#'   * "GDAL_RELEASE_NAME" - e.g., "3.6.3"
#'
#' `gdal_version_num()` returns `as.integer(gdal_version()[2])`
#' @examples
#' gdal_version()
#'
#' gdal_version_num()
gdal_version <- function() {
    .Call(`_gdalraster_gdal_version`)
}

#' @rdname gdal_version
gdal_version_num <- function() {
    .Call(`_gdalraster_gdal_version_num`)
}

#' Retrieve information on GDAL format drivers for raster and vector
#'
#' `gdal_formats()` returns a table of the supported raster and vector
#' formats, with information about the capabilities of each format driver.
#'
#' @param format A character string containing a driver short name. By default,
#' information for all configured raster and vector format drivers will be
#' returned.
#' @returns A data frame containing:
#' * the format short name
#' * file extension(s) if applicable (space-delimited string), or empty string
#' (`""`)
#' * supports raster (logical)
#' * supports multidimensional raster (logical)
#' * supports vector (logical)
#' * supports geography network (logical)
#' * read/write flags (concatenated string, `ro` is read-only, `w` supports
#' CreateCopy, `w+` supports Create, and `u` supports Update is reported if
#' GDAL >= 3.11)
#' * supports virtual I/O e.g. /vsimem/ (logical)
#' * supports subdatasets (logical)
#' * supported SQL dialects reported if GDAL >= 3.6 (e.g.,
#' `"NATIVE OGRSQL SQLITE"`)
#' * creation raster data types (e.g., `"Byte Int16 UInt16 Float32"` etc.)
#' * creation vector field types (e.g.,
#' `"Integer Integer64 Real String Date DateTime Binary"` etc.)
#' * creation field sub-types (e.g., `"Boolean Int16 Float32 JSON"` etc.)
#' * supports multiple vector layers reported if GDAL >= 3.4 (logical)
#' * supports reading field domains if GDAL >= 3.5 (logical)
#' * creation field domain types if GDAL >= 3.5 (supported values are `Coded`,
#' `Range` and `Glob`)
#'
#' @note
#' Virtual I/O refers to operations on GDAL Virtual File Systems. See
#' \url{https://gdal.org/en/stable/user/virtual_file_systems.html#virtual-file-systems}.
#'
#' @examples
#' gdal_formats() |> str()
#'
#' gdal_formats("GPKG")
gdal_formats <- function(format = "") {
    .Call(`_gdalraster_gdal_formats`, format)
}

#' Get GDAL configuration option
#'
#' `get_config_option()` gets the value of GDAL runtime configuration option.
#' Configuration options are essentially global variables the user can set.
#' They are used to alter the default behavior of certain raster format
#' drivers, and in some cases the GDAL core. For a full description and
#' listing of available options see
#' \url{https://gdal.org/en/stable/user/configoptions.html}.
#'
#' @param key Character name of a configuration option.
#' @returns Character. The value of a (key, value) option previously set with
#' `set_config_option()`. An empty string (`""`) is returned if `key` is not
#' found.
#'
#' @seealso
#' [set_config_option()]
#'
#' `vignette("gdal-config-quick-ref")`
#'
#' @examples
#' ## this option is set during initialization of the gdalraster package
#' get_config_option("OGR_CT_FORCE_TRADITIONAL_GIS_ORDER")
get_config_option <- function(key) {
    .Call(`_gdalraster_get_config_option`, key)
}

#' Set GDAL configuration option
#'
#' `set_config_option()` sets a GDAL runtime configuration option.
#' Configuration options are essentially global variables the user can set.
#' They are used to alter the default behavior of certain raster format
#' drivers, and in some cases the GDAL core. For a full description and
#' listing of available options see
#' \url{https://gdal.org/en/stable/user/configoptions.html}.
#'
#' @param key Character name of a configuration option.
#' @param value Character value to set for the option.
#' `value = ""` (empty string) will unset a value previously set by
#' `set_config_option()`.
#' @returns No return value, called for side effects.
#'
#' @note
#' The configuration option `"CPL_LOG_ERRORS"` can be set to `"OFF"` to disable
#' printing error massages to the console by GDAL. This only affects messages
#' printed by GDAL, and does not disable errors, warnings or other messages
#' emitted by \pkg{gdalraster}. The latter can generally be configured using a
#' function argument or object-level setting in most cases.
#'
#' @seealso
#' [get_config_option()]
#'
#' `vignette("gdal-config-quick-ref")`
#'
#' @examples
#' set_config_option("CPL_LOG_ERRORS", "OFF")
#' get_config_option("CPL_LOG_ERRORS")
#' ## unset to default:
#' set_config_option("CPL_LOG_ERRORS", "")
set_config_option <- function(key, value) {
    invisible(.Call(`_gdalraster_set_config_option`, key, value))
}

#' Get the maximum memory size available for the GDAL block cache
#'
#' `get_cache_max()` returns the maximum amount of memory available to the
#' GDALRasterBlock caching system for caching raster read/write data. Wrapper
#' of `GDALGetCacheMax64()` with return value in MB by default.
#'
#' @details
#' The first time this function is called, it will read the `GDAL_CACHEMAX`
#' configuration option to initialize the maximum cache memory. The value of
#' the configuration option can be expressed as x% of the usable physical RAM
#' (which may potentially be used by other processes). Otherwise it is
#' expected to be a value in MB.
#' As of GDAL 3.10, the default value, if `GDAL_CACHEMAX` has not been set
#' explicitly, is 5% of usable physical RAM.
#'
#' @param units Character string specifying units for the return value. One of
#' `"MB"` (the default), `"GB"`, `"KB"` or `"bytes"` (values of `"byte"`,
#' `"B"` and empty string `""` are also recognized to mean bytes).
#' @returns A numeric value carrying the `integer64` class attribute. Maximum
#' cache memory available in the requested units.
#'
#' @note
#' The value of the `GDAL_CACHEMAX` configuration option is only consulted the
#' first time the cache size is requested (i.e., it must be set as a
#' configuration option prior to any raster I/O during the current session).
#' To change this value programmatically during operation of the program it is
#' better to use [set_cache_max()] (in which case, always given in bytes).
#'
#' @seealso
#' [GDAL_CACHEMAX configuration option](https://gdal.org/en/stable/user/configoptions.html#performance-and-caching)
#'
#' [get_config_option()], [set_config_option()], [get_usable_physical_ram()],
#' [get_cache_used()], [set_cache_max()]
#'
#' @examples
#' get_cache_max()
get_cache_max <- function(units = "MB") {
    .Call(`_gdalraster_get_cache_max`, units)
}

#' Get the size of memory in use by the GDAL block cache
#'
#' `get_cache_used()` returns the amount of memory currently in use for
#' GDAL block caching. Wrapper of `GDALGetCacheUsed64()` with return
#' value in MB by default.
#'
#' @param units Character string specifying units for the return value. One of
#' `"MB"` (the default), `"GB"`, `"KB"` or `"bytes"` (values of `"byte"`,
#' `"B"` and empty string `""` are also recognized to mean bytes).
#' @returns A numeric value carrying the `integer64` class attribute. Amount
#' of the available cache memory currently in use in the requested units.
#'
#' @seealso
#' [GDAL Block Cache](https://firelab.github.io/gdalraster/articles/gdal-block-cache.html)
#'
#' [get_cache_max()], [set_cache_max()]
#'
#' @examples
#' get_cache_used()
get_cache_used <- function(units = "MB") {
    .Call(`_gdalraster_get_cache_used`, units)
}

#' Set the maximum memory size for the GDAL block cache
#'
#' `set_cache_max()` sets the maximum amount of memory that GDAL is permitted
#' to use for GDALRasterBlock caching.
#' *The unit of the value to set is bytes.* Wrapper of `GDALSetCacheMax64()`.
#'
#' @param nbytes A numeric value optionally carrying the `integer64` class
#' attribute (assumed to be a whole number, will be coerced to integer by
#' truncation). Specifies the new cache size in bytes (maximum number of bytes
#' for caching).
#' @returns No return value, called for side effects.
#'
#' @note
#' **This function will not make any attempt to check the consistency of the
#' passed value with the effective capabilities of the OS.**
#'
#' It is recommended to consult the documentation for `get_cache_max()` and
#' `get_cache_used()` before using this function.
#'
#' [get_cache_max()], [get_cache_used()]
#'
#' @examples
#' (cachemax <- get_cache_max("bytes"))
#'
#' set_cache_max(1e8)
#' get_cache_max()  # returns in MB by default
#'
#' # reset to original
#' set_cache_max(cachemax)
#' get_cache_max()
set_cache_max <- function(nbytes) {
    invisible(.Call(`_gdalraster_set_cache_max`, nbytes))
}

#' @noRd
.dump_open_datasets <- function(outfile) {
    .Call(`_gdalraster_dump_open_datasets`, outfile)
}

#' Push a new GDAL CPLError handler
#'
#' `push_error_handler()` is a wrapper for
#' `CPLPushErrorHandler()` in the GDAL Common Portability
#' Library.
#' This pushes a new error handler on the thread-local error handler stack.
#' This handler will be used until removed with `pop_error_handler()`.
#' A typical use is to temporarily set `CPLQuietErrorHandler()` which doesn't
#' make any attempt to report passed error or warning messages, but will
#' process debug messages via `CPLDefaultErrorHandler`.
#'
#' @param handler Character name of the error handler to push.
#' One of `quiet`, `logging` or `default`.
#' @returns No return value, called for side effects.
#'
#' @note
#' This function is for advanced use cases. It is intended for setting a
#' _temporary_ error handler in a limited segment of code. A global error
#' handler specific to the R environment is in use by default.
#'
#' Setting `handler = "logging"` will use `CPLLoggingErrorHandler()`, error
#' handler that logs into the file defined by the `"CPL_LOG"` configuration
#' option. Be sure that option is set when using this error handler.
#'
#' This only affects error reporting from GDAL.
#'
#' Also note that the configuration option `"CPL_LOG_ERRORS"` can be set to
#' `"OFF"` to disable globally the printing of error massages to the console
#' by GDAL.
#'
#' @seealso
#' [pop_error_handler()], [set_config_option()]
#'
#' @examples
#' push_error_handler("quiet")
#' # ...
#' pop_error_handler()
push_error_handler <- function(handler) {
    invisible(.Call(`_gdalraster_push_error_handler`, handler))
}

#' Pop error handler off stack
#'
#' `pop_error_handler()` is a wrapper for `CPLPopErrorHandler()` in the GDAL
#' Common Portability Library.
#' Discards the current error handler on the error handler stack, and restores
#' the one in use before the last `push_error_handler()` call. This method has
#' no effect if there are no error handlers on the current thread's error
#' handler stack.
#'
#' @returns No return value, called for side effects.
#'
#' @seealso
#' [push_error_handler()]
#'
#' @examples
#' push_error_handler("quiet")
#' # ...
#' pop_error_handler()
pop_error_handler <- function() {
    invisible(.Call(`_gdalraster_pop_error_handler`))
}

#' Check a filename before passing to GDAL and potentially fix.
#' 'filename' may be a physical file, URL, connection string, filename with
#' additional parameters, etc.
#' Currently, only checks for leading tilde and does path expansion in that
#' case. Returns the filename in UTF-8 encoding if possible using R enc2utf8.
#'
#' @noRd
.check_gdal_filename <- function(filename) {
    .Call(`_gdalraster_check_gdal_filename`, filename)
}

#' Get the number of processors detected by GDAL
#'
#' `get_num_cpus()` returns the number of processors detected by GDAL.
#' Wrapper of `CPLGetNumCPUs()` in the GDAL Common Portability Library.
#'
#' @return Integer scalar, number of CPUs.
#'
#' @examples
#' get_num_cpus()
get_num_cpus <- function() {
    .Call(`_gdalraster_get_num_cpus`)
}

#' Get usable physical RAM reported by GDAL
#'
#' `get_usable_physical_ram()` returns the total physical RAM, usable by a
#' process, in bytes. It will limit to 2 GB for 32 bit processes. Starting
#' with GDAL 2.4.0, it will also take into account resource limits (virtual
#' memory) on Posix systems. Starting with GDAL 3.6.1, it will also take into
#' account RLIMIT_RSS on Linux. Wrapper of `CPLGetUsablePhysicalRAM()` in the
#' GDAL Common Portability Library.
#'
#' @return Numeric scalar, number of bytes as `bit64::integer64` type (or 0 in
#' case of failure).
#'
#' @note
#' This memory may already be partly used by other processes.
#'
#' @examples
#' get_usable_physical_ram()
get_usable_physical_ram <- function() {
    .Call(`_gdalraster_get_usable_physical_ram`)
}

#' Is SpatiaLite available?
#'
#' `has_spatialite()` returns a logical value indicating whether GDAL was
#' built with support for the SpatiaLite library. SpatiaLite extends the
#' SQLite core to support full Spatial SQL capabilities.
#'
#' @details
#' GDAL supports executing SQL statements against a datasource. For most file
#' formats (e.g. Shapefiles, GeoJSON, FlatGeobuf files), the built-in OGR SQL
#' dialect will be used by default. It is also possible to request the
#' alternate `"SQLite"`  dialect, which will use the SQLite engine to evaluate
#' commands on GDAL datasets. This assumes that GDAL is built with support for
#' SQLite, and preferably with Spatialite support too to benefit from spatial
#' functions.
#'
#' @return Logical scalar. `TRUE` if SpatiaLite is available to GDAL.
#'
#' @note
#' All GDAL/OGR drivers for database systems, e.g., PostgreSQL / PostGIS,
#' Oracle Spatial, SQLite / Spatialite RDBMS, GeoPackage, etc., override the
#' `GDALDataset::ExecuteSQL()` function with a dedicated implementation and, by
#' default, pass the SQL statements directly to the underlying RDBMS. In these
#' cases the SQL syntax varies in some particulars from OGR SQL. Also, anything
#' possible in SQL can then be accomplished for these particular databases. For
#' those drivers, it is also possible to explicitly request the `OGRSQL` or
#' `SQLite` dialects, although performance will generally be much less than the
#' native SQL engine of those database systems.
#'
#' @seealso
#' [ogrinfo()], [ogr_execute_sql()]
#'
#' OGR SQL dialect and SQLITE SQL dialect:\cr
#' \url{https://gdal.org/en/stable/user/ogr_sql_sqlite_dialect.html}
#'
#' @examples
#' has_spatialite()
has_spatialite <- function() {
    .Call(`_gdalraster_has_spatialite`)
}

#' Check if GDAL CPLHTTP services can be useful (libcurl)
#'
#' `http_enabled()` returns `TRUE` if `libcurl` support is enabled.
#' Wrapper of `CPLHTTPEnabled()` in the GDAL Common Portability Library.
#'
#' @return Logical scalar, `TRUE` if GDAL was built with `libcurl` support.
#'
#' @examples
#' http_enabled()
http_enabled <- function() {
    .Call(`_gdalraster_http_enabled`)
}

#' @noRd
.cpl_get_filename <- function(full_filename) {
    .Call(`_gdalraster_cpl_get_filename`, full_filename)
}

#' @noRd
.cpl_get_path <- function(full_filename) {
    .Call(`_gdalraster_cpl_get_path`, full_filename)
}

#' @noRd
.cpl_get_dirname <- function(full_filename) {
    .Call(`_gdalraster_cpl_get_dirname`, full_filename)
}

#' @noRd
.cpl_get_basename <- function(full_filename) {
    .Call(`_gdalraster_cpl_get_basename`, full_filename)
}

#' @noRd
.cpl_get_extension <- function(full_filename) {
    .Call(`_gdalraster_cpl_get_extension`, full_filename)
}

#' @noRd
.cpl_launder_for_filename <- function(full_filename) {
    .Call(`_gdalraster_cpl_launder_for_filename`, full_filename)
}

#' @noRd
.cpl_http_cleanup <- function() {
    invisible(.Call(`_gdalraster_cpl_http_cleanup`))
}

#' Apply geotransform - internal wrapper of GDALApplyGeoTransform()
#'
#' `apply_geotransform_()` applies geotransform coefficients to a raster
#' coordinate in pixel/line space (colum/row), converting into a
#' georeferenced (x, y) coordinate.
#'
#' @param gt Numeric vector of length six containing the geotransform to
#' apply.
#' @param pixel Numeric scalar. A raster pixel (column) coordinate.
#' @param line Numeric scalar. A raster line (row) coordinate.
#' @returns Numeric vector of length two containing a geospatial x/y
#' coordinate (spatial reference system of `gt`).
#' @seealso [`GDALRaster$getGeoTransform()`][GDALRaster], [get_pixel_line()],
#' [inv_geotransform()]
#' @noRd
.apply_geotransform <- function(gt, pixel, line) {
    .Call(`_gdalraster_apply_geotransform_`, gt, pixel, line)
}

#' Apply geotransform (raster column/row to geospatial x/y)
#' input as geotransform vector, no bounds checking on col/row
#' @noRd
.apply_geotransform_gt <- function(col_row, gt) {
    .Call(`_gdalraster_apply_geotransform_gt`, col_row, gt)
}

#' Apply geotransform (raster column/row to geospatial x/y)
#' alternate version for GDALRaster input, with bounds checking
#' @noRd
.apply_geotransform_ds <- function(col_row, ds) {
    .Call(`_gdalraster_apply_geotransform_ds`, col_row, ds)
}

#' Invert geotransform
#'
#' `inv_geotransform()` inverts a vector of geotransform coefficients. This
#' converts the equation from being:\cr
#' raster pixel/line (column/row) -> geospatial x/y coordinate\cr
#' to:\cr
#' geospatial x/y coordinate -> raster pixel/line (column/row)
#'
#' @param gt Numeric vector of length six containing the geotransform to
#' invert.
#' @returns Numeric vector of length six containing the inverted
#' geotransform. The output vector will contain NAs if the input geotransform
#' is uninvertable.
#' @seealso [`GDALRaster$getGeoTransform()`][GDALRaster], [get_pixel_line()]
#' @examples
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' ds <- new(GDALRaster, elev_file)
#' invgt <- ds$getGeoTransform() |> inv_geotransform()
#' ds$close()
#'
#' ptX = 324181.7
#' ptY = 5103901.4
#'
#' ## for a point x, y in the spatial reference system of elev_file
#' ## raster pixel (column number):
#' pixel <- floor(invgt[1] +
#'                invgt[2] * ptX +
#'                invgt[3] * ptY)
#'
#' ## raster line (row number):
#' line <- floor(invgt[4] +
#'               invgt[5] * ptX +
#'               invgt[6] * ptY)
#'
#' ## get_pixel_line() applies this conversion
inv_geotransform <- function(gt) {
    .Call(`_gdalraster_inv_geotransform`, gt)
}

#' Raster pixel/line from geospatial x,y coordinates
#' input is gt vector, no bounds checking done on output
#' @noRd
.get_pixel_line_gt <- function(xy, gt) {
    .Call(`_gdalraster_get_pixel_line_gt`, xy, gt)
}

#' Raster pixel/line from geospatial x,y coordinates
#' alternate version for GDALRaster input, with bounds checking
#' @noRd
.get_pixel_line_ds <- function(xy, ds) {
    .Call(`_gdalraster_get_pixel_line_ds`, xy, ds)
}

#' Returns bbox geospatial x,y coordinates (xmin, ymin, xmax, ymax) from
#' inputs of geotransform vector and the grid pixel/line extent
#' @noRd
.bbox_grid_to_geo <- function(gt, grid_xmin, grid_xmax, grid_ymin, grid_ymax) {
    .Call(`_gdalraster_bbox_grid_to_geo_`, gt, grid_xmin, grid_xmax, grid_ymin, grid_ymax)
}

#' Returns a matrix of xchunkoff, ychunkoff, xoff, yoff, xsize, ysize, xmin,
#' xmax, ymin, ymax (i.e., indexing of potentially multi-block chunks defined
#' on block boundaries for iterating I/O operations over a raster).
#' If max_pixels == 0, or any value less than block_xsize * block_ysize, then
#' the chunks are the same as raster blocks.
#' 'max_pixels' is a scalar value, but NumericVector is used so it can
#' optionally carry the bit64::integer64 class attribute.
#' @noRd
.make_chunk_index <- function(raster_xsize, raster_ysize, block_xsize, block_ysize, gt, max_pixels) {
    .Call(`_gdalraster_make_chunk_index_`, raster_xsize, raster_ysize, block_xsize, block_ysize, gt, max_pixels)
}

#' Flip raster data vertically
#' @noRd
.flip_vertical <- function(v, xsize, ysize, nbands) {
    .Call(`_gdalraster_flip_vertical`, v, xsize, ysize, nbands)
}

#' Build a GDAL virtual raster from a list of datasets
#'
#' `buildVRT()` is a wrapper of the \command{gdalbuildvrt} command-line
#' utility for building a VRT (Virtual Dataset) that is a mosaic of the list
#' of input GDAL datasets
#' (see \url{https://gdal.org/en/stable/programs/gdalbuildvrt.html}).
#'
#' @details
#' Several command-line options are described in the GDAL documentation at the
#' URL above. By default, the input files are considered as tiles of a larger
#' mosaic and the VRT file has as many bands as one of the input files.
#' Alternatively, the `-separate` argument can be used to put each input
#' raster into a separate band in the VRT dataset.
#'
#' Some amount of checks are done to assure that all files that will be put in
#' the resulting VRT have similar characteristics: number of bands,
#' projection, color interpretation.... If not, files that do not match the
#' common characteristics will be skipped. (This is true in the default
#' mode for virtual mosaicing, and not when using the `-separate` option).
#'
#' In a virtual mosaic, if there is spatial overlap between
#' input rasters then the order of files appearing in the list of
#' sources matter: files that are listed at the end are the ones
#' from which the data will be fetched. Note that nodata will be taken into
#' account to potentially fetch data from less priority datasets.
#'
#' @param vrt_filename Character string. Filename of the output VRT.
#' @param input_rasters Character vector of input raster filenames.
#' @param cl_arg Optional character vector of command-line arguments to
#' \code{gdalbuildvrt}.
#' @param quiet Logical scalar. If `TRUE`, a progress bar will not be
#' displayed. Defaults to `FALSE`.
#' @returns Logical indicating success (invisible \code{TRUE}).
#' An error is raised if the operation fails.
#'
#' @seealso
#' [rasterToVRT()]
#'
#' @examples
#' # build a virtual 3-band RGB raster from individual Landsat band files
#' b4_file <- system.file("extdata/sr_b4_20200829.tif", package="gdalraster")
#' b5_file <- system.file("extdata/sr_b5_20200829.tif", package="gdalraster")
#' b6_file <- system.file("extdata/sr_b6_20200829.tif", package="gdalraster")
#' band_files <- c(b6_file, b5_file, b4_file)
#' vrt_file <- file.path(tempdir(), "storml_b6_b5_b4.vrt")
#' buildVRT(vrt_file, band_files, cl_arg = "-separate")
#' ds <- new(GDALRaster, vrt_file)
#' ds$getRasterCount()
#' plot_raster(ds, nbands = 3, main = "Landsat 6-5-4 (vegetative analysis)")
#' ds$close()
#' \dontshow{vsi_unlink(vrt_file)}
buildVRT <- function(vrt_filename, input_rasters, cl_arg = NULL, quiet = FALSE) {
    invisible(.Call(`_gdalraster_buildVRT`, vrt_filename, input_rasters, cl_arg, quiet))
}

#' Raster overlay for unique combinations
#'
#' @description
#' `combine()` overlays multiple rasters so that a unique ID is assigned to
#' each unique combination of input values. The input raster layers
#' typically have integer data types (floating point will be coerced to
#' integer by truncation), and must have the same projection, extent and cell
#' size. Pixel counts for each unique combination are obtained, and
#' combination IDs are optionally written to an output raster.
#'
#' Called from and documented in R/gdalraster_proc.R
#' @noRd
.combine <- function(src_files, var_names, bands, dst_filename, fmt, dataType, options, quiet) {
    .Call(`_gdalraster_combine`, src_files, var_names, bands, dst_filename, fmt, dataType, options, quiet)
}

#' Compute for a raster band the set of unique pixel values and their counts
#'
#' @noRd
.value_count <- function(src_ds, band = 1L, quiet = FALSE) {
    .Call(`_gdalraster_value_count`, src_ds, band, quiet)
}

#' Wrapper for GDALDEMProcessing in the GDAL Algorithms C API
#'
#' Called from and documented in R/gdalraster_proc.R
#' @noRd
.dem_proc <- function(mode, src_filename, dst_filename, cl_arg = NULL, col_file = NULL, quiet = FALSE) {
    .Call(`_gdalraster_dem_proc`, mode, src_filename, dst_filename, cl_arg, col_file, quiet)
}

#' Fill selected pixels by interpolation from surrounding areas
#'
#' `fillNodata()` is a wrapper for `GDALFillNodata()` in the GDAL Algorithms
#' API. This algorithm will interpolate values for all designated nodata
#' pixels (pixels having an intrinsic nodata value, or marked by zero-valued
#' pixels in the optional raster specified in `mask_file`). For each nodata
#' pixel, a four direction conic search is done to find values to interpolate
#' from (using inverse distance weighting).
#' Once all values are interpolated, zero or more smoothing iterations
#' (3x3 average filters on interpolated pixels) are applied to smooth out
#' artifacts.
#'
#' @note
#' The input raster will be modified in place. It should not be open in a
#' `GDALRaster` object while processing with `fillNodata()`.
#'
#' @param filename Filename of input raster in which to fill nodata pixels.
#' @param band Integer band number to modify in place.
#' @param mask_file Optional filename of raster to use as a validity mask
#' (band 1 is used, zero marks nodata pixels, non-zero marks valid pixels).
#' @param max_dist Maximum distance (in pixels) that the algorithm
#' will search out for values to interpolate (100 pixels by default).
#' @param smooth_iterations The number of 3x3 average filter smoothing
#' iterations to run after the interpolation to dampen artifacts
#' (0 by default).
#' @param quiet Logical scalar. If `TRUE`, a progress bar will not be
#' displayed. Defaults to `FALSE`.
#' @returns Logical indicating success (invisible \code{TRUE}).
#' An error is raised if the operation fails.
#' @examples
#' ## fill nodata edge pixels
#' f <- system.file("extdata/storml_elev_orig.tif", package="gdalraster")
#'
#' ## get count of nodata
#' tbl <- buildRAT(f)
#' head(tbl)
#' tbl[is.na(tbl$VALUE),]
#'
#' ds <- new(GDALRaster, f)
#' plot_raster(ds, legend = TRUE)
#' ds$close()
#'
#' ## make a copy that will be modified
#' mod_file <- file.path(tempdir(), "storml_elev_fill.tif")
#' file.copy(f,  mod_file)
#'
#' fillNodata(mod_file, band = 1)
#'
#' mod_tbl = buildRAT(mod_file)
#' head(mod_tbl)
#' mod_tbl[is.na(mod_tbl$VALUE),]
#'
#' ds <- new(GDALRaster, mod_file)
#' plot_raster(ds, legend = TRUE)
#' ds$close()
#' \dontshow{deleteDataset(mod_file)}
fillNodata <- function(filename, band, mask_file = "", max_dist = 100, smooth_iterations = 0L, quiet = FALSE) {
    invisible(.Call(`_gdalraster_fillNodata`, filename, band, mask_file, max_dist, smooth_iterations, quiet))
}

#' Compute footprint of a raster
#'
#' `footprint()` is a wrapper of the \command{gdal_footprint} command-line
#' utility (see \url{https://gdal.org/en/stable/programs/gdal_footprint.html}).
#' The function can be used to compute the footprint of a raster file, taking
#' into account nodata values (or more generally the mask band attached to
#' the raster bands), and generating polygons/multipolygons corresponding to
#' areas where pixels are valid, and write to an output vector file.
#' Refer to the GDAL documentation at the URL above for a list of command-line
#' arguments that can be passed in `cl_arg`. Requires GDAL >= 3.8.
#'
#' @details
#' Post-vectorization geometric operations are applied in the following order:
#' * optional splitting (`-split_polys`)
#' * optional densification (`-densify`)
#' * optional reprojection (`-t_srs`)
#' * optional filtering by minimum ring area (`-min_ring_area`)
#' * optional application of convex hull (`-convex_hull`)
#' * optional simplification (`-simplify`)
#' * limitation of number of points (`-max_points`)
#'
#' @param src_filename Character string. Filename of the source raster.
#' @param dst_filename Character string. Filename of the destination vector.
#' If the file and the output layer exist, the new footprint is appended to
#' them, unless the `-overwrite` command-line argument is used.
#' @param cl_arg Optional character vector of command-line arguments for
#' \code{gdal_footprint}.
#' @returns Logical indicating success (invisible \code{TRUE}).
#' An error is raised if the operation fails.
#'
#' @seealso
#' [polygonize()]
#'
#' @examples
#' evt_file <- system.file("extdata/storml_evt.tif", package="gdalraster")
#' out_file <- file.path(tempdir(), "storml.geojson")
#'
#' # Requires GDAL >= 3.8
#' if (gdal_version_num() >= gdal_compute_version(3, 8, 0)) {
#'   # command-line arguments for gdal_footprint
#'   args <- c("-t_srs", "EPSG:4326")
#'   footprint(evt_file, out_file, args)
#'   \dontshow{deleteDataset(out_file)}
#' }
footprint <- function(src_filename, dst_filename, cl_arg = NULL) {
    invisible(.Call(`_gdalraster_footprint`, src_filename, dst_filename, cl_arg))
}

#' Check Line of Sight between pairs of points
#'
#' Interface to GDALIsLineOfSightVisible() in GDAL >= 3.9
#'
#' see also https://github.com/OSGeo/gdal/issues/12458:
#' GDALIsLineOfSightVisible(): points exactly on the DEM surface are never
#' visible
#'
#' Called from and documented in R/gdalraster_proc.R
#'
#' @noRd
.isLineOfSightVisible <- function(ds, band, ptsA, srsA, ZinterpA, ptsB, srsB, ZinterpB, quiet) {
    .Call(`_gdalraster_isLineOfSightVisible`, ds, band, ptsA, srsA, ZinterpA, ptsB, srsB, ZinterpB, quiet)
}

#' Convert vector data between different formats
#'
#' `ogr2ogr()` is a wrapper of the \command{ogr2ogr} command-line
#' utility (see \url{https://gdal.org/en/stable/programs/ogr2ogr.html}).
#' This function can be used to convert simple features data between file
#' formats. It can also perform various operations during the process, such
#' as spatial or attribute selection, reducing the set of attributes, setting
#' the output coordinate system or even reprojecting the features during
#' translation.
#' Refer to the GDAL documentation at the URL above for a description of
#' command-line arguments that can be passed in `cl_arg`.
#'
#' @param src_dsn Character string. Data source name of the source vector
#' dataset.
#' @param dst_dsn Character string. Data source name of the destination vector
#' dataset.
#' @param src_layers Optional character vector of layer names in the source
#' dataset. Defaults to all layers.
#' @param cl_arg Optional character vector of command-line arguments for
#' the GDAL \code{ogr2ogr} command-line utility (see URL above).
#' @param open_options Optional character vector of dataset open options.
#' @returns Logical indicating success (invisible \code{TRUE}).
#' An error is raised if the operation fails.
#'
#' @note
#' For progress reporting, see command-line argument `-progress`: Display
#' progress on terminal. Only works if input layers have the "fast feature
#' count" capability.
#'
#' @seealso
#' [ogrinfo()], the [ogr_manage] utilities
#'
#' [translate()] for raster data
#'
#' @examples
#' src <- system.file("extdata/ynp_fires_1984_2022.gpkg", package="gdalraster")
#'
#' # Convert GeoPackage to Shapefile
#' ynp_shp <- file.path(tempdir(), "ynp_fires.shp")
#' ogr2ogr(src, ynp_shp, src_layers = "mtbs_perims")
#'
#' # Reproject to WGS84
#' ynp_gpkg <- file.path(tempdir(), "ynp_fires.gpkg")
#' args <- c("-t_srs", "EPSG:4326", "-nln", "fires_wgs84")
#' ogr2ogr(src, ynp_gpkg, cl_arg = args)
#'
#' # Clip to a bounding box (xmin, ymin, xmax, ymax in the source SRS)
#' # This will select features whose geometry intersects the bounding box.
#' # The geometries themselves will not be clipped unless "-clipsrc" is
#' # specified.
#' # The source SRS can be overridden with "-spat_srs" "<srs_def>"
#' # Using -update mode to write a new layer in the existing DSN
#' bb <- c(469685.97, 11442.45, 544069.63, 85508.15)
#' args <- c("-update", "-nln", "fires_clip", "-spat", bb)
#' ogr2ogr(src, ynp_gpkg, cl_arg = args)
#'
#' # Filter features by a -where clause
#' sql <- "ig_year >= 2000 ORDER BY ig_year"
#' args <- c("-update", "-nln", "fires_2000-2020", "-where", sql)
#' ogr2ogr(src, ynp_gpkg, src_layers = "mtbs_perims", cl_arg = args)
#'
#' # Dissolve features based on a shared attribute value
#' if (has_spatialite()) {
#'     sql <- "SELECT ig_year, ST_Union(geom) AS geom
#'               FROM mtbs_perims GROUP BY ig_year"
#'     args <- c("-update", "-sql", sql, "-dialect", "SQLITE")
#'     args <- c(args, "-nlt", "MULTIPOLYGON", "-nln", "dissolved_on_year")
#'     ogr2ogr(src, ynp_gpkg, cl_arg = args)
#' }
#' \dontshow{deleteDataset(ynp_shp)}
#' \dontshow{deleteDataset(ynp_gpkg)}
ogr2ogr <- function(src_dsn, dst_dsn, src_layers = NULL, cl_arg = NULL, open_options = NULL) {
    invisible(.Call(`_gdalraster_ogr2ogr`, src_dsn, dst_dsn, src_layers, cl_arg, open_options))
}

#' Retrieve information about a vector data source
#'
#' `ogrinfo()` is a wrapper of the \command{ogrinfo} command-line
#' utility (see \url{https://gdal.org/en/stable/programs/ogrinfo.html}).
#' This function lists information about an OGR-supported data source.
#' It is also possible to edit data with SQL statements.
#' Refer to the GDAL documentation at the URL above for a description of
#' command-line arguments that can be passed in `cl_arg`.
#' Requires GDAL >= 3.7.
#'
#' @param dsn Character string. Data source name (e.g., filename, database
#' connection string, etc.)
#' @param layers Optional character vector of layer names in the source
#' dataset.
#' @param cl_arg Optional character vector of command-line arguments for
#' the \code{ogrinfo} command-line utility in GDAL (see URL above for
#' reference). The default is `c("-so", "-nomd")` (see Note).
#' @param open_options Optional character vector of dataset open options.
#' @param read_only Logical scalar. `TRUE` to open the data source read-only
#' (the default), or `FALSE` to open with write access.
#' @param cout Logical scalar. `TRUE` to write info to the standard C output
#' stream (the default). `FALSE` to suppress console output.
#' @returns Invisibly, a character string containing information about the
#' vector dataset, or empty string (`""`) in case of error.
#'
#' @note
#' The command-line argument `-so` provides a summary only, i.e., does not
#' include details about every single feature of a layer.
#' `-nomd` suppresses metadata printing. Some datasets may contain a lot of
#' metadata strings.
#'
#' @seealso
#' [ogr2ogr()], [ogr_manage]
#'
#' @examplesIf gdal_version_num() >= gdal_compute_version(3, 7, 0)
#' src <- system.file("extdata/ynp_fires_1984_2022.gpkg", package="gdalraster")
#'
#' # Get the names of the layers in a GeoPackage file
#' ogrinfo(src)
#'
#' # Summary of a layer
#' ogrinfo(src, "mtbs_perims")
#'
#' # Query an attribute to restrict the output of the features in a layer
#' args <- c("-ro", "-nomd", "-where", "ig_year = 2020")
#' ogrinfo(src, "mtbs_perims", args)
#'
#' # Copy to a temporary in-memory file that is writeable
#' src_mem <- paste0("/vsimem/", basename(src))
#' vsi_copy_file(src, src_mem)
#'
#' # Add a column to a layer
#' args <- c("-sql", "ALTER TABLE mtbs_perims ADD burn_bnd_ha float")
#' ogrinfo(src_mem, cl_arg = args, read_only = FALSE)
#'
#' # Update values of the column with SQL and specify a dialect
#' sql <- "UPDATE mtbs_perims SET burn_bnd_ha = (burn_bnd_ac / 2.471)"
#' args <- c("-dialect", "sqlite", "-sql", sql)
#' ogrinfo(src_mem, cl_arg = args, read_only = FALSE)
ogrinfo <- function(dsn, layers = NULL, cl_arg = as.character( c("-so", "-nomd")), open_options = NULL, read_only = TRUE, cout = TRUE) {
    invisible(.Call(`_gdalraster_ogrinfo`, dsn, layers, cl_arg, open_options, read_only, cout))
}

#' Wrapper for GDALPolygonize in the GDAL Algorithms C API
#'
#' Called from and documented in R/gdalraster_proc.R
#' @noRd
.polygonize <- function(src_filename, src_band, out_dsn, out_layer, fld_name, mask_file = "", nomask = FALSE, connectedness = 4L, quiet = FALSE) {
    .Call(`_gdalraster_polygonize`, src_filename, src_band, out_dsn, out_layer, fld_name, mask_file, nomask, connectedness, quiet)
}

#' Wrapper for GDALRasterize in the GDAL Algorithms C API
#'
#' Called from and documented in R/gdalraster_proc.R
#' @noRd
.rasterize <- function(src_dsn, dst_filename, dst_dataset, cl_arg, quiet = FALSE) {
    .Call(`_gdalraster_rasterize`, src_dsn, dst_filename, dst_dataset, cl_arg, quiet)
}

#' Remove small raster polygons
#'
#' `sieveFilter()` is a wrapper for `GDALSieveFilter()` in the GDAL Algorithms
#' API. It removes raster polygons smaller than a provided threshold size
#' (in pixels) and replaces them with the pixel value of the largest neighbour
#' polygon.
#'
#' @details
#' Polygons are determined as regions of the raster where the pixels all have
#' the same value, and that are contiguous (connected).
#' Pixels determined to be "nodata" per the mask band will not be
#' treated as part of a polygon regardless of their pixel values. Nodata areas
#' will never be changed nor affect polygon sizes. Polygons smaller than the
#' threshold with no neighbours that are as large as the threshold will not be
#' altered. Polygons surrounded by nodata areas will therefore not be altered.
#'
#' The algorithm makes three passes over the input file to enumerate the
#' polygons and collect limited information about them. Memory use is
#' proportional to the number of polygons (roughly 24 bytes per polygon), but
#' is not directly related to the size of the raster. So very large raster
#' files can be processed effectively if there aren't too many polygons. But
#' extremely noisy rasters with many one pixel polygons will end up being
#' expensive (in memory) to process.
#'
#' The input dataset is read as integer data which means that floating point
#' values are rounded to integers.
#'
#' @param src_filename Filename of the source raster to be processed.
#' @param src_band Band number in the source raster to be processed.
#' @param dst_filename Filename of the output raster. It may be the same as
#' `src_filename` to update the source file in place.
#' @param dst_band Band number in `dst_filename` to write output. It may be
#' the same as `src_band` to update the source raster in place.
#' @param size_threshold Integer. Raster polygons with sizes (in pixels)
#' smaller than this value will be merged into their largest neighbour.
#' @param connectedness Integer. Either `4` indicating that diagonal pixels
#' are not considered directly adjacent for polygon membership purposes, or
#' `8` indicating they are.
#' @param mask_filename Optional filename of raster to use as a mask.
#' @param mask_band Band number in `mask_filename` to use as a mask. All
#' pixels in the mask band with a value other than zero will be considered
#' suitable for inclusion in polygons.
#' @param options Algorithm options as a character vector of name=value pairs.
#' None currently supported.
#' @param quiet Logical scalar. If `TRUE`, a progress bar will not be
#' displayed. Defaults to `FALSE`.
#' @returns Logical indicating success (invisible \code{TRUE}).
#' An error is raised if the operation fails.
#'
#' @examples
#' ## remove single-pixel polygons from the vegetation type layer (EVT)
#' evt_file <- system.file("extdata/storml_evt.tif", package="gdalraster")
#'
#' # create a blank raster to hold the output
#' evt_mmu_file <- file.path(tempdir(), "storml_evt_mmu2.tif")
#' rasterFromRaster(srcfile = evt_file,
#'                  dstfile = evt_mmu_file,
#'                  init = 32767)
#'
#' # create a mask to exclude water pixels from the algorithm
#' # recode water (7292) to 0
#' expr <- "ifelse(EVT == 7292, 0, EVT)"
#' mask_file <- calc(expr = expr,
#'                   rasterfiles = evt_file,
#'                   var.names = "EVT")
#'
#' # create a version of EVT with two-pixel minimum mapping unit
#' sieveFilter(src_filename = evt_file,
#'             src_band = 1,
#'             dst_filename = evt_mmu_file,
#'             dst_band = 1,
#'             size_threshold = 2,
#'             connectedness = 8,
#'             mask_filename = mask_file,
#'             mask_band = 1)
#' \dontshow{deleteDataset(mask_file)}
#' \dontshow{deleteDataset(evt_mmu_file)}
sieveFilter <- function(src_filename, src_band, dst_filename, dst_band, size_threshold, connectedness, mask_filename = "", mask_band = 0L, options = NULL, quiet = FALSE) {
    invisible(.Call(`_gdalraster_sieveFilter`, src_filename, src_band, dst_filename, dst_band, size_threshold, connectedness, mask_filename, mask_band, options, quiet))
}

#' Convert raster data between different formats
#'
#' `translate()` is a wrapper of the \command{gdal_translate} command-line
#' utility (see \url{https://gdal.org/en/stable/programs/gdal_translate.html}).
#'
#' Called from and documented in R/gdal_util.R
#'
#' @noRd
.translate <- function(src_ds, dst_filename, cl_arg = NULL, quiet = FALSE) {
    .Call(`_gdalraster_translate`, src_ds, dst_filename, cl_arg, quiet)
}

#' Raster reprojection and mosaicing
#'
#' `warp()` is a wrapper of the \command{gdalwarp} command-line utility for
#' raster mosaicing, reprojection and warping
#' (see \url{https://gdal.org/en/stable/programs/gdalwarp.html}).
#'
#' Called from and documented in R/gdal_util.R
#'
#' Destination raster is specified here as either:
#' dst_filename - the destination dataset path or ""
#' dst_dataset - list of length 1 containg a GDALRaster object or empty list
#'               (workaround for a nullable dataset argument)
#'
#' @noRd
.warp <- function(src_datasets, dst_filename, dst_dataset, t_srs, cl_arg, quiet) {
    .Call(`_gdalraster_warp`, src_datasets, dst_filename, dst_dataset, t_srs, cl_arg, quiet)
}

#' Create a color ramp
#'
#' `createColorRamp()` is a wrapper for `GDALCreateColorRamp()` in the GDAL
#' API. It automatically creates a color ramp from one color entry to another.
#' Output is an integer matrix in color table format for use with
#' [`GDALRaster$setColorTable()`][GDALRaster].
#'
#' @note
#' `createColorRamp()` could be called several times, using `rbind()` to
#' combine multiple ramps into the same color table. Possible duplicate rows
#' in the resulting table are not a problem when used in
#' `GDALRaster$setColorTable()` (i.e., when `end_color` of one ramp is the
#' same as `start_color` of the next ramp).
#'
#' @param start_index Integer start index (raster value).
#' @param start_color Integer vector of length three or four.
#' A color entry value to start the ramp (e.g., RGB values).
#' @param end_index Integer end index (raster value).
#' @param end_color Integer vector of length three or four.
#' A color entry value to end the ramp (e.g., RGB values).
#' @param palette_interp One of "Gray", "RGB" (the default), "CMYK" or "HLS"
#' describing interpretation of `start_color` and `end_color` values
#' (see \href{https://gdal.org/en/stable/user/raster_data_model.html#color-table}{GDAL
#' Color Table}).
#' @returns Integer matrix with five columns containing the color ramp from
#' `start_index` to `end_index`, with raster index values in column 1 and
#' color entries in columns 2:5).
#'
#' @seealso
#' [`GDALRaster$getColorTable()`][GDALRaster],
#' [`GDALRaster$getPaletteInterp()`][GDALRaster]
#'
#' @examples
#' # create a color ramp for tree canopy cover percent
#' # band 5 of an LCP file contains canopy cover
#' lcp_file <- system.file("extdata/storm_lake.lcp", package="gdalraster")
#' ds <- new(GDALRaster, lcp_file)
#' ds$getDescription(band=5)
#' ds$getMetadata(band=5, domain="")
#' ds$close()
#'
#' # create a GTiff file with Byte data type for the canopy cover band
#' # recode nodata -9999 to 255
#' tcc_file <- calc(expr = "ifelse(CANCOV == -9999, 255, CANCOV)",
#'                  rasterfiles = lcp_file,
#'                  bands = 5,
#'                  var.names = "CANCOV",
#'                  fmt = "GTiff",
#'                  dtName = "Byte",
#'                  nodata_value = 255,
#'                  setRasterNodataValue = TRUE)
#'
#' ds_tcc <- new(GDALRaster, tcc_file, read_only=FALSE)
#'
#' # create a color ramp from 0 to 100 and set as the color table
#' colors <- createColorRamp(start_index = 0,
#'                           start_color = c(211, 211, 211),
#'                           end_index = 100,
#'                           end_color = c(0, 100, 0))
#'
#' print(colors)
#' ds_tcc$setColorTable(band=1, col_tbl=colors, palette_interp = "RGB")
#' ds_tcc$setRasterColorInterp(band = 1, col_interp = "Palette")
#'
#' # close and re-open the dataset in read_only mode
#' ds_tcc$open(read_only=TRUE)
#'
#' plot_raster(ds_tcc, interpolate = FALSE, legend = TRUE,
#'             main = "Storm Lake Tree Canopy Cover (%)")
#' ds_tcc$close()
#' \dontshow{deleteDataset(tcc_file)}
createColorRamp <- function(start_index, start_color, end_index, end_color, palette_interp = "RGB") {
    .Call(`_gdalraster_createColorRamp`, start_index, start_color, end_index, end_color, palette_interp)
}

#' Copy a whole raster band efficiently
#'
#' `bandCopyWholeRaster()` copies the complete raster contents of one band to
#' another similarly configured band. The source and destination bands must
#' have the same xsize and ysize. The bands do not have to have the same data
#' type. It implements efficient copying, in particular "chunking" the copy in
#' substantial blocks. This is a wrapper for `GDALRasterBandCopyWholeRaster()`
#' in the GDAL API.
#'
#' @param src_filename Filename of the source raster.
#' @param src_band Band number in the source raster to be copied.
#' @param dst_filename Filename of the destination raster.
#' @param dst_band Band number in the destination raster to copy into.
#' @param options Optional list of transfer hints in a vector of `"NAME=VALUE"`
#' pairs. The currently supported `options` are:
#'   * `"COMPRESSED=YES"` to force alignment on target dataset block sizes to
#'   achieve best compression.
#'    * `"SKIP_HOLES=YES"` to skip chunks that contain only empty blocks.
#'    Empty blocks are blocks that are generally not physically present in the
#'    file, and when read through GDAL, contain only pixels whose value is the
#'    nodata value when it is set, or whose value is 0 when the nodata value is
#'    not set. The query is done in an efficient way without reading the actual
#'    pixel values (if implemented by the raster format driver, otherwise will
#'    not be skipped).
#' @param quiet Logical scalar. If `TRUE`, a progress bar will not be
#' displayed. Defaults to `FALSE`.
#' @returns Logical indicating success (invisible \code{TRUE}).
#' An error is raised if the operation fails.
#'
#' @seealso
#' [`GDALRaster-class`][GDALRaster], [create()], [createCopy()],
#' [rasterFromRaster()]
#'
#' @examples
#' ## copy Landsat data from a single-band file to a new multi-band image
#' b5_file <- system.file("extdata/sr_b5_20200829.tif", package="gdalraster")
#' dst_file <- file.path(tempdir(), "sr_multi.tif")
#' rasterFromRaster(b5_file, dst_file, nbands = 7, init = 0)
#' opt <- c("COMPRESSED=YES", "SKIP_HOLES=YES")
#' bandCopyWholeRaster(b5_file, 1, dst_file, 5, options = opt)
#' ds <- new(GDALRaster, dst_file)
#' ds$getStatistics(band = 5, approx_ok = FALSE, force = TRUE)
#' ds$close()
#' \dontshow{deleteDataset(dst_file)}
bandCopyWholeRaster <- function(src_filename, src_band, dst_filename, dst_band, options = NULL, quiet = FALSE) {
    invisible(.Call(`_gdalraster_bandCopyWholeRaster`, src_filename, src_band, dst_filename, dst_band, options, quiet))
}

#' Delete named dataset
#'
#' `deleteDataset()` will attempt to delete the named dataset in a format
#' specific fashion. Full featured drivers will delete all associated files,
#' database objects, or whatever is appropriate. The default behavior when no
#' format specific behavior is provided is to attempt to delete all the files
#' that would be returned by `GDALRaster$getFileList()` on the dataset.
#' The named dataset should not be open in any existing `GDALRaster` objects
#' when `deleteDataset()` is called. Wrapper for `GDALDeleteDataset()` in the
#' GDAL API.
#'
#' @note
#' If `format` is set to an empty string `""` (the default) then the function
#' will try to identify the driver from `filename`. This is done internally in
#' GDAL by invoking the `Identify` method of each registered `GDALDriver` in
#' turn. The first driver that successful identifies the file name will be
#' returned. An error is raised if a format cannot be determined from the
#' passed file name.
#'
#' @param filename Filename to delete (should not be open in a `GDALRaster`
#' object).
#' @param format Raster format short name (e.g., "GTiff"). If set to empty
#' string `""` (the default), will attempt to guess the raster format from
#' `filename`.
#' @returns Logical `TRUE` if no error or `FALSE` on failure.
#'
#' @seealso
#' [`GDALRaster-class`][GDALRaster], [create()], [createCopy()],
#' [copyDatasetFiles()], [renameDataset()]
#'
#' @examples
#' b5_file <- system.file("extdata/sr_b5_20200829.tif", package="gdalraster")
#' b5_tmp <- file.path(tempdir(), "b5_tmp.tif")
#' file.copy(b5_file,  b5_tmp)
#'
#' ds <- new(GDALRaster, b5_tmp)
#' ds$buildOverviews("BILINEAR", levels = c(2, 4, 8), bands = c(1))
#' files <- ds$getFileList()
#' print(files)
#' ds$close()
#' file.exists(files)
#' deleteDataset(b5_tmp)
#' file.exists(files)
deleteDataset <- function(filename, format = "") {
    .Call(`_gdalraster_deleteDataset`, filename, format)
}

#' Rename a dataset
#'
#' `renameDataset()` renames a dataset in a format-specific way (e.g.,
#' rename associated files as appropriate). This could include moving the
#' dataset to a new directory or even a new filesystem.
#' The dataset should not be open in any existing `GDALRaster` objects
#' when `renameDataset()` is called. Wrapper for `GDALRenameDataset()` in the
#' GDAL API.
#'
#' @note
#' If `format` is set to an empty string `""` (the default) then the function
#' will try to identify the driver from `old_filename`. This is done
#' internally in GDAL by invoking the `Identify` method of each registered
#' `GDALDriver` in turn. The first driver that successful identifies the file
#' name will be returned. An error is raised if a format cannot be determined
#' from the passed file name.
#'
#' @param new_filename New name for the dataset.
#' @param old_filename Old name for the dataset (should not be open in a
#' `GDALRaster` object).
#' @param format Raster format short name (e.g., "GTiff"). If set to empty
#' string `""` (the default), will attempt to guess the raster format from
#' `old_filename`.
#' @returns Logical `TRUE` if no error or `FALSE` on failure.
#'
#' @seealso
#' [`GDALRaster-class`][GDALRaster], [create()], [createCopy()],
#' [deleteDataset()], [copyDatasetFiles()]
#'
#' @examples
#' b5_file <- system.file("extdata/sr_b5_20200829.tif", package="gdalraster")
#' b5_tmp <- file.path(tempdir(), "b5_tmp.tif")
#' file.copy(b5_file,  b5_tmp)
#'
#' ds <- new(GDALRaster, b5_tmp)
#' ds$buildOverviews("BILINEAR", levels = c(2, 4, 8), bands = c(1))
#' ds$getFileList()
#' ds$close()
#' b5_tmp2 <- file.path(tempdir(), "b5_tmp_renamed.tif")
#' renameDataset(b5_tmp2, b5_tmp)
#' ds <- new(GDALRaster, b5_tmp2)
#' ds$getFileList()
#' ds$close()
#'
#' deleteDataset(b5_tmp2)
renameDataset <- function(new_filename, old_filename, format = "") {
    .Call(`_gdalraster_renameDataset`, new_filename, old_filename, format)
}

#' Copy the files of a dataset
#'
#' `copyDatasetFiles()` copies all the files associated with a dataset.
#' Wrapper for `GDALCopyDatasetFiles()` in the GDAL API.
#'
#' @note
#' If `format` is set to an empty string `""` (the default) then the function
#' will try to identify the driver from `old_filename`. This is done
#' internally in GDAL by invoking the `Identify` method of each registered
#' `GDALDriver` in turn. The first driver that successful identifies the file
#' name will be returned. An error is raised if a format cannot be determined
#' from the passed file name.
#'
#' @param new_filename New name for the dataset (copied to).
#' @param old_filename Old name for the dataset (copied from).
#' @param format Raster format short name (e.g., "GTiff"). If set to empty
#' string `""` (the default), will attempt to guess the raster format from
#' `old_filename`.
#' @returns Logical `TRUE` if no error or `FALSE` on failure.
#'
#' @seealso
#' [`GDALRaster-class`][GDALRaster], [create()], [createCopy()],
#' [deleteDataset()], [renameDataset()], [vsi_copy_file()]
#'
#' @examples
#' lcp_file <- system.file("extdata/storm_lake.lcp", package="gdalraster")
#' ds <- new(GDALRaster, lcp_file)
#' ds$getFileList()
#' ds$close()
#'
#' lcp_tmp <- file.path(tempdir(), "storm_lake_copy.lcp")
#' copyDatasetFiles(lcp_tmp, lcp_file)
#' ds_copy <- new(GDALRaster, lcp_tmp)
#' ds_copy$getFileList()
#' ds_copy$close()
#'
#' deleteDataset(lcp_tmp)
copyDatasetFiles <- function(new_filename, old_filename, format = "") {
    .Call(`_gdalraster_copyDatasetFiles`, new_filename, old_filename, format)
}

#' Identify the GDAL driver that can open a dataset
#'
#' `identifyDriver()` will try to identify the driver that can open the passed
#' file name by invoking the Identify method of each registered GDALDriver in
#' turn. The short name of the first driver that successfully identifies the
#' file name will be returned as a character string. If all drivers fail then
#' `NULL` is returned.
#' Wrapper of `GDALIdentifyDriverEx()` in the GDAL C API.
#'
#' @note
#' In order to reduce the need for such searches to touch the file system
#' machinery of the operating system, it is possible to give an optional list
#' of files. This is the list of all files at the same level in the file
#' system as the target file, including the target file. The filenames should
#' not include any path components. If the target object does not have
#' filesystem semantics then the file list should be `NULL`.
#'
#' At least one of the `raster` or `vector` arguments must be `TRUE`.
#'
#' @param filename Character string containing the name of the file to access.
#' This may not refer to a physical file, but instead contain information for
#' the driver on how to access a dataset (e.g., connection string, URL, etc.)
#' @param raster Logical value indicating whether to include raster format
#' drivers in the search, `TRUE` by default. May be set to `FALSE` to include
#' only vector drivers.
#' @param vector Logical value indicating whether to include vector format
#' drivers in the search, `TRUE` by default. May be set to `FALSE` to include
#' only raster drivers.
#' @param allowed_drivers Optional character vector of driver short names
#' that must be considered. Set to `NULL` to consider all candidate drivers
#' (the default).
#' @param file_list Optional character vector of filenames, including those
#' that are auxiliary to the main filename (see Note). May contain the input
#' `filename` but this is not required. Defaults to `NULL`.
#' @returns A character string with the short name of the first driver that
#' successfully identifies the input file name, or `NULL` on failure.
#'
#' @seealso
#' [gdal_formats()]
#'
#' @examples
#' src <- system.file("extdata/ynp_fires_1984_2022.gpkg", package="gdalraster")
#'
#' identifyDriver(src) |> gdal_formats()
identifyDriver <- function(filename, raster = TRUE, vector = TRUE, allowed_drivers = NULL, file_list = NULL) {
    .Call(`_gdalraster_identifyDriver`, filename, raster, vector, allowed_drivers, file_list)
}

#' Return the list of creation options of a GDAL driver as XML string
#'
#' Called from and documented in R/gdal_helpers.R
#' @noRd
.getCreationOptions <- function(format) {
    .Call(`_gdalraster_getCreationOptions`, format)
}

#' Validate the list of creation options that are handled by a driver
#'
#' `validateCreationOptions()` is a helper function primarily used by GDAL's
#' Create() and CreateCopy() to validate that the passed-in list of creation
#' options is compatible with the GDAL_DMD_CREATIONOPTIONLIST metadata item
#' defined by some drivers. If the GDAL_DMD_CREATIONOPTIONLIST metadata item
#' is not defined, this function will return `TRUE`. Otherwise it will check
#' that the keys and values in the list of creation options are compatible
#' with the capabilities declared by the GDAL_DMD_CREATIONOPTIONLIST metadata
#' item. In case of incompatibility a message will be emitted and `FALSE` will
#' be returned. Wrapper of `GDALValidateCreationOptions()` in the GDAL API.
#'
#' @param format Character string giving a format driver short name
#' (e.g., `"GTiff"`).
#' @param options A character vector of format-specific creation options as
#' `"NAME=VALUE"` pairs.
#' @returns A logical value, `TRUE` if the given creation options are
#' compatible with the capabilities declared by the GDAL_DMD_CREATIONOPTIONLIST
#' metadata item for the specified format driver (or if the
#' GDAL_DMD_CREATIONOPTIONLIST metadata item is not defined for this driver),
#' otherwise `FALSE`.
#'
#' @seealso
#' [getCreationOptions()], [create()], [createCopy()]
#'
#' @examples
#' validateCreationOptions("GTiff", c("COMPRESS=LZW", "TILED=YES"))
validateCreationOptions <- function(format, options) {
    .Call(`_gdalraster_validateCreationOptions`, format, options)
}

#' Get metadata for a GDAL format driver
#'
#' `gdal_get_driver_md()` returns metadata for a driver.
#'
#' @param format Character string giving a format driver short name
#' (e.g., `"GTiff"`).
#' @param mdi_name Optional character string giving the name of a specific
#' metadata item. Defaults to empty string (`""`) meaning fetch all metadata
#' items.
#' @returns Either a named list of metadata items and their values as character
#' strings, or a single character string if `mdi_name` is specified. Returns
#' `NULL` if no metadata items are found for the given inputs.
#'
#' @seealso
#' [getCreationOptions()]
#'
#' @examples
#' dmd <- gdal_get_driver_md("GTiff")
#' str(dmd)
gdal_get_driver_md <- function(format, mdi_name = "") {
    .Call(`_gdalraster_gdal_get_driver_md`, format, mdi_name)
}

#' Add a file inside a new or existing ZIP file
#' Mainly for create/append to Seek-Optimized ZIP
#'
#' @noRd
.addFileInZip <- function(zip_filename, overwrite, archive_filename, in_filename, options, quiet) {
    .Call(`_gdalraster_addFileInZip`, zip_filename, overwrite, archive_filename, in_filename, options, quiet)
}

.gt_from_dim_bbox <- function(dim, bbox) {
    .Call(`_gdalraster_gt_from_dim_bbox_`, dim, bbox)
}

#' Report structure and content of a multidimensional dataset
#'
#' `mdim_info()` is an interface to the \command{gdalmdiminfo} command-line
#' utility (see \url{https://gdal.org/en/stable/programs/gdalmdiminfo.html}).
#' This function lists various information about a GDAL supported
#' multidimensional raster dataset as JSON output. Requires GDAL >= 3.2.
#'
#' @param dsn Character string giving the data source name of the
#' multidimensional raster (e.g., file, VSI path).
#' @param array_name Character string giving the name of the MDarray in
#' `dsn`.
#' @param pretty Logical value, `FALSE` to output a single line without any
#' indentation. Defaults to `TRUE`.
#' @param detailed Logical value, `TRUE` for verbose output. Report attribute
#' data types and array values. Defaults to `FALSE`.
#' @param limit Integer value. Number of values in each dimension that is used
#' to limit the display of array values. By default, unlimited. Only taken into
#' account if used with `detailed = TRUE`. Set to a positive integer to enable.
#' @param stats Logical value, `TRUE` to read and display array statistics.
#' Forces computation if no statistics are stored in an array. Defaults to
#' `FALSE`.
#' @param array_options Optional character vector of `"NAME=VALUE"` pairs to
#' filter reported arrays. Such option is format specific. Consult driver
#' documentation (passed to `GDALGroup::GetMDArrayNames()`).
#' @param allowed_drivers Optional character vector of driver short names that
#' must be considered when opening `dsn`. It is generally not necessary to
#' specify it, but it can be used to skip automatic driver detection, when it
#' fails to select the appropriate driver.
#' @param open_options Optional character vector of format-specific dataset
#' openoptions as `"NAME=VALUE"` pairs.
#' @param cout Logical value, `TRUE` to print info to the console (the
#' default), or `FALSE` to suppress console output.
#' @returns Invisibly, a JSON string containing information about the
#' multidimensional raster dataset. By default, the info string is also printed
#' to the console unless `cout` is set to `FALSE`.
#'
#' @seealso
#' [mdim_as_classic()], [mdim_translate()]
#'
#' @examplesIf gdal_version_num() >= gdal_compute_version(3, 2, 0) && isTRUE(gdal_formats("netCDF")$multidim_raster)
#' f <- system.file("extdata/byte.nc", package="gdalraster")
#' mdim_info(f)
mdim_info <- function(dsn, array_name = "", pretty = TRUE, detailed = FALSE, limit = -1L, stats = FALSE, array_options = NULL, allowed_drivers = NULL, open_options = NULL, cout = TRUE) {
    invisible(.Call(`_gdalraster_mdim_info`, dsn, array_name, pretty, detailed, limit, stats, array_options, allowed_drivers, open_options, cout))
}

#' Convert multidimensional data between different formats, and subset
#'
#' `mdim_translate()` is an interface to the \command{gdalmdimtranslate}
#' command-line utility (see
#' \url{https://gdal.org/en/stable/programs/gdalmdimtranslate.html}).
#' This function converts multidimensional data between different formats and
#' performs subsetting. Requires GDAL >= 3.2.
#'
#' @details
#' \subsection{Array creation options}{
#' Array creation options must be prefixed with `ARRAY:`. The scope may be
#' further restricted to arrays of a certain dimension by adding
#' `IF(DIM={ndims}):` after `ARRAY:`. For example,
#' `"ARRAY:IF(DIM=2):BLOCKSIZE=256,256"` will restrict `BLOCKSIZE=256,256` to
#' arrays of dimension 2. Restriction to arrays of a given name is done with
#' adding `IF(NAME={name}):` after `ARRAY:`. `{name}` can also be a fully
#' qualified name. A non-driver specific array option, `"AUTOSCALE=YES"` can
#' be used to ask (non indexing) variables of type `Float32` or `Float64` to be
#' scaled to `UInt16` with scale and offset values being computed from the
#' minimum and maximum of the source array. The integer data type used can be
#' set with `"AUTOSCALE_DATA_TYPE=Byte|UInt16|Int16|UInt32|Int32"`.
#' }
#'
#' \subsection{`array_specs`}{
#' Instead of converting the whole dataset, select one or more arrays, and
#' possibly perform operations on them. One or more array specifications can
#' be given as elements of a character vector.
#'
#' An array specification may be just an array name, potentially using a fully
#' qualified syntax (`"/group/subgroup/array_name"`). Or it can be a
#' combination of options with the syntax:
#' ```
#' "name={src_array_name}[,dstname={dst_array_name}][,resample=yes][,transpose=[{axis1},{axis2},...][,view={view_expr}]"
#' ```
#' The following options are processed in that order:
#'
#' * `resample=yes` asks for the array to run through
#'   `GDALMDArray::GetResampled()`.
#' * \code{[{axis1},{axis2},...]} is the argument of
#'   `GDALMDArray::Transpose()`. For example, `transpose=[1,0]` switches the
#'   axis order of a 2D array.
#' * `{view_expr}` is the value of the `viewExpr` argument of
#'   `GDALMDArray::GetView()`. When specifying a `view_expr` that performs a
#'   slicing or subsetting on a dimension, the equivalent operation will be
#'   applied to the corresponding indexing variable. See `?mdim_as_classic` for
#'   details on view expressions.
#' }
#'
#' \subsection{`group_specs`}{
#' Instead of converting the whole dataset, select one or more groups, and
#' possibly perform operations on them. One or more group specifications can
#' be given in a character vector, to operate on different groups. If only one
#' group is specified, its content will be copied directly to the target root
#' group. If several are specified, they are copied under the target root
#' group.
#'
#' A group specification may be just a group name, potentially using a fully
#' qualified syntax (`"/group/subgroup/subsubgroup_name"`). Or it can be a
#' combination of options with the syntax:
#' ```
#' "name={src_group_name}[,dstname={dst_group_name}][,recursive=no]"
#' ```
#' }
#'
#' \subsection{`subset_specs`}{
#' Perform subsetting (trimming or slicing) operations along dimensions,
#' provided that the dimension is indexed by a 1D variable of numeric or string
#' data type, and whose values are monotonically sorted. One or more subset
#' specifications can be given in a character vector. A subset specification
#' string follows exactly the OGC WCS 2.0 KVP encoding for subsetting.
#'
#' Syntax is `"dim_name(min_val,max_val)"` or `"dim_name(sliced_val)"`. The
#' first syntax will subset the dimension dim_name to values in the
#' \code{[min_val,max_val]} range. The second syntax will slice the dimension
#' `dim_name` to value `sliced_val` (and this dimension will be removed from
#' the arrays that reference to it)
#'
#' Using a subset specification is incompatible with specifying a view option
#' in `array_specs`.
#' }
#'
#' \subsection{`scaleaxes_specs`}{
#' Applies an integral scale factor to one or several dimensions, i.e., extract
#' 1 value every N values (without resampling). A scale-axes specification
#' string follows exactly the syntax of the KVP encoding of the SCALEAXES
#' parameter of OGC WCS 2.0 Scaling Extension, but limited to integer scale
#' factors.
#'
#' Syntax is a character string of the form:
#' ```
#' "<dim1_name>(<scale_factor>)[,<dim2_name>(<scale_factor>)]..."
#' ```
#'
#' Using a scale-axes specification is incompatible with specifying a view
#' option in `array_specs`.
#' }
#'
#' @param src_dsn Character string giving the name of the source
#' multidimensional raster dataset (e.g., file, VSI path).
#' @param dst_dsn Character string giving the name of the destination
#' multidimensional raster dataset (e.g., file, VSI path).
#' @param output_format Character string giving the output format (driver short
#' name). This can be a format that supports multidimensional output (such as
#' NetCDF: Network Common Data Form, Multidimensional VRT), or a "classic" 2D
#' format, if only one single 2D array results from the other specified
#' conversion operations. When this option is not specified (i.e., empty string
#' `""`), the format is guessed when possible from the extension of `dst_dsn`.
#' @param creation_options Optional character vector of format-specific
#' creation options as `"NAME=VALUE"` pairs. A list of options supported for a
#' format can be obtained with `getCreationOptions()`, but the documentation
#' for the format is the definitive source of information on driver creation
#' options (see \url{https://gdal.org/en/stable/drivers/raster/index.html}).
#' Array-level creation options may be passed by prefixing them with `ARRAY:`
#' (see Details).
#' @param array_specs Optional character vector of one or more array
#' specifications, instead of converting the whole dataset (see Details).
#' @param group_specs Optional character vector of one or more array
#' specifications, instead of converting the whole dataset (see Details).
#' @param subset_specs Optional character vector of one or more subset
#' specifications, that perform trimming or slicing along a dimension, provided
#' that it is indexed by a 1D variable of numeric or string data type, and
#' whose values are monotonically sorted (see Details).
#' @param scaleaxes_specs Optional character string for a scale-axes
#' specification, that apply an integral scale factor to one or several
#' dimensions, i.e., extract 1 value every N values (without resampling) (see
#' Details).
#' @param allowed_drivers Optional character vector of driver short names that
#' must be considered when opening `src_dsn`. It is generally not necessary to
#' specify it, but it can be used to skip automatic driver detection, when it
#' fails to select the appropriate driver.
#' @param open_options Optional character vector of format-specific dataset
#' open options for `src_dsn` as `"NAME=VALUE"` pairs.
#' @param strict Logical value, `FALSE` (the default) some failures during the
#' translation are tolerated, such as not being able to write group attributes.
#' If set to `TRUE`, such failures will cause the process to fail.
#' @param quiet Logical value, set to `TRUE` to disable progress reporting.
#' Defaults to `FALSE`.
#' @returns Logical value indicating success (invisible `TRUE`, output written
#' to `dst_dsn`). An error is raised if the operation fails.
#'
#' @seealso
#' [mdim_as_classic()], [mdim_info()]
#'
#' @examplesIf gdal_version_num() >= gdal_compute_version(3, 2, 0) && isTRUE(gdal_formats("netCDF")$multidim_raster)
#' f_src <- system.file("extdata/byte.nc", package="gdalraster")
#'
#' ## COMPRESS option for MDArray creation
#' opt <- NULL
#' if (isTRUE(gdal_get_driver_md("netCDF")$NETCDF_HAS_NC4 == "YES")) {
#'   # 4 x 4 is for illustration only, the sample dataset is only 20 x 20
#'   opt <- c("ARRAY:IF(DIM=2):BLOCKSIZE=4,4",
#'            "ARRAY:IF(NAME=Band1):COMPRESS=DEFLATE",
#'            "ARRAY:ZLEVEL=6")
#' }
#'
#' f_dst <- tempfile(fileext = ".nc")
#' mdim_translate(f_src, f_dst, creation_options = opt)
#' info <- mdim_info(f_dst, cout = FALSE) |> yyjsonr::read_json_str()
#' # str(info)
#' info$structural_info
#' info$arrays$Band1$block_size
#' info$arrays$Band1$structural_info
#'
#' (ds <- mdim_as_classic(f_dst, "Band1", 1, 0))
#'
#' plot_raster(ds, interpolate = FALSE, legend = TRUE, main = "Band1")
#'
#' ds$close()
#'
#' ## slice along the Y axis with array view
#' f_dst2 <- tempfile(fileext = ".nc")
#' mdim_translate(f_src, f_dst2, array_specs = "name=Band1,view=[10:20,...]")
#' (ds <- mdim_as_classic(f_dst2, "Band1", 1, 0))
#'
#' plot_raster(ds, interpolate = FALSE, legend = TRUE,
#'             main = "Band1[10:20,...]")
#'
#' ds$close()
#'
#' ## trim X and Y by subsetting
#' f_dst3 <- tempfile(fileext = ".nc")
#' subsets <- c("x(441000,441800)", "y(3750400,3751000)")
#' mdim_translate(f_src, f_dst3, subset_specs = subsets)
#' (ds <- mdim_as_classic(f_dst3, "Band1", 1, 0))
#'
#' plot_raster(ds, interpolate = FALSE, legend = TRUE,
#'             main = "Band1 trimmed")
#'
#' ds$close()
#'
#' ## subsample along X and Y
#' f_dst4 <- tempfile(fileext = ".nc")
#' mdim_translate(f_src, f_dst4, scaleaxes_specs = "x(2),y(2)")
#' (ds <- mdim_as_classic(f_dst4, "Band1", 1, 0))
#'
#' plot_raster(ds, interpolate = FALSE, legend = TRUE,
#'             main = "Band1 subsampled")
#'
#' ds$close()
#' \dontshow{deleteDataset(f_dst)}
#' \dontshow{deleteDataset(f_dst2)}
#' \dontshow{deleteDataset(f_dst3)}
#' \dontshow{deleteDataset(f_dst4)}
mdim_translate <- function(src_dsn, dst_dsn, output_format = "", creation_options = NULL, array_specs = NULL, group_specs = NULL, subset_specs = NULL, scaleaxes_specs = NULL, allowed_drivers = NULL, open_options = NULL, strict = FALSE, quiet = FALSE) {
    invisible(.Call(`_gdalraster_mdim_translate`, src_dsn, dst_dsn, output_format, creation_options, array_specs, group_specs, subset_specs, scaleaxes_specs, allowed_drivers, open_options, strict, quiet))
}

#' Copy a source file to a target filename
#'
#' `vsi_copy_file()` is a wrapper for `VSICopyFile()` in the GDAL Common
#' Portability Library. The GDAL VSI functions allow virtualization of disk
#' I/O so that non file data sources can be made to appear as files.
#' See \url{https://gdal.org/en/stable/user/virtual_file_systems.html}.
#' Requires GDAL >= 3.7.
#'
#' @details
#' The following copies are made fully on the target server, without local
#' download from source and upload to target:
#' * /vsis3/ -> /vsis3/
#' * /vsigs/ -> /vsigs/
#' * /vsiaz/ -> /vsiaz/
#' * /vsiadls/ -> /vsiadls/
#' * any of the above or /vsicurl/ -> /vsiaz/ (starting with GDAL 3.8)
#'
#' @param src_file Character string. Filename of the source file.
#' @param target_file Character string. Filename of the target file.
#' @param show_progress Logical scalar. If `TRUE`, a progress bar will be
#' displayed (the size of `src_file` will be retrieved in GDAL with
#' `VSIStatL()`). Default is `FALSE`.
#' @returns `0` on success or `-1` on an error.
#'
#' @note
#' If `target_file` has the form /vsizip/foo.zip/bar, the default options
#' described for the function `addFilesInZip()` will be in effect.
#'
#' @seealso
#' [copyDatasetFiles()], [vsi_stat()], [vsi_sync()]
#'
#' @examples
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' tmp_file <- "/vsimem/elev_temp.tif"
#'
#' # Requires GDAL >= 3.7
#' if (gdal_version_num() >= gdal_compute_version(3, 7, 0)) {
#'   result <- vsi_copy_file(elev_file, tmp_file)
#'   (result == 0)
#'   print(vsi_stat(tmp_file, "size"))
#'
#'   vsi_unlink(tmp_file)
#' }
vsi_copy_file <- function(src_file, target_file, show_progress = FALSE) {
    .Call(`_gdalraster_vsi_copy_file`, src_file, target_file, show_progress)
}

#' Clean cache associated with /vsicurl/ and related file systems
#'
#' `vsi_curl_clear_cache()` cleans the local cache associated with /vsicurl/
#' (and related file systems). This function is a wrapper for
#' `VSICurlClearCache()` and `VSICurlPartialClearCache()` in the GDAL Common
#' Portability Library. See Details for the GDAL documentation.
#'
#' @details
#' /vsicurl/ (and related file systems like /vsis3/, /vsigs/, /vsiaz/,
#' /vsioss/, /vsiswift/) cache a number of metadata and data for faster
#' execution in read-only scenarios. But when the content on the server-side
#' may change during the same process, those mechanisms can prevent opening
#' new files, or give an outdated version of them.
#' If `partial = TRUE`, cleans the local cache associated for a given filename
#' (and its subfiles and subdirectories if it is a directory).
#'
#' @param partial Logical scalar. Whether to clear the cache only for a given
#' filename (see Details).
#' @param file_prefix Character string. Filename prefix to use if
#' `partial = TRUE`.
#' @param quiet Logical scalar. `TRUE` (the default) to wrap the API call in
#' a quiet error handler, or `FALSE` to print any potential error messages to
#' the console.
#' @returns No return value, called for side effects.
#'
#' @examples
#' vsi_curl_clear_cache()
vsi_curl_clear_cache <- function(partial = FALSE, file_prefix = "", quiet = TRUE) {
    invisible(.Call(`_gdalraster_vsi_curl_clear_cache`, partial, file_prefix, quiet))
}

#' Read names in a directory
#'
#' `vsi_read_dir()` abstracts access to directory contents. It returns a
#' character vector containing the names of files and directories in this
#' directory. With `recursive = TRUE`, reads the list of entries in the
#' directory and subdirectories.
#' This function is a wrapper for `VSIReadDirEx()` and `VSIReadDirRecursive()`
#' in the GDAL Common Portability Library.
#'
#' @param path Character string. The relative or absolute path of a
#' directory to read.
#' @param max_files Integer scalar. The maximum number of files after which to
#' stop, or 0 for no limit (see Note). Ignored if `recursive = TRUE`.
#' @param recursive Logical scalar. `TRUE` to read the directory and its
#' subdirectories. Defaults to `FALSE`.
#' @param all_files Logical scalar. If `FALSE` (the default), only the names
#' of visible files are returned (following Unix-style visibility, that is
#' files whose name does not start with a dot). If `TRUE`, all file names
#' will be returned.
#' @returns A character vector containing the names of files and directories
#' in the directory given by `path` (may be an empty vector `character(0)`).
#' The listing is in alphabetical order, and does not include the special
#' entries '.' and '..' even if they are present in the directory. An empty
#' vector (`character(0)`) is returned if `path` does not exist.
#'
#' @note
#' If `max_files` is set to a positive number, directory listing will stop
#' after that limit has been reached. Note that to indicate truncation, at
#' least one element more than the `max_files` limit will be returned. If the
#' length of the returned character vector is lesser or equal to `max_files`,
#' then no truncation occurred. The `max_files` parameter is ignored when
#' `recursive = TRUE`.
#'
#' @seealso
#' [vsi_glob()], [vsi_mkdir()], [vsi_rmdir()], [vsi_stat()], [vsi_sync()]
#'
#' @examples
#' # regular file system for illustration
#' data_dir <- system.file("extdata", package="gdalraster")
#' vsi_read_dir(data_dir)
vsi_read_dir <- function(path, max_files = 0L, recursive = FALSE, all_files = FALSE) {
    .Call(`_gdalraster_vsi_read_dir`, path, max_files, recursive, all_files)
}

#' Get file and directory names matching a pattern that may contain wildcards
#'
#' `vsi_glob()` returns a character vector of file and directory names matching
#' a pattern that can contain wildcards. This function has similar behavior to
#' the POSIX `glob()` function. Wrapper of `VSIGlob()` in the GDAL Common
#' Portability Library. Requires GDAL >= 3.11.
#'
#' @details
#' The following wildcards are supported
#'   * `*`: match any string
#'   * `?`: match any single character
#'   * `[`: match character class or range, with `!` immediately after `[` to
#'   indicate negation
#'
#' Refer to to the `glob()` man page for more details
#' (\url{https://man7.org/linux/man-pages/man7/glob.7.html}).
#' It also supports the `**` recursive wildcard, behaving similarly to Python
#' `glob.glob()` with `recursive = True`. Be careful of the amount of memory
#' and time required when using the recursive wildcard on directories with a
#' large amount of files and subdirectories.
#'
#' Examples, given a file hierarchy:
#'   * `one.tif`
#'   * `my_subdir/two.tif`
#'   * `my_subdir/subsubdir/three.tif`
#'
#' ```
#' vsi_glob("one.tif")
#'   # returns ("one.tif")
#' vsi_glob("*.tif")
#'   # returns ("one.tif")
#' vsi_glob("on?.tif")
#'   # returns ("one.tif")
#' vsi_glob("on[a-z].tif")
#'   # returns ("one.tif")
#' vsi_glob("on[ef].tif")
#'   # returns ("one.tif")
#' vsi_glob("on[!e].tif")
#'   # returns empty vector character()
#' vsi_glob("my_subdir/*.tif")
#'   # returns ("my_subdir/two.tif")
#' vsi_glob("**/*.tif") returns
#'   # returns ("one.tif", "my_subdir/two.tif", "my_subdir/subsubdir/three.tif")
#' ```
#'
#' In the current implementation, matching is done based on the assumption that
#' a character fits into a single byte, which will not work properly on
#' non-ASCII UTF-8 filenames.
#'
#' @param pattern Character string. The relative or absolute path of a
#' directory to read, potentially containing wildcards, assumed to be UTF-8
#' encoded (see Details).
#' @param show_progress Logical scalar. If `TRUE`, a progress bar will be
#' displayed. Default is `FALSE`.
#' @returns A character vector containing the matching names of files and
#' directories.
#'
#' @note
#' GDAL's `VSIGlob()` works with any virtual file systems supported by GDAL,
#' including network file systems such as /vsis3/, /vsigs/, /vsiaz/, etc. But
#' note that for those, the pattern is not passed to the remote server, and
#' thus a large amount of filenames can be transferred from the remote server
#' to the host where the filtering is done.
#'
#' @seealso
#' [vsi_read_dir()]
#'
#' @examplesIf gdal_version_num() >= gdal_compute_version(3, 11, 0)
#' # Requires GDAL >= 3.11
#' data_dir <- system.file("extdata", package="gdalraster")
#' vsi_glob(file.path(data_dir, "ynp*"))
vsi_glob <- function(pattern, show_progress = FALSE) {
    .Call(`_gdalraster_vsi_glob`, pattern, show_progress)
}

#' Synchronize a source file/directory with a target file/directory
#'
#' `vsi_sync()` is a wrapper for `VSISync()` in the GDAL Common Portability
#' Library. The GDAL documentation is given in Details.
#'
#' @details
#' `VSISync()` is an analog of the Linux `rsync` utility. In the current
#' implementation, `rsync` would be more efficient for local file copying,
#' but `VSISync()` main interest is when the source or target is a remote
#' file system like /vsis3/ or /vsigs/, in which case it can take into account
#' the timestamps of the files (or optionally the ETag/MD5Sum) to avoid
#' unneeded copy operations.
#' This is only implemented efficiently for:
#' * local filesystem <--> remote filesystem
#' * remote filesystem <--> remote filesystem (starting with GDAL 3.1)\cr
#' Where the source and target remote filesystems are the same and one of
#' /vsis3/, /vsigs/ or /vsiaz/. Or when the target is /vsiaz/ and the source
#' is /vsis3/, /vsigs/, /vsiadls/ or /vsicurl/ (starting with GDAL 3.8)
#'
#' Similarly to `rsync` behavior, if the source filename ends with a slash, it
#' means that the content of the directory must be copied, but not the
#' directory name. For example, assuming "/home/even/foo" contains a file
#' "bar", `VSISync("/home/even/foo/", "/mnt/media", ...)` will create a
#' "/mnt/media/bar" file.
#' Whereas `VSISync("/home/even/foo", "/mnt/media", ...)` will create a
#' "/mnt/media/foo" directory which contains a bar file.
#'
#' The `options` argument accepts a character vector of name=value pairs.
#' Currently accepted options are:\cr
#' * `RECURSIVE=NO` (the default is `YES`)
#' * `SYNC_STRATEGY=TIMESTAMP/ETAG/OVERWRITE`. Determines which criterion is
#' used to determine if a target file must be replaced when it already exists
#' and has the same file size as the source. Only applies for a source or
#' target being a network filesystem.
#' The default is `TIMESTAMP` (similarly to how 'aws s3 sync' works), that is
#' to say that for an upload operation, a remote file is replaced if it has a
#' different size or if it is older than the source. For a download operation,
#' a local file is replaced if it has a different size or if it is newer than
#' the remote file.
#' The `ETAG` strategy assumes that the ETag metadata of the remote file is
#' the MD5Sum of the file content, which is only true in the case of /vsis3/
#' for files not using KMS server side encryption and uploaded in a single PUT
#' operation (so smaller than 50 MB given the default used by GDAL). Only to
#' be used for /vsis3/, /vsigs/ or other filesystems using a MD5Sum as ETAG.
#' The `OVERWRITE` strategy (GDAL >= 3.2) will always overwrite the target
#' file with the source one.\cr
#' * `NUM_THREADS=integer`. Number of threads to use for parallel file
#' copying. Only use for when /vsis3/, /vsigs/, /vsiaz/ or /vsiadls/ is
#' in source or target. The default is 10 since GDAL 3.3.\cr
#' * `CHUNK_SIZE=integer`. Maximum size of chunk (in bytes) to use to split
#' large objects when downloading them from /vsis3/, /vsigs/, /vsiaz/
#' or /vsiadls/ to local file system, or for upload to /vsis3/, /vsiaz/ or
#' /vsiadls/ from local file system. Only used if `NUM_THREADS > 1`.
#' For upload to /vsis3/, this chunk size must be set at least to 5 MB. The
#' default is 8 MB since GDAL 3.3.\cr
#' * `x-amz-KEY=value`. (GDAL >= 3.5) MIME header to pass during creation of a
#' /vsis3/ object.\cr
#' * `x-goog-KEY=value`. (GDAL >= 3.5) MIME header to pass during creation of a
#' /vsigs/ object.\cr
#' * `x-ms-KEY=value`. (GDAL >= 3.5) MIME header to pass during creation of a
#' /vsiaz/ or /vsiadls/ object.
#'
#' @param src Character string. Source file or directory.
#' @param target Character string. Target file or directory.
#' @param show_progress Logical scalar. If `TRUE`, a progress bar will be
#' displayed. Defaults to `FALSE`.
#' @param options Character vector of `NAME=VALUE` pairs (see Details).
#' @returns Logical scalar, `TRUE` on success or `FALSE` on an error.
#'
#' @seealso
#' [copyDatasetFiles()], [vsi_copy_file()]
#'
#' @examples
#' \dontrun{
#' # sample-data is a directory in the git repository for gdalraster that is
#' # not included in the R package:
#' # https://github.com/firelab/gdalraster/tree/main/sample-data
#' # A copy of sample-data in an AWS S3 bucket, and a partial copy in an
#' # Azure Blob container, were used to generate the example below.
#'
#' src <- "/vsis3/gdalraster-sample-data/"
#' # s3://gdalraster-sample-data is not public, set credentials
#' set_config_option("AWS_ACCESS_KEY_ID", "xxxxxxxxxxxxxx")
#' set_config_option("AWS_SECRET_ACCESS_KEY", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
#' vsi_read_dir(src)
#' #> [1] "README.md"
#' #> [2] "bl_mrbl_ng_jul2004_rgb_720x360.tif"
#' #> [3] "blue_marble_ng_neo_metadata.xml"
#' #> [4] "landsat_c2ard_sr_mt_hood_jul2022_utm.json"
#' #> [5] "landsat_c2ard_sr_mt_hood_jul2022_utm.tif"
#' #> [6] "lf_elev_220_metadata.html"
#' #> [7] "lf_elev_220_mt_hood_utm.tif"
#' #> [8] "lf_fbfm40_220_metadata.html"
#' #> [9] "lf_fbfm40_220_mt_hood_utm.tif"
#'
#' dst <- "/vsiaz/sampledata"
#' set_config_option("AZURE_STORAGE_CONNECTION_STRING",
#'                   "<connection_string_for_gdalraster_account>")
#' vsi_read_dir(dst)
#' #> [1] "lf_elev_220_metadata.html"   "lf_elev_220_mt_hood_utm.tif"
#'
#' # GDAL VSISync() supports direct copy for /vsis3/ -> /vsiaz/ (GDAL >= 3.8)
#' result <- vsi_sync(src, dst, show_progress = TRUE)
#' #> 0...10...20...30...40...50...60...70...80...90...100 - done.
#' print(result)
#' #> [1] TRUE
#' vsi_read_dir(dst)
#' #> [1] "README.md"
#' #> [2] "bl_mrbl_ng_jul2004_rgb_720x360.tif"
#' #> [3] "blue_marble_ng_neo_metadata.xml"
#' #> [4] "landsat_c2ard_sr_mt_hood_jul2022_utm.json"
#' #> [5] "landsat_c2ard_sr_mt_hood_jul2022_utm.tif"
#' #> [6] "lf_elev_220_metadata.html"
#' #> [7] "lf_elev_220_mt_hood_utm.tif"
#' #> [8] "lf_fbfm40_220_metadata.html"
#' #> [9] "lf_fbfm40_220_mt_hood_utm.tif"
#' }
vsi_sync <- function(src, target, show_progress = FALSE, options = NULL) {
    .Call(`_gdalraster_vsi_sync`, src, target, show_progress, options)
}

#' Create a directory
#'
#' `vsi_mkdir()` creates a new directory with the indicated mode.
#' For POSIX-style systems, the mode is modified by the file creation mask
#' (umask). However, some file systems and platforms may not use umask, or
#' they may ignore the mode completely. So a reasonable cross-platform
#' default mode value is `0755`.
#' With `recursive = TRUE`, creates a directory and all its ancestors.
#' This function is a wrapper for `VSIMkdir()` and `VSIMkdirRecursive()` in
#' the GDAL Common Portability Library.
#'
#' @param path Character string. The path to the directory to create.
#' @param mode Character string. The permissions mode in octal with prefix
#' `0`, e.g., `"0755"` (the default).
#' @param recursive Logical scalar. `TRUE` to create the directory and its
#' ancestors. Defaults to `FALSE`.
#' @returns `0` on success or `-1` on an error.
#'
#' @seealso
#' [vsi_read_dir()], [vsi_rmdir()]
#'
#' @examples
#' new_dir <- file.path(tempdir(), "newdir")
#' vsi_mkdir(new_dir)
#' vsi_stat(new_dir, "type")
#' vsi_rmdir(new_dir)
vsi_mkdir <- function(path, mode = "0755", recursive = FALSE) {
    .Call(`_gdalraster_vsi_mkdir`, path, mode, recursive)
}

#' Delete a directory
#'
#' `vsi_rmdir()` deletes a directory object from the file system. On some
#' systems the directory must be empty before it can be deleted.
#' With `recursive = TRUE`, deletes a directory object and its content from
#' the file system.
#' This function goes through the GDAL `VSIFileHandler` virtualization and may
#' work on unusual filesystems such as in memory.
#' It is a wrapper for `VSIRmdir()` and `VSIRmdirRecursive()` in the GDAL
#' Common Portability Library.
#'
#' @param path Character string. The path to the directory to be deleted.
#' @param recursive Logical scalar. `TRUE` to delete the directory and its
#' content. Defaults to `FALSE`.
#' @returns `0` on success or `-1` on an error.
#'
#' @note
#' /vsis3/ has an efficient implementation for deleting recursively. Starting
#' with GDAL 3.4, /vsigs/ has an efficient implementation for deleting
#' recursively, provided that OAuth2 authentication is used.
#'
#' @seealso
#' [deleteDataset()], [vsi_mkdir()], [vsi_read_dir()], [vsi_unlink()]
#'
#' @examples
#' new_dir <- file.path(tempdir(), "newdir")
#' vsi_mkdir(new_dir)
#' vsi_rmdir(new_dir)
vsi_rmdir <- function(path, recursive = FALSE) {
    .Call(`_gdalraster_vsi_rmdir`, path, recursive)
}

#' Delete a file
#'
#' `vsi_unlink()` deletes a file object from the file system.
#' This function goes through the GDAL `VSIFileHandler` virtualization and may
#' work on unusual filesystems such as in memory.
#' It is a wrapper for `VSIUnlink()` in the GDAL Common Portability Library.
#' Analog of the POSIX `unlink()` function.
#'
#' @param filename Character string. The path of the file to be deleted.
#' @returns `0` on success or `-1` on an error.
#'
#' @seealso
#' [deleteDataset()], [vsi_rmdir()], [vsi_unlink_batch()]
#'
#' @examples
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' mem_file <- file.path("/vsimem", "tmp.tif")
#' copyDatasetFiles(mem_file, elev_file)
#' vsi_read_dir("/vsimem")
#' vsi_unlink(mem_file)
#' vsi_read_dir("/vsimem")
vsi_unlink <- function(filename) {
    .Call(`_gdalraster_vsi_unlink`, filename)
}

#' Delete several files in a batch
#'
#' `vsi_unlink_batch()` deletes a list of files passed in a character vector.
#' All files should belong to the same file system handler.
#' This is implemented efficiently for /vsis3/ and /vsigs/ (provided for
#' /vsigs/ that OAuth2 authentication is used).
#' This function is a wrapper for `VSIUnlinkBatch()` in the GDAL Common
#' Portability Library.
#'
#' @param filenames Character vector. The list of files to delete.
#' @returns Logical vector of `length(filenames)` with values depending
#' on the success of deletion of the corresponding file.
#' `NULL` might be returned in case of a more general error (for example,
#' files belonging to different file system handlers).
#'
#' @seealso
#' [deleteDataset()], [vsi_rmdir()], [vsi_unlink()]
#'
#' @examples
#' # regular file system for illustration
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' tcc_file <- system.file("extdata/storml_tcc.tif", package="gdalraster")
#'
#' tmp_elev <- file.path(tempdir(), "tmp_elev.tif")
#' file.copy(elev_file, tmp_elev)
#' tmp_tcc <- file.path(tempdir(), "tmp_tcc.tif")
#' file.copy(tcc_file, tmp_tcc)
#' vsi_unlink_batch(c(tmp_elev, tmp_tcc))
vsi_unlink_batch <- function(filenames) {
    .Call(`_gdalraster_vsi_unlink_batch`, filenames)
}

#' Get filesystem object info
#'
#' These functions work on GDAL virtual file systems such as in-memory
#' (/vsimem/), URLs (/vsicurl/), cloud storage services (e.g., /vsis3/,
#' /vsigs/, /vsiaz/, etc.), compressed archives (e.g., /vsizip, /vsitar/,
#' /vsi7z/, /vsigzip/, etc.), and others including "standard" file systems.
#' See \url{https://gdal.org/en/stable/user/virtual_file_systems.html}.
#'
#' @name vsi_stat
#'
#' @details
#' `vsi_stat()` fetches status information about a single filesystem object
#' (file, directory, etc). It is a wrapper for `VSIStatExL()` in the GDAL
#' Common Portability Library. Analog of the POSIX `stat()` function.
#'
#' `vsi_stat_exists()`, `vsi_stat_type()` and `vsi_stat_size()` are
#' specializations operating on a vector of potentially multiple file system
#' object names, returning, respectfully, a logical vector, a character vector,
#' and a numeric vector carrying the `bit64::integer64` class attribute.
#'
#' @param filename Character string. The path of the filesystem object to be
#' queried.
#' @param info Character string. The type of information to fetch, one of
#' `"exists"` (the default), `"type"` or `"size"`.
#' @param filenames Character vector of filesystem objects to query.
#' @returns
#' If `info = "exists"`, `vsi_stat()` returns logical `TRUE` if the file system
#' object exists, otherwise `FALSE`. If `info = "type"`, returns a character
#' string with one of `"file"` (regular file), `"dir"` (directory),
#' `"symlink"` (symbolic link), or empty string (`""`). If `info = "size"`,
#' returns the file size in bytes (as `bit64::integer64` type), or `-1` if an
#' error occurs.
#' `vsi_stat_exists()` returns a logical vector. `vsi_stat_type()` returns a
#' character vector. `vsi_stat_size()` returns a numeric vector carrying the
#' `bit64::integer64` class attribute.
#'
#' @note
#' For portability, `vsi_stat()` supports a subset of `stat()`-type
#' information for filesystem objects. This function is primarily intended
#' for use with GDAL virtual file systems (e.g., URLs, cloud storage systems,
#' ZIP/GZip/7z/RAR archives, in-memory files), but can also be used on
#' "standard" file systems (e.g., in the / hierarchy on Unix-like systems or
#' in C:, D:, etc. drives on Windows).
#'
#' @seealso
#' GDAL Virtual File Systems:\cr
#' \url{https://gdal.org/en/stable/user/virtual_file_systems.html}
#'
#' @examples
#' data_dir <- system.file("extdata", package="gdalraster")
#' vsi_stat(data_dir)
#' vsi_stat(data_dir, "type")
#' # stat() on a directory doesn't return the sum of the file sizes in it,
#' # but rather how much space is used by the directory entry
#' vsi_stat(data_dir, "size")
#'
#' elev_file <- file.path(data_dir, "storml_elev.tif")
#' vsi_stat(elev_file)
#' vsi_stat(elev_file, "type")
#' vsi_stat(elev_file, "size")
#'
#' nonexistent <- file.path(data_dir, "nonexistent.tif")
#' vsi_stat(nonexistent)
#' vsi_stat(nonexistent, "type")
#' vsi_stat(nonexistent, "size")
#'
#' fs_objects <- c(data_dir, elev_file, nonexistent)
#' vsi_stat_exists(fs_objects)
#' vsi_stat_type(fs_objects)
#' vsi_stat_size(fs_objects)
#'
#' # /vsicurl/ file system handler
#' base_url <- "https://raw.githubusercontent.com/firelab/"
#' f <- "gdalraster/main/sample-data/landsat_c2ard_sr_mt_hood_jul2022_utm.tif"
#' url_file <- paste0("/vsicurl/", base_url, f)
#'
#' # try to be CRAN-compliant for the example:
#' set_config_option("GDAL_HTTP_CONNECTTIMEOUT", "10")
#' set_config_option("GDAL_HTTP_TIMEOUT", "10")
#'
#' vsi_stat(url_file)
#' vsi_stat(url_file, "type")
#' vsi_stat(url_file, "size")
vsi_stat <- function(filename, info = "exists") {
    .Call(`_gdalraster_vsi_stat`, filename, info)
}

#' @rdname vsi_stat
vsi_stat_exists <- function(filenames) {
    .Call(`_gdalraster_vsi_stat_exists`, filenames)
}

#' @rdname vsi_stat
vsi_stat_type <- function(filenames) {
    .Call(`_gdalraster_vsi_stat_type`, filenames)
}

#' @rdname vsi_stat
vsi_stat_size <- function(filenames) {
    .Call(`_gdalraster_vsi_stat_size`, filenames)
}

#' Rename a file
#'
#' `vsi_rename()` renames a file object in the file system. The GDAL
#' documentation states it should be possible to rename a file onto a new
#' filesystem, but it is safest if this function is only used to rename files
#' that remain in the same directory.
#' This function goes through the GDAL `VSIFileHandler` virtualization and may
#' work on unusual filesystems such as in memory.
#' It is a wrapper for `VSIRename()` in the GDAL Common Portability Library.
#' Analog of the POSIX `rename()` function.
#'
#' @param oldpath Character string. The name of the file to be renamed.
#' @param newpath Character string. The name the file should be given.
#' @returns `0` on success or `-1` on an error.
#'
#' @seealso
#' [renameDataset()], [vsi_copy_file()]
#'
#' @examples
#' # regular file system for illustration
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' tmp_file <- tempfile(fileext = ".tif")
#' file.copy(elev_file, tmp_file)
#' new_file <- file.path(dirname(tmp_file), "storml_elev_copy.tif")
#' vsi_rename(tmp_file, new_file)
#' vsi_stat(new_file)
#' vsi_unlink(new_file)
vsi_rename <- function(oldpath, newpath) {
    .Call(`_gdalraster_vsi_rename`, oldpath, newpath)
}

#' Return the list of virtual file system handlers currently registered
#'
#' `vsi_get_fs_prefixes()` returns the list of prefixes for virtual file
#' system handlers currently registered (e.g., `"/vsimem/"`, `"/vsicurl/"`,
#' etc). Wrapper for `VSIGetFileSystemsPrefixes()` in the GDAL API.
#'
#' @returns Character vector containing prefixes of the virtual file system
#' handlers.
#'
#' @seealso
#' [vsi_get_fs_options()]
#'
#' \url{https://gdal.org/en/stable/user/virtual_file_systems.html}
#'
#' @examples
#' vsi_get_fs_prefixes()
vsi_get_fs_prefixes <- function() {
    .Call(`_gdalraster_vsi_get_fs_prefixes`)
}

#' Return the list of options associated with a virtual file system handler
#' as a serialized XML string.
#'
#' Called from and documented in R/gdal_helpers.R
#' @noRd
.vsi_get_fs_options <- function(filename) {
    .Call(`_gdalraster_vsi_get_fs_options_`, filename)
}

#' Return whether the filesystem supports sequential write
#'
#' `vsi_supports_seq_write()` returns whether the filesystem supports
#' sequential write.
#' Wrapper for `VSISupportsSequentialWrite()` in the GDAL API.
#'
#' @param filename Character string. The path of the filesystem object to be
#' tested.
#' @param allow_local_tmpfile Logical scalar. `TRUE` if the filesystem is
#' allowed to use a local temporary file before uploading to the target
#' location.
#' @returns Logical scalar. `TRUE` if sequential write is supported.
#'
#' @note
#' The location GDAL uses for temporary files can be forced via the
#' `CPL_TMPDIR` configuration option.
#'
#' @seealso
#' [vsi_supports_rnd_write()]
#'
#' @examples
#' # Requires GDAL >= 3.6
#' if (gdal_version_num() >= gdal_compute_version(3, 6, 0))
#'   vsi_supports_seq_write("/vsimem/test-mem-file.gpkg", TRUE)
vsi_supports_seq_write <- function(filename, allow_local_tmpfile) {
    .Call(`_gdalraster_vsi_supports_seq_write`, filename, allow_local_tmpfile)
}

#' Return whether the filesystem supports random write
#'
#' `vsi_supports_rnd_write()` returns whether the filesystem supports
#' random write.
#' Wrapper for `VSISupportsRandomWrite()` in the GDAL API.
#'
#' @param filename Character string. The path of the filesystem object to be
#' tested.
#' @param allow_local_tmpfile Logical scalar. `TRUE` if the filesystem is
#' allowed to use a local temporary file before uploading to the target
#' location.
#' @returns Logical scalar. `TRUE` if random write is supported.
#'
#' @note
#' The location GDAL uses for temporary files can be forced via the
#' `CPL_TMPDIR` configuration option.
#'
#' @seealso
#' [vsi_supports_seq_write()]
#'
#' @examples
#' # Requires GDAL >= 3.6
#' if (gdal_version_num() >= gdal_compute_version(3, 6, 0))
#'   vsi_supports_rnd_write("/vsimem/test-mem-file.gpkg", TRUE)
vsi_supports_rnd_write <- function(filename, allow_local_tmpfile) {
    .Call(`_gdalraster_vsi_supports_rnd_write`, filename, allow_local_tmpfile)
}

#' Return free disk space available on the filesystem
#'
#' `vsi_get_disk_free_space()` returns the free disk space available on the
#' filesystem. Wrapper for `VSIGetDiskFreeSpace()` in the GDAL Common
#' Portability Library.
#'
#' @param path Character string. A directory of the filesystem to query.
#' @returns Numeric scalar. The free space in bytes (as `bit64::integer64`
#' type), or `-1` in case of error.
#'
#' @examples
#' tmp_dir <- file.path("/vsimem", "tmpdir")
#' vsi_mkdir(tmp_dir)
#' vsi_get_disk_free_space(tmp_dir)
#' vsi_rmdir(tmp_dir)
vsi_get_disk_free_space <- function(path) {
    .Call(`_gdalraster_vsi_get_disk_free_space`, path)
}

#' Set a path specific option for a given path prefix
#'
#' `vsi_set_path_option()` sets a path specific option for a given path
#' prefix. Such an option is typically, but not limited to, setting
#' credentials for a virtual file system.
#' Wrapper for `VSISetPathSpecificOption()` in the GDAL Common Portability
#' Library. Requires GDAL >= 3.6.
#'
#' @details
#' Options may also be set with `set_config_option()`, but
#' `vsi_set_path_option()` allows specifying them with a granularity at the
#' level of a file path. This makes it easier if using the same virtual file
#' system but with different credentials (e.g., different credentials for
#' buckets "/vsis3/foo" and "/vsis3/bar"). This is supported for the following
#' virtual file systems: /vsis3/, /vsigs/, /vsiaz/, /vsioss/, /vsiwebhdfs,
#' /vsiswift.
#'
#' @param path_prefix Character string. A path prefix of a virtual file system
#' handler. Typically of the form `/vsiXXX/bucket`.
#' @param key Character string. Option key.
#' @param value Character string. Option value. Passing `value = ""` (empty
#' string) will unset a value previously set by `vsi_set_path_option()`.
#' @returns No return value, called for side effect.
#'
#' @note
#' Setting options for a path starting with /vsiXXX/ will also apply for
#' /vsiXXX_streaming/ requests.
#'
#' No particular care is taken to store options in RAM in a secure way.
#' So they might accidentally hit persistent storage if swapping occurs,
#' or someone with access to the memory allocated by the process may be
#' able to read them.
#'
#' @seealso
#' [set_config_option()], [vsi_clear_path_options()]
vsi_set_path_option <- function(path_prefix, key, value) {
    invisible(.Call(`_gdalraster_vsi_set_path_option`, path_prefix, key, value))
}

#' Clear path specific configuration options
#'
#' `vsi_clear_path_options()` clears path specific options previously set
#' with `vsi_set_path_option()`.
#' Wrapper for `VSIClearPathSpecificOptions()` in the GDAL Common Portability
#' Library. Requires GDAL >= 3.6.
#'
#' @param path_prefix Character string. If set to `""` (empty string), all
#' path specific options are cleared. If set to a path prefix, only those
#' options set with `vsi_set_path_option(path_prefix, ...)` will be cleared.
#' @returns No return value, called for side effect.
#'
#' @note
#' No particular care is taken to remove options from RAM in a secure way.
#'
#' @seealso
#' [vsi_set_path_option()]
vsi_clear_path_options <- function(path_prefix) {
    invisible(.Call(`_gdalraster_vsi_clear_path_options`, path_prefix))
}

#' Get metadata on files
#'
#' `vsi_get_file_metadata()` returns metadata for file system objects.
#' Implemented for network-like filesystems. Starting with GDAL 3.7,
#' implemented for /vsizip/ with SOZip metadata.
#' Wrapper of `VSIGetFileMetadata()` in the GDAL Common Portability Library.
#'
#' @details
#' The metadata available depends on the file system. The following are
#' supported as of GDAL 3.9:
#'   * HEADERS: to get HTTP headers for network-like filesystems (/vsicurl/,
#'     /vsis3/, /vsgis/, etc).
#'   * TAGS: for /vsis3/, to get S3 Object tagging information. For /vsiaz/,
#'     to get blob tags.
#'   * STATUS: specific to /vsiadls/: returns all system-defined properties
#'     for a path (seems in practice to be a subset of HEADERS).
#'   * ACL: specific to /vsiadls/ and /vsigs/: returns the access control list
#'     for a path. For /vsigs/, a single `XML=xml_content` string is returned.
#'   * METADATA: specific to /vsiaz/: blob metadata (this will be a subset of
#'     what `domain=HEADERS` returns).
#'   * ZIP: specific to /vsizip/: to obtain ZIP specific metadata, in
#'     particular if a file is SOZIP-enabled (`SOZIP_VALID=YES`).
#'
#' @param filename Character string. The path of the file system object to be
#' queried.
#' @param domain Character string. Metadata domain to query. Depends on the
#' file system, see Details.
#' @returns A named list of values, or `NULL` in case of error or empty list.
#'
#' @seealso
#' [vsi_stat()], [addFilesInZip()]
#'
#' @examplesIf gdal_version_num() >= gdal_compute_version(3, 7, 0)
#' # validate an SOZip-enabled file
#' # Requires GDAL >= 3.7
#' f <- system.file("extdata/ynp_features.zip", package = "gdalraster")
#'
#' zf <- file.path("/vsizip", f)
#' # files in zip archive
#' vsi_read_dir(zf)
#'
#' # SOZip metadata for ynp_features.gpkg
#' zf_gpkg <- file.path(zf, "ynp_features.gpkg")
#' vsi_get_file_metadata(zf_gpkg, domain = "ZIP")
vsi_get_file_metadata <- function(filename, domain) {
    .Call(`_gdalraster_vsi_get_file_metadata`, filename, domain)
}

#' Returns the actual URL of a supplied VSI filename
#'
#' `vsi_get_actual_url()` returns the actual URL of a supplied filename.
#' Currently only returns a non-NULL value for network-based virtual file
#' systems. For example "/vsis3/bucket/filename" will be expanded as
#' "https://bucket.s3.amazon.com/filename".
#' Wrapper for `VSIGetActualURL()` in the GDAL API.
#'
#' @param filename Character string containing a /vsiPREFIX/ filename.
#' @returns Character string containing the actual URL, or `NULL` if
#' `filename` is not a network-based virtual file system.
#'
#' @seealso
#' [vsi_get_signed_url()]
#'
#' @examples
#' \dontrun{
#' f <- "/vsiaz/items/io-lulc-9-class.parquet"
#' set_config_option("AZURE_STORAGE_ACCOUNT", "pcstacitems")
#' # token obtained from:
#' # https://planetarycomputer.microsoft.com/api/sas/v1/token/pcstacitems/items
#' set_config_option("AZURE_STORAGE_SAS_TOKEN","<token>")
#' vsi_get_actual_url(f)
#' #> [1] "https://pcstacitems.blob.core.windows.net/items/io-lulc-9-class.parquet"
#' vsi_get_signed_url(f)
#' #> [1] "https://pcstacitems.blob.core.windows.net/items/io-lulc-9-class.parquet?<token>"
#' }
vsi_get_actual_url <- function(filename) {
    .Call(`_gdalraster_vsi_get_actual_url`, filename)
}

#' Returns a signed URL for a supplied VSI filename
#'
#' `vsi_get_signed_url()` Returns a signed URL of a supplied filename.
#' Currently only returns a non-NULL value for /vsis3/, /vsigs/, /vsiaz/ and
#' /vsioss/ For example "/vsis3/bucket/filename" will be expanded as
#' "https://bucket.s3.amazon.com/filename?X-Amz-Algorithm=AWS4-HMAC-SHA256..."
#' Configuration options that apply for file opening (typically to provide
#' credentials), and are returned by `vsi_get_fs_options()`, are also valid
#' in that context.
#' Wrapper for `VSIGetSignedURL()` in the GDAL API.
#'
#' @details
#' The `options` argument accepts a character vector of name=value pairs.
#' For /vsis3/, /vsigs/, /vsiaz/ and /vsioss/, the following options are
#' supported:
#' * `START_DATE=YYMMDDTHHMMSSZ`: date and time in UTC following ISO 8601
#'   standard, corresponding to the start of validity of the URL. If not
#'   specified, current date time.
#' * `EXPIRATION_DELAY=number_of_seconds`: number between 1 and 604800 (seven
#'   days) for the validity of the signed URL. Defaults to 3600 (one hour).
#' * `VERB=GET/HEAD/DELETE/PUT/POST`: HTTP VERB for which the request will be
#'   used. Defaults to `GET`.
#'
#' /vsiaz/ supports additional options:
#' * `SIGNEDIDENTIFIER=value`: to relate the given shared access signature to
#'   a corresponding stored access policy.
#' * `SIGNEDPERMISSIONS=r|w`: permissions associated with the shared access
#'   signature. Normally deduced from `VERB`.
#'
#' @param filename Character string containing a /vsiPREFIX/ filename.
#' @param options Character vector of `NAME=VALUE` pairs (see Details).
#' @returns Character string containing the signed URL, or `NULL` if
#' `filename` is not a network-based virtual file system.
#'
#' @seealso
#' [vsi_get_actual_url()]
#'
#' @examples
#' \dontrun{
#' f <- "/vsiaz/items/io-lulc-9-class.parquet"
#' set_config_option("AZURE_STORAGE_ACCOUNT", "pcstacitems")
#' # token obtained from:
#' # https://planetarycomputer.microsoft.com/api/sas/v1/token/pcstacitems/items
#' set_config_option("AZURE_STORAGE_SAS_TOKEN", "<token>")
#' vsi_get_actual_url(f)
#' #> [1] "https://pcstacitems.blob.core.windows.net/items/io-lulc-9-class.parquet"
#' vsi_get_signed_url(f)
#' #> [1] "https://pcstacitems.blob.core.windows.net/items/io-lulc-9-class.parquet?<token>"
#' }
vsi_get_signed_url <- function(filename, options = NULL) {
    .Call(`_gdalraster_vsi_get_signed_url`, filename, options)
}

#' Returns if the file/filesystem is "local".
#'
#' `vsi_is_local()` returns whether the file/filesystem is "local".
#' Wrapper for `VSIIsLocal()` in the GDAL API. Requires GDAL >= 3.6.
#'
#' @param filename Character string. The path of the filesystem object to be
#' tested.
#' @returns Logical scalar. `TRUE` if if the input file path is local.
#'
#' @note
#' The concept of local is mostly by opposition with a network / remote file
#' system whose access time can be long.
#'
#' /vsimem/ is considered to be a local file system, although a
#' non-persistent one.
#'
#' @examples
#' # Requires GDAL >= 3.6
#' if (gdal_version_num() >= gdal_compute_version(3, 6, 0))
#'   print(vsi_is_local("/vsimem/test-mem-file.tif"))
vsi_is_local <- function(filename) {
    .Call(`_gdalraster_vsi_is_local`, filename)
}

#' Return VSI compatible paths from URIs / URLs
#'
#' `vsi_uri_to_vsi_path()` substitutes URIs / URLs starting with `s3://`,
#' `gs://`, etc. by their VSI prefix equivalents. If no known substitution is
#' found, the input string is returned unmodified.
#' Wrapper of `VSIURIToVSIPath()` in the GDAL API. Requires GDAL >= 3.12.
#'
#' @param uris Character vector of input URI strings.
#' @returns Character vector of VSI paths.
#'
#' @examplesIf gdal_version_num() >= gdal_compute_version(3, 12, 0)
#' # Requires GDAL >= 3.12
#' vsi_uri_to_vsi_path("gs://cmip6/")
vsi_uri_to_vsi_path <- function(uris) {
    .Call(`_gdalraster_vsi_uri_to_vsi_path`, uris)
}

#' @noRd
.gdal_commands <- function(contains, recurse, console_out) {
    .Call(`_gdalraster_gdal_commands`, contains, recurse, console_out)
}

#' @noRd
.gdal_global_reg_names <- function() {
    .Call(`_gdalraster_gdal_global_reg_names`)
}

#' get GEOS version
#' @noRd
.getGEOSVersion <- function() {
    .Call(`_gdalraster_getGEOSVersion`)
}

#' Is GEOS available?
#'
#' `has_geos()` returns a logical value indicating whether GDAL was built
#' against the GEOS library. GDAL built with GEOS is a system requirement
#' as of `gdalraster` 1.10.0, so this function will always return `TRUE`
#' (may be removed in a future version).
#'
#' @return Logical. `TRUE` if GEOS is available, otherwise `FALSE`.
#'
#' @examples
#' has_geos()
has_geos <- function() {
    .Call(`_gdalraster_has_geos`)
}

#' @noRd
.g_wkb2wkt <- function(geom, as_iso = FALSE) {
    .Call(`_gdalraster_g_wkb2wkt`, geom, as_iso)
}

#' @noRd
.g_wkb_list2wkt <- function(geom, as_iso = FALSE) {
    .Call(`_gdalraster_g_wkb_list2wkt`, geom, as_iso)
}

#' @noRd
.g_wkt2wkb <- function(geom, as_iso = FALSE, byte_order = "LSB") {
    .Call(`_gdalraster_g_wkt2wkb`, geom, as_iso, byte_order)
}

#' @noRd
.g_wkt_vector2wkb <- function(geom, as_iso = FALSE, byte_order = "LSB") {
    .Call(`_gdalraster_g_wkt_vector2wkb`, geom, as_iso, byte_order)
}

#' @noRd
.g_create <- function(geom_type, pts, as_iso = FALSE, byte_order = "LSB") {
    .Call(`_gdalraster_g_create`, geom_type, pts, as_iso, byte_order)
}

#' @noRd
.g_add_geom <- function(sub_geom, container, as_iso = FALSE, byte_order = "LSB") {
    .Call(`_gdalraster_g_add_geom`, sub_geom, container, as_iso, byte_order)
}

#' @noRd
.g_geom_count <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_geom_count`, geom, quiet)
}

#' @noRd
.g_get_geom <- function(container, sub_geom_idx, as_iso, byte_order) {
    .Call(`_gdalraster_g_get_geom`, container, sub_geom_idx, as_iso, byte_order)
}

#' @noRd
.g_build_collection <- function(geoms, coll_type, as_iso, byte_order) {
    .Call(`_gdalraster_g_build_collection`, geoms, coll_type, as_iso, byte_order)
}

#' @noRd
.g_build_polygon_from_edges <- function(lines, auto_close, tolerance, as_iso, byte_order) {
    .Call(`_gdalraster_g_build_polygon_from_edges`, lines, auto_close, tolerance, as_iso, byte_order)
}

#' @noRd
.g_is_valid <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_is_valid`, geom, quiet)
}

#' @noRd
.g_invalid_reason <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_invalid_reason`, geom, quiet)
}

#' @noRd
.g_make_valid <- function(geom, method = "LINEWORK", keep_collapsed = FALSE, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_make_valid`, geom, method, keep_collapsed, as_iso, byte_order, quiet)
}

#' @noRd
.g_normalize <- function(geom, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_normalize`, geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_set_3D <- function(geom, is_3d, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_set_3D`, geom, is_3d, as_iso, byte_order, quiet)
}

#' @noRd
.g_set_measured <- function(geom, is_measured, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_set_measured`, geom, is_measured, as_iso, byte_order, quiet)
}

#' @noRd
.g_swap_xy <- function(geom, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_swap_xy`, geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_is_empty <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_is_empty`, geom, quiet)
}

#' @noRd
.g_is_3D <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_is_3D`, geom, quiet)
}

#' @noRd
.g_is_measured <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_is_measured`, geom, quiet)
}

#' @noRd
.g_is_ring <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_is_ring`, geom, quiet)
}

#' @noRd
.g_name <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_name`, geom, quiet)
}

#' @noRd
.g_summary <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_summary`, geom, quiet)
}

#' @noRd
.g_envelope <- function(geom, as_3d = FALSE, quiet = FALSE) {
    .Call(`_gdalraster_g_envelope`, geom, as_3d, quiet)
}

#' @noRd
.g_intersects <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_intersects`, this_geom, other_geom, quiet)
}

#' @noRd
.g_equals <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_equals`, this_geom, other_geom, quiet)
}

#' @noRd
.g_disjoint <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_disjoint`, this_geom, other_geom, quiet)
}

#' @noRd
.g_touches <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_touches`, this_geom, other_geom, quiet)
}

#' @noRd
.g_contains <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_contains`, this_geom, other_geom, quiet)
}

#' @noRd
.g_within <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_within`, this_geom, other_geom, quiet)
}

#' @noRd
.g_crosses <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_crosses`, this_geom, other_geom, quiet)
}

#' @noRd
.g_overlaps <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_overlaps`, this_geom, other_geom, quiet)
}

#' @noRd
.g_boundary <- function(geom, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_boundary`, geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_buffer <- function(geom, dist, quad_segs = 30L, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_buffer`, geom, dist, quad_segs, as_iso, byte_order, quiet)
}

#' @noRd
.g_convex_hull <- function(geom, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_convex_hull`, geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_concave_hull <- function(geom, ratio, allow_holes, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_concave_hull`, geom, ratio, allow_holes, as_iso, byte_order, quiet)
}

#' @noRd
.g_delaunay_triangulation <- function(geom, constrained = FALSE, tolerance = 0.0, only_edges = FALSE, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_delaunay_triangulation`, geom, constrained, tolerance, only_edges, as_iso, byte_order, quiet)
}

#' @noRd
.g_segmentize <- function(geom, max_length, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_segmentize`, geom, max_length, as_iso, byte_order, quiet)
}

#' @noRd
.g_simplify <- function(geom, tolerance, preserve_topology = TRUE, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_simplify`, geom, tolerance, preserve_topology, as_iso, byte_order, quiet)
}

#' @noRd
.g_unary_union <- function(geom, as_iso, byte_order, quiet) {
    .Call(`_gdalraster_g_unary_union`, geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_intersection <- function(this_geom, other_geom, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_intersection`, this_geom, other_geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_union <- function(this_geom, other_geom, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_union`, this_geom, other_geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_difference <- function(this_geom, other_geom, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_difference`, this_geom, other_geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_sym_difference <- function(this_geom, other_geom, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_sym_difference`, this_geom, other_geom, as_iso, byte_order, quiet)
}

#' @noRd
.g_distance <- function(this_geom, other_geom, quiet = FALSE) {
    .Call(`_gdalraster_g_distance`, this_geom, other_geom, quiet)
}

#' @noRd
.g_length <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_length`, geom, quiet)
}

#' @noRd
.g_area <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_area`, geom, quiet)
}

#' @noRd
.g_geodesic_area <- function(geom, srs, traditional_gis_order = TRUE, quiet = FALSE) {
    .Call(`_gdalraster_g_geodesic_area`, geom, srs, traditional_gis_order, quiet)
}

#' @noRd
.g_geodesic_length <- function(geom, srs, traditional_gis_order = TRUE, quiet = FALSE) {
    .Call(`_gdalraster_g_geodesic_length`, geom, srs, traditional_gis_order, quiet)
}

#' @noRd
.g_centroid <- function(geom, quiet = FALSE) {
    .Call(`_gdalraster_g_centroid`, geom, quiet)
}

#' @noRd
.g_transform <- function(geom, srs_from, srs_to, wrap_date_line = FALSE, date_line_offset = 10L, traditional_gis_order = TRUE, as_iso = FALSE, byte_order = "LSB", quiet = FALSE) {
    .Call(`_gdalraster_g_transform`, geom, srs_from, srs_to, wrap_date_line, date_line_offset, traditional_gis_order, as_iso, byte_order, quiet)
}

#' Get the bounding box of a geometry specified in OGC WKT format
#'
#' `bbox_from_wkt()` returns the bounding box of a WKT 2D geometry
#' (e.g., LINE, POLYGON, MULTIPOLYGON).
#'
#' @param wkt Character. OGC WKT string for a simple feature 2D geometry.
#' @param extend_x Numeric scalar. Distance to extend the output bounding box
#' in both directions along the x-axis
#' (results in `xmin = bbox[1] - extend_x`, `xmax = bbox[3] + extend_x`).
#' @param extend_y Numeric scalar. Distance to extend the output bounding box
#' in both directions along the y-axis
#' (results in `ymin = bbox[2] - extend_y`, `ymax = bbox[4] + extend_y`).
#' @return Numeric vector of length four containing the xmin, ymin,
#' xmax, ymax of the geometry specified by `wkt` (possibly extended by values
#' in `extend_x`, `extend_y`).
#'
#' @seealso
#' [bbox_to_wkt()]
#'
#' @examples
#' bnd <- "POLYGON ((324467.3 5104814.2, 323909.4 5104365.4, 323794.2
#' 5103455.8, 324970.7 5102885.8, 326420.0 5103595.3, 326389.6 5104747.5,
#' 325298.1 5104929.4, 325298.1 5104929.4, 324467.3 5104814.2))"
#' bbox_from_wkt(bnd, 100, 100)
bbox_from_wkt <- function(wkt, extend_x = 0, extend_y = 0) {
    .Call(`_gdalraster_bbox_from_wkt`, wkt, extend_x, extend_y)
}

#' Convert a bounding box to POLYGON in OGC WKT format
#'
#' `bbox_to_wkt()` returns a WKT POLYGON string for the given bounding box.
#'
#' @param bbox Numeric vector of length four containing xmin, ymin,
#' xmax, ymax.
#' @param extend_x Numeric scalar. Distance in units of `bbox` to extend the
#' rectangle in both directions along the x-axis
#' (results in `xmin = bbox[1] - extend_x`, `xmax = bbox[3] + extend_x`).
#' @param extend_y Numeric scalar. Distance in units of `bbox` to extend the
#' rectangle in both directions along the y-axis
#' (results in `ymin = bbox[2] - extend_y`, `ymax = bbox[4] + extend_y`).
#' @return Character string for an OGC WKT polygon.
#'
#' @seealso
#' [bbox_from_wkt()], [g_buffer()]
#'
#' @examples
#' elev_file <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' ds <- new(GDALRaster, elev_file, read_only=TRUE)
#' bbox_to_wkt(ds$bbox())
#' ds$close()
bbox_to_wkt <- function(bbox, extend_x = 0, extend_y = 0) {
    .Call(`_gdalraster_bbox_to_wkt`, bbox, extend_x, extend_y)
}

#' Does vector dataset exist
#'
#' @noRd
.ogr_ds_exists <- function(dsn, with_update = FALSE) {
    .Call(`_gdalraster_ogr_ds_exists`, dsn, with_update)
}

#' Get the format driver short name for a vector dataset
#'
#' @noRd
.ogr_ds_format <- function(dsn) {
    .Call(`_gdalraster_ogr_ds_format`, dsn)
}

#' Test if capabilities are available for a vector dataset
#'
#' @noRd
.ogr_ds_test_cap <- function(dsn, with_update = TRUE) {
    .Call(`_gdalraster_ogr_ds_test_cap`, dsn, with_update)
}

#' Get number of layers in a dataset
#'
#' @noRd
.ogr_ds_layer_count <- function(dsn) {
    .Call(`_gdalraster_ogr_ds_layer_count`, dsn)
}

#' Get names of layers in a dataset
#'
#' @noRd
.ogr_ds_layer_names <- function(dsn) {
    .Call(`_gdalraster_ogr_ds_layer_names`, dsn)
}

#' Return a list of the names of all field domains stored in the dataset
#'
#' @noRd
.ogr_ds_field_domain_names <- function(dsn) {
    .Call(`_gdalraster_ogr_ds_field_domain_names`, dsn)
}

#' Add a field domain to a dataset
#'
#' @noRd
.ogr_ds_add_field_domain <- function(dsn, fld_dom_defn) {
    .Call(`_gdalraster_ogr_ds_add_field_domain`, dsn, fld_dom_defn)
}

#' Delete a field domain from a dataset
#'
#' @noRd
.ogr_ds_delete_field_domain <- function(dsn, domain_name) {
    .Call(`_gdalraster_ogr_ds_delete_field_domain`, dsn, domain_name)
}

#' Does layer exist
#'
#' @noRd
.ogr_layer_exists <- function(dsn, layer) {
    .Call(`_gdalraster_ogr_layer_exists`, dsn, layer)
}

#' Test if capabilities are available for a vector layer
#'
#' @noRd
.ogr_layer_test_cap <- function(dsn, layer, with_update = TRUE) {
    .Call(`_gdalraster_ogr_layer_test_cap`, dsn, layer, with_update)
}

#' Rename a layer in a vector dataset
#'
#' @noRd
.ogr_layer_rename <- function(dsn, layer, new_name) {
    .Call(`_gdalraster_ogr_layer_rename`, dsn, layer, new_name)
}

#' Delete a layer in a vector dataset
#'
#' @noRd
.ogr_layer_delete <- function(dsn, layer) {
    .Call(`_gdalraster_ogr_layer_delete`, dsn, layer)
}

#' Get names of fields on a layer
#'
#' @noRd
.ogr_layer_field_names <- function(dsn, layer) {
    .Call(`_gdalraster_ogr_layer_field_names`, dsn, layer)
}

#' Get field index or -1 if fld_name not found
#'
#' @noRd
.ogr_field_index <- function(dsn, layer, fld_name) {
    .Call(`_gdalraster_ogr_field_index`, dsn, layer, fld_name)
}

#' Create a new field on layer
#'
#' @noRd
.ogr_field_create <- function(dsn, layer, fld_name, fld_type, fld_subtype = "OFSTNone", fld_width = 0L, fld_precision = 0L, is_nullable = TRUE, is_unique = FALSE, default_value = "", domain_name = "") {
    .Call(`_gdalraster_ogr_field_create`, dsn, layer, fld_name, fld_type, fld_subtype, fld_width, fld_precision, is_nullable, is_unique, default_value, domain_name)
}

#' Create a new geom field on layer
#'
#' @noRd
.ogr_geom_field_create <- function(dsn, layer, fld_name, geom_type, srs = "", is_nullable = TRUE) {
    .Call(`_gdalraster_ogr_geom_field_create`, dsn, layer, fld_name, geom_type, srs, is_nullable)
}

#' Rename an attribute field on a vector layer
#'
#' @noRd
.ogr_field_rename <- function(dsn, layer, fld_name, new_name) {
    .Call(`_gdalraster_ogr_field_rename`, dsn, layer, fld_name, new_name)
}

#' Set the field domain of an existing attribute field on a vector layer
#'
#' @noRd
.ogr_field_set_domain_name <- function(dsn, layer, fld_name, domain_name) {
    .Call(`_gdalraster_ogr_field_set_domain_name`, dsn, layer, fld_name, domain_name)
}

#' Delete an attribute field on a vector layer
#'
#' @noRd
.ogr_field_delete <- function(dsn, layer, fld_name) {
    .Call(`_gdalraster_ogr_field_delete`, dsn, layer, fld_name)
}

#' Execute an SQL statement against the data store
#'
#' @noRd
.ogr_execute_sql <- function(dsn, sql, spatial_filter = "", dialect = "") {
    invisible(.Call(`_gdalraster_ogr_execute_sql`, dsn, sql, spatial_filter, dialect))
}

#' @noRd
.progress_bar_cleanup <- function() {
    invisible(.Call(`_gdalraster_progress_bar_cleanup`))
}

#' Rasterize one polygon
#'
#' @noRd
.rasterize_polygon <- function(rasterXsize, rasterYsize, part_sizes, polygonX, polygonY, fnRasterIO, burn_value, attr_value = NA_character_) {
    .Call(`_gdalraster_rasterize_polygon`, rasterXsize, rasterYsize, part_sizes, polygonX, polygonY, fnRasterIO, burn_value, attr_value)
}

#' Get pointer address of R data as a character string
#'
#' @param x Vector of type numeric, integer, raw or complex.
#' @returns Character string pointer address with format suitable as
#' DATAPOINTER for a GDAL MEM dataset.
#' @noRd
.get_data_ptr <- function(x) {
    .Call(`_gdalraster_get_data_ptr`, x)
}

#' @noRd
NULL

#' Convert spatial reference definitions to OGC WKT or PROJJSON
#'
#' These functions convert various spatial reference formats to Well Known
#' Text (WKT) or PROJJSON.
#'
#' @name srs_convert
#'
#' @details
#' `epsg_to_wkt()` exports the spatial reference for an EPSG code to
#' WKT format.
#' Wrapper for `OSRImportFromEPSG()` in the GDAL Spatial Reference System API
#' with output to WKT.
#'
#' `srs_to_wkt()` converts a spatial reference system (SRS) definition
#' in various text formats to WKT. The function will examine the input SRS,
#' try to deduce the format, and then export it to WKT.
#' Wrapper for `OSRSetFromUserInput()` in the GDAL Spatial Reference System
#' API with output to WKT.
#'
#' `srs_to_projjson()` accepts a spatial reference system (SRS) definition in
#' any of the formats supported by `srs_to_wkt()`, and converts into PROJJSON
#' format. Wrapper for `OSRExportToPROJJSON()` in the GDAL Spatial Reference
#' System API.
#'
#' The input SRS may take the following forms:
#'   * WKT - to convert WKT versions (see below)
#'   * EPSG:n - EPSG code n
#'   * AUTO:proj_id,unit_id,lon0,lat0 - WMS auto projections
#'   * urn:ogc:def:crs:EPSG::n - OGC URNs
#'   * PROJ.4 definitions
#'   * filename - file to read for WKT, XML or PROJ.4 definition
#'   * well known name such as NAD27, NAD83, WGS84 or WGS72
#'   * IGNF:xxxx, ESRI:xxxx - definitions from the PROJ database
#'   * PROJJSON (PROJ >= 6.2)
#'
#' `srs_to_wkt()` is intended to be flexible, but by its nature it is
#' imprecise as it must guess information about the format intended.
#' [epsg_to_wkt()] could be used instead for EPSG codes.
#'
#' As of GDAL 3.0, the default format for WKT export is OGC WKT 1.
#' The WKT version can be overridden by using the OSR_WKT_FORMAT
#' configuration option (see [set_config_option()]).
#' Valid values are one of: SFSQL, WKT1_SIMPLE, WKT1, WKT1_GDAL,
#' WKT1_ESRI, WKT2_2015, WKT2_2018, WKT2, DEFAULT.
#' If SFSQL, a WKT1 string without AXIS, TOWGS84, AUTHORITY or
#' EXTENSION node is returned. If WKT1_SIMPLE, a WKT1 string without
#' AXIS, AUTHORITY or EXTENSION node is returned. WKT1 is an alias of
#' WKT1_GDAL. WKT2 will default to the latest revision implemented
#' (currently WKT2_2018). WKT2_2019 can be used as an alias of
#' WKT2_2018 since GDAL 3.2
#'
#' @param epsg Integer EPSG code.
#' @param srs Character string containing an SRS definition in various
#' formats (see Details).
#' @param pretty Logical value. `TRUE` to return a nicely formatted WKT string
#' for display to a person. `FALSE` for a regular WKT string (the default).
#' @return Character string containing OGC WKT.
#' @param gcs_only Logical value. `TRUE` to return only the definition of the
#' GEOGCS node of the input `srs`. Defaults to `FALSE` (see Note).
#' @param multiline Logical value. `TRUE` for PROJJSON multiline output (the
#' default).
#' @param indent_width Integer value. Defaults to `2`. Only used if
#' `multiline = TRUE` for PROJJSON output.
#' @param schema Character string containing URL to PROJJSON schema. Can be
#' set to empty string to disable it.
#'
#' @note
#' Setting `gcs_only = TRUE` in `srs_to_wkt()` is a wrapper of
#' `OSRCloneGeogCS()` in the GDAL API. The returned WKT will be for the GEOGCS
#' node of the input `srs`.
#'
#' @seealso
#' [srs_query]
#'
#' @examples
#' epsg_to_wkt(5070)
#' writeLines(epsg_to_wkt(5070, pretty = TRUE))
#'
#' srs_to_wkt("NAD83")
#' writeLines(srs_to_wkt("NAD83", pretty = TRUE))
#' set_config_option("OSR_WKT_FORMAT", "WKT2")
#' writeLines(srs_to_wkt("NAD83", pretty = TRUE))
#' set_config_option("OSR_WKT_FORMAT", "")
#'
#' srs_to_wkt("EPSG:5070", gcs_only = TRUE)
#'
#' srs_to_projjson("NAD83") |> writeLines()
epsg_to_wkt <- function(epsg, pretty = FALSE) {
    .Call(`_gdalraster_epsg_to_wkt`, epsg, pretty)
}

#' @rdname srs_convert
srs_to_wkt <- function(srs, pretty = FALSE, gcs_only = FALSE) {
    .Call(`_gdalraster_srs_to_wkt`, srs, pretty, gcs_only)
}

#' @rdname srs_convert
srs_to_projjson <- function(srs, multiline = TRUE, indent_width = 2L, schema = NA_character_) {
    .Call(`_gdalraster_srs_to_projjson`, srs, multiline, indent_width, schema)
}

#' Obtain information about a spatial reference system
#'
#' Bindings to a subset of the GDAL Spatial Reference System API
#' (\url{https://gdal.org/en/stable/api/ogr_srs_api.html}).
#' These functions return various information about a spatial reference
#' system passed as text in any of the formats supported by [srs_to_wkt()].
#'
#' @name srs_query
#'
#' @details
#' `srs_find_epsg()` tries to find a matching EPSG code.
#' Matching may be partial, or may fail. If `all_matches = TRUE`, returns a
#' data frame with entries sorted by decreasing match confidence (first
#' entry has the highest match confidence); the default is `FALSE` which
#' returns a character string in the form "EPSG:####" for the first match
#' (highest confidence). Wrapper of `OSRFindMatches()` in the GDAL SRS API.
#'
#' `srs_get_name()` returns the SRS name.
#' Wrapper of `OSRGetName()` in the GDAL API.
#'
#' `srs_is_geographic()` returns `TRUE`  if the root is a GEOGCS node.
#' Wrapper of `OSRIsGeographic()` in the GDAL API.
#'
#' `srs_is_derived_gcs()` returns `TRUE` if the SRS is a derived geographic
#' coordinate system (for example a rotated long/lat grid).
#' Wrapper of `OSRIsDerivedGeographic()` in the GDAL API.
#'
#' `srs_is_local()` returns `TRUE` if the SRS is a local coordinate system
#' (the root is a LOCAL_CS node).
#' Wrapper of `OSRIsLocal()` in the GDAL API.
#'
#' `srs_is_projected()` returns `TRUE` if the SRS contains a PROJCS node
#' indicating a it is a projected coordinate system.
#' Wrapper of `OSRIsProjected()` in the GDAL API.
#'
#' `srs_is_compound()` returns `TRUE` if the SRS is compound.
#' Wrapper of `OSRIsCompound()` in the GDAL API.
#'
#' `srs_is_geocentric()` returns `TRUE` if the SRS is a geocentric coordinate
#' system.
#' Wrapper of `OSRIsGeocentric()` in the GDAL API.
#'
#' `srs_is_vertical()` returns `TRUE` if the SRS is a vertical coordinate
#' system.
#' Wrapper of `OSRIsVertical()` in the GDAL API.
#'
#' `srs_is_dynamic()` returns `TRUE` if the SRS is is a dynamic coordinate
#' system (relies on a dynamic datum, i.e., a datum that is not plate-fixed).
#' Wrapper of `OSRIsDynamic()` in the GDAL API. Requires GDAL >= 3.4.
#'
#' `srs_is_same()` returns `TRUE` if two spatial references describe
#' the same system.
#' Wrapper of `OSRIsSame()` in the GDAL API.
#'
#' `srs_get_angular_units()` fetches the angular geographic coordinate system
#' units. Returns a list of length two: the first element contains the unit
#' name as a character string, and the second element contains a numeric value
#' to multiply by angular distances to transform them to radians.
#' Wrapper of `OSRGetAngularUnits()` in the GDAL API.
#'
#' `srs_get_linear_units()` fetches the linear projection units.
#' Returns a list of length two: the first element contains the unit
#' name as a character string, and the second element contains a numeric value
#' to multiply by linear distances to transform them to meters.
#' If no units are available, values of "Meters" and 1.0 will be assumed.
#' Wrapper of `OSRGetLinearUnits()` in the GDAL API.
#'
#' `srs_get_coord_epoch()` returns the coordinate epoch, as decimal year
#' (e.g. 2021.3), or `0` if not set or not relevant.
#' Wrapper of `OSRGetCoordinateEpoch()` in the GDAL API. Requires GDAL >= 3.4.
#'
#' `srs_get_utm_zone()` returns the UTM zone number or zero if `srs` isn't a
#' UTM definition. A positive value indicates northern hemisphere; a negative
#' value is in the southern hemisphere.
#' Wrapper of `OSRGetUTMZone()` in the GDAL API.
#'
#' `srs_get_axis_mapping_strategy()` returns the data axis to CRS axis mapping
#' strategy as a character string, one of:
#' * `OAMS_TRADITIONAL_GIS_ORDER`: for geographic CRS with lat/long order, the
#' data will still be long/lat ordered. Similarly for a projected CRS with
#' northing/easting order, the data will still be easting/northing ordered.
#' * `OAMS_AUTHORITY_COMPLIANT`: the data axis will be identical to the CRS
#' axis.
#' * `OAMS_CUSTOM`: custom-defined data axis
#'
#' `srs_get_area_of_use()` is a wrapper of `OSRGetAreaOfUse()` in the GDAL API.
#' Returns a named list containing the following elements (or returns `NULL` if
#' the API call does not succeed):
#' * `AreaName`: the area of use
#' * `WestLongitudeDeg`: the western-most longitude expressed in degree, or
#' `NA` if the bounding box is unknown
#' * `SouthLatitudeDeg`: the southern-most latitude expressed in degree, or
#' `NA` if the bounding box is unknown
#' * `EastLongitudeDeg`: the eastern-most longitude expressed in degree, or
#' `NA` if the bounding box is unknown
#' * `NorthLatitudeDeg`: the northern-most latitude expressed in degree, or
#' `NA` if the bounding box is unknown
#'
#' `srs_get_axes_count()` returns the integer number of axes of the coordinate
#' system of the SRS. Wrapper of `OSRGetAxesCount()` in the GDAL API.
#'
#' `srs_get_axes()` returns a named list of the axis names and their
#' orientations. Wrapper of `OSRGetAxis()` in the GDAL API.
#'
#' `srs_epsg_treats_as_lat_long()` returns `TRUE` if this geographic coordinate
#' system should be treated as having latitude/longitude coordinate ordering.
#' Wrapper of `OSREPSGTreatsAsLatLong()` in the GDAL API.
#'
#' `srs_epsg_treats_as_northing_easting()` returns `TRUE` if this geographic
#' coordinate system should be treated as having northing/easting coordinate
#' ordering. Wrapper of `OSREPSGTreatsAsNorthingEasting()` in the GDAL API.
#'
#' `srs_get_celestial_body_name()` returns the name of the celestial body of
#' the SRS, e.g., `"Earth"` for an Earth SRS. Wrapper of
#' `OSRGetCelestialBodyName()` in the GDAL API. Requires GDAL >= 3.12 and
#' PROJ >= 8.1.
#'
#' @param srs Character string containing an SRS definition in various
#' formats (e.g., WKT, PROJ.4 string, well known name such as NAD27, NAD83,
#' WGS84, etc., see [srs_to_wkt()]).
#' @param srs_other Character string containing an SRS definition in various
#' formats(see above).
#' @param criterion Character string. One of `STRICT`, `EQUIVALENT`,
#' `EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS`.
#' Defaults to `EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS`.
#' @param ignore_axis_mapping Logical scalar. If `TRUE`, sets
#' `IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES` in the call to `OSRIsSameEx()`
#' in the GDAL Spatial Reference System API. Defaults to `NO`.
#' @param ignore_coord_epoch Logical scalar. If `TRUE`, sets
#' `IGNORE_COORDINATE_EPOCH=YES` in the call to `OSRIsSameEx()`
#' in the GDAL Spatial Reference System API. Defaults to `NO`.
#' @param all_matches Logical scalar. `TRUE` to return all identified matches
#' in a data frame, including a confidence value (0-100) for each match. The
#' default is `FALSE` which returns a character string in the form
#' `"EPSG:<code>"` for the first match (highest confidence).
#' @param target_key Optional character string giving the coordinate system
#' part to query, either `"PROJCS"` or `"GEOGCS"` (case-insensitive)
#'
#' @seealso
#' [srs_convert]
#'
#' @examples
#' wkt <- 'PROJCS["ETRS89 / UTM zone 32N (N-E)",
#'         GEOGCS["ETRS89",
#'             DATUM["European_Terrestrial_Reference_System_1989",
#'                 SPHEROID["GRS 1980",6378137,298.257222101,
#'                     AUTHORITY["EPSG","7019"]],
#'                 TOWGS84[0,0,0,0,0,0,0],
#'                 AUTHORITY["EPSG","6258"]],
#'             PRIMEM["Greenwich",0,
#'                 AUTHORITY["EPSG","8901"]],
#'             UNIT["degree",0.0174532925199433,
#'                 AUTHORITY["EPSG","9122"]],
#'             AUTHORITY["EPSG","4258"]],
#'         PROJECTION["Transverse_Mercator"],
#'         PARAMETER["latitude_of_origin",0],
#'         PARAMETER["central_meridian",9],
#'         PARAMETER["scale_factor",0.9996],
#'         PARAMETER["false_easting",500000],
#'         PARAMETER["false_northing",0],
#'         UNIT["metre",1,
#'             AUTHORITY["EPSG","9001"]],
#'         AXIS["Northing",NORTH],
#'         AXIS["Easting",EAST]]'
#'
#' srs_find_epsg(wkt)
#'
#' srs_find_epsg(wkt, all_matches = TRUE)
#'
#' srs_get_name("EPSG:5070")
#'
#' srs_is_geographic("EPSG:5070")
#' srs_is_geographic("EPSG:4326")
#'
#' srs_is_derived_gcs("WGS84")
#'
#' srs_is_projected("EPSG:5070")
#' srs_is_projected("EPSG:4326")
#'
#' srs_is_compound("EPSG:4326")
#'
#' srs_is_geocentric("EPSG:7789")
#'
#' srs_is_vertical("EPSG:5705")
#'
#' srs_get_area_of_use("EPSG:3976")
#'
#' srs_get_axes_count("EPSG:4326")
#' srs_get_axes_count("EPSG:4979")
#'
#' # ordered list of axis names and their orientation
#' srs_get_axes("EPSG:4326+5773")
#'
#' srs_epsg_treats_as_lat_long("WGS84")
#'
#' # NAD83 / Conus Albers:
#' srs_epsg_treats_as_northing_easting("EPSG:5070")
#' # WGS 84 / UPS North (N,E):
#' srs_epsg_treats_as_northing_easting("EPSG:32661")
#' # WGS 84 / UPS South (N,E):
#' srs_epsg_treats_as_northing_easting("EPSG:32761")
#'
#' ## Requires GDAL >= 3.12 and PROJ >= 8.1
#' # srs_get_celestial_body_name("EPSG:4326")
#' #> [1] "Earth"
#' # srs_get_celestial_body_name("IAU_2015:30100")
#' #> [1] "Moon"
#'
#' f <- system.file("extdata/storml_elev.tif", package="gdalraster")
#' ds <- new(GDALRaster, f)
#'
#' ds$getProjection() |> srs_is_projected()
#' ds$getProjection() |> srs_get_utm_zone()
#' ds$getProjection() |> srs_get_angular_units()
#' ds$getProjection() |> srs_get_linear_units()
#' ds$getProjection() |> srs_get_axis_mapping_strategy()
#'
#' ds$getProjection() |> srs_is_same("EPSG:26912")
#' ds$getProjection() |> srs_is_same("NAD83")
#'
#' ds$close()
#'
#' ## Requires GDAL >= 3.4
#' if (gdal_version_num() >= gdal_compute_version(3, 4, 0)) {
#'   if (srs_is_dynamic("WGS84"))
#'     print("WGS84 is dynamic")
#'
#'   if (!srs_is_dynamic("NAD83"))
#'     print("NAD83 is not dynamic")
#' }
srs_get_name <- function(srs) {
    .Call(`_gdalraster_srs_get_name`, srs)
}

#' @rdname srs_query
srs_find_epsg <- function(srs, all_matches = FALSE) {
    .Call(`_gdalraster_srs_find_epsg`, srs, all_matches)
}

#' @rdname srs_query
srs_is_geographic <- function(srs) {
    .Call(`_gdalraster_srs_is_geographic`, srs)
}

#' @rdname srs_query
srs_is_derived_gcs <- function(srs) {
    .Call(`_gdalraster_srs_is_derived_gcs`, srs)
}

#' @rdname srs_query
srs_is_local <- function(srs) {
    .Call(`_gdalraster_srs_is_local`, srs)
}

#' @rdname srs_query
srs_is_projected <- function(srs) {
    .Call(`_gdalraster_srs_is_projected`, srs)
}

#' @rdname srs_query
srs_is_compound <- function(srs) {
    .Call(`_gdalraster_srs_is_compound`, srs)
}

#' @rdname srs_query
srs_is_geocentric <- function(srs) {
    .Call(`_gdalraster_srs_is_geocentric`, srs)
}

#' @rdname srs_query
srs_is_vertical <- function(srs) {
    .Call(`_gdalraster_srs_is_vertical`, srs)
}

#' @rdname srs_query
srs_is_dynamic <- function(srs) {
    .Call(`_gdalraster_srs_is_dynamic`, srs)
}

#' @rdname srs_query
srs_is_same <- function(srs, srs_other, criterion = "", ignore_axis_mapping = FALSE, ignore_coord_epoch = FALSE) {
    .Call(`_gdalraster_srs_is_same`, srs, srs_other, criterion, ignore_axis_mapping, ignore_coord_epoch)
}

#' @rdname srs_query
srs_get_angular_units <- function(srs) {
    .Call(`_gdalraster_srs_get_angular_units`, srs)
}

#' @rdname srs_query
srs_get_linear_units <- function(srs) {
    .Call(`_gdalraster_srs_get_linear_units`, srs)
}

#' @rdname srs_query
srs_get_coord_epoch <- function(srs) {
    .Call(`_gdalraster_srs_get_coord_epoch`, srs)
}

#' @rdname srs_query
srs_get_utm_zone <- function(srs) {
    .Call(`_gdalraster_srs_get_utm_zone`, srs)
}

#' @rdname srs_query
srs_get_axis_mapping_strategy <- function(srs) {
    .Call(`_gdalraster_srs_get_axis_mapping_strategy`, srs)
}

#' @rdname srs_query
srs_get_area_of_use <- function(srs) {
    .Call(`_gdalraster_srs_get_area_of_use`, srs)
}

#' @rdname srs_query
srs_get_axes_count <- function(srs) {
    .Call(`_gdalraster_srs_get_axes_count`, srs)
}

#' @rdname srs_query
srs_get_axes <- function(srs, target_key = NULL) {
    .Call(`_gdalraster_srs_get_axes`, srs, target_key)
}

#' @rdname srs_query
srs_epsg_treats_as_lat_long <- function(srs) {
    .Call(`_gdalraster_srs_epsg_treats_as_lat_long`, srs)
}

#' @rdname srs_query
srs_epsg_treats_as_northing_easting <- function(srs) {
    .Call(`_gdalraster_srs_epsg_treats_as_northing_easting`, srs)
}

#' @rdname srs_query
srs_get_celestial_body_name <- function(srs) {
    .Call(`_gdalraster_srs_get_celestial_body_name`, srs)
}

#' Obtain information about coordinate reference systems in the PROJ DB
#'
#' `srs_info_from_db()` returns a data frame containing descriptive information
#' about spatial coordinate reference systems in the PROJ database. Wrapper of
#' `OSRGetCRSInfoListFromDatabase()` in the GDAL SRS API.
#'
#' @details
#' The returned information includes the authority name, object code, object
#' name, object type (e.g., Geographic 2D CRS, Geographic 3D CRS, Projected
#' CRS, etc.), whether the object is deprecated, whether the bounding box
#' values for the area of use are valid, the bounding box values in longitude
#' and latitude degrees, the name of the area of use, the name of the
#' projection method for a projected CRS, and the name of the celestial body of
#' the CRS (e.g., "Earth", populated only if GDAL >= 3.12 and PROJ >= 8.1).
#'
#' @param auth_name Character string containing an authority name used to
#' restrict the search, or empty string (`""`) for all authorities (the
#' default).
#' @returns
#' A data frame (will contain `0` rows if no objects are found that match the
#' search criterion).
#'
#' @seealso
#' [srs_query]
#'
#' @examples
#' epsg <- srs_info_from_db("EPSG")
#' str(epsg)
#'
#' iau <- srs_info_from_db("IAU_2015")
#' str(iau)
srs_info_from_db <- function(auth_name = "") {
    .Call(`_gdalraster_srs_info_from_db`, auth_name)
}

#' get PROJ version
#' @noRd
.getPROJVersion <- function() {
    .Call(`_gdalraster_getPROJVersion`)
}

#' get search path(s) for PROJ resource files
#' @noRd
.getPROJSearchPaths <- function() {
    .Call(`_gdalraster_getPROJSearchPaths`)
}

#' set search path(s) for PROJ resource files
#' @noRd
.setPROJSearchPaths <- function(paths) {
    invisible(.Call(`_gdalraster_setPROJSearchPaths`, paths))
}

#' get whether PROJ networking capabilities are enabled
#' returns logical NA if GDAL < 3.4
#' @noRd
.getPROJEnableNetwork <- function() {
    .Call(`_gdalraster_getPROJEnableNetwork`)
}

#' enable or disable PROJ networking capabilities
#' @noRd
.setPROJEnableNetwork <- function(enabled) {
    invisible(.Call(`_gdalraster_setPROJEnableNetwork`, enabled))
}

#' Inverse project geospatial x/y coordinates to longitude/latitude
#'
#' public wrapper in R/transform.R
#' @noRd
.inv_project <- function(pts, srs, well_known_gcs = "") {
    .Call(`_gdalraster_inv_project`, pts, srs, well_known_gcs)
}

#' Transform geospatial x/y coordinates
#'
#' public wrapper in R/transform.R
#' @noRd
.transform_xy <- function(pts, srs_from, srs_to) {
    .Call(`_gdalraster_transform_xy`, pts, srs_from, srs_to)
}

#' Transform boundary
#'
#' `transform_bounds()` transforms a bounding box, densifying the edges to
#' account for nonlinear transformations along these edges and extracting
#' the outermost bounds. Multiple bounding boxes may be given as rows of a
#' numeric matrix or data frame. Wrapper of `OCTTransformBounds()` in the GDAL
#' Spatial Reference System API. Requires GDAL >= 3.4.
#'
#' @details
#' The following refer to the *output* values `xmin`, `ymin`, `xmax`, `ymax`:
#'
#' If the destination CRS is geographic, the first axis is longitude, and
#' `xmax < xmin` then the bounds crossed the antimeridian. In this scenario
#' there are two polygons, one on each side of the antimeridian. The first
#' polygon should be constructed with `(xmin, ymin, 180, ymax)` and the second
#' with `(-180, ymin, xmax, ymax)`.
#'
#' If the destination CRS is geographic, the first axis is latitude, and
#' `ymax < ymin` then the bounds crossed the antimeridian. In this scenario
#' there are two polygons, one on each side of the antimeridian. The first
#' polygon should be constructed with `(ymin, xmin, ymax, 180)` and the second
#' with `(ymin, -180, ymax, xmax)`.
#'
#' @param bbox Either a numeric vector of length four containing the input
#' bounding box (xmin, ymin, xmax, ymax), or a four-column numeric matrix
#' of bounding boxes (or data frame that can be coerced to a four-column
#' numeric matrix).
#' @param srs_from Character string specifying the spatial reference system
#' for `pts`. May be in WKT format or any of the formats supported by
#' [srs_to_wkt()].
#' @param srs_to Character string specifying the output spatial reference
#' system. May be in WKT format or any of the formats supported by
#' [srs_to_wkt()].
#' @param densify_pts Integer value giving the number of points to use to
#' densify the bounding polygon in the transformation. Recommended to use `21`
#' (the default).
#' @param traditional_gis_order Logical value, `TRUE` to use traditional GIS
#' order of axis mapping (the default) or `FALSE` to use authority compliant
#' axis order (see Note).
#'
#' @returns
#' For a single input bounding box, a numeric vector of length four containing
#' the transformed bounding box in the output spatial reference system
#' (xmin, ymin, xmax, ymax). For input of multiple bounding boxes,
#' a four-column numeric matrix with each row containing the corresponding
#' transformed bounding box (xmin, ymin, xmax, ymax).
#'
#' @note
#' `traditional_gis_order = TRUE` (the default) means that for geographic CRS
#' with lat/long order, the data will still be long/lat ordered. Similarly for
#' a projected CRS with northing/easting order, the data will still be
#' easting/northing ordered (GDAL's OAMS_TRADITIONAL_GIS_ORDER).
#'
#' `traditional_gis_order = FALSE` means that the data axis will be identical
#'  to the CRS axis (GDAL's OAMS_AUTHORITY_COMPLIANT).
#'
#' See
#' \url{https://gdal.org/en/stable/tutorials/osr_api_tut.html#crs-and-axis-order}.
#'
#' @examples
#' bb <- c(-1405880.71737, -1371213.76254, 5405880.71737, 5371213.76254)
#'
#' # traditional GIS axis ordering by  default (lon, lat)
#' transform_bounds(bb, "EPSG:32761", "EPSG:4326")
#'
#' # authority compliant axis ordering
#' transform_bounds(bb, "EPSG:32761", "EPSG:4326",
#'                  traditional_gis_order = FALSE)
transform_bounds <- function(bbox, srs_from, srs_to, densify_pts = 21L, traditional_gis_order = TRUE) {
    .Call(`_gdalraster_transform_bounds`, bbox, srs_from, srs_to, densify_pts, traditional_gis_order)
}

