--- title: "Introduction to sfext" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to sfext} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup, message = FALSE} library(sfext) library(dplyr) library(ggplot2) theme_set(theme_void()) ``` The {sfext} package has several categories of functions: - Functions for reading and writing spatial data - Converting the class or geometry of objects - Modifying sf, sfc, and bbox objects - Getting information about sf, sfc, and bbox objects - Working with units and scales ## Reading and writing sf objects ### Reading sf objects `read_sf_ext()` calls one of four other functions depending on the input parameters: - `read_sf_path()` - `read_sf_url()` - `read_sf_pkg()` - `read_sf_query()` `read_sf_path()` is similar to `sf::read_sf` but has a few additional features. It checks the existence of a file before reading, supports the creation of wkt_filter parameters based on a bounding box, and supports conversion of tabular data to simple feature objects. ```{r} nc <- read_sf_ext(path = system.file("shape/nc.shp", package = "sf")) # This is equivalent to read_sf_path(system.file("shape/nc.shp", package = "sf")) # Or sf::read_sf(dsn = system.file("shape/nc.shp", package = "sf")) glimpse(nc) ``` ```{r} bbox <- as_bbox(nc[10, ]) nc_in_bbox <- read_sf_ext(path = system.file("shape/nc.shp", package = "sf"), bbox = bbox) nc_basemap <- ggplot() + geom_sf(data = nc) nc_basemap + geom_sf(data = nc_in_bbox, fill = "red") ``` The second, `read_sf_url()`, that supports reading url that are already supported by `sf::read_sf` but also supports ArcGIS Feature Layers (using the [{esri2sf}](https://github.com/elipousson/esri2sf) package) and URLs for tabular data (including both CSV files and Google Sheets). Several functions also support queries based on a name and name_col value (generating a simple SQL query) based on the provided values. ```{r} sample_esri_url <- "https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/USA_Boundaries_2022/FeatureServer/1" states <- read_sf_esri(url = sample_esri_url) # This is equivalent to read_sf_url(sample_esri_url) # Or esri2sf::esri2sf(url = sample_esri_url) # read_sf_esri and read_sf_query both support the name and name_col parameters # These parameters also work with read_sf_pkg for cached and extdata files nc_esri <- read_sf_ext(url = sample_esri_url, name_col = "STATE_NAME", name = "North Carolina") ggplot() + geom_sf(data = states) + geom_sf(data = nc_esri, fill = "red") ``` The read functions also support URLs for GitHub Gists (assuming the first file in the Gist is a spatial data file) or Google MyMaps. ```{r} gmap_data <- read_sf_ext(url = "https://www.google.com/maps/d/u/0/viewer?mid=1CEssu_neU7lx_vAZs5qpufOBoUQ&ll=-3.81666561775622e-14%2C0&z=1") ggplot() + geom_sf(data = gmap_data[2, ]) ``` The third, `read_sf_pkg()`, can load spatial data from any installed package including exported data, files in the extdata folder, or data in a package-specific cache folder. This is particularly useful when working with spatial data packages such as [{mapbaltimore}](https://elipousson.github.io/mapbaltimore/) or [{mapmaryland}](https://elipousson.github.io/mapmaryland/). The fourth, `read_sf_query()`, is most similar to `sf::read_sf` but provides an optional spatial filter based on the bbox parameter and supports the creation of queries using a basic name and name_col parameter. ### Writing sf objects `write_sf_ext()` wraps `sf::write_sf` but uses `filenamr::make_filename()` to support the creation of consistent file names using labels or date prefixes to organize exports. ```{r} filenamr::make_filename( name = "Ashe County", label = "NC", prefix = "date", fileext = "gpkg" ) ``` `write_sf_ext()` also supports the automatic creation of destination folders and the use of a package-specific cache folder created by `rappdirs::user_cache_dir()`. These functions are now in the `filenamr` package. ## Converting sf objects ### Checking and converting sf objects There are several helper functions that are used extensively by the package itself. While these conversions are easy to do with existing {sf} functions, these alternatives follow a tidyverse style syntax and support a wider range of input values. ```{r} is_sf(nc) is_sfc(nc$geometry) nc_bbox <- as_bbox(nc) is_bbox(nc_bbox) ``` The ext parameter can be used to make `is_sf()` more general allowing sf, sfc, or bbox objects instead of just sf objects. ```{r} is_sf(nc$geometry) is_sf(nc$geometry, ext = TRUE) ``` There are similar functions for checking and converting the geometry type for an object including `is_point()`, `is_polygon()`, `is_line()`, and others. ```{r} is_point(nc) is_point(suppressWarnings(sf::st_centroid(nc))) # as_point returns an sfg object is_sfg(as_point(nc)) # as_points returns an sfc object (and accepts numeric inputs as well as sfg) is_sfc(as_points(c(-79.40065, 35.55937), crs = 4326)) ``` ### Converting to and from data frames By default the conversion from sf to data frame object, uses the centroid of any polygon. It can also use a surface point from `sf::st_point_on_surface` ```{r} df_centroid <- sf_to_df(nc_in_bbox) df_surface_point <- sf_to_df(nc_in_bbox, geometry = "surface point") ``` These can be converted back to an sf object but the point geometry is used instead of the original polygon: ```{r} df_centroid_sf <- df_to_sf(df_centroid, crs = 3857) df_surface_point_sf <- df_to_sf(df_surface_point, crs = 3857) df_example_map <- ggplot() + geom_sf(data = nc_in_bbox) + geom_sf(data = df_centroid_sf, color = "red", size = 3) + geom_sf(data = df_surface_point_sf, color = "blue", size = 2) df_example_map ``` Alternatively, `sf_to_df()` can also use well known text as an output format: ```{r} df_wkt <- sf_to_df(nc_in_bbox, geometry = "wkt") glimpse(df_wkt) df_example_map + geom_sf(data = df_to_sf(df_wkt), color = "orange", fill = NA) ``` The [tidygeocoder](https://jessecambon.github.io/tidygeocoder/) package is also used to support conversion of address vectors or data frames. ```{r} address_to_sf(x = c("350 Fifth Avenue, New York, NY 10118")) ``` ## Modifying sf objects The next group of functions often provide similar functionality to standard {sf} functions but, again, allow a wider range of inputs and outputs or offer extra features. For example, `st_bbox_ext()` also wraps `sf::st_buffer` and uses a helper function based on `units::set_units` to a buffer distance of any valid distance unit. ```{r} nc_bbox <- st_bbox_ext(nc, class = "sf") # Similar to sf::st_sf(sf::st_as_sfc(sf::st_bbox(nc))) nc_bbox_buffer <- st_bbox_ext(nc, dist = 50, unit = "mi", class = "sf") # Similar to sf::st_buffer(nc, dist = units::as_units(50, "mi")) nc_basemap + geom_sf(data = nc_bbox, fill = NA, color = "blue") + geom_sf(data = nc_bbox_buffer, fill = NA, color = "red") ``` Other notable functions in this category include: - `st_transform_ext()`: takes sf, sfc, or bbox objects as the crs parameter for `sf::st_transform()` and supports lists of sf objects (look for the `allow_list = TRUE` parameter to see which functions support lists) - `st_union_ext()`: unions geometry while also collapsing a name column into a single character string using the `cli::pluralize` function - `st_erase()`: checks the validity of inputs with `sf::st_is_valid` before and after using `sf::st_difference` or (if flip = TRUE) `sf::st_difference` ## Getting information about sf, sfc, and bbox objects There are also several functions that return information about the geometry of input objects. Typically, these functions bind the information as a new column to an existing sf input (and convert sfc input objects to sf results). The `get_length()` function wraps `sf::st_length` and (for POLYGON geometries only) `lwgeom::st_perimeter`: ```{r} example_line <- as_line(as_point(nc[1, ]), as_point(nc[2, ]), crs = 4326) glimpse(get_length(example_line)) ``` The `get_area()` function wraps `sf::st_area()`: ```{r} glimpse(get_area(nc[1:3, ], unit = "mi^2")) ``` `get_dist()` supports a more varied range of options including using the "to" parameter to define a corner or center of the input sf object bounding box. ```{r} nc <- sf::st_transform(nc, 3857) # use drop = TRUE, to drop the units class and return a numeric column dist_example_min <- get_dist(nc, to = c("xmin", "ymin"), unit = "mi", drop = TRUE) glimpse(select(dist_example_min, NAME, dist)) nc_basemap + geom_sf(data = dist_example_min, aes(fill = dist), alpha = 0.5) dist_example_mid <- get_dist(nc, to = c("xmid", "ymid"), unit = "mi", drop = TRUE) nc_basemap + geom_sf(data = dist_example_mid, aes(fill = dist), alpha = 0.5) ```