diff --git a/.travis.yml b/.travis.yml index 9e68419..f492e78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,6 @@ language: R sudo: false cache: packages -r_github_packages: - - tidyverse/ggplot2 - addons: apt: sources: diff --git a/DESCRIPTION b/DESCRIPTION index c3029c7..1e57630 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,17 +1,20 @@ Package: ggspatial Type: Package Title: Spatial Data Framework for ggplot2 -Version: 0.3.0.9000 -Author: Dewey Dunnington +Version: 1.0.0 +Authors@R: c( + person("Dewey", "Dunnington", , "dewey@fishandwhistle.net", c("aut", "cre")), + person("Brent", "Thorne", role = "ctb") + ) Maintainer: Dewey Dunnington Description: Spatial data plus the power of the ggplot2 framework means easier mapping when input - data are already in the form of Spatial* objects. + data are already in the form of spatial objects. License: GPL-3 Depends: R (>= 2.10), - ggplot2 (>= 2.0), - sf + ggplot2 (>= 2.2.1.9000) Imports: + sf, rosm (>= 0.2), abind, reshape2, @@ -31,6 +34,8 @@ Suggests: rgdal, testthat, dplyr, + withr, + ggrepel, covr URL: https://github.com/paleolimbot/ggspatial BugReports: https://github.com/paleolimbot/ggspatial/issues diff --git a/NAMESPACE b/NAMESPACE index 9c55534..6338bfd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,6 +27,7 @@ S3method(geom_spatial,sf) S3method(geom_spatial,sfc) S3method(layer_spatial,Raster) S3method(layer_spatial,default) +S3method(makeContent,geom_spatial_raster_lazy) S3method(spatial_default_aes,Raster) S3method(spatial_default_aes,sf) S3method(spatial_default_aes,sfc) @@ -43,6 +44,10 @@ S3method(spatial_geom,sfc) export(GeomMapTile) export(GeomNorthArrow) export(GeomScaleBar) +export(GeomSpatialRaster) +export(StatSpatialRaster) +export(StatSpatialRasterAnnotation) +export(StatSpatialRasterDf) export(annotation_map_tile) export(annotation_north_arrow) export(annotation_scale) @@ -61,12 +66,16 @@ export(ggraster) export(ggspatial) export(layer_spatial) export(load_longlake_data) +export(north_arrow_fancy_orienteering) +export(north_arrow_minimal) +export(north_arrow_nautical) +export(north_arrow_orienteering) export(spatial_fortify) export(stat_project) export(stat_spatial_identity) export(xy_transform) importFrom(ggplot2,aes) importFrom(ggplot2,fortify) -importFrom(ggplot2,waiver) +importFrom(grid,makeContent) importFrom(grid,unit) importFrom(sf,st_zm) diff --git a/R/geom-osm.R b/R/annotation-map-tile.R similarity index 88% rename from R/geom-osm.R rename to R/annotation-map-tile.R index 6755607..a6db98e 100644 --- a/R/geom-osm.R +++ b/R/annotation-map-tile.R @@ -16,6 +16,13 @@ #' @return A ggplot2 layer #' @export #' +#' @examples +#' load_longlake_data() +#' +#' ggplot() + +#' annotation_map_tile(zoom = 13, cachedir = system.file("rosm.cache", package = "ggspatial")) + +#' geom_sf(data = longlake_waterdf, fill = NA, col = "grey50") +#' annotation_map_tile <- function(type = "osm", zoom = NULL, zoomin = -2, forcedownload = FALSE, cachedir = NULL, progress = c("text", "none"), quiet = TRUE, @@ -90,21 +97,16 @@ GeomMapTile <- ggplot2::ggproto( coord_crs <- sf::st_crs(panel_params$crs) if(!is.null(coord_crs)) { - - proj_corners <- sf::st_sfc( - st_point(c(panel_params$x_range[1], panel_params$y_range[1])), - st_point(c(panel_params$x_range[2], panel_params$y_range[2])), - crs = coord_crs - ) - proj_grid <- sf::st_make_grid(proj_corners, n = 50, what = "corners") - latlon_grid <- sf::st_transform(proj_grid, crs = 4326) - latlon_bbox <- sf::st_bbox(latlon_grid) - sp_bbox <- prettymapr::makebbox( - n = latlon_bbox["ymax"], - e = latlon_bbox["xmax"], - s = latlon_bbox["ymin"], - w = latlon_bbox["xmin"] + sp_bbox <- project_extent( + xmin = panel_params$x_range[1], + ymin = panel_params$y_range[1], + xmax = panel_params$x_range[2], + ymax = panel_params$y_range[2], + from_crs = coord_crs, + to_crs = 4326, + format = "sp" ) + } else { stop("geom_map_tile() requires coord_sf().", call. = FALSE) } diff --git a/R/annotation-north-arrow.R b/R/annotation-north-arrow.R index 226a3c3..d564fac 100644 --- a/R/annotation-north-arrow.R +++ b/R/annotation-north-arrow.R @@ -1,38 +1,53 @@ #' Spatial-aware north arrow #' -#' @param line_width,line_col,fill Parameters for north arrow polygons -#' @param text_col,text_family,text_face,text_angle Parameters for the "N" text #' @param height,width Height and width of north arrow #' @param pad_x,pad_y Padding between north arrow and edge of frame #' @param which_north "grid" results in a north arrow always pointing up; "true" always points to the #' north pole from whichever corner of the map the north arrow is in. #' @param rotation Override the rotation of the north arrow (degrees conterclockwise) #' @param location Where to put the north arrow ("tl" for top left, etc.) +#' @param style A grob or callable that produces a grob that will be drawn as the north arrow. +#' See \link{north_arrow_orienteering} for options. + #' #' @return A ggplot2 layer #' @export #' @importFrom grid unit #' -annotation_north_arrow <- function(line_width = 1, line_col = "black", fill = c("white", "black"), - text_col = "black", text_family = "", text_face = NULL, - text_angle = NULL, - height = unit(1.5, "cm"), width = unit(1.5, "cm"), +#' @examples +#' +#' cities <- data.frame( +#' x = c(-63.58595, 116.41214), +#' y = c(44.64862, 40.19063), +#' city = c("Halifax", "Beijing") +#' ) +#' +#' ggplot(cities) + +#' geom_spatial_point(aes(x, y), crs = 4326) + +#' annotation_north_arrow(which_north = "true") + +#' coord_sf(crs = 3995) +#' +#' ggplot(cities) + +#' geom_spatial_point(aes(x, y), crs = 4326) + +#' annotation_north_arrow(which_north = "grid") + +#' coord_sf(crs = 3995) +#' +annotation_north_arrow <- function(height = unit(1.5, "cm"), width = unit(1.5, "cm"), pad_x = unit(0.25, "cm"), pad_y = unit(0.25, "cm"), which_north = c("grid", "true"), rotation = NULL, - location = c("tr", "bl", "br", "tl")) { + location = c("tr", "bl", "br", "tl"), + style = north_arrow_orienteering) { + which_north <- match.arg(which_north) location <- match.arg(location) stopifnot( - is.numeric(line_width), length(line_width) == 1, - length(line_col) == 1, is.atomic(line_col), grid::is.unit(height), length(height) == 1, grid::is.unit(width), length(width) == 1, grid::is.unit(pad_x), length(pad_x) == 1, grid::is.unit(pad_y), length(pad_y) == 1, - length(text_col) == 1, is.atomic(text_col), - length(fill) == 2, is.atomic(fill) + is_grob_like(style) || is_grob_like(style()) ) ggplot2::layer( @@ -44,18 +59,13 @@ annotation_north_arrow <- function(line_width = 1, line_col = "black", fill = c( show.legend = FALSE, inherit.aes = FALSE, params = list( - line_width = line_width, - line_col = line_col, - fill = fill, - text_col = text_col, - text_family = text_family, - text_face = text_face, height = height, width = width, pad_x = pad_x, pad_y = pad_y, which_north = which_north, - location = location + location = location, + style = style ) ) } @@ -73,12 +83,9 @@ GeomNorthArrow <- ggplot2::ggproto( }, draw_panel = function(data, panel_params, coordinates, - line_width = 1, line_col = "black", fill = c("white", "black"), - text_col = "black", text_family = "", text_face = NULL, - text_angle = NULL, height = unit(1.5, "cm"), width = unit(1.5, "cm"), pad_x = unit(0.25, "cm"), pad_y = unit(0.25, "cm"), which_north = "grid", - rotation = NULL, location = "tr") { + rotation = NULL, location = "tr", style = north_arrow_orienteering) { if(is.null(rotation)) { rotation <- 0 # degrees anticlockwise @@ -102,20 +109,18 @@ GeomNorthArrow <- ggplot2::ggproto( } } - if(is.null(text_angle)) { - text_angle <- -rotation - } - # north arrow grob in npc coordinates - sub_grob <- north_arrow_grob_default( - line_width = line_width, - line_col = line_col, - fill = fill, - text_col = text_col, - text_family = text_family, - text_face = text_face, - text_angle = text_angle - ) + if(is_grob_like(style)) { + sub_grob <- style + } else if(is.function(style)) { + if("text_angle" %in% names(formals(style))) { + sub_grob <- style(text_angle = -rotation) + } else { + sub_grob <- style() + } + } else { + stop("Invalid 'style' argument") + } # position of origin (centre of arrow) based on padding, width, height adj_x <- as.numeric(grepl("r", location)) @@ -125,7 +130,7 @@ GeomNorthArrow <- ggplot2::ggproto( # gtree with a custom viewport grid::gTree( - children = sub_grob, + children = grid::gList(sub_grob), vp = grid::viewport( x = origin_x, y = origin_y, @@ -183,16 +188,49 @@ true_north <- function(x, y, crs, delta_crs = 0.1, delta_lat = 0.1) { rot_degrees } - -# this creates a grob with N arrow and text (using 0...1 coordinates) -# must return a gList() -north_arrow_grob_default <- function(line_width = 1, line_col = "black", fill = c("white", "black"), +#' North arrow styles +#' +#' @param line_width,line_col,fill Parameters customizing the appearance of the north arrow +#' @param text_col,text_family,text_face,text_size,text_angle Parameters customizing the +#' text of the north arrow +#' +#' @return A Grob with npc coordinates (more or less) 0 to 1 +#' @export +#' +#' @examples +#' grid::grid.newpage() +#' grid::grid.draw(north_arrow_orienteering()) +#' +#' grid::grid.newpage() +#' grid::grid.draw(north_arrow_fancy_orienteering()) +#' +#' grid::grid.newpage() +#' grid::grid.draw(north_arrow_minimal()) +#' +#' grid::grid.newpage() +#' grid::grid.draw(north_arrow_nautical()) +#' +north_arrow_orienteering <- function(line_width = 1, line_col = "black", fill = c("white", "black"), text_col = "black", text_family = "", text_face = NULL, - arrow_x = c(0, 0.5, 0.5, 1, 0.5, 0.5), - arrow_y = c(0.1, 1, 0.5, 0.1, 1, 0.5), - arrow_id = c(1, 1, 1, 2, 2, 2), - text_x = 0.5, text_y = 0.1, text_size = 18, text_adj = c(0.5, 0.5), - text_label = "N", text_angle = 0) { + text_size = 10, text_angle = 0) { + + stopifnot( + length(fill) == 2, is.atomic(fill), + length(line_col) == 1, is.atomic(line_col), + length(line_width) == 1, is.atomic(line_width), + length(text_size) == 1, is.numeric(text_size), + length(text_col) == 1, is.atomic(text_col), + is.null(text_face) || (length(text_face) == 1 && is.character(text_face)), + length(text_family) == 1, is.character(text_family) + ) + + arrow_x <- c(0, 0.5, 0.5, 1, 0.5, 0.5) + arrow_y <- c(0.1, 1, 0.5, 0.1, 1, 0.5) + arrow_id <- c(1, 1, 1, 2, 2, 2) + text_x <- 0.5 + text_y <- 0.1 + text_label <- "N" + text_adj <- c(0.5, 0.5) grid::gList( grid::polygonGrob( @@ -213,6 +251,111 @@ north_arrow_grob_default <- function(line_width = 1, line_col = "black", fill = hjust = text_adj[0], vjust = text_adj[1], rot = text_angle, + gp = grid::gpar( + fontfamily = text_family, + fontface = text_face, + fontsize = text_size + 2 + ) + ) + ) +} + +#' @export +#' @rdname north_arrow_orienteering +north_arrow_fancy_orienteering <- function(line_width = 1, line_col = "black", fill = c("white", "black"), + text_col = "black", text_family = "", text_face = NULL, + text_size = 10, text_angle = 0) { + + arrow_x <- c(0.25, 0.5, 0.5, 0.75, 0.5, 0.5) + arrow_y <- c(0.1, 0.8, 0.3, 0.1, 0.8, 0.3) + arrow_id <- c(1, 1, 1, 2, 2, 2) + text_y <- 0.95 + text_x <- 0.5 + + # add Grobs + grid::gList( + grid::circleGrob( + x = 0.505, + y = 0.4, + r = 0.3, + default.units = "npc", + gp = grid::gpar( + fill = NA, + col = line_col, + lwd = line_width + ) + ), + grid::polygonGrob( + x = arrow_x, + y = arrow_y, + id = arrow_id, + default.units = "npc", + gp = grid::gpar( + lwd = line_width, + col = line_col, + fill = fill + ) + ), + grid::textGrob( + label = "N", + x = text_x, + y = text_y, + rot = text_angle, + gp = grid::gpar( + fontfamily = text_family, + fontface = text_face, + fontsize = text_size + ) + ) + ) +} + +#' @export +#' @rdname north_arrow_orienteering +north_arrow_minimal <- function(line_width = 1, line_col = "black", fill = "black", + text_col = "black", text_family = "", text_face = NULL, + text_size = 10) { + + stopifnot( + length(fill) == 1, is.atomic(fill), + length(line_col) == 1, is.atomic(line_col), + length(line_width) == 1, is.atomic(line_width), + length(text_size) == 1, is.numeric(text_size), + length(text_col) == 1, is.atomic(text_col), + is.null(text_face) || (length(text_face) == 1 && is.character(text_face)), + length(text_family) == 1, is.character(text_family) + ) + + text_label = "N" + text_adj = c(0.5, 0.5) + + grid::gList( + grid::polygonGrob( + x = c(0.65, 0.5, 0.5), + y = c(0.65, 1, 0.7), + default.units = "npc", + gp = grid::gpar( + lwd = line_width, + col = line_col, + fill = fill + ) + ), + grid::linesGrob( + x = c(0.5, 0.5), + y = c(0, 1), + default.units = "npc", + gp = grid::gpar( + lwd = line_width, + col = line_col + ) + ), + grid::textGrob( + label = text_label, + x = 0.5, + y = 0.3, + hjust = text_adj[0], + vjust = text_adj[1], + rot = 0, gp = grid::gpar( fontfamily = text_family, fontface = text_face, @@ -220,5 +363,124 @@ north_arrow_grob_default <- function(line_width = 1, line_col = "black", fill = ) ) ) +} + +#' @export +#' @rdname north_arrow_orienteering +north_arrow_nautical <- function(line_width = 1, line_col = "black", fill = c("black", "white"), text_size = 10, + text_face = NULL, text_family = "", text_col = "black", text_angle = 0) { + + stopifnot( + length(fill) == 2, is.atomic(fill), + length(line_col) == 1, is.atomic(line_col), + length(line_width) == 1, is.atomic(line_width), + length(text_size) == 1, is.numeric(text_size), + length(text_col) == 1, is.atomic(text_col), + is.null(text_face) || (length(text_face) == 1 && is.character(text_face)), + length(text_family) == 1, is.character(text_family) + ) + + nautical <- data.frame(x = c(0.5,0.45,0.5,0.5,0.55,0.5, #North + 0.5,0.55,0.5,0.5,0.45,0.5, #South + 0.5,0.6,0.8,0.5,0.6,0.8, #East + 0.5,0.4,0.2,0.5,0.4,0.2, #West + 0.5,0.55,0.65,0.5,0.6,0.65, #NE + 0.5,0.6,0.65,0.5,0.55,0.65, #SE + 0.5,0.4,0.35,0.5,0.45,0.35, #NW + 0.5,0.45,0.35,0.5,0.4,0.35), #SW + y = c(0.5,0.6,0.8,0.5,0.6,0.8, + 0.5,0.4,0.2,0.5,0.4,0.2, + 0.5,0.55,0.5,0.5,0.45,0.5, + 0.5,0.45,0.5,0.5,0.55,0.5, + 0.5,0.6,0.65,0.5,0.55,0.65, + 0.5,0.45,0.35,0.5,0.4,0.35, + 0.5,0.55,0.65,0.5,0.6,0.65, + 0.5,0.4,0.35,0.5,0.45,0.35), + id = c(1,1,1,2,2,2, + 3,3,3,4,4,4, + 5,5,5,6,6,6, + 7,7,7,8,8,8, + 9,9,9,10,10,10, + 11,11,11,12,12,12, + 13,13,13,14,14,14, + 15,15,15,16,16,16)) + + # rescale slightly to fill (0, 1) + nautical$x <- scales::rescale(nautical$x, to = c(0.1, 0.9)) + nautical$y <- scales::rescale(nautical$y, to = c(0, 0.8)) + + + # add Grobs + grid::gList( + grid::circleGrob( + x = 0.5, + y = 0.4, + r = 0.01, + default.units = "npc", + gp = grid::gpar( + fill = fill[2], + col = line_col, + lwd = line_width + ) + ), + grid::circleGrob( + x = 0.5, + y = 0.4, + r = 0.25, + default.units = "npc", + gp = grid::gpar( + col = line_col, + lty = 1, + fill = NA, + lwd = line_width + ) + ), + grid::circleGrob( + x = 0.5, + y = 0.4, + r = 0.255, + default.units = "npc", + gp = grid::gpar( + col = line_col, + fill = NA, + lty = 1, + lwd = line_width + ) + ), + grid::polygonGrob( + name = "nautical", + x = nautical$x, + y = nautical$y, + id = nautical$id, + default.units = "npc", + gp = grid::gpar( + fill = fill, + col = line_col, + lwd = line_width * 0.2 + ) + ), + grid::textGrob( + label = "N", + x = unit(0.5, "npc"), + y = unit(0.9, "npc"), + just = "centre", + hjust = NULL, + vjust = NULL, + rot = text_angle, + check.overlap = FALSE, + default.units = "npc", + name = NULL, + gp = grid::gpar( + fontsize = text_size, + fontface = text_face, + fontfamily = text_family, + col = text_col + ), + vp = NULL + ) + ) +} +is_grob_like <- function(x) { + grid::is.grob(x) || inherits(x, "gList") || inherits(x, "gTree") } diff --git a/R/annotation-scale.R b/R/annotation-scale.R index 9544b7c..fed94cd 100644 --- a/R/annotation-scale.R +++ b/R/annotation-scale.R @@ -20,6 +20,18 @@ #' #' @importFrom grid unit #' +#' @examples +#' cities <- data.frame( +#' x = c(-63.58595, 116.41214), +#' y = c(44.64862, 40.19063), +#' city = c("Halifax", "Beijing") +#' ) +#' +#' ggplot(cities) + +#' geom_spatial_point(aes(x, y), crs = 4326) + +#' annotation_scale() + +#' coord_sf(crs = 3995) +#' annotation_scale <- function(plot_unit = NULL, width_hint = 0.25, unit_category = c("metric", "imperial"), style = c("bar", "ticks"), location = c("bl", "br", "tr", "tl"), diff --git a/R/df-spatial.R b/R/df-spatial.R index c0ac6ad..67e0b16 100644 --- a/R/df-spatial.R +++ b/R/df-spatial.R @@ -7,6 +7,12 @@ #' @return A tibble with coordinates as .x and .y, and features as .feature #' @export #' +#' @examples +#' load_longlake_data() +#' df_spatial(longlake_osm) +#' df_spatial(longlake_depthdf) +#' df_spatial(as(longlake_depthdf, "Spatial")) +#' df_spatial <- function(x, ...) { UseMethod("df_spatial") } diff --git a/R/geom-polypath.R b/R/geom-polypath.R index d9223a6..42aa4db 100644 --- a/R/geom-polypath.R +++ b/R/geom-polypath.R @@ -27,7 +27,7 @@ #' #' @examples #' load_longlake_data() -#' ggplot(spatial_fortify(longlake_waterdf), aes(.long, .lat, group = .group)) + +#' ggplot(df_spatial(longlake_waterdf), aes(x, y, group = piece_id)) + #' geom_polypath() #' geom_polypath <- function (mapping = NULL, data = NULL, stat = "identity", position = "identity", diff --git a/R/geom-spatial.R b/R/geom-spatial.R index 7898933..17d72ec 100644 --- a/R/geom-spatial.R +++ b/R/geom-spatial.R @@ -4,8 +4,8 @@ #' These layers are much like their counterparts, \link[ggplot2]{stat_identity}, #' \link[ggplot2]{geom_point}, \link[ggplot2]{geom_path}, #' and \link[ggplot2]{geom_polygon}, except they have a \code{crs} argument that -#' ensures they are projected when using \link[ggplot2]{coord_sf}. Stats are applied the x and y coordinates -#' have been transformed. +#' ensures they are projected when using \link[ggplot2]{coord_sf}. Stats are applied to the x and y coordinates +#' that have been transformed. #' #' @param mapping An aesthetic mapping created with \link[ggplot2]{aes}. #' @param data A data frame or other object, coerced to a data.frame by \link[ggplot2]{fortify}. @@ -20,6 +20,19 @@ #' @return A ggplot2 layer. #' @export #' +#' @examples +#' cities <- data.frame( +#' x = c(-63.58595, 116.41214, 0), +#' y = c(44.64862, 40.19063, 89.9), +#' city = c("Halifax", "Beijing", "North Pole") +#' ) +#' +#' library(ggrepel) +#' ggplot(cities, aes(x, y)) + +#' geom_spatial_point(crs = 4326) + +#' stat_spatial_identity(aes(label = city), geom = "label_repel") + +#' coord_sf(crs = 3857) +#' stat_spatial_identity <- function( mapping = NULL, data = NULL, crs = NULL, geom = "point", position = "identity", ..., show.legend = NA, inherit.aes = TRUE diff --git a/R/layer-spatial-raster.R b/R/layer-spatial-raster.R index e36a643..44c36e3 100644 --- a/R/layer-spatial-raster.R +++ b/R/layer-spatial-raster.R @@ -9,14 +9,20 @@ #' map specific bands to the fill and alpha aesthetics. #' @param interpolate Interpolate resampling for rendered raster image #' @param is_annotation Lets raster exist without modifying scales +#' @param lazy Delay projection and resample of raster until the plot is being rendered +#' @param dpi if lazy = TRUE, the dpi to which the raster should be resampled #' @param ... Passed to other methods #' #' @return A ggplot2 layer #' @export #' -#' @importFrom ggplot2 waiver +#' @examples +#' load_longlake_data() +#' ggplot() + layer_spatial(longlake_osm) +#' ggplot() + layer_spatial(longlake_depth_raster) + scale_fill_continuous(na.value = NA) #' -layer_spatial.Raster <- function(data, mapping = NULL, interpolate = TRUE, is_annotation = FALSE, ...) { +layer_spatial.Raster <- function(data, mapping = NULL, interpolate = TRUE, is_annotation = FALSE, + lazy = FALSE, dpi = 150, ...) { is_rgb <- is.null(mapping) && (raster::nbands(data) %in% c(3, 4)) @@ -53,7 +59,7 @@ layer_spatial.Raster <- function(data, mapping = NULL, interpolate = TRUE, is_an geom = geom, position = "identity", inherit.aes = FALSE, show.legend = !is_rgb, - params = list(interpolate = interpolate, ...) + params = list(interpolate = interpolate, lazy = lazy, ...) ), # use an emtpy geom_sf() with same CRS as the raster to mimic behaviour of # using the first layer's CRS as the base CRS for coord_sf(). @@ -71,37 +77,54 @@ annotation_spatial.Raster <- function(data, mapping = NULL, interpolate = TRUE, layer_spatial.Raster(data, mapping = mapping, interpolate = interpolate, is_annotation = TRUE, ...) } +#' @rdname layer_spatial.Raster +#' @export StatSpatialRaster <- ggplot2::ggproto( "StatSpatialRaster", Stat, required_aes = "raster", - extra_params = "", compute_layer = function(self, data, params, layout) { - # project to target coordinate system + # raster extents + extents <- lapply(data$raster, raster::extent) + + # project to target coordinate system, if one exists coord_crs <- layout$coord_params$crs if(!is.null(coord_crs)) { - data$raster <- lapply( - data$raster, - raster::projectRaster, - crs = raster::crs(sf::st_crs(coord_crs)$proj4string) - ) + + projected_bboxes <- lapply(seq_along(extents), function(i) { + x <- extents[[i]] + project_extent( + xmin = x@xmin, + xmax = x@xmax, + ymin = x@ymin, + ymax = x@ymax, + from_crs = sf::st_crs(raster::crs(data$raster[[i]])@projargs), + to_crs = sf::st_crs(coord_crs) + ) + }) + + data$xmin <- vapply(projected_bboxes, function(x) x["xmin"], numeric(1)) + data$xmax <- vapply(projected_bboxes, function(x) x["xmax"], numeric(1)) + data$ymin <- vapply(projected_bboxes, function(x) x["ymin"], numeric(1)) + data$ymax <- vapply(projected_bboxes, function(x) x["ymax"], numeric(1)) + } else { warning("Spatial rasters may not be displayed correctly. Use coord_sf().", call. = FALSE) - } - # add extent so that scales are trained properly - data$extent <- lapply(data$raster, raster::extent) - data$xmin <- vapply(data$extent, function(x) x@xmin, numeric(1)) - data$xmax <- vapply(data$extent, function(x) x@xmax, numeric(1)) - data$ymin <- vapply(data$extent, function(x) x@ymin, numeric(1)) - data$ymax <- vapply(data$extent, function(x) x@ymax, numeric(1)) + data$xmin <- vapply(extents, function(x) x@xmin, numeric(1)) + data$xmax <- vapply(extents, function(x) x@xmax, numeric(1)) + data$ymin <- vapply(extents, function(x) x@ymin, numeric(1)) + data$ymax <- vapply(extents, function(x) x@ymax, numeric(1)) + } data } ) +#' @rdname layer_spatial.Raster +#' @export StatSpatialRasterAnnotation <- ggplot2::ggproto( "StatSpatialRaster", StatSpatialRaster, @@ -118,18 +141,37 @@ StatSpatialRasterAnnotation <- ggplot2::ggproto( } ) +#' @rdname layer_spatial.Raster +#' @export StatSpatialRasterDf <- ggplot2::ggproto( "StatSpatialRasterDf", - StatSpatialRasterAnnotation, + Stat, required_aes = "raster", + extra_params = "lazy", + default_aes = ggplot2::aes(fill = stat(band1)), compute_layer = function(self, data, params, layout) { - data <- ggplot2::ggproto_parent(self, StatSpatialRasterAnnotation)$compute_layer(data, params, layout) - data$extent <- NULL + if(params$lazy) stop("Lazy rendering not implemented for mapped rasters") + + # project to target coordinate system # make all rasters data frames using df_spatial, if column still exists + # (because this method gets called multiple times) + if("raster" %in% colnames(data)) { + + coord_crs <- layout$coord_params$crs + if(!is.null(coord_crs)) { + data$raster <- lapply( + data$raster, + raster::projectRaster, + crs = raster::crs(sf::st_crs(coord_crs)$proj4string) + ) + } else { + warning("Spatial rasters may not be displayed correctly. Use coord_sf().", call. = FALSE) + } + data$raster <- lapply(data$raster, df_spatial) tidyr::unnest(data, .data$raster) } else { @@ -138,10 +180,11 @@ StatSpatialRasterDf <- ggplot2::ggproto( } ) +#' @rdname layer_spatial.Raster +#' @export GeomSpatialRaster <- ggplot2::ggproto( "GeomSpatialRaster", ggplot2::Geom, - extra_params = "", required_aesthetics = c("raster", "extent"), @@ -151,23 +194,127 @@ GeomSpatialRaster <- ggplot2::ggproto( data }, - draw_panel = function(data, panel_params, coordinates, interpolate = TRUE, crop = TRUE) { - ext <- data$extent[[1]] - corners <- data.frame(x = c(ext@xmin, ext@xmax), y = c(ext@ymin, ext@ymax)) - corners_trans <- coordinates$transform(corners, panel_params) + draw_panel = function(data, panel_params, coordinates, interpolate = TRUE, lazy = FALSE, dpi = 150) { + rst <- data$raster[[1]] + coord_crs <- panel_params$crs + alpha <- data$alpha[1] + + if(lazy) { + # return a gTree with the right params so that the raster can be rendered later + grid::gTree( + raster_obj = rst, + coord_crs = coord_crs, + coordinates = coordinates, + panel_params = panel_params, + alpha = alpha, + interpolate = interpolate, + dpi = dpi, + cl = "geom_spatial_raster_lazy" + ) - x_rng <- range(corners_trans$x, na.rm = TRUE) - y_rng <- range(corners_trans$y, na.rm = TRUE) + } else { + raster_grob_from_raster( + rst, + coord_crs, + coordinates, + panel_params, + alpha = alpha, + interpolate = interpolate + ) + } + } +) - grid::rasterGrob( - raster_as_array(data$raster[[1]], alpha = data$alpha[1]), - x_rng[1], y_rng[1], - diff(x_rng), diff(y_rng), default.units = "native", - just = c("left","bottom"), interpolate = interpolate +#' @importFrom grid makeContent +#' @export +makeContent.geom_spatial_raster_lazy <- function(x) { + + width_in <- grid::convertWidth(grid::unit(1, "npc"), "in", valueOnly = TRUE) + height_in <- grid::convertHeight(grid::unit(1, "npc"), "in", valueOnly = TRUE) + + width_px <- width_in * x$dpi + height_px <- height_in * x$dpi + + # no idea how to get DPI here...grDevices::dev.size() returns + # incorrect pixel dimensions I think... + # message(sprintf("%s x %s in (%s x %s px) dpi: %s", width_in, height_in, width_px, height_px, x$dpi)) + + if(!is.null(x$coord_crs)) { + # maybe better to use projectExtent() or similar to make + # smallest possible result? + + template_raster <- raster::raster( + xmn = x$panel_params$x_range[1], + ymn = x$panel_params$y_range[1], + xmx = x$panel_params$x_range[2], + ymx = x$panel_params$y_range[2], + ncols = ceiling(width_px), + nrows = ceiling(height_px), + crs = raster::crs(sf::st_crs(x$coord_crs)$proj4string) ) + + } else { + template_raster <- NULL + } + + # add content to the gTree + grid::setChildren( + x, + grid::gList( + raster_grob_from_raster( + x$raster_obj, + x$coord_crs, + x$coordinates, + x$panel_params, + alpha = x$alpha, + interpolate = x$interpolate, + template_raster = template_raster + ) + ) + ) +} + +raster_grob_from_raster <- function(rst, coord_crs, coordinates, panel_params, + template_raster = NULL, alpha = 1, interpolate = TRUE) { + + if(interpolate) { + raster_method <- "bilinear" + } else { + raster_method <- "ngb" + } + + if(!is.null(template_raster) && !is.null(coord_crs)) { + # project + resample + rst <- raster::projectRaster( + rst, + to = template_raster, + method = raster_method + ) + } else if(!is.null(coord_crs)) { + # project + rst <- raster::projectRaster( + rst, + crs = raster::crs(sf::st_crs(coord_crs)$proj4string) + ) + } else if(!is.null(template_raster)) { + # resample (& crop?) + rst <- raster::resample(rst, template_raster, method = raster_method) } -) + ext <- raster::extent(rst) + corners <- data.frame(x = c(ext@xmin, ext@xmax), y = c(ext@ymin, ext@ymax)) + corners_trans <- coordinates$transform(corners, panel_params) + + x_rng <- range(corners_trans$x, na.rm = TRUE) + y_rng <- range(corners_trans$y, na.rm = TRUE) + + grid::rasterGrob( + raster_as_array(rst, alpha = alpha), + x_rng[1], y_rng[1], + diff(x_rng), diff(y_rng), default.units = "native", + just = c("left","bottom"), interpolate = interpolate + ) +} raster_as_array <- function(raster_obj, na.value = NA, alpha = 1) { if(!methods::is(raster_obj, "Raster")) stop("Cannot use raster_as_array with non Raster* object") @@ -220,3 +367,27 @@ raster_as_array <- function(raster_obj, na.value = NA, alpha = 1) { raster } +project_extent <- function(xmin, ymin, xmax, ymax, from_crs = 4326, to_crs = 4326, format = c("sf", "sp"), n = 50) { + format <- match.arg(format) + + proj_corners <- sf::st_sfc( + sf::st_point(c(xmin, ymin)), + sf::st_point(c(xmax, ymax)), + crs = from_crs + ) + + proj_grid <- sf::st_make_grid(proj_corners, n = n, what = "corners") + out_grid <- sf::st_transform(proj_grid, crs = to_crs) + out_bbox <- sf::st_bbox(out_grid) + + if(format == "sp") { + prettymapr::makebbox( + n = out_bbox["ymax"], + e = out_bbox["xmax"], + s = out_bbox["ymin"], + w = out_bbox["xmin"] + ) + } else { + out_bbox + } +} diff --git a/R/layer-spatial.R b/R/layer-spatial.R index 005dd24..ae76bb4 100644 --- a/R/layer-spatial.R +++ b/R/layer-spatial.R @@ -11,6 +11,28 @@ #' @export #' @importFrom ggplot2 aes #' +#' @examples +#' load_longlake_data() +#' +#' ggplot() + +#' +#' # annotation_spatial() layers don't train the scales, so data stays central +#' annotation_spatial(longlake_roadsdf, size = 2, col = "black") + +#' annotation_spatial(longlake_roadsdf, size = 1.6, col = "white") + +#' +#' # raster layers train scales and get projected automatically +#' layer_spatial(longlake_depth_raster, aes(alpha = stat(band1)), fill = "darkblue") + +#' scale_alpha_continuous(na.value = 0) + +#' +#' # layer_spatial() layers train the scales +#' layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) + +#' +#' # spatial-aware automagic scale bar +#' annotation_scale(location = "tl") + +#' +#' # spatial-aware automagic north arrow +#' annotation_north_arrow(location = "br", which_north = "true") +#' layer_spatial <- function(data, mapping, ...) { UseMethod("layer_spatial") } diff --git a/R/longlake-data.R b/R/longlake-data.R index 4ab29f5..5c334e4 100644 --- a/R/longlake-data.R +++ b/R/longlake-data.R @@ -6,7 +6,7 @@ #' @export #' #' @source The Nova Scotia Topographic Database -#' (\url{http://www.mapsnovascotia.com/category.aspx?ic=24}) and +#' (\url{https://geonova.novascotia.ca/}) and #' Open Street Map (\url{http://www.openstreetmap.org}). #' #' @examples diff --git a/README.md b/README.md index 1b0707e..9c4884e 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,17 @@ ggplot() + annotation_spatial(longlake_roadsdf, size = 1.6, col = "white") + # raster layers train scales and get projected automatically - layer_spatial(longlake_depth_raster, aes(fill = NULL, alpha = stat(band1)), fill = "darkblue") + + layer_spatial(longlake_depth_raster, aes(alpha = stat(band1)), fill = "darkblue") + scale_alpha_continuous(na.value = 0) + # layer_spatial trains the scales layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) + # spatial-aware automagic scale bar - annotation_scale(location = "tl") + annotation_scale(location = "tl") + + + # spatial-aware automagic north arrow + annotation_north_arrow(location = "br", which_north = "true") ``` ![](README_files/figure-markdown_github/fig-layer-spatial-sf-1.png) diff --git a/README.rmd b/README.rmd index 456d27d..669d7d7 100644 --- a/README.rmd +++ b/README.rmd @@ -38,12 +38,15 @@ ggplot() + annotation_spatial(longlake_roadsdf, size = 1.6, col = "white") + # raster layers train scales and get projected automatically - layer_spatial(longlake_depth_raster, aes(fill = NULL, alpha = stat(band1)), fill = "darkblue") + + layer_spatial(longlake_depth_raster, aes(alpha = stat(band1)), fill = "darkblue") + scale_alpha_continuous(na.value = 0) + # layer_spatial trains the scales layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) + # spatial-aware automagic scale bar - annotation_scale(location = "tl") + annotation_scale(location = "tl") + + + # spatial-aware automagic north arrow + annotation_north_arrow(location = "br", which_north = "true") ``` diff --git a/README_files/figure-markdown_github/fig-layer-spatial-sf-1.png b/README_files/figure-markdown_github/fig-layer-spatial-sf-1.png index 5674d09..d5092c4 100644 Binary files a/README_files/figure-markdown_github/fig-layer-spatial-sf-1.png and b/README_files/figure-markdown_github/fig-layer-spatial-sf-1.png differ diff --git a/inst/rosm.cache/stamenwatercolor/1_0_0.jpg b/inst/rosm.cache/stamenwatercolor/1_0_0.jpg new file mode 100644 index 0000000..f2970d3 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/1_0_0.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/1_1_0.jpg b/inst/rosm.cache/stamenwatercolor/1_1_0.jpg new file mode 100644 index 0000000..cbc4dd9 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/1_1_0.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_0_0.jpg b/inst/rosm.cache/stamenwatercolor/2_0_0.jpg new file mode 100644 index 0000000..e838fb7 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_0_0.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_0_1.jpg b/inst/rosm.cache/stamenwatercolor/2_0_1.jpg new file mode 100644 index 0000000..4dcb08d Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_0_1.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_1_0.jpg b/inst/rosm.cache/stamenwatercolor/2_1_0.jpg new file mode 100644 index 0000000..11d5715 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_1_0.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_1_1.jpg b/inst/rosm.cache/stamenwatercolor/2_1_1.jpg new file mode 100644 index 0000000..25bd613 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_1_1.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_2_0.jpg b/inst/rosm.cache/stamenwatercolor/2_2_0.jpg new file mode 100644 index 0000000..c4725f2 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_2_0.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_2_1.jpg b/inst/rosm.cache/stamenwatercolor/2_2_1.jpg new file mode 100644 index 0000000..d44a6ed Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_2_1.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_3_0.jpg b/inst/rosm.cache/stamenwatercolor/2_3_0.jpg new file mode 100644 index 0000000..0f163e8 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_3_0.jpg differ diff --git a/inst/rosm.cache/stamenwatercolor/2_3_1.jpg b/inst/rosm.cache/stamenwatercolor/2_3_1.jpg new file mode 100644 index 0000000..8b01469 Binary files /dev/null and b/inst/rosm.cache/stamenwatercolor/2_3_1.jpg differ diff --git a/man/annotation_map_tile.Rd b/man/annotation_map_tile.Rd index 9aa1ed7..827cfea 100644 --- a/man/annotation_map_tile.Rd +++ b/man/annotation_map_tile.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/geom-osm.R +% Please edit documentation in R/annotation-map-tile.R \docType{data} \name{annotation_map_tile} \alias{annotation_map_tile} @@ -37,5 +37,13 @@ A ggplot2 layer } \description{ Uses \link[rosm]{osm.image} to add background tiles. +} +\examples{ +load_longlake_data() + +ggplot() + + annotation_map_tile(zoom = 13, cachedir = system.file("rosm.cache", package = "ggspatial")) + + geom_sf(data = longlake_waterdf, fill = NA, col = "grey50") + } \keyword{datasets} diff --git a/man/annotation_north_arrow.Rd b/man/annotation_north_arrow.Rd index 57195ef..1ae6a4d 100644 --- a/man/annotation_north_arrow.Rd +++ b/man/annotation_north_arrow.Rd @@ -7,20 +7,14 @@ \title{Spatial-aware north arrow} \format{An object of class \code{GeomNorthArrow} (inherits from \code{Geom}, \code{ggproto}, \code{gg}) of length 4.} \usage{ -annotation_north_arrow(line_width = 1, line_col = "black", - fill = c("white", "black"), text_col = "black", text_family = "", - text_face = NULL, text_angle = NULL, height = unit(1.5, "cm"), - width = unit(1.5, "cm"), pad_x = unit(0.25, "cm"), pad_y = unit(0.25, - "cm"), which_north = c("grid", "true"), rotation = NULL, - location = c("tr", "bl", "br", "tl")) +annotation_north_arrow(height = unit(1.5, "cm"), width = unit(1.5, "cm"), + pad_x = unit(0.25, "cm"), pad_y = unit(0.25, "cm"), + which_north = c("grid", "true"), rotation = NULL, location = c("tr", + "bl", "br", "tl"), style = north_arrow_orienteering) GeomNorthArrow } \arguments{ -\item{line_width, line_col, fill}{Parameters for north arrow polygons} - -\item{text_col, text_family, text_face, text_angle}{Parameters for the "N" text} - \item{height, width}{Height and width of north arrow} \item{pad_x, pad_y}{Padding between north arrow and edge of frame} @@ -31,11 +25,33 @@ north pole from whichever corner of the map the north arrow is in.} \item{rotation}{Override the rotation of the north arrow (degrees conterclockwise)} \item{location}{Where to put the north arrow ("tl" for top left, etc.)} + +\item{style}{A grob or callable that produces a grob that will be drawn as the north arrow. +See \link{north_arrow_orienteering} for options.} } \value{ A ggplot2 layer } \description{ Spatial-aware north arrow +} +\examples{ + +cities <- data.frame( + x = c(-63.58595, 116.41214), + y = c(44.64862, 40.19063), + city = c("Halifax", "Beijing") +) + +ggplot(cities) + + geom_spatial_point(aes(x, y), crs = 4326) + + annotation_north_arrow(which_north = "true") + + coord_sf(crs = 3995) + +ggplot(cities) + + geom_spatial_point(aes(x, y), crs = 4326) + + annotation_north_arrow(which_north = "grid") + + coord_sf(crs = 3995) + } \keyword{datasets} diff --git a/man/annotation_scale.Rd b/man/annotation_scale.Rd index e3583ad..356397a 100644 --- a/man/annotation_scale.Rd +++ b/man/annotation_scale.Rd @@ -48,5 +48,18 @@ A ggplot2 layer. } \description{ Spatial-aware scalebar annotation +} +\examples{ +cities <- data.frame( + x = c(-63.58595, 116.41214), + y = c(44.64862, 40.19063), + city = c("Halifax", "Beijing") +) + +ggplot(cities) + + geom_spatial_point(aes(x, y), crs = 4326) + + annotation_scale() + + coord_sf(crs = 3995) + } \keyword{datasets} diff --git a/man/df_spatial.Rd b/man/df_spatial.Rd index 7cedaef..1aa09f4 100644 --- a/man/df_spatial.Rd +++ b/man/df_spatial.Rd @@ -17,3 +17,10 @@ A tibble with coordinates as .x and .y, and features as .feature \description{ Create a ggplot-friendly data frame from a spatial object } +\examples{ +load_longlake_data() +df_spatial(longlake_osm) +df_spatial(longlake_depthdf) +df_spatial(as(longlake_depthdf, "Spatial")) + +} diff --git a/man/geom_polypath.Rd b/man/geom_polypath.Rd index 27e2707..a72d0f2 100644 --- a/man/geom_polypath.Rd +++ b/man/geom_polypath.Rd @@ -39,7 +39,7 @@ similar functionality. } \examples{ load_longlake_data() -ggplot(spatial_fortify(longlake_waterdf), aes(.long, .lat, group = .group)) + +ggplot(df_spatial(longlake_waterdf), aes(x, y, group = piece_id)) + geom_polypath() } diff --git a/man/layer_spatial.Raster.Rd b/man/layer_spatial.Raster.Rd index d4389e2..d1fc7a1 100644 --- a/man/layer_spatial.Raster.Rd +++ b/man/layer_spatial.Raster.Rd @@ -1,15 +1,29 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/layer-spatial-raster.R +\docType{data} \name{layer_spatial.Raster} \alias{layer_spatial.Raster} \alias{annotation_spatial.Raster} +\alias{StatSpatialRaster} +\alias{StatSpatialRasterAnnotation} +\alias{StatSpatialRasterDf} +\alias{GeomSpatialRaster} \title{Spatial ggplot2 layer for raster objects} +\format{An object of class \code{StatSpatialRaster} (inherits from \code{Stat}, \code{ggproto}, \code{gg}) of length 3.} \usage{ \method{layer_spatial}{Raster}(data, mapping = NULL, interpolate = TRUE, - is_annotation = FALSE, ...) + is_annotation = FALSE, lazy = FALSE, dpi = 150, ...) \method{annotation_spatial}{Raster}(data, mapping = NULL, interpolate = TRUE, ...) + +StatSpatialRaster + +StatSpatialRasterAnnotation + +StatSpatialRasterDf + +GeomSpatialRaster } \arguments{ \item{data}{A Raster object} @@ -21,6 +35,10 @@ map specific bands to the fill and alpha aesthetics.} \item{is_annotation}{Lets raster exist without modifying scales} +\item{lazy}{Delay projection and resample of raster until the plot is being rendered} + +\item{dpi}{if lazy = TRUE, the dpi to which the raster should be resampled} + \item{...}{Passed to other methods} } \value{ @@ -30,3 +48,10 @@ A ggplot2 layer This is intended for use with RGB(A) rasters (e.g., georeferenced imagery or photos). To work with bands as if they were columns, use \link{df_spatial} and \link{geom_raster}. } +\examples{ +load_longlake_data() +ggplot() + layer_spatial(longlake_osm) +ggplot() + layer_spatial(longlake_depth_raster) + scale_fill_continuous(na.value = NA) + +} +\keyword{datasets} diff --git a/man/layer_spatial.Rd b/man/layer_spatial.Rd index 27a0f71..296a9cd 100644 --- a/man/layer_spatial.Rd +++ b/man/layer_spatial.Rd @@ -34,3 +34,26 @@ A ggplot2 \link[ggplot2]{layer}. \description{ Turn a spatial object into a ggplot2 layer } +\examples{ +load_longlake_data() + +ggplot() + + + # annotation_spatial() layers don't train the scales, so data stays central + annotation_spatial(longlake_roadsdf, size = 2, col = "black") + + annotation_spatial(longlake_roadsdf, size = 1.6, col = "white") + + + # raster layers train scales and get projected automatically + layer_spatial(longlake_depth_raster, aes(alpha = stat(band1)), fill = "darkblue") + + scale_alpha_continuous(na.value = 0) + + + # layer_spatial() layers train the scales + layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) + + + # spatial-aware automagic scale bar + annotation_scale(location = "tl") + + + # spatial-aware automagic north arrow + annotation_north_arrow(location = "br", which_north = "true") + +} diff --git a/man/load_longlake_data.Rd b/man/load_longlake_data.Rd index f73a54e..0c1abd8 100644 --- a/man/load_longlake_data.Rd +++ b/man/load_longlake_data.Rd @@ -5,7 +5,7 @@ \title{Load longlake test data} \source{ The Nova Scotia Topographic Database -(\url{http://www.mapsnovascotia.com/category.aspx?ic=24}) and +(\url{https://geonova.novascotia.ca/}) and Open Street Map (\url{http://www.openstreetmap.org}). } \usage{ diff --git a/man/north_arrow_orienteering.Rd b/man/north_arrow_orienteering.Rd new file mode 100644 index 0000000..e29d9b4 --- /dev/null +++ b/man/north_arrow_orienteering.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/annotation-north-arrow.R +\name{north_arrow_orienteering} +\alias{north_arrow_orienteering} +\alias{north_arrow_fancy_orienteering} +\alias{north_arrow_minimal} +\alias{north_arrow_nautical} +\title{North arrow styles} +\usage{ +north_arrow_orienteering(line_width = 1, line_col = "black", + fill = c("white", "black"), text_col = "black", text_family = "", + text_face = NULL, text_size = 10, text_angle = 0) + +north_arrow_fancy_orienteering(line_width = 1, line_col = "black", + fill = c("white", "black"), text_col = "black", text_family = "", + text_face = NULL, text_size = 10, text_angle = 0) + +north_arrow_minimal(line_width = 1, line_col = "black", fill = "black", + text_col = "black", text_family = "", text_face = NULL, + text_size = 10) + +north_arrow_nautical(line_width = 1, line_col = "black", fill = c("black", + "white"), text_size = 10, text_face = NULL, text_family = "", + text_col = "black", text_angle = 0) +} +\arguments{ +\item{line_width, line_col, fill}{Parameters customizing the appearance of the north arrow} + +\item{text_col, text_family, text_face, text_size, text_angle}{Parameters customizing the +text of the north arrow} +} +\value{ +A Grob with npc coordinates (more or less) 0 to 1 +} +\description{ +North arrow styles +} +\examples{ +grid::grid.newpage() +grid::grid.draw(north_arrow_orienteering()) + +grid::grid.newpage() +grid::grid.draw(north_arrow_fancy_orienteering()) + +grid::grid.newpage() +grid::grid.draw(north_arrow_minimal()) + +grid::grid.newpage() +grid::grid.draw(north_arrow_nautical()) + +} diff --git a/man/stat_spatial_identity.Rd b/man/stat_spatial_identity.Rd index b0ee93e..8d8ebcc 100644 --- a/man/stat_spatial_identity.Rd +++ b/man/stat_spatial_identity.Rd @@ -41,6 +41,20 @@ A ggplot2 layer. These layers are much like their counterparts, \link[ggplot2]{stat_identity}, \link[ggplot2]{geom_point}, \link[ggplot2]{geom_path}, and \link[ggplot2]{geom_polygon}, except they have a \code{crs} argument that -ensures they are projected when using \link[ggplot2]{coord_sf}. Stats are applied the x and y coordinates -have been transformed. +ensures they are projected when using \link[ggplot2]{coord_sf}. Stats are applied to the x and y coordinates +that have been transformed. +} +\examples{ +cities <- data.frame( + x = c(-63.58595, 116.41214, 0), + y = c(44.64862, 40.19063, 89.9), + city = c("Halifax", "Beijing", "North Pole") +) + +library(ggrepel) +ggplot(cities, aes(x, y)) + + geom_spatial_point(crs = 4326) + + stat_spatial_identity(aes(label = city), geom = "label_repel") + + coord_sf(crs = 3857) + } diff --git a/tests/testthat/test-geom-osm.R b/tests/testthat/test-annotation-map-tile.R similarity index 100% rename from tests/testthat/test-geom-osm.R rename to tests/testthat/test-annotation-map-tile.R diff --git a/tests/testthat/test-annotation-north-arrow.R b/tests/testthat/test-annotation-north-arrow.R index 2608c8b..4b9bb93 100644 --- a/tests/testthat/test-annotation-north-arrow.R +++ b/tests/testthat/test-annotation-north-arrow.R @@ -90,3 +90,43 @@ test_that("true north arrow points in the right direction", { expect_true(TRUE) }) + +test_that("all built-in styles of north arrow rotate properly", { + + p <- ggplot() + + geom_spatial_point( + mapping = aes(x, y), + data = data.frame(x = c(-63.58595, 116.41214), y = c(44.64862, 40.19063), city = c("Halifax", "Beijing")), + crs = 4326 + ) + + coord_sf(crs = 3995) + + print( + p + + annotation_north_arrow(location = "tl", which_north = "true", style = north_arrow_orienteering) + + annotation_north_arrow(location = "tr", which_north = "true", style = north_arrow_fancy_orienteering) + + annotation_north_arrow(location = "bl", which_north = "true", style = north_arrow_minimal) + + annotation_north_arrow(location = "br", which_north = "true", style = north_arrow_nautical) + + labs(caption = "All four arrows should point to the north pole and have different styles") + ) + + print( + p + + annotation_north_arrow(location = "tl", which_north = "true", style = north_arrow_orienteering()) + + annotation_north_arrow(location = "tr", which_north = "true", style = north_arrow_fancy_orienteering()) + + annotation_north_arrow(location = "bl", which_north = "true", style = north_arrow_minimal()) + + annotation_north_arrow(location = "br", which_north = "true", style = north_arrow_nautical()) + + labs(caption = "All four arrows should point to the north pole and have different styles with rotated text") + ) + + print( + p + + annotation_north_arrow(location = "tl", which_north = "grid", style = north_arrow_orienteering) + + annotation_north_arrow(location = "tr", which_north = "grid", style = north_arrow_fancy_orienteering) + + annotation_north_arrow(location = "bl", which_north = "grid", style = north_arrow_minimal) + + annotation_north_arrow(location = "br", which_north = "grid", style = north_arrow_nautical) + + labs(caption = "All four arrows should point straight up and have different styles") + ) + + expect_true(TRUE) +}) diff --git a/tests/testthat/test-annotation-scale.R b/tests/testthat/test-annotation-scale.R index 6d114a8..f63006f 100644 --- a/tests/testthat/test-annotation-scale.R +++ b/tests/testthat/test-annotation-scale.R @@ -2,7 +2,7 @@ context("test-annotation-scale.R") test_that("scale bar parameters are generated correctly", { load_longlake_data() - nc <- read_sf(system.file("shape/nc.shp", package="sf")) + nc <- sf::read_sf(system.file("shape/nc.shp", package="sf")) expect_equal( scalebar_params( @@ -42,7 +42,7 @@ test_that("scale bar parameters are generated correctly", { test_that("annotation scale works as intended", { load_longlake_data() - nc <- read_sf(system.file("shape/nc.shp", package="sf")) + nc <- sf::read_sf(system.file("shape/nc.shp", package="sf")) # defaults are ok print( diff --git a/tests/testthat/test-df_spatial.R b/tests/testthat/test-df_spatial.R index 4b3f2cc..039c58b 100644 --- a/tests/testthat/test-df_spatial.R +++ b/tests/testthat/test-df_spatial.R @@ -28,76 +28,78 @@ test_that("Spatial* objects are fortified correctly", { # load the long lake test data load_longlake_data() - # SpatialPoints - spoints_df <- as(longlake_depthdf, "Spatial") - spoints <- sp::SpatialPoints(spoints_df, proj4string = spoints_df@proj4string) - expect_df_spatial(spoints) - expect_equal(nrow(df_spatial(spoints)), length(spoints)) - expect_df_spatial(spoints_df, c("NOTES", "DEPTH.M")) - - # SpatialMultiPoints - spmultipoints_sf <- dplyr::summarise(dplyr::group_by(longlake_depthdf, NOTES), one = 1) - spmultipoints_df <- as(spmultipoints_sf, "Spatial") - spmultipoints <- as(spmultipoints_df, "SpatialMultiPoints") - - expect_df_spatial(spmultipoints) - expect_df_spatial(spmultipoints_df, c("NOTES", "one")) - expect_true(setequal(df_spatial(spmultipoints_df)$NOTES, c("mouth of inlet", "reeds", NA))) - - # SpatialLines - splines_df <- as(sf::st_zm(longlake_roadsdf), "Spatial") - splines <- sp::SpatialLines(splines_df@lines, proj4string = splines_df@proj4string) - line <- splines@lines[[1]]@Lines[[1]] - lines <- splines@lines[[1]] - - expect_df_spatial(line) - - expect_df_spatial(lines, c("coordinate_id", "piece_id")) - expect_is(df_spatial(lines)$coordinate_id, "integer") - expect_is(df_spatial(lines)$piece_id, "factor") - - expect_df_spatial(splines, c("coordinate_id", "piece_id")) - expect_is(df_spatial(splines)$coordinate_id, "integer") - expect_is(df_spatial(splines)$piece_id, "factor") - - expect_df_spatial(splines_df, c("coordinate_id", "piece_id", "FEAT_CODE")) - expect_is(df_spatial(splines_df)$coordinate_id, "integer") - expect_is(df_spatial(splines_df)$piece_id, "factor") - - # manual check of success - # ggplot(df_spatial(splines)) + geom_path(aes(x, y, group = piece_id)) - # ggplot(df_spatial(splines_df)) + geom_path(aes(x, y, group = piece_id)) - - # SpatialPolygons - spoly_df <- as(sf::st_zm(longlake_waterdf), "Spatial") - spoly <- as(spoly_df, "SpatialPolygons") - polygon <- spoly@polygons[[1]]@Polygons[[1]] - polygons <- spoly@polygons[[1]] - - expect_df_spatial(polygon, c("is_hole", "ring_direction")) - - expect_df_spatial(polygons, c("coordinate_id", "piece_id")) - expect_is(df_spatial(polygons)$coordinate_id, "integer") - expect_is(df_spatial(polygons)$piece_id, "factor") - - expect_df_spatial(spoly, c("coordinate_id", "piece_id")) - expect_is(df_spatial(spoly)$coordinate_id, "integer") - expect_is(df_spatial(spoly)$piece_id, "factor") - - expect_df_spatial(spoly_df, c("coordinate_id", "piece_id")) - expect_is(df_spatial(spoly_df)$coordinate_id, "integer") - expect_is(df_spatial(spoly_df)$piece_id, "factor") - - # manual check of success - # ggplot(df_spatial(spoly)) + geom_polypath(aes(x, y, group = piece_id)) - # ggplot(df_spatial(spoly_df)) + geom_polypath(aes(x, y, group = piece_id)) - - # sf points, multipoints, lines, polygons, and sfc - expect_identical(df_spatial(longlake_depthdf), df_spatial(spoints_df)) - expect_identical(df_spatial(spmultipoints_sf), df_spatial(spmultipoints_df)) - expect_identical(df_spatial(longlake_roadsdf), df_spatial(splines_df)) - expect_identical(df_spatial(longlake_waterdf), df_spatial(spoly_df)) - expect_df_spatial(longlake_depthdf$geometry) + withr::with_package("sf", { + # SpatialPoints + spoints_df <- as(longlake_depthdf, "Spatial") + spoints <- sp::SpatialPoints(spoints_df, proj4string = spoints_df@proj4string) + expect_df_spatial(spoints) + expect_equal(nrow(df_spatial(spoints)), length(spoints)) + expect_df_spatial(spoints_df, c("NOTES", "DEPTH.M")) + + # SpatialMultiPoints + spmultipoints_sf <- dplyr::summarise(dplyr::group_by(longlake_depthdf, NOTES), one = 1) + spmultipoints_df <- as(spmultipoints_sf, "Spatial") + spmultipoints <- as(spmultipoints_df, "SpatialMultiPoints") + + expect_df_spatial(spmultipoints) + expect_df_spatial(spmultipoints_df, c("NOTES", "one")) + expect_true(setequal(df_spatial(spmultipoints_df)$NOTES, c("mouth of inlet", "reeds", NA))) + + # SpatialLines + splines_df <- as(sf::st_zm(longlake_roadsdf), "Spatial") + splines <- sp::SpatialLines(splines_df@lines, proj4string = splines_df@proj4string) + line <- splines@lines[[1]]@Lines[[1]] + lines <- splines@lines[[1]] + + expect_df_spatial(line) + + expect_df_spatial(lines, c("coordinate_id", "piece_id")) + expect_is(df_spatial(lines)$coordinate_id, "integer") + expect_is(df_spatial(lines)$piece_id, "factor") + + expect_df_spatial(splines, c("coordinate_id", "piece_id")) + expect_is(df_spatial(splines)$coordinate_id, "integer") + expect_is(df_spatial(splines)$piece_id, "factor") + + expect_df_spatial(splines_df, c("coordinate_id", "piece_id", "FEAT_CODE")) + expect_is(df_spatial(splines_df)$coordinate_id, "integer") + expect_is(df_spatial(splines_df)$piece_id, "factor") + + # manual check of success + # ggplot(df_spatial(splines)) + geom_path(aes(x, y, group = piece_id)) + # ggplot(df_spatial(splines_df)) + geom_path(aes(x, y, group = piece_id)) + + # SpatialPolygons + spoly_df <- as(sf::st_zm(longlake_waterdf), "Spatial") + spoly <- as(spoly_df, "SpatialPolygons") + polygon <- spoly@polygons[[1]]@Polygons[[1]] + polygons <- spoly@polygons[[1]] + + expect_df_spatial(polygon, c("is_hole", "ring_direction")) + + expect_df_spatial(polygons, c("coordinate_id", "piece_id")) + expect_is(df_spatial(polygons)$coordinate_id, "integer") + expect_is(df_spatial(polygons)$piece_id, "factor") + + expect_df_spatial(spoly, c("coordinate_id", "piece_id")) + expect_is(df_spatial(spoly)$coordinate_id, "integer") + expect_is(df_spatial(spoly)$piece_id, "factor") + + expect_df_spatial(spoly_df, c("coordinate_id", "piece_id")) + expect_is(df_spatial(spoly_df)$coordinate_id, "integer") + expect_is(df_spatial(spoly_df)$piece_id, "factor") + + # manual check of success + # ggplot(df_spatial(spoly)) + geom_polypath(aes(x, y, group = piece_id)) + # ggplot(df_spatial(spoly_df)) + geom_polypath(aes(x, y, group = piece_id)) + + # sf points, multipoints, lines, polygons, and sfc + expect_identical(df_spatial(longlake_depthdf), df_spatial(spoints_df)) + expect_identical(df_spatial(spmultipoints_sf), df_spatial(spmultipoints_df)) + expect_identical(df_spatial(longlake_roadsdf), df_spatial(splines_df)) + expect_identical(df_spatial(longlake_waterdf), df_spatial(spoly_df)) + expect_df_spatial(longlake_depthdf$geometry) + }) }) test_that("Raster* objects are converted properly by df_spatial", { diff --git a/tests/testthat/test-layer-spatial-raster.R b/tests/testthat/test-layer-spatial-raster.R index 9780201..0e70286 100644 --- a/tests/testthat/test-layer-spatial-raster.R +++ b/tests/testthat/test-layer-spatial-raster.R @@ -43,7 +43,7 @@ test_that("layer-spatial works for raster objects", { annotation_spatial(longlake_osm, alpha = 0.7) + layer_spatial(longlake_depthdf) + coord_sf(crs = 3978) + - labs(caption = "Should have no grey area around the sides, rotated projection") + labs(caption = "Should have no grey area around the sides, rotated projection, slight transparency") ) # with aesthetics @@ -87,3 +87,103 @@ test_that("layer-spatial works for raster objects", { # graphical tests so... expect_true(TRUE) }) + + +test_that("layer-spatial works for raster objects", { + load_longlake_data() + + # should have little grey thing around it + print( + ggplot() + + layer_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + labs(caption = "Should have a little grey area around the sides, roughly N-S projection") + ) + + # try on gr device with no pixel concept + withr::with_pdf(file.path(tempdir(), "test.pdf"), { + print( + ggplot() + + layer_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + labs(caption = "Should have a little grey area around the sides, roughly N-S projection") + ) + }, height = 10, width = 10) + + withr::with_cairo_pdf(file.path(tempdir(), "test.pdf"), { + print( + ggplot() + + layer_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + labs(caption = "Should have a little grey area around the sides, roughly N-S projection") + ) + }, height = 10, width = 10) + + withr::with_png(file.path(tempdir(), "test.pdf"), { + print( + ggplot() + + layer_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + labs(caption = "Should have a little grey area around the sides, roughly N-S projection") + ) + }, res = 300) + + # should not have little grey thing around it + print( + ggplot() + + annotation_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + labs(caption = "Should have no grey area around the sides, roughly N-S projection") + ) + + # grey thing + print( + ggplot() + + layer_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + coord_sf(crs = 3978) + + labs(caption = "Should have a little grey area around the sides, rotated projection") + ) + + # no grey thing + print( + ggplot() + + annotation_spatial(longlake_osm, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + coord_sf(crs = 3978) + + labs(caption = "Should have no grey area around the sides, rotated projection") + ) + + # with alpha + print( + ggplot() + + annotation_spatial(longlake_osm, alpha = 0.7, lazy = TRUE) + + layer_spatial(longlake_depthdf) + + coord_sf(crs = 3978) + + labs(caption = "Should have no grey area around the sides, rotated projection, slight transparency") + ) + + # with aesthetics (currently not implemented) + # print( + # ggplot() + + # layer_spatial(longlake_osm, aes(), lazy = TRUE) + + # layer_spatial(longlake_depthdf) + # ) + # + # print( + # ggplot() + + # layer_spatial(longlake_osm, aes(alpha = stat(band1), fill = NULL), lazy = TRUE) + + # layer_spatial(longlake_depthdf) + # ) + # + # print( + # ggplot() + + # layer_spatial(longlake_depth_raster, lazy = TRUE) + + # layer_spatial(longlake_depthdf) + + # coord_sf(crs = 3978) + # ) + + # graphical tests so... + expect_true(TRUE) +}) + diff --git a/vignettes/ggspatial.Rmd b/vignettes/ggspatial.Rmd index ecfcee6..446b8a8 100644 --- a/vignettes/ggspatial.Rmd +++ b/vignettes/ggspatial.Rmd @@ -1,5 +1,5 @@ --- -title: "A framework for plotting spatial objects using ggplot2" +title: "Spatial objects using ggspatial and ggplot2" author: "Dewey Dunnington" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -11,18 +11,75 @@ vignette: > ```{r setup, include = FALSE} library(ggspatial) +rosm::set_default_cachedir(system.file("rosm.cache", package = "ggspatial")) +knitr::opts_chunk$set(fig.width = 4, fig.height = 3, dpi = 150) ``` -On first use, a GIS user who stumbles across [ggplot2](https://cran.r-project.org/package=ggplot2) will recognize much of the syntax of how plots are built from GIS language: there are layers, geometries, coordinate systems, and the ability to map attributes to the appearance of the layer (aesthetics). Using ggplot2 to plot spatial line and polygon objects has long been possible thanks to the `fortify()` implementation for `SpatialLines` and `SpatialPolygons` provided in the `ggplot2` package, and there is even an [entire wiki article](https://github.com/tidyverse/ggplot2/wiki/plotting-polygon-shapefiles) on ploting spatial polygons using ggplot2. As is, the syntax for plotting spatial objects ussing `ggplot()` is verbose and unlikely to inspire its use for the rapid creation of maps. The `ggspatial` package is designed to bridge the syntax gap between spatial objects and ggplot2 layers, allowing spatial data to be plotted efficiently. - -That said, it should be noted that `ggplot2` is **not** a GIS. The grammar of graphics (outlined by Leland Wilkinson in his [excellent book](http://www.springer.com/gp/book/9780387245447)) was never designed to produce maps, and is almost infinitely flexible at (it could be argued) the expense of performance. So to qualify the above statement, it is more accurate to say that `ggspatial` is designed to leverage the existing facilities of (the `ggplot2` implementation of) the grammar of graphics to produce effecive maps with a small amount of readable code. For large data files, it is unlikely that this package will produce these plots quickly, in the computing sense. There are many packages in the [spatial task view](https://CRAN.R-project.org/view=Spatial) that are well suited to this task. +On first use, a GIS user who stumbles across [ggplot2](https://cran.r-project.org/package=ggplot2) will recognize much of the syntax of how plots are built from GIS language: there are layers, geometries, coordinate systems, and the ability to map attributes to the appearance of the layer (aesthetics). With **ggplot** version 3.0, `geom_sf()` and `coord_sf()` let users pass simple features (**sf** package) objects as layers. Non-spatial data (data frames with XY or lon/lat coluns) and raster data are not well-supported in **ggplot2**, which the gap filled by this package. This vignette assumes that readers are familar with the usage of `ggplot2`. There are many excellent resources for learning to use `ggplot2`, one of which is the [data visualization chapter](http://r4ds.had.co.nz/data-visualisation.html) in Hadley Wickham's excellent book, [R for Data Science](http://r4ds.had.co.nz). ## Sample data +This vignette uses some data that was used in/collected for my honours thesis [@dunnington11]. The data is included as files within the package, and can be loaded using `load_longlake_data()`. + ```{r} +library(ggspatial) load_longlake_data() ``` +## Using `layer_spatial()` and `annotation_spatial()` + +Any spatial layer can be added to a `ggplot()` using `layer_spatial()` (well, any object from the **sf**, **sp**, or **raster** packages...). These layers will train the scales, meaning they will be visible unless you explicitly set the X or Y scale limits. Unlike `geom_` or `stat_` functions, `layer_spatial()` always takes its data first. Aesthetics can be passed for most types of objects, the exception being RGB rasters, which are more like backround images than data that should be mapped to a scale. Unlike `layer_spatial()`, `annotation_spatial()` layers never train the scales, so they can be used as a backdrop for other layers. + +```{r} +ggplot() + + annotation_spatial(longlake_waterdf) + + layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) +``` + +## Using north arrow and scalebar + +North arrows are added using the `annotation_north_arrow()` function, and scalebars can be added using `annotation_scale()`. These functions are "spatial-aware", meaning they know where north is and what the distance is across the plot. Thus, they don't need any arguments (unless you think the defaults aren't pretty). There are two styles for scalebars and four styles for north arrows (see `?annotation_scale` and `?annotation_north_arrow` for details). + +```{r} +ggplot() + + annotation_spatial(longlake_waterdf) + + layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) + + annotation_scale(location = "tl") + + annotation_north_arrow(location = "br", which_north = "true") +``` + +## Tile map layers + +Using the **rosm** package, **ggspatial** can add tile layers from a few predefined tile sources (see `rosm::osm.types()`), or from a custom URL pattern. The tiles will get projected into whatever projection is defined by `coord_sf()` (this defaults to the CRS of the first `geom_sf()` or `layer_spatial()` layer that was added to the plot). It's usually necessary to adjust the zoom to the right level when you know how the plot will be used...the default is to be a little more zoomed out than usual, so that the plot loads quickly. Higher zoom levels will make the plot render slowly quite fast. + +```{r, message=FALSE} +ggplot() + + annotation_map_tile(type = "osm") + + layer_spatial(longlake_depthdf, aes(col = DEPTH.M)) +``` + +There are a number of url patterns available for tiles, although how they are formatted varies. The **rosm** package uses `${x}`, `${y}`, and `${z}` for the x, y , and zoom of the tile (or `${q}` for the quadkey, if you're using Microsoft Bing maps), which is for the most part the same as for QGIS3. For some freely available tile sources, see [this blog post](https://www.xyht.com/spatial-itgis/using-openstreetmap-basemaps-qgis-3-0/), and for a number of other tile sources that are less open you'll have to dig around yourself. Bing Virtual Earth is a particularly good one (`type = "http://ecn.t3.tiles.virtualearth.net/tiles/a${q}.jpeg?g=1"`). + +## Data frames with coordinates + +Lots of good spatial data comes in tables with a longitude and latitude column (or sometimes UTM easting/northing columns). In **ggspatial** you can use `df_spatial()` to get a spatial object into a data frame with coordinate columms (much like `fortify()` does, but with a bit better coverage of spatial types). Conversely, you can use a data frame with coordinate columns in `geom_spatial_*` functions to use these data with `geom_sf()`/`coord_sf()`/`layer_spatial()`. More generally, you can use `stat_spatial_identity()` to use any geometry with `coord_sf()`. For example, a polar perspective on two cities across the world from eachother could look like this: + +```{r, message=FALSE, warning=FALSE} +cities <- data.frame( + x = c(-63.58595, 116.41214), + y = c(44.64862, 40.19063), + city = c("Halifax", "Beijing") +) + +library(ggrepel) # needed for geom_text_repel() + +ggplot(cities, aes(x, y)) + + annotation_map_tile(type = "stamenwatercolor") + + geom_spatial_point() + + stat_spatial_identity(aes(label = city), geom = "text_repel", box.padding = 1) + + coord_sf(crs = 3995) +``` +Using `df_spatial()` is probably useful mostly with point geometries so they can be used with `stat_spatial_identity()` and `geom_text_repel()`. It can also be useful with **raster** objects that need calculations done on them, as it translates them into a data frame.