diff --git a/.travis.yml b/.travis.yml index 3ae6344..7c149dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,25 @@ -language: c -sudo: required +language: r dist: trusty -script: ./travis-tool.sh run_tests +sudo: required -before_script: +apt_packages: + - libudunits2-dev + +before_install: + - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable --yes - sudo apt-get --yes --force-yes update -qq - - sudo apt-get install -y gdal-bin libgdal-dev libgdal1-dev netcdf-bin libproj-dev libv8-dev - - curl -OL http://raw.github.com/craigcitro/r-travis/master/scripts/travis-tool.sh - - chmod 755 ./travis-tool.sh - - ./travis-tool.sh bootstrap - - ./travis-tool.sh install_deps + - sudo apt-get install -y libudunits2-dev libproj-dev libgeos-dev libgdal-dev netcdf-bin libv8-dev -install_github: - - ropensci/gistr +compiler: + - clang -after_failure: - - ./travis-tool.sh dump_logs +warnings_are_errors: false + +r_packages: + - colorspace + +r_github_packages: + - ropensci/gistr env: global: diff --git a/DESCRIPTION b/DESCRIPTION index 537ba09..19ad6f7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -34,6 +34,7 @@ Suggests: gistr, testthat, knitr, - leaflet + leaflet, + sf Enhances: RColorBrewer RoxygenNote: 5.0.1 diff --git a/NAMESPACE b/NAMESPACE index 8297e53..c77a705 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,6 +28,9 @@ S3method(geojson_json,data.frame) S3method(geojson_json,geo_list) S3method(geojson_json,list) S3method(geojson_json,numeric) +S3method(geojson_json,sf) +S3method(geojson_json,sfc) +S3method(geojson_json,sfg) S3method(geojson_list,SpatialCollections) S3method(geojson_list,SpatialGrid) S3method(geojson_list,SpatialGridDataFrame) @@ -46,6 +49,9 @@ S3method(geojson_list,geo_list) S3method(geojson_list,json) S3method(geojson_list,list) S3method(geojson_list,numeric) +S3method(geojson_list,sf) +S3method(geojson_list,sfc) +S3method(geojson_list,sfg) S3method(geojson_read,character) S3method(geojson_read,location) S3method(geojson_sp,geo_json) @@ -71,6 +77,9 @@ S3method(geojson_write,geo_list) S3method(geojson_write,json) S3method(geojson_write,list) S3method(geojson_write,numeric) +S3method(geojson_write,sf) +S3method(geojson_write,sfc) +S3method(geojson_write,sfg) S3method(lint,SpatialGrid) S3method(lint,SpatialGridDataFrame) S3method(lint,SpatialLines) diff --git a/R/geojson_json.R b/R/geojson_json.R index dd026f2..e067781 100644 --- a/R/geojson_json.R +++ b/R/geojson_json.R @@ -2,7 +2,7 @@ #' #' @export #' -#' @param input Input list, data.frame, or spatial class. Inputs can also be dplyr \code{tbl_df} +#' @param input Input list, data.frame, spatial class, or sf class. Inputs can also be dplyr \code{tbl_df} #' class since it inherits from \code{data.frame}. #' @param lat (character) Latitude name. The default is \code{NULL}, and we attempt to guess. #' @param lon (character) Longitude name. The default is \code{NULL}, and we attempt to guess. @@ -25,6 +25,15 @@ #' Also note that with sp classes we do make a round-trip, using \code{\link[rgdal]{writeOGR}} #' to write GeoJSON to disk, then read it back in. This is fast and we don't have to think #' about it too much, but this disk round-trip is not ideal. +#' +#' For sf classes (sf, sfc, sfg), the following conversions are made: +#' +#' \itemize{ +#' \item sfg: the approprite geometry \code{Point, LineString, Polygon, MultiPoint, +#' MultiLineString, MultiPolygon, GeometryCollection} +#' \item sfc: \code{GeometryCollection}, unless the sfc is length 1, then the geometry as above +#' \item sf: \code{FeatureCollection} +#' } #' #' @examples \dontrun{ #' # From a numeric vector of length 2, making a point type @@ -170,7 +179,29 @@ #' poly <- SpatialPolygons(list(poly1, poly2), 1:2) #' dat <- SpatialCollections(pts, polygons = poly) #' geojson_json(dat) -#' +#' +#' # From sf classes: +#' if (require(sf)) { +#' ## sfg (a single simple features geometry) +#' p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +#' poly <- rbind(c(1,1), c(1,2), c(2,2), c(1,1)) +#' poly_sfg <-st_polygon(list(p1)) +#' geojson_json(poly_sfg) +#' +#' ## sfc (a collection of geometries) +#' p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +#' p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) +#' poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) +#' geojson_json(poly_sfc) +#' +#' ## sf (collection of geometries with attributes) +#' p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +#' p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) +#' poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) +#' poly_sf <- st_sf(foo = c("a", "b"), bar = 1:2, poly_sfc) +#' geojson_json(poly_sf) +#' } +#' #' ## Pretty print a json string #' geojson_json(c(-99.74,32.45)) #' geojson_json(c(-99.74,32.45)) %>% pretty @@ -242,10 +273,30 @@ geojson_json.SpatialPixelsDataFrame <- function(input, lat = NULL, lon = NULL, g class_json(geojson_rw(input, target = "char"), ...) } +# sf classes --------------------------------- + +#' @export +geojson_json.sf <- function(input, lat = NULL, lon = NULL, group = NULL, + geometry = "point", type='FeatureCollection', ...) { + as.json(geojson_list(input)) +} + +#' @export +geojson_json.sfc <- function(input, lat = NULL, lon = NULL, group = NULL, + geometry = "point", type='FeatureCollection', ...) { + as.json(geojson_list(input)) +} + +#' @export +geojson_json.sfg <- function(input, lat = NULL, lon = NULL, group = NULL, + geometry = "point", type='FeatureCollection', ...) { + as.json(geojson_list(input)) +} + # spatial classes from rgeos -------------------------- #' @export geojson_json.SpatialRings <- function(input, lat = NULL, lon = NULL, group = NULL, - geometry = "point", type='FeatureCollection', ...) { + geometry = "point", type='FeatureCollection', ...) { class_json(geojson_rw(input, target = "char"), ...) } @@ -257,7 +308,7 @@ geojson_json.SpatialRingsDataFrame <- function(input, lat = NULL, lon = NULL, gr #' @export geojson_json.SpatialCollections <- function(input, lat = NULL, lon = NULL, group = NULL, - geometry = "point", type='FeatureCollection', ...) { + geometry = "point", type='FeatureCollection', ...) { lapply(geojson_rw(input, target = "char", ...), class_json) } @@ -292,6 +343,6 @@ geojson_json.list <- function(input, lat = NULL, lon = NULL, group = NULL, #' @export geojson_json.geo_list <- function(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) { - + to_json(unclass(input), ...) } diff --git a/R/geojson_list.R b/R/geojson_list.R index f892fa8..5174e0a 100644 --- a/R/geojson_list.R +++ b/R/geojson_list.R @@ -2,7 +2,7 @@ #' #' @export #' -#' @param input Input list, data.frame, or spatial class. Inputs can also be dplyr \code{tbl_df} +#' @param input Input list, data.frame, spatial class, or sf class. Inputs can also be dplyr \code{tbl_df} #' class since it inherits from \code{data.frame}. #' @param lat (character) Latitude name. The default is \code{NULL}, and we attempt to guess. #' @param lon (character) Longitude name. The default is \code{NULL}, and we attempt to guess. @@ -24,6 +24,15 @@ #' Also note that with sp classes we do make a round-trip, using \code{\link[rgdal]{writeOGR}} #' to write GeoJSON to disk, then read it back in. This is fast and we don't have to think #' about it too much, but this disk round-trip is not ideal. +#' +#' For sf classes (sf, sfc, sfg), the following conversions are made: +#' +#' \itemize{ +#' \item sfg: the approprite geometry \code{Point, LineString, Polygon, MultiPoint, +#' MultiLineString, MultiPolygon, GeometryCollection} +#' \item sfc: \code{GeometryCollection}, unless the sfc is length 1, then the geometry as above +#' \item sf: \code{FeatureCollection} +#' } #' #' For \code{list} and \code{data.frame} objects, you don't have to pass in \code{lat} and #' \code{lon} parameters if they are named appropriately (e.g., lat/latitude, lon/long/longitude), @@ -158,6 +167,29 @@ #' out$SpatialPoints #' out$SpatialPolygons #' } +#' +#' # From sf classes: +#' if (require(sf)) { +#' ## sfg (a single simple features geometry) +#' p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +#' poly <- rbind(c(1,1), c(1,2), c(2,2), c(1,1)) +#' poly_sfg <-st_polygon(list(p1)) +#' geojson_list(poly_sfg) +#' +#' ## sfc (a collection of geometries) +#' p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +#' p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) +#' poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) +#' geojson_list(poly_sfc) +#' +#' ## sf (collection of geometries with attributes) +#' p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +#' p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) +#' poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) +#' poly_sf <- st_sf(foo = c("a", "b"), bar = 1:2, poly_sfc) +#' geojson_list(poly_sf) +#' } +#' geojson_list <- function(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) { @@ -259,6 +291,138 @@ donotnull <- function(x, fun, ...) { } } +# sf classes --------------------------------- + +#' @export +geojson_list.sf <- function(input, lat = NULL, lon = NULL, group = NULL, + geometry = "point", type = "FeatureCollection", ...) { + + # input <- detect_convert_crs(input) + is_wgs84(input) + + sf_col <- get_sf_column_name(input) + ## Get the sfc column + sfc <- unclass(input[[sf_col]]) + ## remove the sf class so can extract the attributes using `[` + attr_df <- as.data.frame(input)[, setdiff(names(input), sf_col), + drop = FALSE] + + type <- "FeatureCollection" + features <- lapply(seq_len(nrow(input)), + function(i) { + list(type = "Feature", + properties = as.list(attr_df[i, , drop = FALSE]), + geometry = unclass(geojson_list(sfc[[i]])) + ) + }) + + out <- list(type = type, features = features) + + as.geo_list(tg_compact(out), from = "sf") +} + +#' @export +geojson_list.sfc <- function(input, lat = NULL, lon = NULL, group = NULL, + geometry = "point", type = "FeatureCollection", ...) { + # input <- detect_convert_crs(input) + is_wgs84(input) + ## A GeometryCollection except if length 1, then just return the geometry + + if (length(input) == 1) { + return(geojson_list(input[[1]])) + } else { + out <- list(type = "GeometryCollection", + geometries = lapply(input, function(x) unclass(geojson_list(x)))) + } + as.geo_list(out, from = "sfc") +} + +#' @export +geojson_list.sfg <- function(input, lat = NULL, lon = NULL, group = NULL, + geometry = "point", type = "FeatureCollection", ...) { + type <- switch_geom_type(get_geometry_type(input)) + + # input <- detect_convert_crs(input) + + if (type == "GeometryCollection") { + geometries <- lapply(input, function(x) unclass(geojson_list(x))) + out <- list(type = type, geometries = geometries) + } else { + coordinates <- make_coords(input) + out <- list(type = type, coordinates = coordinates) + } + as.geo_list(out, from = "sfg") +} + +switch_geom_type <- function(x) { + switch(x, + "POINT" = "Point", + "LINESTRING" = "LineString", + "POLYGON" = "Polygon", + "MULTIPOINT" = "MultiPoint", + "MULTILINESTRING" = "MultiLineString", + "MULTIPOLYGON" = "MultiPolygon", + "GEOMETRY" = "GeometryCollection", + "GEOMETRYCOLLECTION" = "GeometryCollection" + ) +} + +get_sf_column_name <- function(x) attr(x, "sf_column") + +## Get the geometry type +get_geometry_type <- function(x) UseMethod("get_geometry_type") +get_geometry_type.sfc <- function(x) strsplit(class(x)[1], "_")[[1]][2] +get_geometry_type.sfg <- function(x) class(x)[2] + +## Make coordinates, dropping M dimension if it's there +make_coords <- function(input) { + dim <- class(input)[1] + m_loc <- regexpr("M", dim) + + if (m_loc > 0) { + message("removing M dimension as not supported in GeoJSON format") + return(drop_m(unclass(input), m_loc)) + } + + unclass(input) +} + +drop_m <- function(input, m_loc) UseMethod("drop_m") +drop_m.list <- function(input, m_loc) lapply(input, drop_m, m_loc = m_loc) +drop_m.numeric <- function(input, m_loc) input[-m_loc] +drop_m.matrix <- function(input, m_loc) input[, -m_loc, drop = FALSE] + +# detect_convert_crs <- function(x) { +# if (!is_wgs84(x, warn = FALSE)) { +# if (!requireNamespace("sf", quietly = TRUE)) { +# stop("Your input is not in a CRS that geojson supports and you don't have the 'sf' package installed. Please install and try again") +# } else { +# message("Converting CRS from EPSG:", get_epsg(x), " to WGS84.") +# x <- sf::st_transform(x, 4326) +# } +# } +# x +# } + +is_wgs84 <- function(x, warn = TRUE) { + epsg <- get_epsg(x) + is_it <- is.na(epsg) || epsg == 4326 # Give NA epsg the benefit of the doubt + if (!is_it && warn) { + warning("Input CRS is not WGS84 (epsg:4326), the standard for GeoJSON") + } + is_it +} + +## Get epsg code +get_epsg <- function(x) UseMethod("get_epsg") + +get_epsg.sf <- function(x) { + geom_col <- get_sf_column_name(x) + get_epsg(x[[geom_col]]) +} + +get_epsg.sfc <- function(x) attr(x, "crs")[["epsg"]] + # regular R classes -------------------------- #' @export geojson_list.numeric <- function(input, lat = NULL, lon = NULL, group = NULL, @@ -269,7 +433,7 @@ geojson_list.numeric <- function(input, lat = NULL, lon = NULL, group = NULL, #' @export geojson_list.data.frame <- function(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) { - + tmp <- guess_latlon(names(input), lat, lon) as.geo_list(df_to_geo_list(x = input, lat = tmp$lat, lon = tmp$lon, geometry = geometry, type = type, group = group), "data.frame") @@ -278,7 +442,7 @@ geojson_list.data.frame <- function(input, lat = NULL, lon = NULL, group = NULL, #' @export geojson_list.list <- function(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) { - + if (geometry == "polygon") lint_polygon_list(input) tmp <- if (!is.named(input)) { list(lon = NULL, lat = NULL) @@ -291,7 +455,7 @@ geojson_list.list <- function(input, lat = NULL, lon = NULL, group = NULL, #' @export geojson_list.geo_list <- function(input, lat = NULL, lon = NULL, group = NULL, - geometry = "point", type = "FeatureCollection", ...) { + geometry = "point", type = "FeatureCollection", ...) { return(input) } @@ -299,7 +463,7 @@ geojson_list.geo_list <- function(input, lat = NULL, lon = NULL, group = NULL, #' @export geojson_list.json <- function(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) { - + output_list <- jsonlite::fromJSON(input, FALSE, ...) as.geo_list(output_list, from = "json") } diff --git a/R/geojson_write.r b/R/geojson_write.r index 68be340..447d808 100644 --- a/R/geojson_write.r +++ b/R/geojson_write.r @@ -4,7 +4,7 @@ #' @importFrom jsonlite toJSON fromJSON unbox #' @export #' -#' @param input Input list, data.frame, or spatial class. Inputs can also be +#' @param input Input list, data.frame, spatial class, or sf class. Inputs can also be #' dplyr \code{tbl_df} class since it inherits from \code{data.frame}. #' @param lat (character) Latitude name. The default is \code{NULL}, and we #' attempt to guess. @@ -135,6 +135,13 @@ #' dat <- SpatialCollections(points = us_cities, polygons = poly) #' geojson_write(dat) #' } +#' +#' # From sf classes: +#' if (require(sf)) { +#' file <- system.file("examples", "feature_collection.geojson", package = "geojsonio") +#' sf_fc <- st_read(file, quiet = TRUE) +#' geojson_write(sf_fc) +#' } geojson_write <- function(input, lat = NULL, lon = NULL, geometry = "point", group = NULL, file = "myfile.geojson", @@ -211,16 +218,16 @@ geojson_write.SpatialGridDataFrame <- function(input, lat = NULL, lon = NULL, ge #' @export geojson_write.SpatialPixels <- function(input, lat = NULL, lon = NULL, geometry = "point", - group = NULL, file = "myfile.geojson", - overwrite = TRUE, precision = NULL, ...) { + group = NULL, file = "myfile.geojson", + overwrite = TRUE, precision = NULL, ...) { write_geojson(as(input, "SpatialPointsDataFrame"), file, precision = precision, ...) return(as.geojson(file, "SpatialPixels")) } #' @export geojson_write.SpatialPixelsDataFrame <- function(input, lat = NULL, lon = NULL, geometry = "point", - group = NULL, file = "myfile.geojson", - overwrite = TRUE, precision = NULL, ...) { + group = NULL, file = "myfile.geojson", + overwrite = TRUE, precision = NULL, ...) { write_geojson(as(input, "SpatialPointsDataFrame"), file, precision = precision, ...) return(as.geojson(file, "SpatialPixelsDataFrame")) } @@ -244,7 +251,7 @@ geojson_write.SpatialRingsDataFrame <- function(input, lat = NULL, lon = NULL, g #' @export geojson_write.SpatialCollections <- function(input, lat = NULL, lon = NULL, geometry = "point", - group = NULL, file = "myfile.geojson", + group = NULL, file = "myfile.geojson", overwrite = TRUE, precision = NULL, ...) { ptfile <- iter_spatialcoll(input@pointobj, file, precision = precision, ...) lfile <- iter_spatialcoll(input@lineobj, file, precision = precision, ...) @@ -260,6 +267,28 @@ iter_spatialcoll <- function(z, file, precision = NULL, ...) { } } +## sf classes ----------------------------------------------------------------- +#' @export +geojson_write.sf <- function(input, lat = NULL, lon = NULL, geometry = "point", + group = NULL, file = "myfile.geojson", + overwrite = TRUE, ...) { + geojson_write(geojson_list(input), file = file, overwrite = overwrite) +} + +#' @export +geojson_write.sfc <- function(input, lat = NULL, lon = NULL, geometry = "point", + group = NULL, file = "myfile.geojson", + overwrite = TRUE, ...) { + geojson_write(geojson_list(input), file = file, overwrite = overwrite) +} + +#' @export +geojson_write.sfg <- function(input, lat = NULL, lon = NULL, geometry = "point", + group = NULL, file = "myfile.geojson", + overwrite = TRUE, ...) { + geojson_write(geojson_list(input), file = file, overwrite = overwrite) +} + ## normal R classes ----------------- #' @export geojson_write.numeric <- function(input, lat = NULL, lon = NULL, geometry = "point", diff --git a/man/geojson_json.Rd b/man/geojson_json.Rd index 9695953..a822b74 100644 --- a/man/geojson_json.Rd +++ b/man/geojson_json.Rd @@ -8,7 +8,7 @@ geojson_json(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) } \arguments{ -\item{input}{Input list, data.frame, or spatial class. Inputs can also be dplyr \code{tbl_df} +\item{input}{Input list, data.frame, spatial class, or sf class. Inputs can also be dplyr \code{tbl_df} class since it inherits from \code{data.frame}.} \item{lat}{(character) Latitude name. The default is \code{NULL}, and we attempt to guess.} @@ -42,6 +42,15 @@ option for sp class objects. Also note that with sp classes we do make a round-trip, using \code{\link[rgdal]{writeOGR}} to write GeoJSON to disk, then read it back in. This is fast and we don't have to think about it too much, but this disk round-trip is not ideal. + +For sf classes (sf, sfc, sfg), the following conversions are made: + +\itemize{ + \item sfg: the approprite geometry \code{Point, LineString, Polygon, MultiPoint, + MultiLineString, MultiPolygon, GeometryCollection} + \item sfc: \code{GeometryCollection}, unless the sfc is length 1, then the geometry as above + \item sf: \code{FeatureCollection} +} } \examples{ \dontrun{ @@ -189,6 +198,28 @@ poly <- SpatialPolygons(list(poly1, poly2), 1:2) dat <- SpatialCollections(pts, polygons = poly) geojson_json(dat) +# From sf classes: +if (require(sf)) { +## sfg (a single simple features geometry) + p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) + poly <- rbind(c(1,1), c(1,2), c(2,2), c(1,1)) + poly_sfg <-st_polygon(list(p1)) + geojson_json(poly_sfg) + +## sfc (a collection of geometries) + p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) + p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) + poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) + geojson_json(poly_sfc) + +## sf (collection of geometries with attributes) + p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) + p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) + poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) + poly_sf <- st_sf(foo = c("a", "b"), bar = 1:2, poly_sfc) + geojson_json(poly_sf) +} + ## Pretty print a json string geojson_json(c(-99.74,32.45)) geojson_json(c(-99.74,32.45)) \%>\% pretty diff --git a/man/geojson_list.Rd b/man/geojson_list.Rd index 53f3844..bbcc9dd 100644 --- a/man/geojson_list.Rd +++ b/man/geojson_list.Rd @@ -8,7 +8,7 @@ geojson_list(input, lat = NULL, lon = NULL, group = NULL, geometry = "point", type = "FeatureCollection", ...) } \arguments{ -\item{input}{Input list, data.frame, or spatial class. Inputs can also be dplyr \code{tbl_df} +\item{input}{Input list, data.frame, spatial class, or sf class. Inputs can also be dplyr \code{tbl_df} class since it inherits from \code{data.frame}.} \item{lat}{(character) Latitude name. The default is \code{NULL}, and we attempt to guess.} @@ -41,6 +41,15 @@ Also note that with sp classes we do make a round-trip, using \code{\link[rgdal] to write GeoJSON to disk, then read it back in. This is fast and we don't have to think about it too much, but this disk round-trip is not ideal. +For sf classes (sf, sfc, sfg), the following conversions are made: + +\itemize{ + \item sfg: the approprite geometry \code{Point, LineString, Polygon, MultiPoint, + MultiLineString, MultiPolygon, GeometryCollection} + \item sfc: \code{GeometryCollection}, unless the sfc is length 1, then the geometry as above + \item sf: \code{FeatureCollection} +} + For \code{list} and \code{data.frame} objects, you don't have to pass in \code{lat} and \code{lon} parameters if they are named appropriately (e.g., lat/latitude, lon/long/longitude), as they will be auto-detected. If they can not be found, the function will stop and warn @@ -175,5 +184,28 @@ out <- geojson_list(dat) out$SpatialPoints out$SpatialPolygons } + +# From sf classes: +if (require(sf)) { +## sfg (a single simple features geometry) + p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) + poly <- rbind(c(1,1), c(1,2), c(2,2), c(1,1)) + poly_sfg <-st_polygon(list(p1)) + geojson_list(poly_sfg) + +## sfc (a collection of geometries) + p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) + p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) + poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) + geojson_list(poly_sfc) + +## sf (collection of geometries with attributes) + p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) + p2 <- rbind(c(5,5), c(5,6), c(4,5), c(5,5)) + poly_sfc <- st_sfc(st_polygon(list(p1)), st_polygon(list(p2))) + poly_sf <- st_sf(foo = c("a", "b"), bar = 1:2, poly_sfc) + geojson_list(poly_sf) +} + } diff --git a/man/geojson_write.Rd b/man/geojson_write.Rd index 1ba46fc..b898ffe 100644 --- a/man/geojson_write.Rd +++ b/man/geojson_write.Rd @@ -9,7 +9,7 @@ geojson_write(input, lat = NULL, lon = NULL, geometry = "point", precision = NULL, ...) } \arguments{ -\item{input}{Input list, data.frame, or spatial class. Inputs can also be +\item{input}{Input list, data.frame, spatial class, or sf class. Inputs can also be dplyr \code{tbl_df} class since it inherits from \code{data.frame}.} \item{lat}{(character) Latitude name. The default is \code{NULL}, and we @@ -150,6 +150,13 @@ coordinates(us_cities) <- ~long+lat dat <- SpatialCollections(points = us_cities, polygons = poly) geojson_write(dat) } + +# From sf classes: +if (require(sf)) { + file <- system.file("examples", "feature_collection.geojson", package = "geojsonio") + sf_fc <- st_read(file, quiet = TRUE) + geojson_write(sf_fc) +} } \seealso{ \code{\link{geojson_list}}, \code{\link{geojson_json}} diff --git a/tests/testthat/test-sf_classes.R b/tests/testthat/test-sf_classes.R new file mode 100644 index 0000000..333f639 --- /dev/null +++ b/tests/testthat/test-sf_classes.R @@ -0,0 +1,324 @@ +context("sf classes") +suppressPackageStartupMessages(library(sf, quietly = TRUE)) + +file <- system.file("examples", "feature_collection.geojson", package = "geojsonio") +testfc <- st_read(file, quiet = TRUE) + +test_that("fc utility functions work", { + expect_equal(get_epsg(testfc), 4326) + expect_equal(get_epsg(testfc$geometry), 4326) + expect_equal(get_sf_column_name(testfc), "geometry") + expect_true(is_wgs84(testfc)) + + expect_equal(get_geometry_type(testfc$geometry), "GEOMETRY") + expect_equal(switch_geom_type(get_geometry_type(testfc$geometry)), "GeometryCollection") + + testfc_3005 <- st_transform(testfc, 3005) + expect_false(suppressWarnings(is_wgs84(testfc_3005))) + expect_warning(is_wgs84(testfc_3005), "WGS84") + # expect_message(detect_convert_crs(testfc_3005), "EPSG:3005 to WGS84") + # expect_true(is_wgs84(suppressMessages(detect_convert_crs(testfc_3005)))) +}) + +## POINT +p_list <- lapply(list(c(3.2,4), c(3,4.6), c(3.8,4.4)), st_point) +pt_sfc <- st_sfc(p_list) +pt_sf <- st_sf(x = c("a", "b", "c"), pt_sfc) + +test_that("geojson_list works with points", { + point_sfg_list <- geojson_list(pt_sfc[[1]]) + point_sfc_list <- geojson_list(pt_sfc) + point_sf_list <- geojson_list(pt_sf) + + expect_s3_class(point_sfg_list, "geo_list") + expect_s3_class(point_sfc_list, "geo_list") + expect_s3_class(point_sf_list, "geo_list") + + expect_length(point_sfg_list, 2) + expect_equal(point_sfg_list$type, "Point") + expect_length(point_sfg_list$coordinates, 2) + + expect_length(point_sfc_list, 2) + expect_equal(point_sfc_list$type, "GeometryCollection") + expect_length(point_sfc_list$geometries, 3) + expect_equal(sapply(point_sfc_list$geometries, function(x) length(x$coordinates)), + c(2,2,2)) + + expect_length(point_sf_list, 2) + expect_equal(point_sf_list$type, "FeatureCollection") + expect_length(point_sf_list$features, 3) + expect_equal(lapply(point_sf_list$features, `[[`, "geometry"), + point_sfc_list$geometries) +}) + +test_that("geojson_json works with points", { + pt_sfg_json <- geojson_json(pt_sfc[[1]]) + pt_sfc_json <- geojson_json(pt_sfc) + pt_sf_json <- geojson_json(pt_sf) + + expect_equal(unclass(pt_sfg_json), + "{\"type\":\"Point\",\"coordinates\":[3.2,4]}") + + expect_equal(unclass(pt_sfc_json), + "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[3.2,4]},{\"type\":\"Point\",\"coordinates\":[3,4.6]},{\"type\":\"Point\",\"coordinates\":[3.8,4.4]}]}") + + expect_equal(unclass(pt_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[3.2,4]}},{\"type\":\"Feature\",\"properties\":{\"x\":\"b\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[3,4.6]}},{\"type\":\"Feature\",\"properties\":{\"x\":\"c\"},\"geometry\":{\"type\":\"Point\",\"coordinates\":[3.8,4.4]}}]}") +}) + +# ## MULTIPOINT +p <- rbind(c(3.2,4), c(3,4.6), c(3.8,4.4), c(3.5,3.8), c(3.4,3.6), c(3.9,4.5)) +mp_sfg <- st_multipoint(p) +mp_sfc <- st_sfc(mp_sfg) +mp_sf <- st_sf(x = "a", mp_sfc) + +test_that("geojson_list works with multipoints", { + mp_sfg_list <- geojson_list(mp_sfg) + mp_sfc_list <- geojson_list(mp_sfc) + mp_sf_list <- geojson_list(mp_sf) + + expect_s3_class(mp_sfg_list, "geo_list") + expect_s3_class(mp_sfc_list, "geo_list") + expect_s3_class(mp_sf_list, "geo_list") +}) + +test_that("geojson_json works with multipoints", { + mp_sfg_json <- geojson_json(mp_sfg) + mp_sfc_json <- geojson_json(mp_sfc) + mp_sf_json <- geojson_json(mp_sf) + + expect_s3_class(mp_sfg_json, "geo_json") + expect_s3_class(mp_sfc_json, "geo_json") + expect_s3_class(mp_sf_json, "geo_json") + + expect_equal(unclass(mp_sfg_json), + "{\"type\":\"MultiPoint\",\"coordinates\":[[3.2,4],[3,4.6],[3.8,4.4],[3.5,3.8],[3.4,3.6],[3.9,4.5]]}") + + expect_equal(unclass(mp_sfc_json), + "{\"type\":\"MultiPoint\",\"coordinates\":[[3.2,4],[3,4.6],[3.8,4.4],[3.5,3.8],[3.4,3.6],[3.9,4.5]]}") + + expect_equal(unclass(mp_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"MultiPoint\",\"coordinates\":[[3.2,4],[3,4.6],[3.8,4.4],[3.5,3.8],[3.4,3.6],[3.9,4.5]]}}]}") +}) + +## POLYGON +p1 <- rbind(c(0,0), c(1,0), c(3,2), c(2,4), c(1,4), c(0,0)) +p2 <- rbind(c(1,1), c(1,2), c(2,2), c(1,1)) +pol_sfg <-st_polygon(list(p1,p2)) +pol_sfc <- st_sfc(pol_sfg) +pol_sf <- st_sf(x = "a", pol_sfc) + +test_that("geojson_list works with polygons", { + pol_sfg_list <- geojson_list(pol_sfg) + pol_sfc_list <- geojson_list(pol_sfc) + pol_sf_list <- geojson_list(pol_sf) + + expect_s3_class(pol_sfg_list, "geo_list") + expect_s3_class(pol_sfc_list, "geo_list") + expect_s3_class(pol_sf_list, "geo_list") +}) + +test_that("geojson_json works with polygons", { + pol_sfg_json <- geojson_json(pol_sfg) + pol_sfc_json <- geojson_json(pol_sfc) + pol_sf_json <- geojson_json(pol_sf) + + expect_s3_class(pol_sfg_json, "geo_json") + expect_s3_class(pol_sfc_json, "geo_json") + expect_s3_class(pol_sf_json, "geo_json") + + expect_equal(unclass(pol_sfg_json), + "{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]]}") + + expect_equal(unclass(pol_sfc_json), + "{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]]}") + + expect_equal(unclass(pol_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]]}}]}") +}) + +## MULTIPOLYGON +p3 <- rbind(c(3,0), c(4,0), c(4,1), c(3,1), c(3,0)) +p4 <- rbind(c(3.3,0.3), c(3.8,0.3), c(3.8,0.8), c(3.3,0.8), c(3.3,0.3))[5:1,] +p5 <- rbind(c(3,3), c(4,2), c(4,3), c(3,3)) +mpol_sfg <- st_multipolygon(list(list(p1,p2), list(p3,p4), list(p5))) +mpol_sfc <- st_sfc(mpol_sfg) +mpol_sf <- st_sf(x = "a", mpol_sfc) + +test_that("geojson_list works with multipolygons", { + mpol_sfg_list <- geojson_list(mpol_sfg) + mpol_sfc_list <- geojson_list(mpol_sfc) + mpol_sf_list <- geojson_list(mpol_sf) + + expect_s3_class(mpol_sfg_list, "geo_list") + expect_s3_class(mpol_sfc_list, "geo_list") + expect_s3_class(mpol_sf_list, "geo_list") +}) + +test_that("geojson_json works with multipolygons", { + mpol_sfg_json <- geojson_json(mpol_sfg) + mpol_sfc_json <- geojson_json(mpol_sfc) + mpol_sf_json <- geojson_json(mpol_sf) + + expect_s3_class(mpol_sfg_json, "geo_json") + expect_s3_class(mpol_sfc_json, "geo_json") + expect_s3_class(mpol_sf_json, "geo_json") + + expect_equal(unclass(mpol_sfg_json), + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]],[[[3,0],[4,0],[4,1],[3,1],[3,0]],[[3.3,0.3],[3.3,0.8],[3.8,0.8],[3.8,0.3],[3.3,0.3]]],[[[3,3],[4,2],[4,3],[3,3]]]]}") + + expect_equal(unclass(mpol_sfc_json), + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]],[[[3,0],[4,0],[4,1],[3,1],[3,0]],[[3.3,0.3],[3.3,0.8],[3.8,0.8],[3.8,0.3],[3.3,0.3]]],[[[3,3],[4,2],[4,3],[3,3]]]]}") + + expect_equal(unclass(mpol_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]],[[[3,0],[4,0],[4,1],[3,1],[3,0]],[[3.3,0.3],[3.3,0.8],[3.8,0.8],[3.8,0.3],[3.3,0.3]]],[[[3,3],[4,2],[4,3],[3,3]]]]}}]}") +}) + +## TO TEST + +## LINESTRING +s1 <- rbind(c(0,3),c(0,4),c(1,5),c(2,5)) +ls_sfg <- st_linestring(s1) +ls_sfc <- st_sfc(ls_sfg) +ls_sf <- st_sf(x = "a", ls_sfc) + +test_that("geojson_list works with linestrings", { + ls_sfg_list <- geojson_list(ls_sfg) + ls_sfc_list <- geojson_list(ls_sfc) + ls_sf_list <- geojson_list(ls_sf) + + expect_s3_class(ls_sfg_list, "geo_list") + expect_s3_class(ls_sfc_list, "geo_list") + expect_s3_class(ls_sf_list, "geo_list") +}) + +test_that("geojson_json works with multilinestrings", { + + ls_sfg_json <- geojson_json(ls_sfg) + ls_sfc_json <- geojson_json(ls_sfc) + ls_sf_json <- geojson_json(ls_sf) + + expect_s3_class(ls_sfg_json, "geo_json") + expect_s3_class(ls_sfc_json, "geo_json") + expect_s3_class(ls_sf_json, "geo_json") + + expect_equal(unclass(ls_sfg_json), + "{\"type\":\"LineString\",\"coordinates\":[[0,3],[0,4],[1,5],[2,5]]}") + + expect_equal(unclass(ls_sfc_json), + "{\"type\":\"LineString\",\"coordinates\":[[0,3],[0,4],[1,5],[2,5]]}") + + expect_equal(unclass(ls_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[0,3],[0,4],[1,5],[2,5]]}}]}") +}) + +## MULTILINESTRING +s2 <- rbind(c(0.2,3), c(0.2,4), c(1,4.8), c(2,4.8)) +s3 <- rbind(c(0,4.4), c(0.6,5)) +mls_sfg <- st_multilinestring(list(s1,s2,s3)) +mls_sfc <- st_sfc(mls_sfg) +mls_sf <- st_sf(x = "a", mls_sfc) + +test_that("geojson_list works with multilinestrings", { + mls_sfg_list <- geojson_list(ls_sfg) + mls_sfc_list <- geojson_list(ls_sfc) + mls_sf_list <- geojson_list(ls_sf) + + expect_s3_class(mls_sfg_list, "geo_list") + expect_s3_class(mls_sfc_list, "geo_list") + expect_s3_class(mls_sf_list, "geo_list") +}) + +test_that("geojson_json works with multilinestrings", { + + mls_sfg_json <- geojson_json(mls_sfg) + mls_sfc_json <- geojson_json(mls_sfc) + mls_sf_json <- geojson_json(mls_sf) + + expect_s3_class(mls_sfg_json, "geo_json") + expect_s3_class(mls_sfc_json, "geo_json") + expect_s3_class(mls_sf_json, "geo_json") + + expect_equal(unclass(mls_sfg_json), + "{\"type\":\"MultiLineString\",\"coordinates\":[[[0,3],[0,4],[1,5],[2,5]],[[0.2,3],[0.2,4],[1,4.8],[2,4.8]],[[0,4.4],[0.6,5]]]}") + + expect_equal(unclass(mls_sfc_json), + "{\"type\":\"MultiLineString\",\"coordinates\":[[[0,3],[0,4],[1,5],[2,5]],[[0.2,3],[0.2,4],[1,4.8],[2,4.8]],[[0,4.4],[0.6,5]]]}") + + expect_equal(unclass(mls_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"MultiLineString\",\"coordinates\":[[[0,3],[0,4],[1,5],[2,5]],[[0.2,3],[0.2,4],[1,4.8],[2,4.8]],[[0,4.4],[0.6,5]]]}}]}") +}) + +# ## GEOMETRYCOLLECTION +gc_sfg <- st_geometrycollection(list(mp_sfg, mpol_sfg, ls_sfg)) +gc_sfc <- st_sfc(gc_sfg) +gc_sf <- st_sf(x = "a", gc_sfc) + +test_that("geojson_list works with geometry collections", { + gc_sfg_list <- geojson_list(gc_sfg) + gc_sfc_list <- geojson_list(gc_sfc) + gc_sf_list <- geojson_list(gc_sf) + + expect_s3_class(gc_sfg_list, "geo_list") + expect_s3_class(gc_sfc_list, "geo_list") + expect_s3_class(gc_sf_list, "geo_list") +}) + +test_that("geojson_json works with geometry collections", { + + gc_sfg_json <- geojson_json(gc_sfg) + gc_sfc_json <- geojson_json(gc_sfc) + gc_sf_json <- geojson_json(gc_sf) + + expect_s3_class(gc_sfg_json, "geo_json") + expect_s3_class(gc_sfc_json, "geo_json") + expect_s3_class(gc_sf_json, "geo_json") + + expect_equal(unclass(gc_sfg_json), + "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiPoint\",\"coordinates\":[[3.2,4],[3,4.6],[3.8,4.4],[3.5,3.8],[3.4,3.6],[3.9,4.5]]},{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]],[[[3,0],[4,0],[4,1],[3,1],[3,0]],[[3.3,0.3],[3.3,0.8],[3.8,0.8],[3.8,0.3],[3.3,0.3]]],[[[3,3],[4,2],[4,3],[3,3]]]]},{\"type\":\"LineString\",\"coordinates\":[[0,3],[0,4],[1,5],[2,5]]}]}") + + expect_equal(unclass(gc_sfc_json), + "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiPoint\",\"coordinates\":[[3.2,4],[3,4.6],[3.8,4.4],[3.5,3.8],[3.4,3.6],[3.9,4.5]]},{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]],[[[3,0],[4,0],[4,1],[3,1],[3,0]],[[3.3,0.3],[3.3,0.8],[3.8,0.8],[3.8,0.3],[3.3,0.3]]],[[[3,3],[4,2],[4,3],[3,3]]]]},{\"type\":\"LineString\",\"coordinates\":[[0,3],[0,4],[1,5],[2,5]]}]}") + + expect_equal(unclass(gc_sf_json), + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"x\":\"a\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiPoint\",\"coordinates\":[[3.2,4],[3,4.6],[3.8,4.4],[3.5,3.8],[3.4,3.6],[3.9,4.5]]},{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0],[1,0],[3,2],[2,4],[1,4],[0,0]],[[1,1],[1,2],[2,2],[1,1]]],[[[3,0],[4,0],[4,1],[3,1],[3,0]],[[3.3,0.3],[3.3,0.8],[3.8,0.8],[3.8,0.3],[3.3,0.3]]],[[[3,3],[4,2],[4,3],[3,3]]]]},{\"type\":\"LineString\",\"coordinates\":[[0,3],[0,4],[1,5],[2,5]]}]}}]}") +}) + +test_that("Deals with Z and M dimensions: points", { + pt_xyz <- st_point(c(3,4,5), dim = "XYZ") + pt_xym <- st_point(c(3,4,5), dim = "XYM") + pt_xyzm <- st_point(c(3,4,5,6), dim = "XYZM") + + expect_equal(geojson_list(pt_xyz)$coordinates, c(3,4,5)) + expect_equal(geojson_list(pt_xym)$coordinates, c(3,4)) + expect_equal(geojson_list(pt_xyzm)$coordinates, c(3,4,5)) + + p_list_xyzm <- lapply(list(c(3.2,4, 5, 6), c(3,4.6, 6, 7), c(3.8,4.4, 7, 8)), + st_point, dim = "XYZM") + pt_sfc_xyzm <- st_sfc(p_list_xyzm) + pt_sf_xyzm <- st_sf(x = c("a", "b", "c"), pt_sfc_xyzm) + + + + expect_equal +}) + +test_that("Deal with M dimensions: multipoint", { + p <- rbind(c(3.2,4, 5, 6), c(3,4.6, 7, 8), c(3.8,4.4, 9, 10), + c(3.5,3.8, 11, 12), c(3.4,3.6, 13, 14), c(3.9,4.5, 15, 16)) + mp_sfg <- st_multipoint(p, dim = "XYZM") + mp_sfc <- st_sfc(mp_sfg) + mp_sf <- st_sf(x = "a", mp_sfc) + + out <- geojson_list(mp_sf) + expect_equal(dim(out$features[[1]]$geometry$coordinates), c(6, 3)) +}) + + +## Big test ------------------------------------------------------ +## devtools::install_github("bcgov/bcmaps") +# library(bcmaps) +# eco_sf <- st_as_sf(ecoprovinces) +# eco_sf <- st_transform(eco_sf, 4326) +# eco_geojson <- geojson_json(eco_sf) +# map_gist(eco_geojson)