R/crs.R

Defines functions fm_sp_get_crs fm_as_sp_crs fm_requires_PROJ6 fm_fallback_PROJ6 fm_not_for_PROJ4 fm_not_for_PROJ6 fm_has_PROJ6 fm_spTransform.inla.mesh fm_spTransform.inla.mesh.segment fm_spTransform.inla.mesh.lattice fm_spTransform.SpatialPointsDataFrame fm_spTransform.SpatialPoints fm_spTransform.default fm_spTransform fm_transform.inla.mesh.segment fm_transform.inla.mesh.lattice fm_transform.inla.mesh fm_CRS.inla.mesh.segment fm_CRS.inla.mesh.lattice fm_CRS.inla.mesh fm_CRS.inla.CRS is.na.inla.CRS `fm_crs_oblique<-.inla.CRS` fm_crs.inla.mesh.segment fm_crs.inla.mesh.lattice fm_crs.inla.mesh fm_crs.inla.CRS `fm_length_unit<-.inla.CRS` `fm_ellipsoid_radius<-.inla.CRS` fm_transform.fm_list fm_transform.fm_segm fm_transform.fm_lattice_2d fm_transform.fm_mesh_2d fm_transform.Spatial fm_transform.sfg fm_transform.sfc fm_transform.sf fm_transform_sf fm_transform.matrix fm_transform_raw fm_transform.NULL fm_transform.default fm_transform fm_detect_manifold.fm_mesh_2d fm_detect_manifold.matrix fm_detect_manifold.numeric fm_detect_manifold.CRS fm_detect_manifold.crs fm_crs_detect_manifold fm_detect_manifold fm_identical_CRS fm_crs_is_identical fm_internal_update_crs fm_crs_bounds_check fm_crs_bounds fm_crs_projection_type fm_wkt_projection_type fm_wkt_tree_projection_type fm_crs_transform_oblique fm_rotmat3123 fm_rotmat3213 fm_crs_get_wkt fm_proj4string fm_wkt print.fm_CRS fm_CRSargs_as_list fm_list_as_CRSargs fm_CRSargs fm_list_as_CRS fm_CRS_as_list fm_wkt_tree_set_item fm_wkt_tree_get_item fm_wkt_tree_as_wkt fm_wkt_as_wkt_tree fm_wkt_predef fm_CRS.default fm_CRS.CRS fm_CRS.matrix fm_CRS.fm_list fm_CRS.fm_segm fm_CRS.fm_lattice fm_CRS.fm_mesh_2d fm_CRS.sfg fm_CRS.sfc fm_CRS.sf fm_CRS.fm_CRS fm_CRS.Spatial fm_CRS.fm_crs fm_CRS.crs is.na.fm_CRS fm_CRS `fm_crs_oblique<-.fm_lattice_2d` `fm_crs_oblique<-.fm_mesh_2d` `fm_crs_oblique<-.fm_segm` `fm_crs_oblique<-.fm_crs` `fm_crs_oblique<-.fm_CRS` `fm_crs_oblique<-.CRS` `fm_crs_oblique<-.crs` `fm_crs<-.Spatial` `fm_crs<-.sfc` `fm_crs<-.sfg` `fm_crs<-.sf` `fm_crs<-.fm_lattice_2d` `fm_crs<-.fm_mesh_2d` `fm_crs<-.fm_list` `fm_crs<-.fm_segm` `fm_crs_oblique<-.NULL` `fm_crs<-.NULL` `fm_crs_oblique<-` `fm_crs<-` fm_crs.matrix fm_crs.fm_list fm_crs.fm_segm fm_crs.fm_lattice_2d fm_crs.fm_mesh_2d fm_crs.sfg fm_crs.sfc fm_crs.sf fm_crs.SpatRaster fm_crs.SpatVector fm_crs.Spatial fm_crs.character fm_crs.fm_CRS fm_crs.fm_crs fm_crs.crs fm_crs.default print.fm_crs `$.fm_crs` st_crs.fm_crs fm_crs_oblique is.na.fm_crs fm_crs_is_null fm_crs `fm_length_unit<-.fm_crs` `fm_length_unit<-.crs` `fm_length_unit<-.fm_CRS` `fm_length_unit<-.CRS` `fm_length_unit<-.character` `fm_length_unit<-` fm_length_unit.character fm_length_unit.default fm_length_unit fm_crs_set_lengthunit fm_crs_get_lengthunit fm_wkt_set_lengthunit fm_wkt_get_lengthunit fm_wkt_unit_params fm_crs_set_ellipsoid_radius `fm_ellipsoid_radius<-.fm_crs` `fm_ellipsoid_radius<-.crs` `fm_ellipsoid_radius<-.fm_CRS` `fm_ellipsoid_radius<-.CRS` `fm_ellipsoid_radius<-.character` `fm_ellipsoid_radius<-` fm_wkt_set_ellipsoid_radius fm_ellipsoid_radius.character fm_ellipsoid_radius.default fm_ellipsoid_radius fm_crs_get_ellipsoid_radius fm_wkt_get_ellipsoid_radius fm_crs_is_geocent fm_wkt_is_geocent

Documented in fm_as_sp_crs fm_crs fm_CRS fm_CRSargs fm_CRSargs_as_list fm_CRS_as_list fm_crs_bounds fm_crs.character fm_crs.crs fm_CRS.crs fm_CRS.CRS fm_crs.default fm_CRS.default fm_crs_detect_manifold fm_crs.fm_crs fm_crs.fm_CRS fm_CRS.fm_crs fm_CRS.fm_CRS fm_CRS.fm_lattice fm_crs.fm_lattice_2d fm_crs.fm_list fm_CRS.fm_list fm_crs.fm_mesh_2d fm_CRS.fm_mesh_2d fm_crs.fm_segm fm_CRS.fm_segm fm_crs_get_ellipsoid_radius fm_crs_get_lengthunit fm_crs_get_wkt fm_crs.inla.CRS fm_CRS.inla.CRS fm_crs.inla.mesh fm_CRS.inla.mesh fm_crs.inla.mesh.lattice fm_CRS.inla.mesh.lattice fm_crs.inla.mesh.segment fm_CRS.inla.mesh.segment fm_crs_is_geocent fm_crs_is_identical fm_crs_is_null fm_crs.matrix fm_CRS.matrix fm_crs_oblique fm_crs_projection_type fm_crs_set_ellipsoid_radius fm_crs_set_lengthunit fm_crs.sf fm_CRS.sf fm_crs.sfc fm_CRS.sfc fm_crs.sfg fm_CRS.sfg fm_crs.Spatial fm_CRS.Spatial fm_crs.SpatRaster fm_crs.SpatVector fm_detect_manifold fm_detect_manifold.crs fm_detect_manifold.CRS fm_detect_manifold.fm_mesh_2d fm_detect_manifold.matrix fm_detect_manifold.numeric fm_ellipsoid_radius fm_ellipsoid_radius.character fm_ellipsoid_radius.default fm_fallback_PROJ6 fm_has_PROJ6 fm_identical_CRS fm_length_unit fm_length_unit.character fm_length_unit.default fm_list_as_CRS fm_list_as_CRSargs fm_not_for_PROJ4 fm_not_for_PROJ6 fm_proj4string fm_requires_PROJ6 fm_sp_get_crs fm_spTransform fm_spTransform.default fm_spTransform.inla.mesh fm_spTransform.inla.mesh.lattice fm_spTransform.inla.mesh.segment fm_spTransform.SpatialPoints fm_spTransform.SpatialPointsDataFrame fm_transform fm_transform.default fm_transform.fm_lattice_2d fm_transform.fm_list fm_transform.fm_mesh_2d fm_transform.fm_segm fm_transform.inla.mesh fm_transform.inla.mesh.lattice fm_transform.inla.mesh.segment fm_transform.matrix fm_transform.NULL fm_transform.sf fm_transform.sfc fm_transform.sfg fm_transform.Spatial fm_wkt fm_wkt_as_wkt_tree fm_wkt_get_ellipsoid_radius fm_wkt_get_lengthunit fm_wkt_is_geocent fm_wkt_predef fm_wkt_projection_type fm_wkt_set_ellipsoid_radius fm_wkt_set_lengthunit fm_wkt_tree_as_wkt fm_wkt_tree_get_item fm_wkt_tree_projection_type fm_wkt_tree_set_item fm_wkt_unit_params is.na.fm_crs is.na.fm_CRS is.na.inla.CRS print.fm_crs print.fm_CRS st_crs.fm_crs

#' @include deprecated.R
#' @include print.R

#' @importFrom sp coordinates proj4string `proj4string<-`


#' @title Handling CRS/WKT
#' @description Get and set CRS object or WKT string properties.
#' @export
#' @name fm_crs_wkt
#' @rdname fm_crs_wkt

fm_wkt_is_geocent <- function(wkt) {
  if (is.null(wkt) || identical(wkt, "") || is.na(wkt)) {
    return(FALSE)
  }
  # See https://proceedings.esri.com/library/userconf/proc17/tech-workshops/tw_2588-212.pdf
  geo_crs_items <- c(
    "GEODCRS", "GEOGCRS",
    "BASEGEODCRS", "BASEGEOGCRS"
  )
  wt <- fm_wkt_as_wkt_tree(wkt)
  if (identical(wt[["label"]], "BOUNDCRS")) {
    wt <- fm_wkt_tree_get_item(wt, "SOURCECRS")
    wt <- fm_wkt_tree_get_item(wt, c("PROJCRS", geo_crs_items))
  }
  if (identical(wt[["label"]], "PROJCRS")) {
    wt <- fm_wkt_tree_get_item(wt, geo_crs_items)
  }
  if (!(wt[["label"]] %in% geo_crs_items)) {
    return(FALSE)
  }
  cs <- fm_wkt_tree_get_item(wt, "CS")
  if (is.null(cs)) {
    return(FALSE)
  }
  cart <- ((cs[["params"]][[1]] == "Cartesian") &&
    (cs[["params"]][[2]] == "3"))
  if (!cart) {
    return(FALSE)
  }
  axis_names <- c('"(X)"', '"(Y)"', '"(Z)"')
  axis_types <- c("geocentricX", "geocentricY", "geocentricZ")
  for (k in seq_len(3)) {
    axis <- fm_wkt_tree_get_item(wt, "AXIS", k)
    if (!((axis[["params"]][[1]] == axis_names[k]) &&
      (axis[["params"]][[2]] == axis_types[k]))) {
      return(FALSE)
    }
  }
  TRUE
}

#' @export
#' @rdname fm_crs_wkt

fm_crs_is_geocent <- function(crs) {
  wkt <- fm_wkt(crs)
  result <- fm_wkt_is_geocent(wkt)
  result
}


# ellipsoid_radius ----

#' @rdname fm_crs_wkt
#' @export

fm_wkt_get_ellipsoid_radius <- function(wkt) {
  geo_crs_items <- c(
    "GEODCRS", "GEOGCRS",
    "BASEGEODCRS", "BASEGEOGCRS"
  )
  wt <- fm_wkt_as_wkt_tree(wkt)

  if (identical(wt[["label"]], "BOUNDCRS")) {
    wt <- fm_wkt_tree_get_item(wt, "SOURCECRS")
    wt <- fm_wkt_tree_get_item(wt, c("PROJCRS", geo_crs_items))
  }
  if (identical(wt[["label"]], "PROJCRS")) {
    wt <- fm_wkt_tree_get_item(wt, geo_crs_items)
  }
  if (is.null(wt) || !(wt[["label"]] %in% geo_crs_items)) {
    stop("Ellipsoid settings not found (no geo crs)")
  }

  datum <- fm_wkt_tree_get_item(wt, c("DATUM", "ENSEMBLE"))
  if (is.null(datum)) {
    stop("Ellipsoid settings not found (no datum/ensemble)")
  }
  ellipsoid <- fm_wkt_tree_get_item(datum, "ELLIPSOID")
  if (is.null(ellipsoid)) {
    stop("Ellipsoid settings not found (no ellipsoid)")
  }
  as.numeric(ellipsoid[["params"]][[2]])
}

#' @rdname fm_crs_wkt
#' @export

fm_crs_get_ellipsoid_radius <- function(crs) {
  fm_wkt_get_ellipsoid_radius(fm_wkt(crs))
}

#' @param x crs object to extract value from or assign values in
#' @rdname fm_crs_wkt
#' @export

fm_ellipsoid_radius <- function(x) {
  UseMethod("fm_ellipsoid_radius")
}

#' @rdname fm_crs_wkt
#' @export

fm_ellipsoid_radius.default <- function(x) {
  fm_wkt_get_ellipsoid_radius(fm_wkt(x))
}

#' @rdname fm_crs_wkt
#' @export

fm_ellipsoid_radius.character <- function(x) {
  fm_wkt_get_ellipsoid_radius(x)
}


#' @param radius numeric; The new radius value
#' @rdname fm_crs_wkt
#' @export

fm_wkt_set_ellipsoid_radius <- function(wkt, radius) {
  geo_crs_items <- c(
    "GEODCRS", "GEOGCRS",
    "BASEGEODCRS", "BASEGEOGCRS"
  )

  set_radius <- function(wt) {
    if (is.null(wt)) {
      stop("Ellipsoid settings not found")
    } else if (wt[["label"]] %in% geo_crs_items) {
      datum <- fm_wkt_tree_get_item(wt, c("DATUM", "ENSEMBLE"))
      null_datum <- is.null(datum)
      if (null_datum) {
        stop("Ellipsoid settings not found (no datum/ensemble)")
      }
      ellipsoid <- fm_wkt_tree_get_item(datum, "ELLIPSOID")
      if (is.null(ellipsoid)) {
        stop("Ellipsoid settings not found")
      }
      ellipsoid[["params"]][[2]] <- as.character(radius)
      datum <- fm_wkt_tree_set_item(datum, ellipsoid)
      wt <- fm_wkt_tree_set_item(wt, datum)
    } else if (wt[["label"]] %in% c("BOUNDCRS", "SOURCECRS", "PROJCRS")) {
      wt_sub <- fm_wkt_tree_get_item(
        wt,
        c(
          "BOUNDCRS", "SOURCECRS", "PROJCRS",
          geo_crs_items
        )
      )
      if (is.null(wt_sub)) {
        stop("Ellipsoid settings not found")
      }
      wt_sub_new <- set_radius(wt_sub)
      wt <- fm_wkt_tree_set_item(wt, wt_sub_new)
    } else {
      stop("Ellipsoid settings not found")
    }
    wt
  }

  wt <- fm_wkt_as_wkt_tree(wkt)
  wt <- set_radius(wt)
  fm_wkt_tree_as_wkt(wt)
}

#' @rdname fm_crs_wkt
#' @param value Value to assign
#' @export

`fm_ellipsoid_radius<-` <- function(x, value) {
  UseMethod("fm_ellipsoid_radius<-")
}

#' @rdname fm_crs_wkt
#' @export
`fm_ellipsoid_radius<-.character` <- function(x, value) {
  x <- fm_wkt_set_ellipsoid_radius(x, value)
  invisible(x)
}

#' @rdname fm_crs_wkt
#' @export
`fm_ellipsoid_radius<-.CRS` <- function(x, value) {
  crs <- fm_crs(x)
  fm_ellipsoid_radius(crs) <- value
  new_crs <- fm_CRS(crs)

  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_ellipsoid_radius<-.fm_CRS` <- function(x, value) {
  crs <- fm_crs(x)
  fm_ellipsoid_radius(crs) <- value
  new_crs <- fm_CRS(crs)

  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_ellipsoid_radius<-.crs` <- function(x, value) {
  wkt <- fm_wkt(x)
  fm_ellipsoid_radius(wkt) <- value
  new_crs <- fm_crs(wkt)
  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_ellipsoid_radius<-.fm_crs` <- function(x, value) {
  wkt <- fm_wkt(x)
  fm_ellipsoid_radius(wkt) <- value
  x$crs <- fm_crs(wkt)
  x
}


#' @rdname fm_crs_wkt
#' @export

fm_crs_set_ellipsoid_radius <- function(crs, radius) {
  fm_ellipsoid_radius(crs) <- radius
  crs
}

# Length unit ----



#' @param crs An `sf::crs`, `sp::CRS`, `fm_crs` or `inla.CRS` object
#' @param wkt A WKT2 character string
#' @param unit character, name of a unit. Supported names are
#' "metre", "kilometre", and the aliases "meter", "m", International metre",
#' "kilometer", and "km", as defined by `fm_wkt_unit_params` or the
#' `params` argument. (For legacy PROJ4 use, only "m" and "km" are
#' supported)
#' @param params Length unit definitions, in the list format produced by
#' `fm_wkt_unit_params()`, Default: NULL, which invokes
#' `fm_wkt_unit_params()`
#' @return For `fm_wkt_unit_params`, a
#' list of named unit definitions
#' @author Finn Lindgren \email{finn.lindgren@@gmail.com}
#' @examples
#' \donttest{
#' c1 <- fm_crs("globe")
#' fm_crs_get_lengthunit(c1)
#' c2 <- fm_crs_set_lengthunit(c1, "m")
#' fm_crs_get_lengthunit(c2)
#' }
#' @export
#' @seealso [fm_crs()]
#' @aliases fm_crs_wkt
#' @rdname fm_crs_wkt

fm_wkt_unit_params <- function() {
  params <- list(
    "metre" =
      list(
        '"metre"',
        "1",
        list(
          label = "ID",
          params = list('"EPSG"', "9001")
        )
      ),
    "kilometre" =
      list(
        '"kilometre"',
        "1000",
        list(
          label = "ID",
          params = list('"EPSG"', "9036")
        )
      )
  )
  params[["meter"]] <- params[["metre"]]
  params[["m"]] <- params[["metre"]]
  params[["International metre"]] <- params[["metre"]]
  params[["kilometer"]] <- params[["kilometre"]]
  params[["km"]] <- params[["kilometre"]]
  params
}

#' @export
#' @rdname fm_crs_wkt
#' @return For `fm_wkt_get_lengthunit`, a
#' list of length units used in the wkt string, excluding the ellipsoid radius
#' unit.

fm_wkt_get_lengthunit <- function(wkt) {
  if (is.null(wkt) || identical(wkt, "") || is.na(wkt)) {
    return(NA_character_)
  }
  extract <- function(wt) {
    # 1. Recursively find LENGTHUNIT, except within ELLIPSOID
    # 2. Return unit

    if (wt[["label"]] == "LENGTHUNIT") {
      result <- list(wt[["params"]])
    } else if (wt[["label"]] != "ELLIPSOID") {
      result <- list()
      for (k in seq_along(wt$params)) {
        if (is.list(wt[["params"]][[k]])) {
          result <- c(result, extract(wt[["params"]][[k]]))
        }
      }
    } else {
      result <- list()
    }
    result
  }

  wt <- fm_wkt_as_wkt_tree(wkt)
  params <- unique(extract(wt))
  names(params) <-
    vapply(
      params,
      function(x) {
        gsub('"', "", x[[1]])
      },
      ""
    )
  params
}

#' @export
#' @rdname fm_crs_wkt
#' @return For `fm_wkt_set_lengthunit`, a
#' WKT2 string with altered length units.
#' Note that the length unit for the ellipsoid radius is unchanged.

fm_wkt_set_lengthunit <- function(wkt, unit, params = NULL) {
  convert <- function(wt, unit) {
    # 1. Recursively find LENGTHUNIT, except within ELLIPSOID
    # 2. Change unit

    if (wt[["label"]] == "LENGTHUNIT") {
      wt[["params"]] <- unit
    } else if (wt[["label"]] != "ELLIPSOID") {
      if ((wt[["label"]] == "PARAMETER") &&
        (wt[["params"]][[1]] %in% c('"False easting"', '"False northing"'))) {
        orig_unit <- (wt[["params"]][[3]][["params"]][[1]])
        new_unit <- (unit[[1]])
        if (orig_unit != new_unit) {
          if (orig_unit == '"metre"' && new_unit == '"kilometre"') {
            wt[["params"]][[2]] <-
              as.character(as.numeric(wt[["params"]][[2]]) / 1000)
          } else if (orig_unit == '"kilometre"' && new_unit == '"metre"') {
            wt[["params"]][[2]] <-
              as.character(as.numeric(wt[["params"]][[2]]) * 1000)
          } else {
            warning("False easting/northing could not be properly converted.")
          }
        }
      }
      for (k in seq_along(wt$params)) {
        if (is.list(wt[["params"]][[k]])) {
          wt[["params"]][[k]] <- convert(wt[["params"]][[k]], unit)
        }
      }
    }
    wt
  }

  if (is.null(params)) {
    params <- fm_wkt_unit_params()
  }
  if (!(unit %in% names(params))) {
    warning(paste0(
      "'fm_wkt_set_lengthunit' unit conversion to '",
      unit,
      "' not supported. Unit left unchanged."
    ))
    return(wkt)
  }

  wt <- fm_wkt_as_wkt_tree(wkt)
  wt <- convert(wt, params[[unit]])
  fm_wkt_tree_as_wkt(wt)
}


#' @return For `fm_crs_get_lengthunit`, a
#' list of length units used in the wkt string, excluding the ellipsoid radius
#' unit. (For legacy PROJ4 code, the raw units from the proj4string are
#' returned, if present.)
#' @export
#' @rdname fm_crs_wkt

fm_crs_get_lengthunit <- function(crs) {
  fm_wkt_get_lengthunit(fm_wkt(crs))
}


#' @rdname fm_crs_wkt
#' @export

fm_crs_set_lengthunit <- function(crs, unit) {
  fm_length_unit(crs) <- unit
  crs
}


#' @rdname fm_crs_wkt
#' @export

fm_length_unit <- function(x) {
  UseMethod("fm_length_unit")
}

#' @rdname fm_crs_wkt
#' @export

fm_length_unit.default <- function(x) {
  fm_wkt_get_lengthunit(fm_wkt(x))
}

#' @rdname fm_crs_wkt
#' @export

fm_length_unit.character <- function(x) {
  fm_wkt_get_lengthunit(x)
}




#' @return For `fm_length_unit<-`, a crs object with
#' altered length units.
#' Note that the length unit for the ellipsoid radius is unchanged.
#' @rdname fm_crs_wkt
#' @export

`fm_length_unit<-` <- function(x, value) {
  UseMethod("fm_length_unit<-")
}

#' @rdname fm_crs_wkt
#' @export
`fm_length_unit<-.character` <- function(x, value) {
  x <- fm_wkt_set_lengthunit(x, value)
  invisible(x)
}

#' @rdname fm_crs_wkt
#' @export
`fm_length_unit<-.CRS` <- function(x, value) {
  crs <- fm_crs(x)
  fm_length_unit(crs) <- value
  new_crs <- fm_CRS(crs)

  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_length_unit<-.fm_CRS` <- function(x, value) {
  crs <- fm_crs(x)
  fm_length_unit(crs) <- value
  new_crs <- fm_CRS(crs)

  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_length_unit<-.crs` <- function(x, value) {
  wkt <- fm_wkt(x)
  fm_length_unit(wkt) <- value
  new_crs <- fm_crs(wkt)
  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_length_unit<-.fm_crs` <- function(x, value) {
  wkt <- fm_wkt(x)
  fm_length_unit(wkt) <- value
  x$crs <- fm_crs(wkt)
  x
}



# fm_crs ----

#' @title Obtain coordinate reference system object
#'
#' @description Obtain an `sf::crs` or `fm_crs` object from a spatial object, or
#' convert crs information to construct a new `sf::crs` object.
#'
#' @param x Object to convert to `crs` or  to extract `crs` information from.
#' If `character`, a string suitable for `sf::st_crs(x)`, or the name of a
#' predefined `wkt` string from ``names(fm_wkt_predef())`.
#' @param \dots Additional arguments passed on the `sf::st_crs()`
#' @param crsonly `r lifecycle::badge("deprecated")` logical;
#' if TRUE, remove `oblique` information from `fm_crs`
#' objects and return a plain `crs` object instead. For `crsonly = TRUE`, use
#' `oblique = NA` instead. For `crsonly = FALSE`, use default, NULL, or non-NA
#' `oblique`.
#' @param oblique Numeric vector of length at most 4 of rotation angles (in degrees)
#' for an oblique projection, all values defaulting to zero. The values
#' indicate (longitude, latitude, orientation, orbit), as explained in the
#' Details section below. When
#' `oblique` is non-NULL, used to override the obliqueness parameters of a
#' `fm_crs` object. When `NA`, remove obliqueness from the object, resulting
#' in a return class of `sf::st_crs()`. When `NULL`, pass though any oblique
#' information in the object, returning an `fm_crs()` object if needed.
#'
#' @details The first two
#' elements of the `oblique` vector are the (longitude, latitude)
#' coordinates for the oblique centre point. The third value (orientation) is a
#' counter-clockwise rotation angle for an observer looking at the centre point
#' from outside the sphere. The fourth value is the quasi-longitude (orbit
#' angle) for a rotation along the oblique observers equator.
#'
#' Simple oblique: `oblique=c(0, 45)`
#'
#' Polar: `oblique=c(0, 90)`
#'
#' Quasi-transversal: `oblique=c(0, 0, 90)`
#'
#' Satellite orbit viewpoint: `oblique=c(lon0-time*v1, 0, orbitangle,
#' orbit0+time*v2)`, where `lon0` is the longitude at which a satellite
#' orbit crosses the equator at `time=0`, when the satellite is at an
#' angle `orbit0` further along in its orbit.  The orbital angle relative
#' to the equatorial plane is `orbitangle`, and `v1` and `v2`
#' are the angular velocities of the planet and the satellite, respectively.
#' Note that "forward" from the satellite's point of view is "to the right" in
#' the projection.
#'
#' When `oblique[2]` or `oblique[3]` are non-zero, the resulting
#' projection is only correct for perfect spheres.
#' @param \dots Additional parameters. Not currently in use.
#' @return Either an `sf::crs` object or an `fm_crs` object,
#' depending on if the coordinate reference system described by the parameters
#' can be expressed with a pure `crs` object or not.
#'
#' @returns A `crs` object ([sf::st_crs()]) or a `fm_crs` object.
#' An S3 `fm_crs` object is a list with elements `crs` and `oblique`.
#' @author Finn Lindgren \email{finn.lindgren@@gmail.com}
#' @seealso [sf::st_crs()], [`fm_crs_wkt`]
#' @examples
#' crs1 <- fm_crs("longlat_globe")
#' crs2 <- fm_crs("lambert_globe")
#' crs3 <- fm_crs("mollweide_norm")
#' crs4 <- fm_crs("hammer_globe")
#' crs5 <- fm_crs("sphere")
#' crs6 <- fm_crs("globe")
#' @export
#' @seealso fm_crs_is_null
#' @rdname fm_crs
#' @seealso [fm_crs<-()], [fm_crs_oblique<-()]
fm_crs <- function(x, oblique = NULL, ..., crsonly = deprecated()) {
  if (lifecycle::is_present(crsonly)) {
    if (crsonly) {
      if (is.null(oblique) || all(is.na(oblique))) {
        msg <- c(
          "`crsonly = TRUE`, so we will use `oblique = NA`."
        )
        oblique <- NA
        return(fm_crs(x, ..., oblique = NA))
      } else {
        msg <- c(
          "Ignoring `crsonly = TRUE`, as it contradicts with the given `oblique`.",
          "We will use `oblique = oblique`."
        )
      }
    } else {
      msg <- c(
        "`crsonly = FALSE`, so we will use `oblique = oblique`."
      )
    }

    lifecycle::deprecate_soft(
      "0.0.1",
      "fm_crs(crsonly=' should no longer be used')",
      "fm_crs(oblique)",
      c(
        "For `crsonly = TRUE`, use `oblique = NA`.",
        "For `crsonly = FALSE`, use default, NULL, or non-NA `oblique`.",
        msg
      )
    )
    return(fm_crs(x, oblique = oblique, ...))
  }

  UseMethod("fm_crs")
}




#' @title Check if a crs is NULL or NA
#'
#' @description
#' Methods of checking whether various kinds of CRS objects are `NULL` or `NA`.
#' Logically equivalent to either `is.na(fm_crs(x))` or
#' `is.na(fm_crs(x, oblique = NA))`, but with a short-cut pre-check for `is.null(x)`.
#'
#' @describeIn fm_crs_is_null Check if an object is or has `NULL` or `NA` CRS information.
#' If not `NULL`, `is.na(fm_crs(x))` is returned. This allows the input to be e.g.
#' a proj4string or epsg number, since the default [fm_crs()] method passes
#' its argument on to `sf::st_crs()`.
#'
#' @param x An object supported by `fm_crs(x)`
#' @param crsonly For crs objects with extended functionality, such as
#' [fm_crs()] objects with `oblique` information, `crsonly = TRUE` only checks
#' the plain CRS part.
#'
#' @returns logical
#'
#' @examples
#' fm_crs_is_null(NULL)
#' fm_crs_is_null(27700)
#' fm_crs_is_null(fm_crs())
#' fm_crs_is_null(fm_crs(27700))
#' fm_crs_is_null(fm_crs(oblique = c(1, 2, 3, 4)))
#' fm_crs_is_null(fm_crs(oblique = c(1, 2, 3, 4)), crsonly = TRUE)
#' fm_crs_is_null(fm_crs(27700, oblique = c(1, 2, 3, 4)))
#' fm_crs_is_null(fm_crs(27700, oblique = c(1, 2, 3, 4)), crsonly = TRUE)
#'
#' @seealso [fm_crs()], [fm_CRS()], [fm_crs_is_identical()]
#' @export
fm_crs_is_null <- function(x, crsonly = FALSE) {
  if (crsonly) {
    return(is.null(x) || is.na(fm_crs(x, oblique = NA)))
  }
  is.null(x) || is.na(fm_crs(x))
}

#' @describeIn fm_crs_is_null Check if a `fm_crs` has `NA` crs information and `NA`
#' obliqueness
#' @export
is.na.fm_crs <- function(x) {
  is.na(x[["crs"]]) && all(is.na(x[["oblique"]]))
}


#' @export
#' @describeIn fm_crs Return `NA` for object with no oblique information,
#' and otherwise a length 4 numeric vector.
fm_crs_oblique <- function(x) {
  if (is.null(x)) {
    return(NA)
  }
  if (inherits(x, c("fm_crs", "inla.CRS"))) {
    return(x[["oblique"]])
  }
  NA
}

#' @importFrom sf st_crs
#' @exportS3Method sf::st_crs fm_crs
#' @describeIn fm_crs `st_crs(x, ...)` is equivalent to
#' `fm_crs(x, ... oblique = NA)`
#' when `x` is a `fm_crs` object.
st_crs.fm_crs <- function(x, ...) {
  fm_crs(x, ..., oblique = NA)
}

#' @rawNamespace S3method("$", fm_crs)
#' @describeIn fm_crs For a `fm_crs` object `x`, `x$name` calls the accessor method for the
#' `crs` object inside it. If `name` is "crs", the internal crs object itself is returned.
#' If `name` is "oblique", the internal oblique angle parameter vector is returned.
#' @param name element name
`$.fm_crs` <- function(x, name) {
  if (name %in% c("crs", "oblique")) {
    x[[name]]
  } else {
    eval(parse(text = paste0('x[["crs"]]$', name)))
  }
}

#' @export
#' @rdname fmesher-print
print.fm_crs <- function(x, ...) {
  print(x[["crs"]], ...)
  cat(paste0("Oblique: c(", paste0(x[["oblique"]], collapse = ", "), ")\n"))
  invisible(x)
}



#' @export
#' @rdname fm_crs
fm_crs.default <- function(x, oblique = NULL, ...) {
  if (missing(x) || is.null(x) || is.na(x)) {
    x <- sf::NA_crs_
  } else if (!inherits(x, "crs")) {
    x <- sf::st_crs(x, ...)
  }
  fm_crs.crs(x, oblique = oblique, ...)
}

#' @export
#' @rdname fm_crs
fm_crs.crs <- function(x, oblique = NULL, ...) {
  if (!is.null(oblique) && !all(is.na(oblique))) {
    stopifnot(is.vector(oblique))
    if (length(oblique) > 4) {
      warning(
        paste0(
          "'oblique' vector length too long (",
          length(oblique),
          " > 4). Truncating to length 4."
        )
      )
      oblique <- oblique[seq_len(4)]
    }
    if (length(oblique) < 4) {
      oblique <- c(oblique, rep(0, 4 - length(oblique)))
    }
    x <- structure(
      list(crs = x, oblique = oblique),
      class = "fm_crs"
    )
  }
  x
}




#' @export
#' @rdname fm_crs
fm_crs.fm_crs <- function(x, oblique = NULL, ...) {
  if (!is.null(oblique)) {
    fm_crs_oblique(x) <- oblique
  }
  x
}

#' @export
#' @rdname fm_crs
fm_crs.fm_CRS <- function(x, oblique = NULL, ...) {
  fm_crs(
    x[["crs"]],
    oblique = if (is.null(oblique)) x[["oblique"]] else oblique,
    ...
  )
}

#' @export
#' @rdname fm_crs
fm_crs.character <- function(x, oblique = NULL, ...) {
  predef <- fm_wkt_predef()
  if (x %in% names(predef)) {
    x <- predef[[x]]
  }
  if (identical(x, "")) {
    x <- NA_character_
  }
  y <- sf::st_crs(x, ...)
  # Would like nicer proj4string/input display.
  # Possible approach: sf::st_crs(as(sf::st_crs(x), "CRS"))
  # Borrowing from the sf::CRS_from_crs code:
  x <- y$proj4string
  if (!is.na(x) && !identical(x, y$input)) {
    y <- sf::st_crs(x, ...)
  }
  if (!is.null(oblique)) {
    fm_crs_oblique(y) <- oblique
  }
  y
}


#' @rdname fm_crs
#' @export
fm_crs.Spatial <- function(x, oblique = NULL, ...) {
  if (is.null(x)) {
    crs <- sf::NA_crs_
  } else {
    suppressWarnings(crs <- fm_crs(sp::wkt(x)))
  }
  if (!is.null(oblique)) {
    fm_crs_oblique(crs) <- oblique
  }
  crs
}



#' @rdname fm_crs
#' @export
fm_crs.SpatVector <- function(x, oblique = NULL, ...) {
  fm_require_stop("terra")
  tcrs <- terra::crs(x)
  if (is.null(tcrs) || is.na(tcrs) || identical(tcrs, "")) {
    y <- fm_crs(oblique = oblique)
  } else {
    y <- fm_crs(tcrs, oblique = oblique, ...)
  }
  y
}

#' @rdname fm_crs
#' @export
fm_crs.SpatRaster <- function(x, oblique = NULL, ...) {
  fm_require_stop("terra")
  tcrs <- terra::crs(x)
  if (is.null(tcrs) || is.na(tcrs) || identical(tcrs, "")) {
    y <- fm_crs(oblique = oblique)
  } else {
    y <- fm_crs(tcrs, ..., oblique = oblique)
  }
  y
}

#' @rdname fm_crs
#' @export
fm_crs.sf <- function(x, oblique = NULL, ...) {
  fm_crs(sf::st_crs(x, ...), oblique = NULL)
}

#' @rdname fm_crs
#' @export
fm_crs.sfc <- function(x, oblique = NULL, ...) {
  fm_crs(sf::st_crs(x, ...), oblique = NULL)
}

#' @rdname fm_crs
#' @export
fm_crs.sfg <- function(x, oblique = NULL, ...) {
  fm_crs(sf::st_crs(x, ...), oblique = NULL)
}

#' @rdname fm_crs
#' @export
fm_crs.fm_mesh_2d <- function(x, oblique = NULL, ...) {
  fm_crs(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_crs
#' @export
fm_crs.fm_lattice_2d <- function(x, oblique = NULL, ...) {
  fm_crs(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_crs
#' @export
fm_crs.fm_segm <- function(x, oblique = NULL, ...) {
  fm_crs(x[["crs"]], oblique = oblique, ...)
}

#' @describeIn fm_crs returns a list of 'crs' objects, one for each list element
#' @export
fm_crs.fm_list <- function(x, oblique = NULL, ...) {
  lapply(x, function(xx) fm_crs(xx, oblique = oblique, ...))
}

#' @export
#' @rdname fm_crs
fm_crs.matrix <- function(x, oblique = NULL, ...) {
  x <- sf::NA_crs_
  fm_crs.crs(x, oblique = oblique, ...)
}




# crs assignment operators ####

#' @title
#' Assignment operators for crs information objects
#'
#' @description
#' Assigns new crs information.
#'
#' @describeIn fm_crs-set
#' Automatically converts the input value with
#' `fm_crs(value)`, `fm_crs(value, oblique = NA)`,
#' `fm_CRS(value)`, or `fm_CRS(value, oblique = NA)`, depending on the type of `x`.
#'
#' @param x Object to assign crs information to
#' @param value For `fm_crs<-()`, object supported by `fm_crs(value)`.
#'
#' For `fm_crs_oblique<-()`, `NA` or a numeric vector, see
#' the `oblique` argument for [fm_crs()]. For assignment, `NULL` is treated as `NA`.
#' @returns The modified object
#' @seealso [fm_crs()]
#' @export
#' @examples
#' x <- fm_segm()
#' fm_crs(x) <- fm_crs("+proj=longlat")
#' fm_crs(x)$proj4string
`fm_crs<-` <- function(x, value) {
  UseMethod("fm_crs<-")
}

#' @describeIn fm_crs-set Assigns new `oblique` information.
#' @export
`fm_crs_oblique<-` <- function(x, value) {
  UseMethod("fm_crs_oblique<-")
}

#' @rdname fm_crs-set
#' @usage
#' ## S3 replacement method for class 'NULL'
#' fm_crs(x) <- value
#' @export
`fm_crs<-.NULL` <- function(x, value) {
  fm_crs(value)
}

#' @rdname fm_crs-set
#' @usage
#' ## S3 replacement method for class 'NULL'
#' fm_crs_oblique(x) <- value
#' @export
`fm_crs_oblique<-.NULL` <- function(x, value) {
  `fm_crs_oblique<-`(fm_crs(), value)
}

#' @rdname fm_crs-set
#' @export
`fm_crs<-.fm_segm` <- function(x, value) {
  x[["crs"]] <- fm_crs(value)
  x
}

#' @rdname fm_crs-set
#' @export
`fm_crs<-.fm_list` <- function(x, value) {
  y <- fm_as_list(
    lapply(
      x,
      function(xx) {
        if (!is.null(xx)) {
          fm_crs(xx) <- fm_crs(value)
        }
        xx
      }
    ),
    .class_stub = fm_class_stub(x)
  )
  y
}

#' @rdname fm_crs-set
#' @export
`fm_crs<-.fm_mesh_2d` <- function(x, value) {
  x[["crs"]] <- fm_crs(value)
  x
}

#' @rdname fm_crs-set
#' @export
`fm_crs<-.fm_lattice_2d` <- function(x, value) {
  x[["crs"]] <- fm_crs(value)
  x
}

#' @rdname fm_crs-set
#' @export
`fm_crs<-.sf` <- function(x, value) {
  sf::st_crs(x) <- fm_crs(value, oblique = NA)
  x
}
#' @rdname fm_crs-set
#' @export
`fm_crs<-.sfg` <- function(x, value) {
  sf::st_crs(x) <- fm_crs(value, oblique = NA)
  x
}
#' @rdname fm_crs-set
#' @export
`fm_crs<-.sfc` <- function(x, value) {
  sf::st_crs(x) <- fm_crs(value, oblique = NA)
  x
}
#' @rdname fm_crs-set
#' @export
`fm_crs<-.Spatial` <- function(x, value) {
  sp::proj4string(x) <- fm_CRS(value, oblique = NA)
  x
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.crs` <- function(x, value) {
  if (is.null(value) || all(is.na(value))) {
    return(x)
  }
  fm_crs(x, oblique = if (is.null(value)) NA else value)
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.CRS` <- function(x, value) {
  fm_CRS(x,
    oblique = if (is.null(value)) NA else value
  )
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.fm_CRS` <- function(x, value) {
  fm_CRS(x[["crs"]],
    oblique = if (is.null(value)) NA else value
  )
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.fm_crs` <- function(x, value) {
  fm_crs(x[["crs"]],
    oblique = if (is.null(value)) NA else value
  )
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.fm_segm` <- function(x, value) {
  fm_crs(x) <-
    fm_crs(x,
      oblique = if (is.null(value)) NA else value
    )
  x
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.fm_mesh_2d` <- function(x, value) {
  fm_crs(x) <-
    fm_crs(x,
      oblique = if (is.null(value)) NA else value
    )
  x
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.fm_lattice_2d` <- function(x, value) {
  fm_crs(x) <-
    fm_crs(x,
      oblique = if (is.null(value)) NA else value
    )
  x
}





# fm_CRS ----

#' Create a coordinate reference system object
#'
#' Creates either a CRS object or an inla.CRS object, describing a coordinate
#' reference system
#'
#' The first two
#' elements of the `oblique` vector are the (longitude, latitude)
#' coordinates for the oblique centre point. The third value (orientation) is a
#' counterclockwise rotation angle for an observer looking at the centre point
#' from outside the sphere. The fourth value is the quasi-longitude (orbit
#' angle) for a rotation along the oblique observers equator.
#'
#' Simple oblique: `oblique=c(0, 45)`
#'
#' Polar: `oblique=c(0, 90)`
#'
#' Quasi-transversal: `oblique=c(0, 0, 90)`
#'
#' Satellite orbit viewpoint: `oblique=c(lon0-time*v1, 0, orbitangle,
#' orbit0+time*v2)`, where `lon0` is the longitude at which a satellite
#' orbit crosses the equator at `time=0`, when the satellite is at an
#' angle `orbit0` further along in its orbit.  The orbital angle relative
#' to the equatorial plane is `orbitangle`, and `v1` and `v2`
#' are the angular velocities of the planet and the satellite, respectively.
#' Note that "forward" from the satellite's point of view is "to the right" in
#' the projection.
#'
#' When `oblique[2]` or `oblique[3]` are non-zero, the resulting
#' projection is only correct for perfect spheres.
#'
#' @param oblique Vector of length at most 4 of rotation angles (in degrees)
#' for an oblique projection, all values defaulting to zero. The values
#' indicate (longitude, latitude, orientation, orbit), as explained in the
#' Details section for [fm_crs()].
#' @param projargs Either 1) a projection argument string suitable as input to
#' `sp::CRS`, or 2) an existing `CRS` object, or 3) a shortcut
#' reference string to a predefined projection; run
#' `names(fm_wkt_predef())` for valid predefined projections. (projargs is a
#' compatibility parameter that can be used for the default `fm_CRS()` method)
#' @param doCheckCRSArgs ignored.
#' @param args An optional list of name/value pairs to add to and/or override
#' the PROJ4 arguments in `projargs`.  `name=value` is converted to
#' `"+name=value"`, and `name=NA` is converted to `"+name"`.
#' @param SRS_string a WKT2 string defining the coordinate system;
#' see `sp::CRS`. This takes precedence over `projargs`.
#' @param \dots Additional parameters, passed on to sub-methods.
#' @return Either an `sp::CRS` object or an `inla.CRS` object,
#' depending on if the coordinate reference system described by the parameters
#' can be expressed with a pure `sp::CRS` object or not.
#'
#' An S3 `inla.CRS` object is a list, usually (but not necessarily)
#' containing at least one element: \item{crs }{The basic `sp::CRS`
#' object}
#' @author Finn Lindgren \email{finn.lindgren@@gmail.com}
#' @seealso [fm_crs()], [sp::CRS()], [`fm_crs_wkt`],
#' [fm_sp_get_crs()], [fm_crs_is_identical()]
#' @examples
#' crs1 <- fm_CRS("longlat_globe")
#' crs2 <- fm_CRS("lambert_globe")
#' crs3 <- fm_CRS("mollweide_norm")
#' crs4 <- fm_CRS("hammer_globe")
#' crs5 <- fm_CRS("sphere")
#' crs6 <- fm_CRS("globe")
#' @export
#' @rdname fm_CRS_sp
fm_CRS <- function(x, oblique = NULL, ...) {
  UseMethod("fm_CRS")
}

#' @describeIn fm_CRS_sp Check if a `fm_CRS` has `NA` crs information and `NA`
#' obliqueness
#' @export
is.na.fm_CRS <- function(x) {
  is.na(x[["crs"]]) && all(is.na(x[["oblique"]]))
}


#' @export
#' @param x Object to convert to CRS or to extract CRS information from.
#' @rdname fm_CRS_sp
fm_CRS.crs <- function(x, oblique = NULL, ...) {
  if (is.na(x)) {
    y <- sp::CRS()
  } else {
    y <- fm_CRS(SRS_string = x$wkt)
  }
  fm_CRS.CRS(y, oblique = oblique)
}

#' @export
#' @rdname fm_CRS_sp
fm_CRS.fm_crs <- function(x, oblique = NULL, ...) {
  fm_CRS(
    x[["crs"]],
    oblique = if (is.null(oblique)) x[["oblique"]] else oblique,
    ...
  )
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.Spatial <- function(x, oblique = NULL, ...) {
  suppressWarnings(crs <- sp::CRS(SRS_string = sp::wkt(x)))
  if (!is.null(oblique)) {
    fm_crs_oblique(crs) <- oblique
  }
  crs
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.fm_CRS <- function(x, oblique = NULL, ...) {
  if (!is.null(oblique)) {
    fm_crs_oblique(x) <- oblique
  }
  x
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.sf <- function(x, oblique = NULL, ...) {
  fm_CRS(sf::st_crs(x, ...), oblique = oblique)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.sfc <- function(x, oblique = NULL, ...) {
  fm_CRS(sf::st_crs(x, ...), oblique = oblique)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.sfg <- function(x, oblique = NULL, ...) {
  fm_CRS(sf::st_crs(x, ...), oblique = oblique)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.fm_mesh_2d <- function(x, oblique = NULL, ...) {
  fm_CRS(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.fm_lattice <- function(x, oblique = NULL, ...) {
  fm_CRS(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.fm_segm <- function(x, oblique = NULL, ...) {
  fm_CRS(x[["crs"]], oblique = oblique, ...)
}

#' @describeIn fm_crs returns a list of 'CRS' objects, one for each list element
#' @export
fm_CRS.fm_list <- function(x, oblique = NULL, ...) {
  lapply(x, function(xx) fm_CRS(xx, oblique = oblique, ...))
}

#' @export
#' @rdname fm_CRS_sp
fm_CRS.matrix <- function(x, oblique = NULL, ...) {
  x <- sp::CRS()
  fm_CRS.CRS(x, oblique = oblique, ...)
}



#' @export
#' @rdname fm_CRS_sp
fm_CRS.CRS <- function(x, oblique = NULL,
                       ...) {
  if (!is.null(oblique) && !all(is.na(oblique))) {
    stopifnot(is.vector(oblique))
    if (length(oblique) > 4) {
      warning(
        paste0(
          "'oblique' vector length too long (",
          length(oblique),
          " > 4). Truncating to length 4."
        )
      )
      oblique <- oblique[seq_len(4)]
    }
    if (length(oblique) < 4) {
      oblique <- c(oblique, rep(0, 4 - length(oblique)))
    }
    x <- structure(
      list(crs = x, oblique = oblique),
      class = "fm_CRS"
    )
  }
  x
}

#' @export
#' @rdname fm_CRS_sp
fm_CRS.default <- function(x, oblique = NULL,
                           projargs = NULL,
                           doCheckCRSArgs = NULL,
                           args = NULL,
                           SRS_string = NULL,
                           ...) {
  # Handle renaming of projargs to match fm_CRS generic arguments
  if (missing(x)) {
    x <- projargs
  }
  projargs <- x

  if (identical(projargs, "")) {
    projargs <- NULL
  }
  if (is.null(SRS_string) &&
    !is.null(projargs) &&
    !is.na(projargs) &&
    is.character(projargs)) {
    if (projargs %in% c("hammer", "lambert", "longlat", "mollweide")) {
      warning(paste0(
        "Use of old predefined projection '",
        projargs,
        "' is deprecated. Converting to '",
        projargs,
        "_norm'"
      ))
      projargs <- paste0(projargs, "_norm")
    }
    predef <- fm_wkt_predef()
    if (projargs %in% names(predef)) {
      SRS_string <- predef[[projargs]]
      projargs <- NULL
    } else {
      if (!is.null(args)) {
        x <- fm_crs(projargs)
        if (typeof(args) != "list") {
          stop("'args' must be NULL or a list of name=value pairs.")
        }
        xargs <- fm_CRS_as_list(x)
        for (name in names(args)) {
          xargs[[name]] <- args[[name]]
        }
        projargs <- fm_list_as_CRSargs(xargs)
      }
      SRS_string <- fm_crs(projargs)$wkt
      projargs <- NULL
    }
  }

  if (!is.null(SRS_string)) {
    if (!is.null(projargs)) {
      warning("SRS_string specified. Ignoring non-null projargs.")
    }
    x <- as(sf::st_crs(SRS_string), "CRS")
  } else if (inherits(projargs, "CRS")) {
    x <- projargs
  } else {
    x <- sp::CRS(NA_character_)
  }

  if (!is.null(oblique) && !all(is.na(oblique))) {
    x <- fm_CRS.CRS(x, oblique = oblique)
  }
  x
}

#' @return `fm_wkt_predef` returns a WKT2 string defining a projection
#' @examples
#' names(fm_wkt_predef())
#' @export
#' @rdname fm_crs

fm_wkt_predef <- function() {
  list(
    hammer_norm = 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["unknown",ELLIPSOID["unknown",0.707106781186548,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Reference meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["unknown",METHOD["PROJ hammer"]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]',
    lambert_norm = 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["unknown",ELLIPSOID["unknown",1,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Reference meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["unknown",METHOD["Lambert Cylindrical Equal Area (Spherical)",ID["EPSG",9834]],PARAMETER["Latitude of 1st standard parallel",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8823]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]',
    longlat_norm = 'GEOGCRS["unknown",DATUM["unknown",ELLIPSOID["unknown",1,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Reference meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]]',
    mollweide_norm = 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["unknown",ELLIPSOID["unknown",0.707106781186548,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Reference meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["unknown",METHOD["Mollweide"],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]',
    hammer_globe = 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["Unknown based on Normal Sphere (r=6370997) ellipsoid",ELLIPSOID["Normal Sphere (r=6370997)",6370997,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["unknown",METHOD["PROJ hammer"]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]]]',
    lambert_globe = 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["Unknown based on Normal Sphere (r=6370997) ellipsoid",ELLIPSOID["Normal Sphere (r=6370997)",6370997,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["unknown",METHOD["Lambert Cylindrical Equal Area (Spherical)",ID["EPSG",9834]],PARAMETER["Latitude of 1st standard parallel",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8823]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["kilometre",1000],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["kilometre",1000],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]]]',
    longlat_globe = 'GEOGCRS["unknown",DATUM["Unknown based on Normal Sphere (r=6370997) ellipsoid",ELLIPSOID["Normal Sphere (r=6370997)",6370997,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]],CS[ellipsoidal,2],AXIS["longitude",east,ORDER[1],ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],AXIS["latitude",north,ORDER[2],ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]]',
    mollweide_globe = 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["Unknown based on Normal Sphere (r=6370997) ellipsoid",ELLIPSOID["Normal Sphere (r=6370997)",6370997,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]]],CONVERSION["unknown",METHOD["Mollweide"],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["kilometre",1000],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["kilometre",1000],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]]]',
    sphere = 'GEODCRS["unknown",DATUM["unknown",ELLIPSOID["unknown",1,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Reference meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]],CS[Cartesian,3],AXIS["(X)",geocentricX,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(Y)",geocentricY,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(Z)",geocentricZ,ORDER[3],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]',
    globe = 'GEODCRS["unknown",DATUM["Unknown based on Normal Sphere (r=6370997) ellipsoid",ELLIPSOID["Normal Sphere (r=6370997)",6370997,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8901]],CS[Cartesian,3],AXIS["(X)",geocentricX,ORDER[1],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]],AXIS["(Y)",geocentricY,ORDER[2],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]],AXIS["(Z)",geocentricZ,ORDER[3],LENGTHUNIT["kilometre",1000,ID["EPSG",9036]]]]'
  )
}





#' Internal WKT handling
#'
#' Conversion between WKT and a tree representation
#'
#' @param x A WKT2 string, or a `wkt_tree` list structure
#' @param \dots Unused
#' @returns A hierarchical list, describing WKT information as a tree
#' @rdname wkt_tree
#' @export
#' @keywords internal
#' @examples
#' str(fm_wkt_as_wkt_tree(fm_crs("longlat_norm")$wkt))
#'
fm_wkt_as_wkt_tree <- function(x, ...) {
  if (is.null(x) || is.na(x)) {
    return(list())
  }

  # Basic parsing of WKT string
  # ITEM[Param1, Param2, ...]
  # Param can be a constant or an ITEM[...]
  parse_item <- function(x) {
    # Parse item label
    n <- regexpr("\\[", x)
    item_label <- substr(x, 1, n - 1)
    x <- substr(x, n + 1, nchar(x))
    params <- list()
    # Parse parameters
    done <- FALSE
    while (!done) {
      # If [ comes before , or ], it's an Item, otherwise a constant
      n <- regexpr("[],[]", x)
      if (n < 1) {
        # Nothing found
        done <- TRUE
        break
      }
      if (substr(x, n, n) == "[") {
        n <- regexpr("[^ ]", x)
        x <- substr(x, n, nchar(x))
        item_info <- parse_item(x)
        params[[length(params) + 1]] <- item_info$item
        x <- item_info$x
      } else {
        const <- substr(x, 1, n - 1)
        params[[length(params) + 1]] <- const
        x <- substr(x, n, nchar(x))
      }
      n <- regexpr("[],]", x)
      done <- (substr(x, n, n) == "]")
      x <- substr(x, n + 1, nchar(x))
    }
    list(item = list(label = item_label, params = params), x = x)
  }

  x <- gsub("\n", "", x)
  item_info <- parse_item(x)
  item <- item_info$item
  item
}

#' @param pretty logical; If TRUE, use pretty formatting. Default: FALSE
#' @rdname wkt_tree
#' @returns `fm_wkt_tree_as_wkt` character; the WKT corresponding to the tree.
#' @export

fm_wkt_tree_as_wkt <- function(x, pretty = FALSE, ...) {
  construct_item <- function(x, level) {
    paste0(
      if (pretty) {
        paste0(rep("    ", level), collapse = "")
      } else {
        ""
      },
      x[["label"]],
      "[",
      paste0(
        vapply(
          x[["params"]],
          function(param) {
            if (!is.list(param)) {
              paste0(param)
            } else {
              paste0(
                if (pretty) {
                  "\n"
                } else {
                  ""
                },
                construct_item(param,
                  level = level + 1
                )
              )
            }
          },
          ""
        ),
        collapse = ","
      ),
      "]"
    )
  }
  construct_item(x, 0)
}

#' @param item character vector with item labels identifying a parameter item
#' entry.
#' @param duplicate For items that have more than one match, `duplicate`
#' indicates the index number of the desired version. Default: 1
#' @rdname wkt_tree
#' @returns `fm_wkt_tree_get_item` returns the value of an item found in the tree
#' @export

fm_wkt_tree_get_item <- function(x, item, duplicate = 1) {
  for (k in seq_along(x[["params"]])) {
    if (is.list(x[["params"]][[k]]) &&
      (!is.null(x[["params"]][[k]][["label"]])) &&
      (x[["params"]][[k]][["label"]] %in% item)) {
      if (duplicate == 1) {
        return(x[["params"]][[k]])
      }
      duplicate <- duplicate - 1
    }
  }
  NULL
}

#' @param item_tree An item tree identifying a parameter item entry
#' @rdname wkt_tree
#' @returns `fm_wkt_tree_set_item` returns the modified tree
#' @export

fm_wkt_tree_set_item <- function(x, item_tree, duplicate = 1) {
  success <- FALSE
  for (k in seq_along(x[["params"]])) {
    if (is.list(x[["params"]][[k]]) && (x[["params"]][[k]][["label"]] == item_tree[["label"]])) {
      if (duplicate == 1) {
        x[["params"]][[k]] <- item_tree
      }
      duplicate <- duplicate - 1
      success <- TRUE
      break
    }
  }
  if (!success) {
    x[["params"]] <- c(x[["params"]], list(item_tree))
  }
  x
}




#' @export
#' @rdname fm_CRSargs
fm_CRS_as_list <- function(x, ...) {
  fm_CRSargs_as_list(fm_proj4string(x))
}


#' @export
#' @rdname fm_CRSargs
fm_list_as_CRS <- function(x, ...) {
  fm_CRS(fm_list_as_CRSargs(x))
}

#' Show expanded CRS arguments
#'
#' `r lifecycle::badge("deprecated")`
#' Wrappers for `sp::CRS` and `inla.CRS` objects to handle the
#' coordinate reference system argument string.
#' These methods should no longer be used with PROJ6/rgdal3;
#' see [fm_wkt()] and [fm_proj4string()] for a new approach.
#'
#' @aliases fm_CRSargs fm_CRS_as_list fm_CRSargs_as_list fm_list_as_CRS
#' fm_list_as_CRSargs
#' @param x An `sp::CRS` or `inla.CRS` object (for
#' `fm_CRSargs` and `fm_CRS_as_list`), a character string (for
#' `fm_CRSargs_as_list`), or a list (for `fm_list_as_CRS` and
#' `fm_list_as_CRSargs`).
#' @param \dots Additional arguments passed on to other methods.
#' @return For `fm_CRSargs` and `fm_list_as_CRSargs`, a character
#' string with PROJ.4 arguments.
#'
#' For `fm_CRS_as_list` and `fm_CRSargs_as_list`, a list of
#' name/value pairs.
#'
#' For `fm_list_as_CRS`, a `CRS` or `inla.CRS` object.
#' @author Finn Lindgren <finn.lindgren@@gmail.com>
#' @seealso [fm_CRS()]
#' @export
#' @keywords internal
#' @examples
#'
#' crs0 <- fm_CRS("longlat_norm")
#' p4s <- fm_proj4string(crs0)
#' lst <- fm_CRSargs_as_list(p4s)
#' crs1 <- fm_list_as_CRS(lst)
#' lst$a <- 2
#' crs2 <- fm_CRS(p4s, args = lst)
#' print(fm_proj4string(crs0))
#' print(fm_proj4string(crs1))
#' print(fm_proj4string(crs2))
fm_CRSargs <- function(x, ...) {
  lifecycle::deprecate_warn("0.0.1", "fm_CRSargs()", "fm_proj4string()")

  fm_proj4string(x)
}


#' @return For `fm_list_as_CRSargs()`, a CRS proj4 string for name=value pair list
#' @rdname fm_CRSargs
fm_list_as_CRSargs <- function(x, ...) {
  paste(
    lapply(
      names(x),
      function(xx) {
        if (is.na(x[[xx]])) {
          paste("+", xx, sep = "")
        } else {
          paste("+", xx, "=", x[[xx]], sep = "")
        }
      }
    ),
    collapse = " "
  )
}

#' @return For `fm_CRSargs_as_list()`, a list of name=value pairs from CRS proj4 string
#' @rdname fm_CRSargs
#' @export
fm_CRSargs_as_list <- function(x, ...) {
  if (is.na(x)) {
    return(list())
  }
  if (!is.character(x)) {
    stop("proj4string must be of class character")
  }
  do.call(c, lapply(
    strsplit(
      x = strsplit(
        x = paste(" ", x, sep = ""),
        split = " \\+"
      )[[1]][-1],
      split = "="
    ),
    function(x) {
      xx <- list(x[2])
      names(xx) <- x[1]
      xx
    }
  ))
}


#' @export
#' @rdname fmesher-print
print.fm_CRS <- function(x, ...) {
  print(x[["crs"]], ...)
  cat(paste0("Oblique: c(", paste0(x[["oblique"]], collapse = ", "), ")\n"))
  invisible(x)
}





# fm_wkt ----

#' @describeIn fm_crs_wkt Returns a WKT2 string, for any input supported by [fm_crs()].
#' @export

fm_wkt <- function(crs) {
  fm_crs(crs, oblique = NA)$wkt
}

#' @describeIn fm_crs_wkt Returns a proj4 string, for any input supported by [fm_crs()].
#' @export
fm_proj4string <- function(crs) {
  fm_crs(crs, oblique = NA)$proj4string
}

#' @export
#' @describeIn fm_crs_wkt `r lifecycle::badge("deprecated")` Use [fm_wkt()]
#' instead.

fm_crs_get_wkt <- function(crs) {
  lifecycle::deprecate_warn(
    "0.0.1",
    "fm_crs_get_wkt()",
    "fm_wkt()"
  )
  fm_wkt(crs)
}


fm_rotmat3213 <- function(rot) {
  cs <- cos(rot[1])
  sn <- sin(rot[1])
  R <- matrix(c(
    cs, -sn, 0,
    sn, cs, 0,
    0, 0, 1
  ), 3, 3)
  cs <- cos(rot[2])
  sn <- sin(rot[2])
  R <- R %*% matrix(c(
    cs, 0, sn,
    0, 1, 0,
    -sn, 0, cs
  ), 3, 3)
  cs <- cos(rot[3])
  sn <- sin(rot[3])
  R <- R %*% matrix(c(
    1, 0, 0,
    0, cs, -sn,
    0, sn, cs
  ), 3, 3)
  cs <- cos(rot[4])
  sn <- sin(rot[4])
  R <- R %*% matrix(c(
    cs, -sn, 0,
    sn, cs, 0,
    0, 0, 1
  ), 3, 3)
  R
}

fm_rotmat3123 <- function(rot) {
  cs <- cos(rot[4])
  sn <- sin(rot[4])
  R <- matrix(c(
    cs, -sn, 0,
    sn, cs, 0,
    0, 0, 1
  ), 3, 3)
  cs <- cos(rot[3])
  sn <- sin(rot[3])
  R <- R %*% matrix(c(
    1, 0, 0,
    0, cs, -sn,
    0, sn, cs
  ), 3, 3)
  cs <- cos(rot[2])
  sn <- sin(rot[2])
  R <- R %*% matrix(c(
    cs, 0, sn,
    0, 1, 0,
    -sn, 0, cs
  ), 3, 3)
  cs <- cos(rot[1])
  sn <- sin(rot[1])
  R <- R %*% matrix(c(
    cs, -sn, 0,
    sn, cs, 0,
    0, 0, 1
  ), 3, 3)
  R
}


fm_crs_transform_oblique <- function(x, oblique, to.oblique = TRUE) {
  if (to.oblique) {
    ## Transform to oblique orientation
    ## 1) Rotate -oblique[1] around (0,0,1)
    ## 2) Rotate +oblique[2] around (0,1,0)
    ## 3) Rotate -oblique[3] around (1,0,0)
    ## 3) Rotate -oblique[4] around (0,0,1)
    x %*% fm_rotmat3213(c(-1, 1, -1, -1) * oblique * pi / 180)
  } else {
    ## Transform back from oblique orientation
    ## 1) Rotate +oblique[4] around (0,0,1)
    ## 2) Rotate +oblique[3] around (1,0,0)
    ## 3) Rotate -oblique[2] around (0,1,0)
    ## 4) Rotate +oblique[1] around (0,0,1)
    x %*% fm_rotmat3123(c(1, -1, 1, 1) * oblique * pi / 180)
  }
}



#' @describeIn fm_crs_wkt Returns "longlat", "lambert", "mollweide", "hammer", "tmerc", or `NULL`
#' @param wt A parsed wkt tree, see [fm_wkt_as_wkt_tree()]
#' @export
fm_wkt_tree_projection_type <- function(wt) {
  axis1 <- fm_wkt_tree_get_item(wt, "AXIS", 1)
  axis2 <- fm_wkt_tree_get_item(wt, "AXIS", 2)
  if (identical(axis1[["params"]][[1]], '"longitude"') &&
    identical(axis2[["params"]][[1]], '"latitude"')) {
    return("longlat")
  }
  conversion <- fm_wkt_tree_get_item(wt, "CONVERSION")
  if (!is.null(conversion)) {
    method <- fm_wkt_tree_get_item(conversion, "METHOD")
    if (identical(method[["params"]][[1]], '"Lambert Cylindrical Equal Area (Spherical)"')) {
      return("lambert")
    }
    if (identical(method[["params"]][[1]], '"Mollweide"')) {
      return("mollweide")
    }
    if (identical(method[["params"]][[1]], '"PROJ hammer"')) {
      return("hammer")
    }
    if (identical(method[["params"]][[1]], '"tmerc"')) {
      return("tmerc")
    }
  }
  NULL
}

#' @describeIn fm_crs_wkt See `fm_wkt_tree_projection_type`
#' @export
fm_wkt_projection_type <- function(wkt) {
  wt <- fm_wkt_as_wkt_tree(wkt)
  fm_wkt_tree_projection_type(wt)
}

#' @describeIn fm_crs_wkt See `fm_wkt_tree_projection_type`
#' @export
fm_crs_projection_type <- function(crs) {
  wkt <- fm_wkt(crs)
  fm_wkt_projection_type(wkt)
}

## +proj=longlat in (-180,180)x(-90,90)
## +proj=moll in (-2,2)x(-1,1) scaled by +a and +b, and +units
## +proj=lambert in (-pi,pi)x(-1,1) scaled by +a and +b, and +units
#' @describeIn fm_crs_wkt Returns bounds information for a projection, as
#' a list with elements `type` ("rectangle" or "ellipse"), `xlim`, `ylim`, and
#' `polygon`.
#' @param warn.unknown logical, default `FALSE`. Produce warning if the shape
#' of the projection bounds is unknown.
#' @export
fm_crs_bounds <- function(crs, warn.unknown = FALSE) {
  wkt <- fm_wkt(crs)
  wt <- fm_wkt_as_wkt_tree(wkt)
  type <- fm_wkt_tree_projection_type(wt)

  if (is.null(type)) {
    if (fm_wkt_is_geocent(wkt)) {
      bounds <- list(type = "rectangle", xlim = c(-Inf, Inf), ylim = c(-Inf, Inf))
    } else {
      if (warn.unknown) {
        warning("Could not determine shape of transformation bounds. Using infinite rectangle.")
      }
      bounds <- list(type = "rectangle", xlim = c(-Inf, Inf), ylim = c(-Inf, Inf))
    }
  } else if (type == "longlat") {
    bounds <- list(type = "rectangle", xlim = c(-180, 180), ylim = c(-90, 90))
  } else if (type == "lambert") {
    axis <- c(pi, 1)
    radius <- fm_wkt_get_ellipsoid_radius(wkt)
    axis[1] <- axis[1] * radius
    # TODO: handle eccentricity
    axis[2] <- axis[2] * sqrt(radius) * sqrt(radius)
    # TODO: Handle units"
    bounds <- list(
      type = "rectangle",
      xlim = c(-1, 1) * axis[1],
      ylim = c(-1, 1) * axis[2]
    )
  } else if (type %in% c("mollweide", "hammer")) {
    axis <- c(2, 1)
    center <- c(0, 0)
    radius <- fm_wkt_get_ellipsoid_radius(wkt)
    axis[1] <- axis[1] * radius / sqrt(1 / 2)
    axis[2] <- axis[2] * radius / sqrt(1 / 2)
    # TODO: Handle "units"
    bounds <- list(
      type = "ellipse", axis = axis, center = center,
      xlim = center[1] + c(-1, 1) * axis[1],
      ylim = center[2] + c(-1, 1) * axis[2]
    )
  } else if (type == "tmerc") {
    bounds <- list(type = "rectangle", xlim = c(-Inf, Inf), ylim = c(-Inf, Inf))
  } else {
    stop("'fm_crs_bounds' internal error: transformation detected but not handled.")
  }

  if (bounds$type == "rectangle") {
    bounds$polygon <- cbind(
      bounds$xlim[c(1, 2, 2, 1, 1)],
      bounds$ylim[c(1, 1, 2, 2, 1)]
    )
  } else if (bounds$type == "ellipse") {
    theta <- seq(0, 2 * pi, length.out = 1000)
    bounds$polygon <- cbind(
      bounds$center[1] + bounds$axis[1] * cos(theta),
      bounds$center[2] + bounds$axis[2] * sin(theta)
    )
  } else {
    stop("Unknown transformation type. This should not happen.")
  }
  bounds
}

## TRUE/FALSE for points inside/outside projection domain.

# ! sp code here
fm_crs_bounds_check <- function(x, bounds) {
  stopifnot(inherits(x, "matrix"))
  if (all(is.finite(bounds$xlim)) && all(is.finite(bounds$ylim))) {
    (sp::point.in.polygon(
      x[, 1], x[, 2],
      bounds$polygon[, 1], bounds$polygon[, 2]
    )
    > 0)
  } else {
    rep(TRUE, nrow(x))
  }
}


# ! show() for "CRS" and "crs" class appear identical
fm_internal_update_crs <- function(crs, newcrs, mismatch.allowed) {
  if (fm_crs_is_null(crs)) {
    return(newcrs)
  }

  if (!mismatch.allowed && !fm_crs_is_identical(crs, newcrs)) {
    methods::show(crs)
    methods::show(newcrs)
    stop("CRS information mismatch.")
  }

  crs
}


#' @title Check if two CRS objects are identical
#' @param crs0,crs1 Two `sf::crs`, `sp::CRS`, `fm_crs` or `inla.CRS` objects to be compared.
#' @param crsonly logical. If `TRUE` and any of `crs0` and `crs1` are `fm_crs` or `inla.CRS`
#' objects, extract and compare only the `sf::crs` or `sp::CRS` aspects. Default: `FALSE`
#' @export
#' @returns logical, indicating if the two crs objects are identical in the
#' specified sense (see the `crsonly` argument)
#' @seealso [fm_crs()], [fm_CRS()], [fm_crs_is_null()]
#' @examples
#'
#' crs0 <- crs1 <- fm_crs("longlat_globe")
#' fm_crs_oblique(crs1) <- c(0, 90)
#' print(c(
#'   fm_crs_is_identical(crs0, crs0),
#'   fm_crs_is_identical(crs0, crs1),
#'   fm_crs_is_identical(crs0, crs1, crsonly = TRUE)
#' ))
fm_crs_is_identical <- function(crs0, crs1, crsonly = FALSE) {
  crs0_ <- fm_crs(crs0, oblique = NA)
  crs1_ <- fm_crs(crs1, oblique = NA)
  crs_ident <- identical(crs0_, crs1_)
  if (crsonly) {
    return(crs_ident)
  }
  return(crs_ident &&
    identical(fm_crs_oblique(crs0), fm_crs_oblique(crs1)))
}

#' @describeIn fm_crs_is_identical `r lifecycle::badge("deprecated")`
#' by `fm_crs_is_identical()`.
#' @export
fm_identical_CRS <- function(crs0, crs1, crsonly = FALSE) {
  lifecycle::deprecate_soft(
    "0.1.0",
    "fm_identical_CRS()",
    "fm_crs_is_identical()"
  )
  fm_crs_is_identical(crs0, crs1, crsonly = crsonly)
}



# fm_detect_manifold ####

#' @title Detect manifold type
#' @description
#' Detect if a 2d object is on "R2", "S2", or "M2"
#' @param x Object to investigate
#' @returns A string containing the detected manifold classification
#' @export
#' @examples
#' fm_detect_manifold(1:4)
#' fm_detect_manifold(rbind(c(1, 0, 0), c(0, 1, 0), c(1, 1, 0)))
#' fm_detect_manifold(rbind(c(1, 0, 0), c(0, 1, 0), c(0, 0, 1)))
#'
fm_detect_manifold <- function(x) {
  UseMethod("fm_detect_manifold")
}

#' @export
#' @describeIn fm_detect_manifold Detect if a crs is on "R2" or "S2"
#' (if `fm_crs_is_geocent(crs)` is `TRUE`). Returns `NA_character_` if the crs
#' is NULL or NA.
fm_crs_detect_manifold <- function(x) {
  if (is.null(x) || is.na(x)) {
    return(NA_character_)
  }
  if (fm_crs_is_geocent(x)) {
    return("S2")
  }
  "R2"
}

#' @rdname fm_detect_manifold
#' @export
fm_detect_manifold.crs <- function(x) {
  fm_crs_detect_manifold(x)
}

#' @rdname fm_detect_manifold
#' @export
fm_detect_manifold.CRS <- function(x) {
  fm_crs_detect_manifold(x)
}

#' @rdname fm_detect_manifold
#' @export
fm_detect_manifold.numeric <- function(x) {
  return("R1")
}

#' @rdname fm_detect_manifold
#' @export
fm_detect_manifold.matrix <- function(x) {
  if (ncol(x) <= 1) {
    return("R1")
  }
  if (ncol(x) == 2) {
    return("R2")
  }
  tol <- 1e-10
  if (all(abs(x[, 3]) < tol)) {
    return("R2")
  }
  radii <- rowSums(x^2)^0.5
  radius <- mean(radii)
  if (all(abs(radii - radius) < radius * tol)) {
    return("S2")
  }
  return("M2")
}

#' @rdname fm_detect_manifold
#' @export
fm_detect_manifold.fm_mesh_2d <- function(x) {
  if (ncol(x[["loc"]] <= 2)) {
    return("R2")
  }
  tol <- 1e-10
  if (all(abs(x[["loc"]][, 3]) < tol)) {
    if (identical(fm_crs_detect_manifold(x[["crs"]]), "S2")) {
      return("S2")
    }
    return("R2")
  }
  radii <- rowSums(x[["loc"]]^2)^0.5
  radius <- mean(radii)
  if (all(abs(radii - radius) < radius * tol)) {
    return("S2")
  }
  return("M2")
}




# fm_transform ----

#' @title Object coordinate transformation
#'
#' @description
#' Handle transformation of various inla objects according to coordinate
#' reference systems of `crs` (from `sf::st_crs()`), `fm_crs`, `sp::CRS` or
#' `INLA::inla.CRS` class.
#'
#' @param x
#' The object that should be transformed from it's current CRS to a new CRS
#' @param crs
#' The target crs object
#' @param crs0
#' The source crs object for spatial classes without crs information
#' @param passthrough
#' Default is FALSE.
#' Setting to TRUE allows objects with no CRS information to be passed
#' through without transformation. Use with care!
#' @param \dots
#' Potential additional arguments
#' @returns A transformed object, normally of the same class as
#' the input object.
#' @seealso [fm_CRS()]
#' @export
#' @examples
#' fm_transform(
#'   rbind(c(0, 0), c(0, 90)),
#'   crs = fm_crs("sphere"),
#'   crs0 = fm_crs("longlat_norm")
#' )
#'
fm_transform <- function(x, crs, ...) {
  UseMethod("fm_transform")
}

#' @rdname fm_transform
#' @export
fm_transform.default <- function(x, crs, ..., crs0 = NULL) {
  stop(paste0(
    "fm_transform() for '",
    paste0(class(x), sep = ", "),
    "' not yet implemented."
  ))
}

#' @rdname fm_transform
#' @usage
#' ## S3 method for class 'NULL'
#' fm_transform(x, crs, ...)
#' @export
fm_transform.NULL <- function(x, crs, ...) {
  NULL
}



fm_transform_raw <- function(x, from, to) {
  adjust_input <- function(x, crs) {
    if (fm_crs_is_geocent(crs) &&
      ncol(x) == 2) {
      if (nrow(x) > 0) {
        x <- cbind(x, 0)
      } else {
        x <- matrix(0, 0, ncol(x) + 1)
      }
    }
    x
  }

  adjust_output <- function(x, crs) {
    if (!fm_crs_is_geocent(crs) &&
      ncol(x) == 3) {
      if (nrow(x) > 0) {
        x <- x[, 1:2, drop = FALSE]
      } else {
        x <- matrix(0, 0, 2)
      }
    }
    x
  }

  x <- adjust_input(x, crs = to)
  if (nrow(x) == 0) {
    x
  } else {
    y <- sf::sf_project(
      pts = x,
      from = from,
      to = to,
      keep = TRUE
    )
    adjust_output(y, to)
  }
}


#' @rdname fm_transform
#' @export
fm_transform.matrix <- function(x, crs, ..., passthrough = FALSE, crs0 = NULL) {
  crs1 <- fm_crs(crs)
  crs0 <- fm_crs(crs0)
  if (fm_crs_is_null(crs0) || fm_crs_is_null(crs1)) {
    if (!passthrough) {
      if (fm_crs_is_null(crs0)) {
        stop("'crs0' is either NA or an invalid coordinate reference object.")
      }
      if (fm_crs_is_null(crs1)) {
        stop("'crs' is either NA or an invalid coordinate reference object.")
      }
    }
    return(x)
  }
  #  if (ncol(x) == 2) {
  #    x <- cbind(x, 0)
  #  }
  sphere_radius_0 <- fm_crs_get_ellipsoid_radius(crs0)
  sphere_radius_1 <- fm_crs_get_ellipsoid_radius(crs1)
  different_radii <- (sphere_radius_0 != sphere_radius_1)
  longlat_norm <- fm_crs("longlat_norm")
  longlat_0 <- fm_crs_set_ellipsoid_radius(longlat_norm, sphere_radius_0)
  longlat_1 <- fm_crs_set_ellipsoid_radius(longlat_norm, sphere_radius_1)

  crs_sphere <- fm_crs("sphere")
  onsphere_0 <- fm_crs_is_identical(crs0, crs_sphere, crsonly = TRUE)
  onsphere_1 <- fm_crs_is_identical(crs1, crs_sphere, crsonly = TRUE)
  is_geocentric_0 <- fm_crs_is_geocent(crs0)
  is_geocentric_1 <- fm_crs_is_geocent(crs1)
  if (is_geocentric_0) {
    ok <- rep(TRUE, nrow(x))
  } else {
    bounds <- fm_crs_bounds(crs0)
    if (identical(fm_crs_projection_type(crs0), "longlat")) {
      ## Wrap longitudes to [-180,180]
      needswrap <- (x[, 1] < -180) | (x[, 1] > 180)
      if (any(needswrap)) {
        x[needswrap, 1] <- ((x[needswrap, 1] + 180) %% 360) - 180
      }
    }
    ok <- fm_crs_bounds_check(x, bounds)
    if (!all(ok)) {
      xx <- x
    }
  }
  do_work_on_sphere <-
    inherits(crs0, "fm_crs") ||
      inherits(crs1, "fm_crs") ||
      different_radii
  x <- x[ok, , drop = FALSE]
  current_crs <- fm_crs(crs0, oblique = NA)
  if (do_work_on_sphere) {
    if (!onsphere_0) {
      if (sphere_radius_0 != 1) {
        x <- fm_transform_raw(
          x,
          from = current_crs,
          to = longlat_0
        )
        # x can now be treated as being in longlat_norm coordinates
        current_crs <- longlat_norm
      }
      x <- fm_transform_raw(
        x,
        from = current_crs,
        to = crs_sphere
      )
      current_crs <- crs_sphere
    }
    obl0 <- fm_crs_oblique(crs0)
    if (!is.na(obl0)) {
      x <- fm_crs_transform_oblique(
        x,
        obl0,
        to.oblique = FALSE
      )
    }

    obl1 <- fm_crs_oblique(crs1)
    if (!is.na(obl1)) {
      x <- fm_crs_transform_oblique(
        x,
        obl1,
        to.oblique = TRUE
      )
    }
    if (sphere_radius_1 != 1) {
      x <- fm_transform_raw(
        x,
        from = current_crs,
        to = longlat_norm
      )
      # x can now be treated as being in longlat_1
      current_crs <- longlat_1
    }
  }

  x <- fm_transform_raw(
    x,
    from = current_crs,
    to = fm_crs(crs1, oblique = NA)
  )

  if (!all(ok)) {
    xx[ok, ] <- x
    xx[!ok, ] <- NA
    x <- xx
  }

  x
}


# Internal helper function for fm_transform.sf/sfc/sfg
fm_transform_sf <- function(x, crs, ..., passthrough) {
  crs1 <- fm_crs(crs)
  crs0 <- fm_crs(x)
  if (fm_crs_is_null(crs0) || fm_crs_is_null(crs1)) {
    if (!passthrough) {
      if (fm_crs_is_null(crs0)) {
        stop("'crs0' is an invalid coordinate reference object.")
      }
      if (fm_crs_is_null(crs1)) {
        stop("'crs' is an invalid coordinate reference object.")
      }
    }
    return(x)
  }

  if (inherits(x, "sfc_POINT")) {
    adjust_input <- function(x, crs) {
      if (fm_crs_is_geocent(crs) &&
        length(x) &&
        inherits(x[[1]], "XY")) {
        x <- sf::st_zm(x = x, drop = FALSE, what = "Z")
      }
      x
    }

    x <- adjust_input(x, crs1)
    coord <- sf::st_coordinates(x)
    M <- if ("M" %in% colnames(coord)) coord[, "M"] else NULL
    coord <- coord[, intersect(colnames(coord), c("X", "Y", "Z")), drop = FALSE]
    coord <- fm_transform(coord, crs = crs1, crs0 = crs0, passthrough = passthrough)
    if (is.null(M)) {
      the_dim <- c("X", "XY", "XYZ")[ncol(coord)]
    } else {
      the_dim <- c("XM", "XYM", "XYZM")[ncol(coord)]
      coord <- cbind(coord, M)
    }
    x <-
      do.call(sf::st_sfc, c(
        lapply(
          seq_len(nrow(coord)),
          function(k) sf::st_point(coord[k, , drop = FALSE], dim = the_dim)
        ),
        list(crs = sf::st_crs(crs1))
      ))
  } else {
    x <- sf::st_transform(x, sf::st_crs(crs1))
  }
  x
}

#' @export
#' @rdname fm_transform
fm_transform.sf <- function(x, crs, ..., passthrough = FALSE) {
  geo <- fm_transform(sf::st_geometry(x), crs = crs, ..., passthrough = passthrough)
  sf::st_geometry(x) <- geo
  x
}
#' @export
#' @rdname fm_transform
fm_transform.sfc <- function(x, crs, ..., passthrough = FALSE) {
  fm_transform_sf(x, crs = crs, ..., passthrough = passthrough)
}
#' @export
#' @rdname fm_transform
fm_transform.sfg <- function(x, crs, ..., passthrough = FALSE) {
  fm_transform_sf(x, crs = crs, ..., passthrough = passthrough)
}

#' @export
#' @rdname fm_transform
fm_transform.Spatial <- function(x, crs, ..., passthrough = FALSE) {
  orig_class <- class(x)
  as(
    as(
      fm_transform(
        sf::st_as_sf(x),
        crs = crs,
        ...,
        passthrough = passthrough
      ),
      "Spatial"
    ),
    orig_class[1]
  )
}


#' @export
#' @rdname fm_transform
fm_transform.fm_mesh_2d <- function(x,
                                    crs = fm_crs(x),
                                    ...,
                                    passthrough = FALSE,
                                    crs0 = fm_crs(x)) {
  x$loc <- fm_transform(x$loc, crs = crs, ..., crs0 = crs0, passthrough = passthrough)
  x$crs <- fm_crs(crs)
  x$manifold <- fm_detect_manifold(x)
  x
}

#' @export
#' @rdname fm_transform
fm_transform.fm_lattice_2d <- function(x,
                                       crs = fm_crs(x),
                                       ...,
                                       passthrough = FALSE,
                                       crs0 = fm_crs(x)) {
  x$segm <- fm_transform(x$segm, crs = crs, crs0 = crs0, ..., passthrough = passthrough)
  x$loc <- fm_transform(x$loc, crs = crs, crs0 = crs0, ..., passthrough = passthrough)
  x$crs <- fm_crs(crs)

  x
}


#' @export
#' @rdname fm_transform
fm_transform.fm_segm <- function(x,
                                 crs = fm_crs(x),
                                 ...,
                                 passthrough = FALSE,
                                 crs0 = fm_crs(x)) {
  if (!is.null(x$loc)) {
    x$loc <-
      fm_transform(x$loc,
        crs = crs,
        crs0 = crs0,
        ...,
        passthrough = passthrough
      )
  }
  x$crs <- fm_crs(crs)

  x
}


#' @rdname fm_transform
#' @export
fm_transform.fm_list <- function(x, crs, ...) {
  cl <- class(x)
  y <- lapply(x, function(xx) {
    if (is.null(crs)) {
      crs <- fm_crs(xx)
    }
    fm_transform(xx, crs = crs, ...)
  })
  class(y) <- cl
  y
}




# Legacy class support ####

#' @rdname fm_crs_wkt
#' @export
`fm_ellipsoid_radius<-.inla.CRS` <- function(x, value) {
  crs <- fm_crs(x)
  fm_ellipsoid_radius(crs) <- value
  new_crs <- fm_CRS(crs)

  new_crs
}

#' @rdname fm_crs_wkt
#' @export
`fm_length_unit<-.inla.CRS` <- function(x, value) {
  crs <- fm_crs(x)
  fm_length_unit(crs) <- value
  new_crs <- fm_CRS(crs)

  new_crs
}

#' @export
#' @rdname fm_crs
fm_crs.inla.CRS <- function(x, oblique = NULL, ...) {
  fm_crs(
    x[["crs"]],
    oblique = if (is.null(oblique)) x[["oblique"]] else oblique,
    ...
  )
}

#' @rdname fm_crs
#' @export
#' @method fm_crs inla.mesh
fm_crs.inla.mesh <- function(x, oblique = NULL, ...) {
  fm_crs(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_crs
#' @export
fm_crs.inla.mesh.lattice <- function(x, oblique = NULL, ...) {
  fm_crs(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_crs
#' @export
fm_crs.inla.mesh.segment <- function(x, oblique = NULL, ...) {
  fm_crs(x[["crs"]], oblique = oblique, ...)
}

#' @export
#' @rdname fm_crs-set
`fm_crs_oblique<-.inla.CRS` <- function(x, value) {
  fm_CRS(x[["crs"]],
    oblique = if (is.null(value)) NA else value
  )
}

#' @describeIn fm_CRS_sp Check if a `inla.CRS` has `NA` crs information and `NA`
#' obliqueness
#' @export
is.na.inla.CRS <- function(x) {
  is.na(fm_CRS(x))
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.inla.CRS <- function(x, oblique = NULL, ...) {
  fm_CRS(fm_crs(x, oblique = oblique, ...))
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.inla.mesh <- function(x, oblique = NULL, ...) {
  fm_CRS(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.inla.mesh.lattice <- function(x, oblique = NULL, ...) {
  fm_CRS(x[["crs"]], oblique = oblique, ...)
}

#' @rdname fm_CRS_sp
#' @export
fm_CRS.inla.mesh.segment <- function(x, oblique = NULL, ...) {
  fm_CRS(x[["crs"]], oblique = oblique, ...)
}

#' @export
#' @rdname fm_transform
fm_transform.inla.mesh <- function(x,
                                   crs = fm_crs(x),
                                   ...) {
  fm_transform.fm_mesh_2d(fm_as_mesh_2d(x), crs = crs, ...)
}

#' @export
#' @rdname fm_transform
fm_transform.inla.mesh.lattice <- function(x, crs, ...) {
  fm_transform.fm_lattice_2d(fm_as_lattice_2d(x), crs = crs, ...)
}
#' @export
#' @rdname fm_transform
fm_transform.inla.mesh.segment <- function(x, crs, ...) {
  fm_transform.fm_segm(fm_as_segm(x), crs = crs, ...)
}


# fm_spTransform ----

#' @describeIn fmesher-deprecated
#' `r lifecycle::badge("deprecated")` (See [fm_transform()] instead)
#' Handle transformation of various inla objects according to coordinate
#' reference systems of `sp::CRS` or `INLA::inla.CRS` class.
#'
#' @param x
#' The object that should be transformed from it's current CRS to a new CRS
#' @param crs0
#' The source sp::CRS or inla.CRS object
#' @param crs1
#' The target sp::CRS or inla.CRS object
#' @param CRSobj
#' The target sp::CRS or inla.CRS object
#' @param passthrough
#' Default is FALSE.
#' Setting to TRUE allows objects with no CRS information to be passed
#' through without transformation.
#' @param \dots
#' Potential additional arguments
#' @seealso [fm_transform()]
#' @export
fm_spTransform <- function(x, ...) {
  lifecycle::deprecate_soft("0.0.1", "fm_spTransform()", "fm_transform()")
  UseMethod("fm_spTransform")
}

#' @describeIn fmesher-deprecated The default method handles low level transformation of raw
#' coordinates.
#' @export
fm_spTransform.default <- function(x, crs0 = NULL, crs1 = NULL, passthrough = FALSE, ...) {
  fm_transform(x, crs = crs1, crs0 = crs0, passthrough = passthrough)
}

#' @export
#' @rdname fmesher-deprecated
fm_spTransform.SpatialPoints <- function(x, CRSobj, passthrough = FALSE, ...) {
  fm_transform(x, crs = CRSobj, passthrough = passthrough)
}

#' @export
#' @rdname fmesher-deprecated
fm_spTransform.SpatialPointsDataFrame <- function(x,
                                                  CRSobj,
                                                  passthrough = FALSE,
                                                  ...) {
  fm_transform(x, crs = CRSobj, passthrough = passthrough)
}

#' @export
#' @rdname fmesher-deprecated
fm_spTransform.inla.mesh.lattice <- function(x, CRSobj, passthrough = FALSE, ...) {
  fm_transform(x, crs = CRSobj, passthrough = passthrough)
}

#' @export
#' @rdname fmesher-deprecated
fm_spTransform.inla.mesh.segment <- function(x, CRSobj, passthrough = FALSE, ...) {
  fm_transform(x, crs = CRSobj, passthrough = passthrough)
}

#' @export
#' @rdname fmesher-deprecated
fm_spTransform.inla.mesh <- function(x, CRSobj, passthrough = FALSE, ...) {
  fm_transform(x, crs = CRSobj, passthrough = passthrough)
}



# Deprecated methods ####

#' @describeIn fmesher-deprecated Detect whether PROJ6 is available
#'
#' @export

fm_has_PROJ6 <- function() {
  lifecycle::deprecate_warn("0.0.1",
    "fm_has_PROJ6()",
    details = c(
      i = "Since inlabru 2.7.1, fm_has_PROJ6() always returns TRUE",
      i = "rgdal/PROJ4 is no longer supported."
    )
  )
  TRUE
}

#' @describeIn fmesher-deprecated `fm_not_for_PROJ6` is called to warn about using old PROJ4
#' features even though PROJ6 is available

fm_not_for_PROJ6 <- function(fun = NULL) {
  lifecycle::deprecate_stop("0.0.1",
    "fm_not_for_PROJ6()",
    details = c(x = "rgdal/PROJ4 is no longer supported.")
  )
}

#' @describeIn fmesher-deprecated `fm_not_for_PROJ4` is called to give an error when
#' calling methods that are only available for PROJ6

fm_not_for_PROJ4 <- function(fun = NULL) {
  lifecycle::deprecate_stop("0.0.1",
    "fm_not_for_PROJ4()",
    details = c(x = "rgdal/PROJ4 is no longer supported.")
  )
}

#' @describeIn fmesher-deprecated Called to warn about falling back
#' to using old PROJ4 methods when a PROJ6 method hasn't been implemented

fm_fallback_PROJ6 <- function(fun = NULL) {
  lifecycle::deprecate_stop("0.0.1",
    "fm_not_for_PROJ4()",
    details = c(x = "rgdal/PROJ4 requested by PROJ4 is no longer supported.")
  )
}


#' @param fun The name of the function that requires PROJ6. Default: NULL,
#' which uses the name of the calling function.
#' @describeIn fmesher-deprecated Called to give an error when PROJ6
#' is required but not available

fm_requires_PROJ6 <- function(fun = NULL) {
  lifecycle::deprecate_stop("0.0.1",
    "fm_requires_PROJ6()",
    details = c(x = "rgdal/PROJ4 is no longer supported.")
  )
}


#' @describeIn fmesher-deprecated Wrapper for [fm_CRS()]
#' `sp::Spatial` and `sp::CRS` objects.
#' @export
fm_as_sp_crs <- function(x, ...) {
  lifecycle::deprecate_soft(
    "0.0.1",
    "fm_as_sp_crs()",
    "fm_CRS()"
  )
  fm_CRS(x, ...)
}




#' @describeIn fmesher-deprecated Wrapper for CRS(projargs) (PROJ4) and CRS(wkt) for
#' `sp::Spatial` objects.
#' @param x A `sp::Spatial` object
#' @return A `CRS` object, or NULL if no valid CRS identified
#' @author Finn Lindgren \email{finn.lindgren@@gmail.com}
#' @details This function is a convenience method to workaround PROJ4/PROJ6
#' differences, and the lack of a crs extraction method for Spatial objects.
#' For newer code, use [fm_crs()] instead, that returns `crs` objects,
#' and use [fm_CRS()] to extract/construct/convert to old style `sp::CRS` objects.
#' @examples
#' if (fm_safe_sp()) {
#'   s <- sp::SpatialPoints(matrix(1:6, 3, 2), proj4string = fm_CRS("sphere"))
#'   fm_CRS(s)
#' }
#' @export

fm_sp_get_crs <- function(x) {
  lifecycle::deprecate_warn("0.0.1", "fm_sp_get_crs()", "fm_CRS()")
  fm_CRS(x)
}

Try the fmesher package in your browser

Any scripts or data that you put into this service are public.

fmesher documentation built on Nov. 2, 2023, 5:35 p.m.