diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/R-CMD-check-docker.yaml b/.github/workflows/R-CMD-check-docker.yaml deleted file mode 100644 index 064ba77..0000000 --- a/.github/workflows/R-CMD-check-docker.yaml +++ /dev/null @@ -1,29 +0,0 @@ -on: [push, pull_request] - -name: R-CMD-check-docker - -jobs: - R-CMD-check-docker: - runs-on: ubuntu-latest - container: jakubnowosad/geocompr_proj6 - steps: - - uses: actions/checkout@v1 - - - name: Install dependencies - run: | - install.packages('remotes') - install.packages('rcmdcheck') - remotes::install_deps(dependencies = TRUE, repos = "https://cran.rstudio.com", upgrade = TRUE) - shell: Rscript {0} - - - name: Check - run: | - rcmdcheck::rcmdcheck(args = '--no-manual', error_on = 'warning', check_dir = 'check') - shell: Rscript {0} - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@master - with: - name: ${{ runner.os }}-docker-geocompr-results - path: check diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 2be71e6..dcc3f64 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,4 +1,14 @@ -on: [push, pull_request] +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +# +# NOTE: This workflow is overkill for most R packages and +# check-standard.yaml is likely a better choice. +# usethis::use_github_action("check-standard") will install it. +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] name: R-CMD-check @@ -6,118 +16,45 @@ jobs: R-CMD-check: runs-on: ${{ matrix.config.os }} - name: ${{ matrix.config.os }} (${{ matrix.config.r }} ${{ matrix.config.v8 }}) + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) strategy: fail-fast: false matrix: config: - - { os: windows-latest, r: 'release', args: "--no-manual"} - - { os: macOS-latest, r: 'release', args: "--no-manual"} - - { os: macOS-latest, r: 'release', args: "--no-manual", no_node: true} - - { os: ubuntu-18.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest", v8: "libnode-dev", args: "--no-manual"} - - { os: ubuntu-18.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest", v8: "libnode-dev"} - - { os: ubuntu-18.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest", v8: "libv8-dev"} - - { os: ubuntu-18.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest", v8: "libnode-dev", args: "--no-manual"} + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} + env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} - CRAN: ${{ matrix.config.cran }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - cache-version: v3 + R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-node@v1 - if: matrix.config.no_node == false + - uses: actions/setup-node@v3 + with: + node-version: '16' - - name: install mapshaper node lib - if: matrix.config.no_node == false + - name: Install mapshaper run: npm install -g mapshaper - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@master - - - uses: r-lib/actions/setup-tinytex@master - if: contains(matrix.config.args, 'no-manual') == false - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), "depends.Rds", version = 2) - shell: Rscript {0} - - - name: Cache R packages - uses: actions/cache@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ env.cache-version }}-${{ runner.os }}-r-${{ matrix.config.r }}-${{ hashFiles('depends.Rds') }} - restore-keys: ${{ env.cache-version }}-${{ runner.os }}-r-${{ matrix.config.r }}- - - - name: install macOS system dependencies - if: runner.os == 'macOS' - continue-on-error: true - run: | - brew install pkg-config gdal openssl udunits v8 protobuf + extra-packages: any::rcmdcheck + needs: check - - name: add modern cran/v8 ppa - # default libv8-dev on Xenial (16) and Bionic (18) is old libv8-3.14.5. - # To test on new, add the cran/v8 ppa and install current libnode-dev, - # To test on old, install libv8-dev from existing default ppa - if: runner.os == 'Linux' && contains(matrix.config.v8, 'libnode-dev') == true - run: | - sudo add-apt-repository -y ppa:cran/v8 - sudo apt-get -y update - - - name: Install remotes package - run: Rscript -e "install.packages('remotes')" - - - name: Install system dependencies - if: runner.os == 'Linux' - env: - RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - Rscript -e "remotes::install_github('r-hub/sysreqs')" - sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - sudo add-apt-repository -y ppa:cran/v8 - sudo -s eval "$sysreqs" - # install spatial dependencies - sudo apt update - sudo apt install \ - libudunits2-dev \ - libgdal-dev \ - libgeos-dev \ - libproj-dev \ - ${{ matrix.config.v8 }} - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran('rcmdcheck') - shell: Rscript {0} - - - name: Check - env: - TMPDIR: ${{ runner.temp }} - run: | - rcmdcheck::rcmdcheck(args = "${{ matrix.config.args }}", error_on = 'warning', check_dir = 'check') - shell: Rscript {0} - - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@master + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check - - - name: Test coverage - if: matrix.config.os == 'macOS-latest' && matrix.config.r == 'release' - continue-on-error: true - run: | - Rscript -e 'remotes::install_github("r-lib/covr@gh-actions")' - Rscript -e 'covr::codecov(token = "${{secrets.CODECOV_TOKEN}}")' + upload-snapshots: true diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..2c5bb50 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +name: test-coverage + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v3 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage + + - name: Test coverage + run: | + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) + shell: Rscript {0} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/DESCRIPTION b/DESCRIPTION index 0bc244a..184c75a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -21,33 +21,29 @@ Description: Edit and simplify 'geojson', 'Spatial', and 'sf' by Matthew Bloch to perform topologically-aware polygon simplification, as well as other operations such as clipping, erasing, dissolving, and converting - 'multi-part' to 'single-part' geometries. It relies on the - 'geojsonio' package for working with 'geojson' objects, the 'sf' - package for working with 'sf' objects, and the 'sp' and 'rgdal' - packages for working with 'Spatial' objects. + 'multi-part' to 'single-part' geometries. License: MIT + file LICENSE URL: https://github.com/ateucher/rmapshaper BugReports: https://github.com/ateucher/rmapshaper/issues Imports: - geojsonio (>= 0.9.4), - geojsonlint (>= 0.4.0), - jsonlite (>= 1.7.0), methods, - readr (>= 1.4.0), - sf (>= 0.9-0), + geojsonsf (>= 2.0.2), + jsonify (>= 1.2.0), + readr (>= 2.1.0), + sf (>= 1.0.0), sp (>= 1.4-0), - V8 (>= 3.4.2) + V8 (>= 4.0.0) Suggests: + geojsonio, knitr, magrittr, - rgdal, - rgeos, rmarkdown, testthat (>= 2.1.0), + jsonlite, covr, units VignetteBuilder: knitr Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.3 diff --git a/NAMESPACE b/NAMESPACE index 314f786..514a36a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,74 +1,63 @@ # Generated by roxygen2: do not edit by hand S3method(drop_null_geometries,character) -S3method(drop_null_geometries,geo_json) -S3method(drop_null_geometries,geo_list) +S3method(drop_null_geometries,json) S3method(ms_clip,SpatialLines) S3method(ms_clip,SpatialPoints) S3method(ms_clip,SpatialPolygons) S3method(ms_clip,character) -S3method(ms_clip,geo_json) -S3method(ms_clip,geo_list) +S3method(ms_clip,json) S3method(ms_clip,sf) S3method(ms_clip,sfc) S3method(ms_dissolve,SpatialPoints) S3method(ms_dissolve,SpatialPolygons) S3method(ms_dissolve,character) -S3method(ms_dissolve,geo_json) -S3method(ms_dissolve,geo_list) +S3method(ms_dissolve,json) S3method(ms_dissolve,sf) S3method(ms_dissolve,sfc) S3method(ms_erase,SpatialLines) S3method(ms_erase,SpatialPoints) S3method(ms_erase,SpatialPolygons) S3method(ms_erase,character) -S3method(ms_erase,geo_json) -S3method(ms_erase,geo_list) +S3method(ms_erase,json) S3method(ms_erase,sf) S3method(ms_erase,sfc) S3method(ms_explode,SpatialLines) S3method(ms_explode,SpatialPolygons) S3method(ms_explode,character) -S3method(ms_explode,geo_json) -S3method(ms_explode,geo_list) +S3method(ms_explode,json) S3method(ms_explode,sf) S3method(ms_explode,sfc) S3method(ms_filter_fields,SpatialLinesDataFrame) S3method(ms_filter_fields,SpatialPointsDataFrame) S3method(ms_filter_fields,SpatialPolygonsDataFrame) S3method(ms_filter_fields,character) -S3method(ms_filter_fields,geo_json) -S3method(ms_filter_fields,geo_list) +S3method(ms_filter_fields,json) S3method(ms_filter_fields,sf) S3method(ms_filter_islands,SpatialPolygons) S3method(ms_filter_islands,character) -S3method(ms_filter_islands,geo_json) -S3method(ms_filter_islands,geo_list) +S3method(ms_filter_islands,json) S3method(ms_filter_islands,sf) S3method(ms_filter_islands,sfc) S3method(ms_innerlines,SpatialPolygons) S3method(ms_innerlines,character) -S3method(ms_innerlines,geo_json) -S3method(ms_innerlines,geo_list) +S3method(ms_innerlines,json) S3method(ms_innerlines,sf) S3method(ms_innerlines,sfc) S3method(ms_lines,SpatialPolygons) S3method(ms_lines,character) -S3method(ms_lines,geo_json) -S3method(ms_lines,geo_list) +S3method(ms_lines,json) S3method(ms_lines,sf) S3method(ms_lines,sfc) S3method(ms_points,SpatialPolygons) S3method(ms_points,character) -S3method(ms_points,geo_json) -S3method(ms_points,geo_list) +S3method(ms_points,json) S3method(ms_points,sf) S3method(ms_points,sfc) S3method(ms_simplify,SpatialLines) S3method(ms_simplify,SpatialPolygons) S3method(ms_simplify,character) -S3method(ms_simplify,geo_json) -S3method(ms_simplify,geo_list) +S3method(ms_simplify,json) S3method(ms_simplify,sf) S3method(ms_simplify,sfc) export(apply_mapshaper_commands) @@ -85,7 +74,6 @@ export(ms_lines) export(ms_points) export(ms_simplify) importFrom(V8,v8) -importFrom(geojsonlint,geojson_validate) importFrom(methods,.hasSlot) importFrom(methods,as) importFrom(methods,is) diff --git a/NEWS.md b/NEWS.md index 4424e9e..9de5762 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # rmapshaper (development version) +* Switched to using the `geojsonsf` package instead of `geojsonio` for object conversion (#118). +* Dropped support for `geojson_list` objects. This was a rarely-used class from the `geojsonio` package (#118) +* Arguments `force_FC`, `sys`, and `sys_mem` are now passed to `apply_mapshaper_commands` via `...` rather than explicitly, so they are now documented in the `...` section of each function. This may break some existing code if you were passing values to these arguments by position rather than by name, especially using `force_FC` in `ms_simplify` as it was not at the end of the argument list. It may also change the class of the return value for some input classes and functions (such as `ms_lines` and `ms_innerlines`) as `force_FC` will inherit the default `TRUE` for all functions. +* Added `quiet` option to silence mapshaper console messages when using `sys = TRUE` (#125) +* Added ability to globally set the system memory when using the system mapshaper via `options("mapshaper.sys_mem"=X)`, where `X` is the amount of memory in GB. + # rmapshaper 0.4.6 * Fixed a long-standing issue where `units` columns in `sf` objects would cause failures; all numeric columns of class `"units"` are now converted to numeric before running through mapshaper commands. (#116, thanks @Robinlovelace) diff --git a/R/clip_erase.R b/R/clip_erase.R index 1d715f5..9c5919d 100755 --- a/R/clip_erase.R +++ b/R/clip_erase.R @@ -5,29 +5,27 @@ #' @param target the target layer from which to remove portions. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} points, lines, or polygons; -#' \item \code{geo_list} points, lines, or polygons; #' \item \code{SpatialPolygons}, \code{SpatialLines}, \code{SpatialPoints}; #' \item \code{sf} or \code{sfc} points, lines, or polygons object #' } #' @param clip the clipping layer (polygon). One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons; -#' \item \code{geo_list} polygons; #' \item \code{SpatialPolygons*}; #' \item \code{sf} or \code{sfc} polygons object #' } #' @param bbox supply a bounding box instead of a clipping layer to extract from #' the target layer. Supply as a numeric vector: \code{c(minX, minY, maxX, maxY)}. #' @param remove_slivers Remove tiny sliver polygons created by clipping. (Default \code{FALSE}) -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return clipped target in the same class as the input target #' #' @examples #' #' if (rmapshaper:::check_v8_major_version() >= 6L) { -#' library(geojsonio, quietly = TRUE) -#' library(sp) +#' library(geojsonsf, quietly = TRUE) +#' library(sf) #' #' poly <- structure("{\"type\":\"FeatureCollection\", #' \"features\":[{\"type\":\"Feature\",\"properties\":{}, @@ -39,8 +37,8 @@ #' [52.0329,-49.5677],[50.1747,-52.1814],[49.0098,-52.3641], #' [52.7068,-45.7639],[43.2278,-47.1908],[48.4755,-45.1388], #' [50.327,-43.5207],[48.0804,-41.2784],[49.6307,-40.6159], -#' [52.8658,-44.7219]]]}}]}", class = c("json", "geo_json")) -#' poly <- geojson_sp(poly) +#' [52.8658,-44.7219]]]}}]}", class = c("geojson", "json")) +#' poly <- geojson_sf(poly) #' plot(poly) #' #' clip_poly <- structure('{ @@ -58,8 +56,8 @@ #' ] #' ] #' } -#' }', class = c("json", "geo_json")) -#' clip_poly <- geojson_sp(clip_poly) +#' }', class = c("geojson", "json")) +#' clip_poly <- geojson_sf(clip_poly) #' plot(clip_poly) #' #' out <- ms_clip(poly, clip_poly) @@ -67,70 +65,60 @@ #' } #' #' @export -ms_clip <- function(target, clip = NULL, bbox = NULL, remove_slivers = FALSE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { - if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE") +ms_clip <- function(target, clip = NULL, bbox = NULL, remove_slivers = FALSE, ...) { stop_for_old_v8() UseMethod("ms_clip") } #' @export ms_clip.character <- function(target, clip = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { target <- check_character_input(target) clip_erase_json(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export -ms_clip.geo_json <- function(target, clip = NULL, bbox = NULL, remove_slivers = FALSE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_clip.json <- function(target, clip = NULL, bbox = NULL, remove_slivers = FALSE, ...) { clip_erase_json(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) -} - -#' @export -ms_clip.geo_list <- function(target, clip = NULL, bbox = NULL, remove_slivers = FALSE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { - clip_erase_geo_list(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_clip.SpatialPolygons <- function(target, clip = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sp(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_clip.SpatialLines <- function(target, clip = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sp(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_clip.SpatialPoints <- function(target, clip = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sp(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_clip.sf <- function(target, clip = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sf(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_clip.sfc <- function(target, clip = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sf(target = target, overlay_layer = clip, type = "clip", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' Remove features or portions of features that fall inside a specified area @@ -140,25 +128,23 @@ ms_clip.sfc <- function(target, clip = NULL, bbox = NULL, #' @param target the target layer from which to remove portions. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} points, lines, or polygons; -#' \item \code{geo_list} points, lines, or polygons; #' \item \code{SpatialPolygons}, \code{SpatialLines}, \code{SpatialPoints} #' } #' @param erase the erase layer (polygon). One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons; -#' \item \code{geo_list} polygons; #' \item \code{SpatialPolygons*} #' } #' @param bbox supply a bounding box instead of an erasing layer to remove from #' the target layer. Supply as a numeric vector: \code{c(minX, minY, maxX, maxY)}. #' @param remove_slivers Remove tiny sliver polygons created by erasing. (Default \code{FALSE}) -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return erased target in the same format as the input target #' @examples #' if (rmapshaper:::check_v8_major_version() >= 6L) { -#' library(geojsonio, quietly = TRUE) -#' library(sp) +#' library(geojsonsf, quietly = TRUE) +#' library(sf) #' #' points <- structure("{\"type\":\"FeatureCollection\", #' \"features\":[{\"type\":\"Feature\",\"properties\":{}, @@ -175,8 +161,8 @@ ms_clip.sfc <- function(target, clip = NULL, bbox = NULL, #' {\"type\":\"Point\",\"coordinates\":[61.0835,-40.7529]}}, #' {\"type\":\"Feature\",\"properties\":{},\"geometry\": #' {\"type\":\"Point\",\"coordinates\":[58.0202,-43.634]}}]}", -#' class = c("json", "geo_json")) -#' points <- geojson_sp(points) +#' class = c("geojson", "json")) +#' points <- geojson_sf(points) #' plot(points) #' #' erase_poly <- structure('{ @@ -194,8 +180,8 @@ ms_clip.sfc <- function(target, clip = NULL, bbox = NULL, #' ] #' ] #' } -#' }', class = c("json", "geo_json")) -#' erase_poly <- geojson_sp(erase_poly) +#' }', class = c("geojson", "json")) +#' erase_poly <- geojson_sf(erase_poly) #' #' out <- ms_erase(points, erase_poly) #' plot(out, add = TRUE) @@ -203,73 +189,67 @@ ms_clip.sfc <- function(target, clip = NULL, bbox = NULL, #' #'@export ms_erase <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE") + remove_slivers = FALSE, ...) { stop_for_old_v8() UseMethod("ms_erase") } #' @export ms_erase.character <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { target <- check_character_input(target) clip_erase_json(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export -ms_erase.geo_json <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_erase.json <- function(target, erase = NULL, bbox = NULL, + remove_slivers = FALSE, ...) { clip_erase_json(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) -} - -#' @export -ms_erase.geo_list <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - clip_erase_geo_list(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_erase.SpatialPolygons <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sp(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_erase.SpatialLines <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sp(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_erase.SpatialPoints <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sp(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_erase.sf <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sf(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } #' @export ms_erase.sfc <- function(target, erase = NULL, bbox = NULL, - remove_slivers = FALSE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + remove_slivers = FALSE, ...) { clip_erase_sf(target = target, overlay_layer = erase, type = "erase", - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + remove_slivers = remove_slivers, bbox = bbox, ...) } clip_erase_json <- function(target, overlay_layer, bbox, remove_slivers, type, - force_FC, sys, sys_mem) { + force_FC = TRUE, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { check_overlay_bbox(overlay_layer = overlay_layer, bbox = bbox, type = type) @@ -278,39 +258,23 @@ clip_erase_json <- function(target, overlay_layer, bbox, remove_slivers, type, } mapshaper_clip_erase(target_layer = target, overlay_layer = overlay_layer, type = type, - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) -} - -clip_erase_geo_list <- function(target, overlay_layer, bbox, type, - remove_slivers, force_FC, sys, sys_mem) { - - check_overlay_bbox(overlay_layer = overlay_layer, bbox = bbox, type = type) - - if (is.null(bbox)) { - if (!is(overlay_layer, "geo_list")) stop("both target and ", type, " must be class geo_list") - overlay_layer <- geo_list_to_json(overlay_layer) - } - target <- geo_list_to_json(target) - ret <- clip_erase_json(target = target, overlay_layer = overlay_layer, type = type, - remove_slivers = remove_slivers, bbox = bbox, force_FC = force_FC, sys = sys, sys_mem = sys_mem) - geojsonio::geojson_list(ret) + remove_slivers = remove_slivers, bbox = bbox, + force_FC = force_FC, sys = sys, sys_mem = sys_mem, quiet = quiet) } -clip_erase_sp <- function(target, overlay_layer, bbox, type, remove_slivers, force_FC, sys, sys_mem) { +clip_erase_sp <- function(target, overlay_layer, bbox, type, remove_slivers, + force_FC = TRUE, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { check_overlay_bbox(overlay_layer = overlay_layer, bbox = bbox, type = type) - target_proj <- slot(target, "proj4string") + target_proj <- methods::slot(target, "proj4string") if (is.null(bbox)) { if (!is(overlay_layer, "SpatialPolygons")) stop(type, " must be of class SpatialPolygons or SpatialPolygonsDataFrame") if (!sp::identicalCRS(target, overlay_layer)) { - warning("target and ", type, " do not have identical CRS. Transforming ", - type, " to target CRS") - if (!requireNamespace("rgdal")) { - stop("You need the rgdal package to use transform non-equivalent projections.") - } - overlay_layer <- sp::spTransform(overlay_layer, target_proj) + stop("target and ", type, " do not have identical CRS.", call. = FALSE) } overlay_geojson <- sp_to_GeoJSON(overlay_layer, file = sys) } @@ -332,7 +296,10 @@ clip_erase_sp <- function(target, overlay_layer, bbox, type, remove_slivers, for ret } -clip_erase_sf <- function(target, overlay_layer, bbox, type, remove_slivers, force_FC, sys, sys_mem) { +clip_erase_sf <- function(target, overlay_layer, bbox, type, remove_slivers, + force_FC = TRUE, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { check_overlay_bbox(overlay_layer = overlay_layer, bbox = bbox, type = type) @@ -348,10 +315,8 @@ clip_erase_sf <- function(target, overlay_layer, bbox, type, remove_slivers, for !all(sf::st_is(overlay_layer, c("POLYGON", "MULTIPOLYGON")))) { stop(type, " must be an sf or sfc object with POLYGON or MULTIPLOYGON geometry") } - if (sf::st_crs(target) != sf::st_crs(overlay_layer)) { - warning("target and ", type, " do not have identical CRS. Transforming ", - type, " to target CRS") - overlay_layer <- sf::st_transform(overlay_layer, target_proj) + if (target_proj != sf::st_crs(overlay_layer)) { + stop("target and ", type, " do not have identical CRS.", call. = FALSE) } overlay_geojson <- sf_to_GeoJSON(overlay_layer, file = sys) } @@ -394,7 +359,9 @@ check_overlay_bbox <- function(overlay_layer, bbox, type) { } mapshaper_clip_erase <- function(target_layer, overlay_layer, bbox, type, - remove_slivers, force_FC, sys, sys_mem) { + remove_slivers, force_FC = TRUE, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { remove_slivers <- ifelse(remove_slivers, "remove-slivers", "") @@ -402,13 +369,13 @@ mapshaper_clip_erase <- function(target_layer, overlay_layer, bbox, type, if (!is.null(bbox)) { cmd <- paste0("-", type, " bbox=",paste0(bbox, collapse = ","), " ", remove_slivers) - out <- apply_mapshaper_commands(target_layer, cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + out <- apply_mapshaper_commands(target_layer, cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem, quiet = quiet) } else if (!is.null(overlay_layer)) { if (sys) { on.exit(unlink(c(target_layer, overlay_layer)), add = TRUE) cmd <- paste0("-", type) - out <- sys_mapshaper(data = target_layer, data2 = overlay_layer, command = cmd, sys_mem = sys_mem) + out <- sys_mapshaper(data = target_layer, data2 = overlay_layer, command = cmd, sys_mem = sys_mem, quiet = quiet) } else { ms <- ms_make_ctx() diff --git a/R/dissolve.R b/R/dissolve.R index 3b5c34f..dd8d144 100644 --- a/R/dissolve.R +++ b/R/dissolve.R @@ -6,7 +6,6 @@ #' @param input spatial object to dissolve. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} points or polygons; -#' \item \code{geo_list} points or polygons; #' \item \code{SpatialPolygons}, or \code{SpatialPoints} #' } #' @param snap Snap together vertices within a small distance threshold to fix @@ -16,13 +15,13 @@ #' @param copy_fields fields to copy. The first instance of each field will be #' copied to the aggregated feature. #' @param weight Name of an attribute field for generating weighted centroids (points only). -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return the same class as the input #' #' @examples -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' #' poly <- structure('{"type":"FeatureCollection", #' "features":[ @@ -35,100 +34,85 @@ #' "properties":{"a": 5, "b": 3}, #' "geometry":{"type":"Polygon","coordinates":[[ #' [100,0],[100,1],[101,1],[101,0],[100,0] -#' ]]}}]}', class = c("json", "geo_json")) -#' poly <- geojson_sp(poly) +#' ]]}}]}', class = c("geojson", "json")) +#' poly <- geojson_sf(poly) #' plot(poly) #' length(poly) -#' poly@data +#' poly #' #' # Dissolve the polygon #' out <- ms_dissolve(poly) #' plot(out) #' length(out) -#' out@data +#' out #' #' # Dissolve and summing columns #' out <- ms_dissolve(poly, sum_fields = c("a", "b")) #' plot(out) -#' out@data +#' out #' #' @export ms_dissolve <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + weight = NULL, snap = TRUE, ...) { UseMethod("ms_dissolve") } #' @export ms_dissolve.character <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + weight = NULL, snap = TRUE, ...) { input <- check_character_input(input) call <- make_dissolve_call(field = field, sum_fields = sum_fields, weight = weight, copy_fields = copy_fields, snap = snap) - apply_mapshaper_commands(data = input, command = call, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = call, ...) } #' @export -ms_dissolve.geo_json <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - - call <- make_dissolve_call(field = field, sum_fields = sum_fields, weight = weight, - copy_fields = copy_fields, snap = snap) - - apply_mapshaper_commands(data = input, command = call, force_FC = force_FC, sys = sys, sys_mem = sys_mem) -} - -#' @export -ms_dissolve.geo_list <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - +ms_dissolve.json <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, + weight = NULL, snap = TRUE, ...) { call <- make_dissolve_call(field = field, sum_fields = sum_fields, weight = weight, copy_fields = copy_fields, snap = snap) - geojson <- geo_list_to_json(input) - - ret <- apply_mapshaper_commands(data = geojson, command = call, force_FC = force_FC, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) + apply_mapshaper_commands(data = input, command = call, ...) } #' @export ms_dissolve.SpatialPolygons <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + weight = NULL, snap = TRUE, ...) { dissolve_sp(input = input, field = field, sum_fields = sum_fields, copy_fields = copy_fields, - weight = weight, snap = snap, sys = sys, sys_mem = sys_mem) + weight = weight, snap = snap, ...) } #' @export ms_dissolve.SpatialPoints <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + weight = NULL, snap = TRUE, ...) { dissolve_sp(input = input, field = field, sum_fields = sum_fields, copy_fields = copy_fields, - weight = weight, snap = snap, sys = sys, sys_mem = sys_mem) + weight = weight, snap = snap, ...) } #' @export ms_dissolve.sf <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + weight = NULL, snap = TRUE, ...) { if (!is.null(weight) && !(weight %in% names(input))) { stop("specified 'weight' column not present in input data", call. = FALSE) } dissolve_sf(input = input, field = field, sum_fields = sum_fields, copy_fields = copy_fields, - weight = weight, snap = snap, sys = sys, sys_mem = sys_mem) + weight = weight, snap = snap, ...) } #' @export ms_dissolve.sfc <- function(input, field = NULL, sum_fields = NULL, copy_fields = NULL, - weight = NULL, snap = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + weight = NULL, snap = TRUE, ...) { if (!is.null(weight)) { warning("'weight' cannot be used with sfc objects. Ignoring it and proceeding...") } dissolve_sf(input = input, field = field, sum_fields = sum_fields, copy_fields = copy_fields, - weight = NULL, snap = snap, sys = sys, sys_mem = sys_mem) + weight = NULL, snap = snap, ...) } make_dissolve_call <- function(field, sum_fields, copy_fields, weight, snap) { @@ -158,7 +142,7 @@ make_dissolve_call <- function(field, sum_fields, copy_fields, weight, snap) { call } -dissolve_sp <- function(input, field, sum_fields, copy_fields, weight, snap, sys, sys_mem) { +dissolve_sp <- function(input, field, sum_fields, copy_fields, weight, snap, ...) { if (!inherits(input, "SpatialPointsDataFrame") && !is.null(weight)) { stop("weight arguments only applies to points with attributes", call. = FALSE) @@ -171,10 +155,10 @@ dissolve_sp <- function(input, field, sum_fields, copy_fields, weight, snap, sys call <- make_dissolve_call(field = field, sum_fields = sum_fields, copy_fields = copy_fields, weight = weight, snap = snap) - ms_sp(input = input, call = call, sys = sys, sys_mem = sys_mem) + ms_sp(input = input, call = call, ...) } -dissolve_sf <- function(input, field, sum_fields, copy_fields, weight, snap, sys, sys_mem) { +dissolve_sf <- function(input, field, sum_fields, copy_fields, weight, snap, ...) { if (!all(sf::st_is(input, c("POINT", "MULTIPOINT", "POLYGON", "MULTIPOLYGON")))) { stop("ms_dissolve only works with (MULTI)POINT or (MULTI)POLYGON", call. = FALSE) @@ -187,5 +171,5 @@ dissolve_sf <- function(input, field, sum_fields, copy_fields, weight, snap, sys call <- make_dissolve_call(field = field, sum_fields = sum_fields, copy_fields = copy_fields, weight = weight, snap = snap) - ms_sf(input = input, call = call, sys = sys, sys_mem = sys_mem) + ms_sf(input = input, call = call, ...) } diff --git a/R/drop_null_geometries.R b/R/drop_null_geometries.R index bfb91de..a4f7680 100644 --- a/R/drop_null_geometries.R +++ b/R/drop_null_geometries.R @@ -1,8 +1,8 @@ -#' Drop features from a \code{geo_list} or \code{geo_json} FeatureCollection with null geometries +#' Drop features from a \code{geo_json} FeatureCollection with null geometries #' -#' @param x a \code{geo_list} or \code{geo_json} FeatureCollection +#' @param x a \code{geo_json} FeatureCollection #' -#' @return a \code{geo_list} or \code{geo_json} FeatureCollection with Features with null geometries +#' @return a \code{geo_json} FeatureCollection with Features with null geometries #' removed #' @export drop_null_geometries <- function(x) { @@ -10,32 +10,9 @@ drop_null_geometries <- function(x) { } #' @export -drop_null_geometries.geo_json <- function(x) { +drop_null_geometries.json <- function(x) { apply_mapshaper_commands(x, "-filter remove-empty", TRUE) } #' @export -drop_null_geometries.character <- drop_null_geometries.geo_json - -#' @export -drop_null_geometries.geo_list <- function(x) { - # Using -filter mapshaper command - geojson <- geo_list_to_json(x) - ret <- drop_null_geometries.geo_json(geojson) - geojsonio::geojson_list(ret) - - ## Use the list directly - it's faster - # drop_null_geometries_list(x) -} - -# drop_null_geometries_list <- function(x) { -# if (x$type != "FeatureCollection") { -# stop("type must be a FeatureCollection") -# } -# -# features_to_keep <- vapply(x$features, -# function(y) !is.null(y$geometry), -# logical(1)) -# x$features <- x$features[features_to_keep] -# x -# } +drop_null_geometries.character <- drop_null_geometries.json diff --git a/R/explode.R b/R/explode.R index 5f282a6..8684161 100644 --- a/R/explode.R +++ b/R/explode.R @@ -8,76 +8,63 @@ #' @param input One of: #' \itemize{ #' \item \code{geo_json} or \code{character} multipart lines, or polygons; -#' \item \code{geo_list} multipart lines, or polygons; #' \item multipart \code{SpatialPolygons}, \code{SpatialLines}; #' \item \code{sf} or \code{sfc} multipart lines, or polygons object #' } -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return same class as input #' #' @examples -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' -#' poly <- structure("{\"type\":\"FeatureCollection\",\"crs\": -#' {\"type\":\"name\",\"properties\":{\"name\": -#' \"urn:ogc:def:crs:OGC:1.3:CRS84\"}},\"features\": +#' poly <- "{\"type\":\"FeatureCollection\",\"features\": #' [\n{\"type\":\"Feature\",\"geometry\":{\"type\": #' \"MultiPolygon\",\"coordinates\":[[[[102,2],[102,3], #' [103,3],[103,2],[102,2]]],[[[100,0],[100,1],[101,1], -#' [101,0],[100,0]]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", -#' class = c("json", "geo_json")) +#' [101,0],[100,0]]]]},\"properties\":{\"a\":0}}\n]}" #' -#' poly <- geojson_sp(poly) +#' poly <- geojson_sf(poly) #' plot(poly) #' length(poly) -#' poly@data +#' poly #' #' # Explode the polygon #' out <- ms_explode(poly) #' plot(out) #' length(out) -#' out@data +#' out #' #' @export -ms_explode <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_explode <- function(input, ...) { UseMethod("ms_explode") } #' @export -ms_explode.character <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_explode.character <- function(input, ...) { input <- check_character_input(input) - apply_mapshaper_commands(data = input, command = "-explode", force_FC = force_FC, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = "-explode", ...) } #' @export -ms_explode.geo_json <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - apply_mapshaper_commands(data = input, command = "-explode", force_FC = force_FC, sys = sys, sys_mem = sys_mem) -} - -#' @export -ms_explode.geo_list <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - geojson <- geo_list_to_json(input) - - ret <- apply_mapshaper_commands(data = geojson, command = "-explode", force_FC = force_FC, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) +ms_explode.json <- function(input, ...) { + apply_mapshaper_commands(data = input, command = "-explode", ...) } ## The method using mapshaper's explode works, but is waaaay slower than ## sp::disaggregate due to converstion to/from geojson #' @export -ms_explode.SpatialPolygons <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - explode_sp(input, sys = sys, sys_mem = sys_mem) +ms_explode.SpatialPolygons <- function(input, ...) { + explode_sp(input, ...) } #' @export -ms_explode.SpatialLines <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - explode_sp(input, sys = sys, sys_mem = sys_mem) +ms_explode.SpatialLines <- function(input, ...) { + explode_sp(input, ...) } # #' @describeIn ms_explode Method for SpatialPoints @@ -86,20 +73,20 @@ ms_explode.SpatialLines <- function(input, force_FC = TRUE, sys = FALSE, sys_mem # explode_sp(input, force_FC) # } -explode_sp <- function(input, sys, sys_mem) { - ms_sp(input = input, call = "-explode", sys = sys, sys_mem = sys_mem) +explode_sp <- function(input, ...) { + ms_sp(input = input, call = "-explode", ...) } #' @export -ms_explode.sf <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - explode_sf(input = input, sys = sys, sys_mem = sys_mem) +ms_explode.sf <- function(input, ...) { + explode_sf(input = input, ...) } #' @export -ms_explode.sfc <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - explode_sf(input = input, sys = sys, sys_mem = sys_mem) +ms_explode.sfc <- function(input, ...) { + explode_sf(input = input, ...) } -explode_sf <- function(input, sys, sys_mem) { - ms_sf(input = input, call = "-explode", sys = sys, sys_mem = sys_mem) +explode_sf <- function(input, ...) { + ms_sf(input = input, call = "-explode", ...) } diff --git a/R/filter_fields.R b/R/filter_fields.R index 3191f8e..e02f272 100644 --- a/R/filter_fields.R +++ b/R/filter_fields.R @@ -5,83 +5,71 @@ #' @param input spatial object to filter fields on. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} points, lines, or polygons; -#' \item \code{geo_list} points, lines, or polygons; #' \item \code{SpatialPolygonsDataFrame}, \code{SpatialLinesDataFrame}, \code{SpatialPointsDataFrame}; #' \item \code{sf} object #' } #' @param fields character vector of fields to retain. -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands sys sys_mem quiet #' #' @return object with only specified attributes retained, in the same class as #' the input #' #' @examples -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' #' poly <- structure("{\"type\":\"FeatureCollection\", #' \"features\":[{\"type\":\"Feature\", #' \"properties\":{\"a\": 1, \"b\":2, \"c\": 3}, #' \"geometry\":{\"type\":\"Polygon\", #' \"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]}}]}", -#' class = c("json", "geo_json")) -#' poly <- geojson_sp(poly) -#' poly@data +#' class = c("geojson", "json")) +#' poly <- geojson_sf(poly) +#' poly #' #' # Filter (keep) fields a and b, drop c #' out <- ms_filter_fields(poly, c("a", "b")) -#' out@data +#' out #' #' @export -ms_filter_fields <- function(input, fields, sys = FALSE, sys_mem = 8) { +ms_filter_fields <- function(input, fields, ...) { if (!is.character(fields)) stop("fields must be a character vector") UseMethod("ms_filter_fields") } #' @export -ms_filter_fields.character <- function(input, fields, sys = FALSE, sys_mem = 8) { +ms_filter_fields.character <- function(input, fields, ...) { input <- check_character_input(input) cmd <- make_filterfields_call(fields) - apply_mapshaper_commands(data = input, command = cmd, force_FC = FALSE, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = cmd, force_FC = FALSE, ...) } #' @export -ms_filter_fields.geo_json <- function(input, fields, sys = FALSE, sys_mem = 8) { +ms_filter_fields.json <- function(input, fields, ...) { cmd <- make_filterfields_call(fields) - apply_mapshaper_commands(data = input, command = cmd, force_FC = FALSE, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = cmd, force_FC = FALSE, ...) } #' @export -ms_filter_fields.geo_list <- function(input, fields, sys = FALSE, sys_mem = 8) { - geojson <- geo_list_to_json(input) - - cmd <- make_filterfields_call(fields) - - ret <- apply_mapshaper_commands(data = geojson, command = cmd, force_FC = FALSE, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) -} - -#' @export -ms_filter_fields.SpatialPolygonsDataFrame <- function(input, fields, sys = FALSE, sys_mem = 8) { - ms_filter_fields_sp(input, fields, sys = sys, sys_mem = sys_mem) +ms_filter_fields.SpatialPolygonsDataFrame <- function(input, fields, ...) { + ms_filter_fields_sp(input, fields, ...) } #' @export -ms_filter_fields.SpatialPointsDataFrame <- function(input, fields, sys = FALSE, sys_mem = 8) { - ms_filter_fields_sp(input, fields, sys = sys, sys_mem = sys_mem) +ms_filter_fields.SpatialPointsDataFrame <- function(input, fields, ...) { + ms_filter_fields_sp(input, fields, ...) } #' @export -ms_filter_fields.SpatialLinesDataFrame <- function(input, fields, sys = FALSE, sys_mem = 8) { - ms_filter_fields_sp(input, fields, sys = sys, sys_mem = sys_mem) +ms_filter_fields.SpatialLinesDataFrame <- function(input, fields, ...) { + ms_filter_fields_sp(input, fields, ...) } #' @export -ms_filter_fields.sf <- function(input, fields, sys = FALSE, sys_mem = 8) { +ms_filter_fields.sf <- function(input, fields, ...) { if (!all(fields %in% names(input))) { stop("Not all fields are in input") } @@ -93,7 +81,7 @@ ms_filter_fields.sf <- function(input, fields, sys = FALSE, sys_mem = 8) { input[, fields, drop = FALSE] } -ms_filter_fields_sp <- function(input, fields, sys, sys_mem) { +ms_filter_fields_sp <- function(input, fields, ...) { # cmd <- make_filterfields_call(fields) # diff --git a/R/filter_islands.R b/R/filter_islands.R index 9e140b2..68e5022 100644 --- a/R/filter_islands.R +++ b/R/filter_islands.R @@ -6,7 +6,6 @@ #' @param input spatial object to filter. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons; -#' \item \code{geo_list} polygons; #' \item \code{SpatialPolygons*}; #' \item \code{sf} or \code{sfc} polygons object #' } @@ -17,14 +16,14 @@ #' @param drop_null_geometries should features with empty geometries be dropped? #' Default \code{TRUE}. Ignored for \code{SpatialPolyons*}, as it is always #' \code{TRUE}. -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return object with only specified features retained, in the same class as #' the input #' #' @examples -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' #' poly <- structure("{\"type\":\"FeatureCollection\", #' \"features\":[{\"type\":\"Feature\",\"properties\":{}, @@ -36,9 +35,9 @@ #' {\"type\":\"Feature\",\"properties\":{}, #' \"geometry\":{\"type\":\"Polygon\", #' \"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}}]}", -#' class = c("json", "geo_json")) +#' class = c("geojson", "json")) #' -#' poly <- geojson_sp(poly) +#' poly <- geojson_sf(poly) #' plot(poly) #' #' out <- ms_filter_islands(poly, min_area = 12391399903) @@ -46,69 +45,55 @@ #' #' @export ms_filter_islands <- function(input, min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { + ...) { if (!is.null(min_area) && !is.numeric(min_area)) stop("min_area must be numeric") if (!is.null(min_vertices) && !is.numeric(min_vertices)) stop("min_vertices must be numeric") if (!is.logical(drop_null_geometries)) stop("drop_null_geometries must be TRUE or FALSE") - if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE") UseMethod("ms_filter_islands") } #' @export ms_filter_islands.character <- function(input, min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { + ...) { input <- check_character_input(input) cmd <- make_filterislands_call(min_area = min_area, min_vertices = min_vertices, drop_null_geometries = drop_null_geometries) - apply_mapshaper_commands(data = input, command = cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = cmd, ...) } #' @export -ms_filter_islands.geo_json <- function(input, min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_filter_islands.json <- function(input, min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, + ...) { cmd <- make_filterislands_call(min_area = min_area, min_vertices = min_vertices, drop_null_geometries = drop_null_geometries) - apply_mapshaper_commands(data = input, command = cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) -} - -#' @export -ms_filter_islands.geo_list <- function(input, min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { - geojson <- geo_list_to_json(input) - - cmd <- make_filterislands_call(min_area = min_area, min_vertices = min_vertices, - drop_null_geometries = drop_null_geometries) - - ret <- apply_mapshaper_commands(data = geojson, command = cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) + apply_mapshaper_commands(data = input, command = cmd, ...) } #' @export ms_filter_islands.SpatialPolygons <- function(input, min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, - force_FC = TRUE, sys = FALSE, sys_mem = 8) { - ms_filter_islands_sp(input, min_area = min_area, min_vertices = min_vertices, sys = sys, sys_mem = sys_mem) + ...) { + ms_filter_islands_sp(input, min_area = min_area, min_vertices = min_vertices, ...) } -ms_filter_islands_sp <- function(input, min_area = NULL, min_vertices = NULL, sys, sys_mem) { +ms_filter_islands_sp <- function(input, min_area = NULL, min_vertices = NULL, ...) { cmd <- make_filterislands_call(min_area = min_area, min_vertices = min_vertices, drop_null_geometries = TRUE) - ms_sp(input = input, call = cmd, sys = sys, sys_mem = sys_mem) + ms_sp(input = input, call = cmd, ...) } #' @export ms_filter_islands.sf <- function(input, min_area = NULL, min_vertices = NULL, - drop_null_geometries = TRUE, force_FC = TRUE, sys = FALSE, sys_mem = 8) { + drop_null_geometries = TRUE, ...) { cmd <- make_filterislands_call(min_area = min_area, min_vertices = min_vertices, drop_null_geometries = TRUE) - ms_sf(input = input, call = cmd, sys = sys, sys_mem = sys_mem) + ms_sf(input = input, call = cmd, ...) } #' @export diff --git a/R/inner_lines.R b/R/inner_lines.R index a6267e8..fe16ca9 100644 --- a/R/inner_lines.R +++ b/R/inner_lines.R @@ -3,17 +3,16 @@ #' @param input input polygons object to convert to inner lines. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons; -#' \item \code{geo_list} polygons; #' \item \code{SpatialPolygons*}; #' \item \code{sf} or \code{sfc} polygons object #' } -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return lines in the same class as the input layer, but without attributes #' #' @examples -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' #' poly <- structure('{"type":"FeatureCollection", #' "features":[ @@ -36,58 +35,45 @@ #' "properties":{"foo": "b"}, #' "geometry":{"type":"Polygon","coordinates":[[ #' [103,1],[103,2],[104,2],[104,1],[103,1] -#' ]]}}]}', class = c("json", "geo_json")) +#' ]]}}]}', class = c("geojson", "json")) #' -#' poly <- geojson_sp(poly) +#' poly <- geojson_sf(poly) #' plot(poly) #' #' out <- ms_innerlines(poly) #' plot(out) #' #' @export -ms_innerlines <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE") +ms_innerlines <- function(input, ...) { UseMethod("ms_innerlines") } #' @export -ms_innerlines.character <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_innerlines.character <- function(input, ...) { input <- check_character_input(input) - apply_mapshaper_commands(data = input, command = "-innerlines", - force_FC = force_FC, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = "-innerlines", ...) } #' @export -ms_innerlines.geo_json <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - apply_mapshaper_commands(data = input, command = "-innerlines", - force_FC = force_FC, sys = sys, sys_mem = sys_mem) +ms_innerlines.json <- function(input, ...) { + apply_mapshaper_commands(data = input, command = "-innerlines", ...) } #' @export -ms_innerlines.geo_list <- function(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - geojson <- geo_list_to_json(input) - - ret <- apply_mapshaper_commands(data = geojson, command = "-innerlines", - force_FC = force_FC, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) -} - -#' @export -ms_innerlines.SpatialPolygons <- function(input, force_FC, sys = FALSE, sys_mem = 8) { - ms_sp(as(input, "SpatialPolygons"), "-innerlines", sys = sys, sys_mem = sys_mem) +ms_innerlines.SpatialPolygons <- function(input, ...) { + ms_sp(as(input, "SpatialPolygons"), "-innerlines", ...) } #' @export -ms_innerlines.sf <- function(input, force_FC, sys = FALSE, sys_mem = 8) { - ms_sf(sf::st_geometry(input), "-innerlines", sys = sys, sys_mem = sys_mem) +ms_innerlines.sf <- function(input, ...) { + ms_sf(sf::st_geometry(input), "-innerlines", ...) } #' @export -ms_innerlines.sfc <- function(input, force_FC, sys = FALSE, sys_mem = 8) { - ms_sf(input, "-innerlines", sys = sys, sys_mem = sys_mem) +ms_innerlines.sfc <- function(input, ...) { + ms_sf(input, "-innerlines", ...) } diff --git a/R/lines.R b/R/lines.R index b7622c3..a92f19d 100644 --- a/R/lines.R +++ b/R/lines.R @@ -4,7 +4,6 @@ #' @param input input polygons object to convert to inner lines. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons; -#' \item \code{geo_list} polygons; #' \item \code{SpatialPolygons*}; #' \item \code{sf} or \code{sfc} polygons object #' } @@ -14,14 +13,14 @@ #' intermediate level of hierarchy at TYPE 1, with the lowest-level internal #' boundaries set to TYPE 2. Supplying a character vector of field names adds #' additional levels of hierarchy. -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return topological boundaries as lines, in the same class as the input #' #' @examples #' -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' #' poly <- structure('{"type":"FeatureCollection", #' "features":[ @@ -39,9 +38,9 @@ #' "properties":{"foo": "b"}, #' "geometry":{"type":"Polygon","coordinates":[[ #' [102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1] -#' ]]}}]}', class = c("json", "geo_json")) +#' ]]}}]}', class = c("geojson", "json")) #' -#' poly <- geojson_sp(poly) +#' poly <- geojson_sf(poly) #' summary(poly) #' plot(poly) #' @@ -50,45 +49,30 @@ #' plot(out) #' #' @export -ms_lines <- function(input, fields = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_lines <- function(input, fields = NULL, ...) { if (!is.null(fields) && !is.character(fields)) stop("fields must be a character vector of field names") - if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE") UseMethod("ms_lines") } #' @export -ms_lines.character <- function(input, fields = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_lines.character <- function(input, fields = NULL, ...) { input <- check_character_input(input) command <- make_lines_call(fields) - apply_mapshaper_commands(data = input, command = command, force_FC = force_FC, - sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = command, ...) } #' @export -ms_lines.geo_json <- function(input, fields = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_lines.json <- function(input, fields = NULL, ...) { command <- make_lines_call(fields) - apply_mapshaper_commands(data = input, command = command, force_FC = force_FC, - sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = command, ...) } #' @export -ms_lines.geo_list <- function(input, fields = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - geojson <- geo_list_to_json(input) - - command <- make_lines_call(fields) - - ret <- apply_mapshaper_commands(data = geojson, command = command, - force_FC = force_FC, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) -} - -#' @export -ms_lines.SpatialPolygons <- function(input, fields = NULL, force_FC, sys = FALSE, sys_mem = 8) { +ms_lines.SpatialPolygons <- function(input, fields = NULL, ...) { if (.hasSlot(input, "data")) { if (!all(fields %in% names(input@data))) { @@ -98,37 +82,37 @@ ms_lines.SpatialPolygons <- function(input, fields = NULL, force_FC, sys = FALSE command <- make_lines_call(fields) - ms_sp(input, command, sys = sys, sys_mem = sys_mem) + ms_sp(input, command, ...) } #' @export -ms_lines.sf <- function(input, fields = NULL, force_FC, sys = FALSE, sys_mem = 8) { +ms_lines.sf <- function(input, fields = NULL, ...) { if (!all(fields %in% names(input))) { stop("not all fields specified exist in input data") } - lines_sf(input = input, fields = fields, sys = sys, sys_mem = sys_mem) + lines_sf(input = input, fields = fields, ...) } #' @export -ms_lines.sfc <- function(input, fields = NULL, force_FC, sys = FALSE, sys_mem = 8) { +ms_lines.sfc <- function(input, fields = NULL, ...) { if (!is.null(fields)) { stop("Do not specify fields for sfc classes", call. = FALSE) } - lines_sf(input = input, fields = fields, sys = sys, sys_mem = sys_mem) + lines_sf(input = input, fields = fields, ...) } -lines_sf <- function(input, fields, sys, sys_mem) { +lines_sf <- function(input, fields, ...) { if (!all(sf::st_is(input, c("POLYGON", "MULTIPOLYGON")))) { stop("ms_lines only works with (MULTI)POLYGON") } command <- make_lines_call(fields) - ms_sf(input, command, sys = sys, sys_mem = sys_mem) + ms_sf(input, command, ...) } make_lines_call <- function(fields) { diff --git a/R/points.R b/R/points.R index 6f44b50..2f7f96b 100644 --- a/R/points.R +++ b/R/points.R @@ -7,7 +7,6 @@ #' @param input input polygons object to convert to points. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons; -#' \item \code{geo_list} polygons; #' \item \code{SpatialPolygons*}; #' \item \code{sf} or \code{sfc} polygons object #' } @@ -21,13 +20,13 @@ #' \code{location} is specified. #' @param y name of field containing y coordinate values. Must be \code{NULL} if #' \code{location} is specified. -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return points in the same class as the input. #' #' @examples -#' library(geojsonio) -#' library(sp) +#' library(geojsonsf) +#' library(sf) #' #' poly <- structure("{\"type\":\"FeatureCollection\", #' \"features\":[{\"type\":\"Feature\",\"properties\": @@ -40,9 +39,9 @@ #' {\"type\":\"Feature\",\"properties\":{\"x_pos\": 5, \"y_pos\": 6}, #' \"geometry\":{\"type\":\"Polygon\", #' \"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}}]}", -#' class = c("json", "geo_json")) +#' class = c("geojson", "json")) #' -#' poly <- geojson_sp(poly) +#' poly <- geojson_sf(poly) #' summary(poly) #' plot(poly) #' @@ -57,53 +56,41 @@ #' plot(out) #' #' @export -ms_points <- function(input, location = NULL, x = NULL, y = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE") +ms_points <- function(input, location = NULL, x = NULL, y = NULL, ...) { UseMethod("ms_points") } #' @export -ms_points.character <- function(input, location = NULL, x = NULL, y = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_points.character <- function(input, location = NULL, x = NULL, y = NULL, ...) { input <- check_character_input(input) cmd <- make_points_call(location = location, x = x, y = y) - apply_mapshaper_commands(data = input, command = cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = cmd, ...) } #' @export -ms_points.geo_json <- function(input, location = NULL, x = NULL, y = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +ms_points.json <- function(input, location = NULL, x = NULL, y = NULL, ...) { cmd <- make_points_call(location = location, x = x, y = y) - apply_mapshaper_commands(data = input, command = cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + apply_mapshaper_commands(data = input, command = cmd, ...) } #' @export -ms_points.geo_list <- function(input, location = NULL, x = NULL, y = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) { - cmd <- make_points_call(location = location, x = x, y = y) - - geojson <- geo_list_to_json(input) - - ret <- apply_mapshaper_commands(data = geojson, command = cmd, force_FC = force_FC, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) -} - -#' @export -ms_points.SpatialPolygons <- function(input, location = NULL, x = NULL, y = NULL, force_FC, sys = FALSE, sys_mem = 8) { +ms_points.SpatialPolygons <- function(input, location = NULL, x = NULL, y = NULL, ...) { cmd <- make_points_call(location = location, x = x, y = y) - ms_sp(input, cmd, sys = sys, sys_mem = sys_mem) + ms_sp(input, cmd, ...) } #' @export -ms_points.sf <- function(input, location = NULL, x = NULL, y = NULL, force_FC, sys = FALSE, sys_mem = 8) { +ms_points.sf <- function(input, location = NULL, x = NULL, y = NULL, ...) { cmd <- make_points_call(location = location, x = x, y = y) - ms_sf(input, cmd, sys = sys, sys_mem = sys_mem) + ms_sf(input, cmd, ...) } #' @export diff --git a/R/rmapshaper.R b/R/rmapshaper.R index 6114aed..8eedab1 100644 --- a/R/rmapshaper.R +++ b/R/rmapshaper.R @@ -5,9 +5,6 @@ #' \url{https://github.com/mbloch/mapshaper/} to perform topologically-aware #' polygon simplification, as well as other operations such as clipping, #' erasing, dissolving, and converting 'multi-part' to 'single-part' geometries. -#' It relies on the 'geojsonio' package for working with 'geojson' objects, the 'sf' -#' package for working with 'sf' objects, and the 'sp' and 'rgdal' packages for -#' working with 'Spatial' objects. #' #' @section rmapshaper functions: #' diff --git a/R/simplify.R b/R/simplify.R index cad1b2f..1a267ec 100644 --- a/R/simplify.R +++ b/R/simplify.R @@ -6,7 +6,6 @@ #' @param input spatial object to simplify. One of: #' \itemize{ #' \item \code{geo_json} or \code{character} polygons or lines; -#' \item \code{geo_list} polygons or lines; #' \item \code{SpatialPolygons*} or \code{SpatialLines*}; #' \item \code{sf} or \code{sfc} polygons or lines object #' } @@ -34,7 +33,7 @@ #' Ignored for \code{Spatial*} objects, as it is always \code{TRUE}. #' @param snap_interval Specify snapping distance in source units, must be a #' numeric. Default \code{NULL} -#' @inheritParams apply_mapshaper_commands +#' @inheritDotParams apply_mapshaper_commands force_FC sys sys_mem quiet #' #' @return a simplified representation of the geometry in the same class as the #' input @@ -68,73 +67,56 @@ #' [-70.603637, -33.399918] #' ]] #' } -#' }', class = c("json", "geo_json")) +#' }', class = c("geojson", "json")) #' #' ms_simplify(poly, keep = 0.1) #' -#' # With a SpatialPolygonsDataFrame: +#' # With an sf object #' -#' poly_sp <- geojsonio::geojson_sp(poly) -#' ms_simplify(poly_sp, keep = 0.5) +#' poly_sf <- geojsonsf::geojson_sf(poly) +#' ms_simplify(poly_sf, keep = 0.5) #' #' @export ms_simplify <- function(input, keep = 0.05, method = NULL, weighting = 0.7, keep_shapes = FALSE, no_repair = FALSE, snap = TRUE, - explode = FALSE, force_FC = TRUE, drop_null_geometries = TRUE, - snap_interval = NULL, sys = FALSE, sys_mem = 8) { + explode = FALSE, drop_null_geometries = TRUE, + snap_interval = NULL, ...) { UseMethod("ms_simplify") } #' @export ms_simplify.character <- function(input, keep = 0.05, method = NULL, weighting = 0.7, keep_shapes = FALSE, no_repair = FALSE, - snap = TRUE, explode = FALSE, force_FC = TRUE, - drop_null_geometries = TRUE, snap_interval = NULL, sys = FALSE, sys_mem = 8) { + snap = TRUE, explode = FALSE, + drop_null_geometries = TRUE, snap_interval = NULL, ...) { input <- check_character_input(input) ms_simplify_json(input = input, keep = keep, method = method, weighting = weighting, keep_shapes = keep_shapes, no_repair = no_repair, snap = snap, explode = explode, - force_FC = force_FC, drop_null_geometries = drop_null_geometries, - snap_interval = snap_interval, sys = sys, sys_mem = sys_mem) + drop_null_geometries = drop_null_geometries, + snap_interval = snap_interval, ...) } #' @export -ms_simplify.geo_json <- function(input, keep = 0.05, method = NULL, weighting = 0.7, +ms_simplify.json <- function(input, keep = 0.05, method = NULL, weighting = 0.7, keep_shapes = FALSE, no_repair = FALSE, - snap = TRUE, explode = FALSE, force_FC = TRUE, - drop_null_geometries = TRUE, snap_interval = NULL, sys = FALSE, sys_mem = 8) { + snap = TRUE, explode = FALSE, + drop_null_geometries = TRUE, snap_interval = NULL, ...) { ms_simplify_json(input = input, keep = keep, method = method, weighting = weighting, keep_shapes = keep_shapes, no_repair = no_repair, snap = snap, explode = explode, - force_FC = force_FC, drop_null_geometries = drop_null_geometries, - snap_interval = snap_interval, sys = sys, sys_mem = sys_mem) -} - -#' @export -ms_simplify.geo_list <- function(input, keep = 0.05, method = NULL, - weighting = 0.7, keep_shapes = FALSE, - no_repair = FALSE, snap = TRUE, explode = FALSE, - force_FC = TRUE, drop_null_geometries = TRUE, - snap_interval = NULL, sys = FALSE, sys_mem = 8) { - geojson <- geo_list_to_json(input) - - ret <- ms_simplify_json(input = geojson, keep = keep, method = method, - weighting = weighting, keep_shapes = keep_shapes, - no_repair = no_repair, snap = snap, explode = explode, - force_FC = force_FC, drop_null_geometries = drop_null_geometries, - snap_interval = snap_interval, sys = sys, sys_mem = sys_mem) - - geojsonio::geojson_list(ret) + drop_null_geometries = drop_null_geometries, + snap_interval = snap_interval, ...) } #' @export ms_simplify.SpatialPolygons <- function(input, keep = 0.05, method = NULL, weighting = 0.7, keep_shapes = FALSE, no_repair = FALSE, snap = TRUE, explode = FALSE, - force_FC = TRUE, drop_null_geometries = TRUE, - snap_interval = NULL, sys = FALSE, sys_mem = 8) { + drop_null_geometries = TRUE, + snap_interval = NULL, ...) { if (!is(input, "Spatial")) stop("input must be a spatial object") @@ -143,7 +125,7 @@ ms_simplify.SpatialPolygons <- function(input, keep = 0.05, method = NULL, weigh snap = snap, explode = explode, drop_null_geometries = !keep_shapes, snap_interval = snap_interval) - ms_sp(input, call, sys = sys, sys_mem = sys_mem) + ms_sp(input, call, ...) } @@ -154,8 +136,8 @@ ms_simplify.SpatialLines <- ms_simplify.SpatialPolygons ms_simplify.sf <- function(input, keep = 0.05, method = NULL, weighting = 0.7, keep_shapes = FALSE, no_repair = FALSE, snap = TRUE, explode = FALSE, - force_FC = TRUE, drop_null_geometries = TRUE, - snap_interval = NULL, sys = FALSE, sys_mem = 8) { + drop_null_geometries = TRUE, + snap_interval = NULL, ...) { if (!all(sf::st_geometry_type(input) %in% c("LINESTRING", "MULTILINESTRING", "POLYGON", "MULTIPOLYGON"))) { @@ -169,21 +151,21 @@ ms_simplify.sf <- function(input, keep = 0.05, method = NULL, weighting = 0.7, drop_null_geometries = !keep_shapes, snap_interval = snap_interval) - ms_sf(input, call, sys = sys, sys_mem = sys_mem) + ms_sf(input, call, ...) } #' @export ms_simplify.sfc <- ms_simplify.sf ms_simplify_json <- function(input, keep, method, weighting, keep_shapes, no_repair, snap, - explode, force_FC, drop_null_geometries, snap_interval, sys, sys_mem) { + explode, drop_null_geometries, snap_interval, ...) { call <- make_simplify_call(keep = keep, method = method, weighting = weighting, keep_shapes = keep_shapes, no_repair = no_repair, snap = snap, explode = explode, drop_null_geometries = drop_null_geometries, snap_interval = snap_interval) - ret <- apply_mapshaper_commands(data = input, command = call, force_FC = force_FC, sys = sys, sys_mem = sys_mem) + ret <- apply_mapshaper_commands(data = input, command = call, ...) ret } diff --git a/R/utils.R b/R/utils.R index a469b89..59ed420 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,24 +1,34 @@ #' Apply a mapshaper command string to a geojson object #' -#' @param data geojson object or path to geojson file. If a file path, \code{sys} -#' must be true +#' @param data character containing geojson or path to geojson file. +#' If a file path, \code{sys} must be true. #' @param command valid mapshaper command string -#' @param force_FC should the output be forced to be a FeatureCollection (or -#' Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -#' FeatureCollections are more compatible with rgdal::readOGR and -#' geojsonio::geojson_sp. If FALSE and there are no attributes associated with -#' the geometries, a GeometryCollection (or Spatial object with no dataframe) -#' will be output. +#' @param force_FC should the output be forced to be a FeatureCollection (or sf object or +#' Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +#' there are no attributes associated with the geometries, a +#' GeometryCollection (or Spatial object with no dataframe, or sfc) will be output. #' @param sys Should the system mapshaper be used instead of the bundled mapshaper? Gives #' better performance on large files. Requires the mapshaper node package to be installed #' and on the PATH. #' @param sys_mem How much memory (in GB) should be allocated if using the system #' mapshaper (`sys = TRUE`)? Default 8. Ignored if `sys = FALSE`. +#' This can also be set globally with the option `"mapshaper.sys_mem"` +#' @param quiet If `sys = TRUE`, should the mapshaper messages be silenced? Default `FALSE`. +#' This can also be set globally with the option `"mapshaper.sys_quiet"` #' #' @return geojson #' @export +#' @examples #' -apply_mapshaper_commands <- function(data, command, force_FC = TRUE, sys = FALSE, sys_mem = 8) { +#' nc <- sf::read_sf(system.file("gpkg/nc.gpkg", package = "sf")) +#' rmapshaper::apply_mapshaper_commands(geojsonsf::sf_geojson(nc), "-clean") +#' +apply_mapshaper_commands <- function(data, command, force_FC = TRUE, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { + if (!is.logical(force_FC)) stop("force_FC must be TRUE or FALSE", call. = FALSE) + if (!is.logical(sys)) stop("sys must be TRUE or FALSE", call. = FALSE) + if (!is.numeric(sys_mem)) stop("sys_mem must be numeric", call. = FALSE) data <- as.character(data) @@ -43,7 +53,7 @@ apply_mapshaper_commands <- function(data, command, force_FC = TRUE, sys = FALSE command <- paste(ms_compact(command), collapse = " ") if (sys) { - ret <- sys_mapshaper(data = data, command = command, sys_mem = sys_mem) + ret <- sys_mapshaper(data = data, command = command, sys_mem = sys_mem, quiet = quiet) } else { ms <- ms_make_ctx() @@ -75,7 +85,9 @@ ms_make_ctx <- function() { ctx } -sys_mapshaper <- function(data, data2 = NULL, command, sys_mem = 8) { +sys_mapshaper <- function(data, data2 = NULL, command, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { # Get full path to sys mapshaper, use mapshaper-xl ms_path <- paste0(check_sys_mapshaper("mapshaper-xl", verbose = FALSE)) @@ -83,6 +95,9 @@ sys_mapshaper <- function(data, data2 = NULL, command, sys_mem = 8) { # by write_sf or writeOGR read_write <- !file.exists(data) + in_data_file <- data + in_data_file2 <- data2 + if (read_write) { in_data_file <- temp_geojson() readr::write_file(data, in_data_file) @@ -93,18 +108,19 @@ sys_mapshaper <- function(data, data2 = NULL, command, sys_mem = 8) { readr::write_file(data2, in_data_file2) on.exit(unlink(in_data_file2), add = TRUE) } - - } else { - in_data_file <- data - in_data_file2 <- data2 } out_data_file <- temp_geojson() - if (!is.null(data2)) { - cmd_args <- c(sys_mem, shQuote(in_data_file), command, shQuote(in_data_file2), "-o", shQuote(out_data_file)) - } else { - cmd_args <- c(sys_mem, shQuote(in_data_file), command, "-o", shQuote(out_data_file)) - } + + cmd_args <- c( + sys_mem, + shQuote(in_data_file), + command, + shQuote(in_data_file2), # will be NULL if no data2/in_data_file2 + "-o", shQuote(out_data_file), + if (quiet) "-quiet" + ) + system2(ms_path, cmd_args) if (read_write) { @@ -131,7 +147,9 @@ return_data = data; }" } -ms_sp <- function(input, call, sys = FALSE, sys_mem = 8) { +ms_sp <- function(input, call, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { has_data <- .hasSlot(input, "data") if (has_data) { @@ -140,7 +158,7 @@ ms_sp <- function(input, call, sys = FALSE, sys_mem = 8) { geojson <- sp_to_GeoJSON(input, file = sys) - ret <- apply_mapshaper_commands(data = geojson, command = call, force_FC = TRUE, sys = sys, sys_mem = sys_mem) + ret <- apply_mapshaper_commands(data = geojson, command = call, force_FC = TRUE, sys = sys, sys_mem = sys_mem, quiet = quiet) if (!sys & grepl('^\\{"type":"GeometryCollection"', ret)) { stop("Cannot convert result to a Spatial* object. @@ -148,7 +166,7 @@ ms_sp <- function(input, call, sys = FALSE, sys_mem = 8) { were reduced to null.", call. = FALSE) } - ret <- GeoJSON_to_sp(ret, proj = attr(geojson, "proj")) + ret <- GeoJSON_to_sp(ret, crs = attr(geojson, "crs")) # remove data slot if input didn't have one (default out_class is the class of the input) if (!has_data) { @@ -160,25 +178,29 @@ ms_sp <- function(input, call, sys = FALSE, sys_mem = 8) { ret } -GeoJSON_to_sp <- function(geojson, proj = NULL) { - x_sf <- GeoJSON_to_sf(geojson, proj) +GeoJSON_to_sp <- function(geojson, crs = NULL) { + x_sf <- GeoJSON_to_sf(geojson, crs) as(x_sf, "Spatial") } sp_to_GeoJSON <- function(sp, file = FALSE){ - proj <- slot(sp, "proj4string") + + crs <- methods::slot(sp, "proj4string") + if (file) { - js <- sf_sp_to_tempfile(sp) + js <- sp_to_tempfile(sp) } else { - js_tmp <- sf_sp_to_tempfile(sp) + js_tmp <- sp_to_tempfile(sp) js <- readr::read_file(js_tmp, locale = readr::locale()) on.exit(unlink(js_tmp)) } - structure(js, proj = proj) + structure(js, crs = crs) } ## Utilties for sf -ms_sf <- function(input, call, sys = FALSE, sys_mem = 8) { +ms_sf <- function(input, call, sys = FALSE, + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE)) { has_data <- is(input, "sf") if (has_data) { @@ -191,7 +213,7 @@ ms_sf <- function(input, call, sys = FALSE, sys_mem = 8) { geojson <- sf_to_GeoJSON(input, file = sys) - ret <- apply_mapshaper_commands(data = geojson, command = call, force_FC = TRUE, sys = sys, sys_mem = sys_mem) + ret <- apply_mapshaper_commands(data = geojson, command = call, force_FC = TRUE, sys = sys, sys_mem = sys_mem, quiet = quiet) if (!sys & grepl('^\\{"type":"GeometryCollection"', ret)) { stop("Cannot convert result to an sf object. @@ -199,7 +221,7 @@ ms_sf <- function(input, call, sys = FALSE, sys_mem = 8) { were reduced to null.", call. = FALSE) } - ret <- GeoJSON_to_sf(ret, proj = attr(geojson, "proj")) + ret <- GeoJSON_to_sf(ret, crs = attr(geojson, "crs")) ## Only return sfc if that's all that was input if (!has_data) { @@ -216,41 +238,49 @@ ms_sf <- function(input, call, sys = FALSE, sys_mem = 8) { ret } -GeoJSON_to_sf <- function(geojson, proj = NULL) { - sf <- suppressWarnings( - sf::st_read(unclass(geojson), quiet = TRUE, stringsAsFactors = FALSE) - ) - if (!is.null(proj)) { - suppressWarnings(sf::st_crs(sf) <- proj) +GeoJSON_to_sf <- function(geojson, crs = NULL) { + sf <- geojsonsf::geojson_sf(geojson) + if (!is.null(crs)) { + suppressWarnings(sf::st_crs(sf) <- crs) } curly_brace_na(sf) } sf_to_GeoJSON <- function(sf, file = FALSE) { - proj <- sf::st_crs(sf) - if (file) { - js <- sf_sp_to_tempfile(sf) - } else { - ## Use this instead of geojsonio::geojson_json to avoid - ## the geo_json classing that goes on there - js <- geo_list_to_json(sf) - } - structure(js, proj = proj) + crs <- sf::st_crs(sf) + + js <- if (inherits(sf, "sf")) { + geojsonsf::sf_geojson(sf, simplify = FALSE) + } else { + json <- geojsonsf::sfc_geojson(sf) + paste0("{\"type\":\"GeometryCollection\",\"geometries\":[", + paste(json, collapse = ","), + "]}") + } + + if (file) { + path <- tempfile(fileext = ".geojson") + writeLines(js, con = path) + js <- path + } + structure(js, crs = crs) } -geo_list_to_json <- function(x) { - suppressMessages( - jsonlite::toJSON(unclass( - geojsonio::geojson_list(x, type = "auto") - ), auto_unbox = TRUE, digits = 7, na = "null") - ) + +sp_to_tempfile <- function(obj) { + obj <- sf::st_as_sf(sp_to_spdf(obj)) + path <- tempfile(fileext = ".geojson") + sf::st_write(obj, path, driver = "GeoJSON", quiet = TRUE, delete_dsn = TRUE) + normalizePath(path, winslash = "/", mustWork = TRUE) } -sf_sp_to_tempfile <- function(obj) { - path <- suppressMessages( - geojsonio::geojson_write(obj, file = temp_geojson()) - ) - normalizePath(path[["path"]], winslash = "/", mustWork = TRUE) +sp_to_spdf <- function(obj) { + non_df_classes <- c("SpatialLines", "SpatialPolygons", "SpatialPoints") + cls <- inherits(obj, non_df_classes, which = TRUE) + if (!any(cls)) { + return(obj) + } + as(obj, paste0(non_df_classes[as.logical(cls)], "DataFrame")) } #' Check the system mapshaper @@ -316,16 +346,15 @@ add_dummy_id_command <- function() { } class_geo_json <- function(x) { - structure(x, class = c("json", "geo_json")) + structure(x, class = c("geojson", "json")) } -#' @importFrom geojsonlint geojson_validate check_character_input <- function(x) { ## Collapse to character vector of length one if many lines (e.g., if used readLines) if (length(x) > 1) { x <- paste0(x, collapse = "") } - if (!geojsonlint::geojson_validate(x)) stop("Input is not valid geojson") + if (!jsonify::validate_json(x)) stop("Input is not valid geojson") x } @@ -361,6 +390,9 @@ curly_brace_na.sf <- function(x) { col_classes <- function(df) { classes <- lapply(df, function(x) { out <- list() + if (inherits(x, "POSIXlt")) { + stop("POSIXlt classes not supported. Please convert to POSIXct", call. = FALSE) + } out$class <- class(x) if (is.factor(x)) { out$levels <- levels(x) diff --git a/README.Rmd b/README.Rmd index 424e022..fdc245e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -1,7 +1,7 @@ --- output: - md_document: - variant: markdown_github + github_document: + html_preview: true --- @@ -17,10 +17,10 @@ options(warnPartialMatchArgs = FALSE) [![Codecov test coverage](https://codecov.io/gh/ateucher/rmapshaper/branch/master/graph/badge.svg)](https://app.codecov.io/gh/ateucher/rmapshaper?branch=master) -[![R build status](https://github.com/ateucher/rmapshaper/workflows/R-CMD-check/badge.svg)](https://github.com/ateucher/rmapshaper) [![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/rmapshaper)](https://cran.r-project.org/package=rmapshaper) [![CRAN Downloads per month](http://cranlogs.r-pkg.org/badges/rmapshaper)](https://cran.r-project.org/package=rmapshaper) [![CRAN total downloads](http://cranlogs.r-pkg.org/badges/grand-total/rmapshaper?color=lightgrey)](https://cran.r-project.org/package=rmapshaper) +[![R-CMD-check](https://github.com/ateucher/rmapshaper/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/ateucher/rmapshaper/actions/workflows/R-CMD-check.yaml) # rmapshaper @@ -64,84 +64,76 @@ install_github("ateucher/rmapshaper") ### Usage -rmapshaper works with geojson strings (character objects of class `geo_json`) and `list` -geojson objects of class `geo_list`. These classes are defined in the `geojsonio` -package. It also works with `Spatial` classes from the `sp` package, and with `sf` and `scf` objects from the `sf` package. +rmapshaper works with `sf` objects as well as geojson strings (character objects of class `geo_json`). It also works with `Spatial` classes from the `sp` package, though this will likely be retired in the future; users are encouraged to use the more modern `sf` package. -We will use the `states` dataset from the `geojsonio` package and first turn it -into a `geo_json` object: +We will use the `nc.gpkg` file (North Carolina county boundaries) +from the `sf` package and read it in as an `sf` object: ```{r} -library(geojsonio) library(rmapshaper) -library(sp) library(sf) -## First convert to json -states_json <- geojson_json(states, geometry = "polygon", group = "group") +file <- system.file("gpkg/nc.gpkg", package = "sf") +nc_sf <- read_sf(file) +``` -## For ease of illustration via plotting, we will convert to a `SpatialPolygonsDataFrame`: -states_sp <- geojson_sp(states_json) +Plot the original: -## Plot the original -plot(states_sp) +```{r} +plot(nc_sf["FIPS"]) +``` -## Now simplify using default parameters, then plot the simplified states -states_simp <- ms_simplify(states_sp) -plot(states_simp) +Now simplify using default parameters, then plot the simplified North Carolina counties: +```{r} +nc_simp <- ms_simplify(nc_sf) +plot(nc_simp["FIPS"]) ``` You can see that even at very high levels of simplification, the mapshaper -simplification algorithm preserves the topology, including shared boundaries: +simplification algorithm preserves the topology, including shared boundaries. The `keep` +parameter specifies what proportion of vertices to keep: ```{r} -states_very_simp <- ms_simplify(states_sp, keep = 0.001) -plot(states_very_simp) +nc_very_simp <- ms_simplify(nc_sf, keep = 0.001) +plot(nc_very_simp["FIPS"]) ``` -Compare this to the output using `rgeos::gSimplify`, where overlaps and gaps are evident: +Compare this to the output using `sf::st_simplify`, where overlaps and gaps are evident: ```{r} -library(rgeos) -states_gsimp <- gSimplify(states_sp, tol = 1, topologyPreserve = TRUE) -plot(states_gsimp) + +nc_stsimp <- st_simplify(nc_sf, preserveTopology = TRUE, dTolerance = 10000) # dTolerance specified in meters +plot(nc_stsimp["FIPS"]) ``` -The package also works with `sf` objects. This time we'll demonstrate the `ms_innerlines` function: +This time we'll demonstrate the `ms_innerlines` function: ```{r} -library(sf) - -states_sf <- st_as_sf(states_sp) -states_sf_innerlines <- ms_innerlines(states_sf) -plot(states_sf_innerlines) - +nc_sf_innerlines <- ms_innerlines(nc_sf) +plot(nc_sf_innerlines) ``` - -All of the functions are quite fast with `geo_json` character objects and `geo_list` -list objects. They are slower with the `Spatial` classes due to internal conversion -to/from json. Operating on `sf` objects is faster than with `Spatial` objects, but not as fast as with the `geo_json` or `geo_list`. If you are going to do multiple operations on large `Spatial` objects, -it's recommended to first convert to json using `geojson_list` or `geojson_json` from -the `geojsonio` package. All of the functions have the input object as the first argument, +All of the functions are quite fast with `geojson` character objects. They are slower with the `sf` and +`Spatial` classes due to internal conversion to/from json. If you are going to do multiple +operations on large `sf` objects, +it's recommended to first convert to json using `geojsonsf::sf_geojson()`, or `geojsonio::geojson_json()`. +All of the functions have the input object as the first argument, and return the same class of object as the input. As such, they can be chained together. -For a contrived example, using `states_sp` as created above: +For a totally contrived example, using `nc_sf` as created above: -```{r eval=rmapshaper:::check_v8_major_version() >= 6L} -library(geojsonio) +```{r eval=rmapshaper:::check_v8_major_version() >= 6} +library(geojsonsf) library(rmapshaper) -library(sp) -library(magrittr) - -## First convert 'states' dataframe from geojsonio pkg to json -states_json <- geojson_json(states, lat = "lat", lon = "long", group = "group", - geometry = "polygon") - -states_json %>% - ms_erase(bbox = c(-107, 36, -101, 42)) %>% # Cut a big hole in the middle - ms_dissolve() %>% # Dissolve state borders - ms_simplify(keep_shapes = TRUE, explode = TRUE) %>% # Simplify polygon - geojson_sp() %>% # Convert to SpatialPolygonsDataFrame +library(sf) + +## First convert 'states' dataframe from geojsonsf pkg to json + +nc_sf %>% + sf_geojson() |> + ms_erase(bbox = c(-80, 35, -79, 35.5)) |> # Cut a big hole in the middle + ms_dissolve() |> # Dissolve county borders + ms_simplify(keep_shapes = TRUE, explode = TRUE) |> # Simplify polygon + geojson_sf() |> # Convert to sf object plot(col = "blue") # plot ``` @@ -149,7 +141,7 @@ states_json %>% Sometimes if you are dealing with a very large spatial object in R, `rmapshaper` functions will take a very long time or not work at all. As of version `0.4.0`, -you can make use of the system `mapshaper` library if you have it installed. +you can make use of the system `mapshaper` library if you have it installed. This will allow you to work with very large spatial objects. First make sure you have mapshaper installed: @@ -159,27 +151,33 @@ check_sys_mapshaper() ``` If you get an error, you will need to install mapshaper. First install node -(https://nodejs.org/en/) and then install mapshaper with: +(https://nodejs.org/en/) and then install mapshaper in a command prompt with: ``` -npm install -g mapshaper +$ npm install -g mapshaper ``` + Then you can use the `sys` argument in any rmapshaper function: ```{r eval=nzchar(Sys.which("mapshaper"))} -states_simp_internal <- ms_simplify(states_sf) -states_simp_sys <- ms_simplify(states_sf, sys = TRUE, sys_mem=8) #sys_mem specifies the amout of memory to use in Gb. It defaults to 8 if omitted. +nc_simp_internal <- ms_simplify(nc_sf) +nc_simp_sys <- ms_simplify(nc_sf, sys = TRUE, sys_mem=8) #sys_mem specifies the amount of memory to use in Gb. It defaults to 8 if omitted. par(mfrow = c(1,2)) -plot(st_geometry(states_simp_internal), main = "internal") -plot(st_geometry(states_simp_sys), main = "system") +plot(st_geometry(nc_simp_internal), main = "internal") +plot(st_geometry(nc_simp_sys), main = "system") ``` ### Thanks -This package uses the [V8](https://cran.r-project.org/package=V8) package to provide an environment in which to run mapshaper's javascript code in R. It relies heavily on all of the great spatial packages that already exist (especially `sp` and `rgdal`), the `geojsonio` package for converting between `geo_list`, `geo_json`, and `sf` and `Spatial` objects, and the `jsonlite` package for converting between json strings and R objects. +This package uses the [V8](https://cran.r-project.org/package=V8) +package to provide an environment in which to run mapshaper’s javascript +code in R. It relies heavily on all of the great spatial packages that +already exist (especially `sf`), and the `geojsonio` and the `geojsonsf` packages for +converting between `geojson`, `sf` and `Spatial` +object. Thanks to [timelyportfolio](https://github.com/timelyportfolio) for helping me wrangle the javascript to the point where it works in V8. He also wrote the [mapshaper htmlwidget](https://github.com/timelyportfolio/mapshaper_htmlwidget), which provides access to the mapshaper web interface, right in your R session. We have plans to combine the two in the future. diff --git a/README.md b/README.md index 291085b..0fa8a1f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ + [![Codecov test coverage](https://codecov.io/gh/ateucher/rmapshaper/branch/master/graph/badge.svg)](https://app.codecov.io/gh/ateucher/rmapshaper?branch=master) -[![R build -status](https://github.com/ateucher/rmapshaper/workflows/R-CMD-check/badge.svg)](https://github.com/ateucher/rmapshaper) [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/rmapshaper)](https://cran.r-project.org/package=rmapshaper) [![CRAN Downloads per month](http://cranlogs.r-pkg.org/badges/rmapshaper)](https://cran.r-project.org/package=rmapshaper) [![CRAN total downloads](http://cranlogs.r-pkg.org/badges/grand-total/rmapshaper?color=lightgrey)](https://cran.r-project.org/package=rmapshaper) +[![R-CMD-check](https://github.com/ateucher/rmapshaper/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/ateucher/rmapshaper/actions/workflows/R-CMD-check.yaml) # rmapshaper @@ -32,24 +32,23 @@ wrapping most of the core functionality of mapshaper into R functions. So far, `rmapshaper` provides the following functions: -- `ms_simplify` - simplify polygons or lines -- `ms_clip` - clip an area out of a layer using a polygon layer or a - bounding box. Works on polygons, lines, and points -- `ms_erase` - erase an area from a layer using a polygon layer or a - bounding box. Works on polygons, lines, and points -- `ms_dissolve` - aggregate polygon features, optionally specifying a - field to aggregate on. If no field is specified, will merge all - polygons into one. -- `ms_explode` - convert multipart shapes to single part. Works with - polygons, lines, and points in geojson format, but currently only - with polygons and lines in the `Spatial` classes (not - `SpatialMultiPoints` and `SpatialMultiPointsDataFrame`). -- `ms_lines` - convert polygons to topological boundaries (lines) -- `ms_innerlines` - convert polygons to shared inner boundaries - (lines) -- `ms_points` - create points from a polygon layer -- `ms_filter_fields` - Remove fields from the attributes -- `ms_filter_islands` - Remove small detached polygons +- `ms_simplify` - simplify polygons or lines +- `ms_clip` - clip an area out of a layer using a polygon layer or a + bounding box. Works on polygons, lines, and points +- `ms_erase` - erase an area from a layer using a polygon layer or a + bounding box. Works on polygons, lines, and points +- `ms_dissolve` - aggregate polygon features, optionally specifying a + field to aggregate on. If no field is specified, will merge all + polygons into one. +- `ms_explode` - convert multipart shapes to single part. Works with + polygons, lines, and points in geojson format, but currently only with + polygons and lines in the `Spatial` classes (not `SpatialMultiPoints` + and `SpatialMultiPointsDataFrame`). +- `ms_lines` - convert polygons to topological boundaries (lines) +- `ms_innerlines` - convert polygons to shared inner boundaries (lines) +- `ms_points` - create points from a polygon layer +- `ms_filter_fields` - Remove fields from the attributes +- `ms_filter_islands` - Remove small detached polygons If you run into any bugs or have any feature requests, please file an [issue](https://github.com/ateucher/rmapshaper/issues/) @@ -72,131 +71,100 @@ install_github("ateucher/rmapshaper") ### Usage -rmapshaper works with geojson strings (character objects of class -`geo_json`) and `list` geojson objects of class `geo_list`. These -classes are defined in the `geojsonio` package. It also works with -`Spatial` classes from the `sp` package, and with `sf` and `scf` objects -from the `sf` package. +rmapshaper works with `sf` objects as well as geojson strings (character +objects of class `geo_json`). It also works with `Spatial` classes from +the `sp` package, though this will likely be retired in the future; +users are encouraged to use the more modern `sf` package. -We will use the `states` dataset from the `geojsonio` package and first -turn it into a `geo_json` object: +We will use the `nc.gpkg` file (North Carolina county boundaries) from +the `sf` package and read it in as an `sf` object: ``` r -library(geojsonio) -#> Registered S3 method overwritten by 'geojsonsf': -#> method from -#> print.geojson geojson -#> -#> Attaching package: 'geojsonio' -#> The following object is masked from 'package:base': -#> -#> pretty library(rmapshaper) -#> Registered S3 method overwritten by 'geojsonlint': -#> method from -#> print.location dplyr -library(sp) library(sf) -#> Linking to GEOS 3.10.2, GDAL 3.4.2, PROJ 8.2.1; sf_use_s2() is TRUE +#> Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE -## First convert to json -states_json <- geojson_json(states, geometry = "polygon", group = "group") -#> Assuming 'long' and 'lat' are longitude and latitude, respectively +file <- system.file("gpkg/nc.gpkg", package = "sf") +nc_sf <- read_sf(file) +``` -## For ease of illustration via plotting, we will convert to a `SpatialPolygonsDataFrame`: -states_sp <- geojson_sp(states_json) +Plot the original: -## Plot the original -plot(states_sp) +``` r +plot(nc_sf["FIPS"]) ``` -![](tools/readme/unnamed-chunk-2-1.png) +![](tools/readme/unnamed-chunk-3-1.png) -``` r +Now simplify using default parameters, then plot the simplified North +Carolina counties: -## Now simplify using default parameters, then plot the simplified states -states_simp <- ms_simplify(states_sp) -#> Warning in sp::proj4string(sp): CRS object has comment, which is lost in output; in tests, see -#> https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html -plot(states_simp) +``` r +nc_simp <- ms_simplify(nc_sf) +plot(nc_simp["FIPS"]) ``` -![](tools/readme/unnamed-chunk-2-2.png) +![](tools/readme/unnamed-chunk-4-1.png) You can see that even at very high levels of simplification, the mapshaper simplification algorithm preserves the topology, including -shared boundaries: +shared boundaries. The `keep` parameter specifies what proportion of +vertices to keep: ``` r -states_very_simp <- ms_simplify(states_sp, keep = 0.001) -#> Warning in sp::proj4string(sp): CRS object has comment, which is lost in output; in tests, see -#> https://cran.r-project.org/web/packages/sp/vignettes/CRS_warnings.html -plot(states_very_simp) +nc_very_simp <- ms_simplify(nc_sf, keep = 0.001) +plot(nc_very_simp["FIPS"]) ``` -![](tools/readme/unnamed-chunk-3-1.png) +![](tools/readme/unnamed-chunk-5-1.png) -Compare this to the output using `rgeos::gSimplify`, where overlaps and +Compare this to the output using `sf::st_simplify`, where overlaps and gaps are evident: ``` r -library(rgeos) -#> rgeos version: 0.5-9, (SVN revision 684) -#> GEOS runtime version: 3.10.2-CAPI-1.16.0 -#> Please note that rgeos will be retired by the end of 2023, -#> plan transition to sf functions using GEOS at your earliest convenience. -#> GEOS using OverlayNG -#> Linking to sp version: 1.4-7 -#> Polygon checking: TRUE -states_gsimp <- gSimplify(states_sp, tol = 1, topologyPreserve = TRUE) -plot(states_gsimp) + +nc_stsimp <- st_simplify(nc_sf, preserveTopology = TRUE, dTolerance = 10000) # dTolerance specified in meters +plot(nc_stsimp["FIPS"]) ``` -![](tools/readme/unnamed-chunk-4-1.png) +![](tools/readme/unnamed-chunk-6-1.png) -The package also works with `sf` objects. This time we’ll demonstrate -the `ms_innerlines` function: +This time we’ll demonstrate the `ms_innerlines` function: ``` r -library(sf) - -states_sf <- st_as_sf(states_sp) -states_sf_innerlines <- ms_innerlines(states_sf) -plot(states_sf_innerlines) +nc_sf_innerlines <- ms_innerlines(nc_sf) +plot(nc_sf_innerlines) ``` -![](tools/readme/unnamed-chunk-5-1.png) +![](tools/readme/unnamed-chunk-7-1.png) -All of the functions are quite fast with `geo_json` character objects -and `geo_list` list objects. They are slower with the `Spatial` classes -due to internal conversion to/from json. Operating on `sf` objects is -faster than with `Spatial` objects, but not as fast as with the -`geo_json` or `geo_list`. If you are going to do multiple operations on -large `Spatial` objects, it’s recommended to first convert to json using -`geojson_list` or `geojson_json` from the `geojsonio` package. All of -the functions have the input object as the first argument, and return -the same class of object as the input. As such, they can be chained -together. For a contrived example, using `states_sp` as created above: +All of the functions are quite fast with `geojson` character objects. +They are slower with the `sf` and `Spatial` classes due to internal +conversion to/from json. If you are going to do multiple operations on +large `sf` objects, it’s recommended to first convert to json using +`geojsonsf::sf_geojson()`, or `geojsonio::geojson_json()`. All of the +functions have the input object as the first argument, and return the +same class of object as the input. As such, they can be chained +together. For a totally contrived example, using `nc_sf` as created +above: ``` r -library(geojsonio) +library(geojsonsf) library(rmapshaper) -library(sp) -library(magrittr) - -## First convert 'states' dataframe from geojsonio pkg to json -states_json <- geojson_json(states, lat = "lat", lon = "long", group = "group", - geometry = "polygon") - -states_json %>% - ms_erase(bbox = c(-107, 36, -101, 42)) %>% # Cut a big hole in the middle - ms_dissolve() %>% # Dissolve state borders - ms_simplify(keep_shapes = TRUE, explode = TRUE) %>% # Simplify polygon - geojson_sp() %>% # Convert to SpatialPolygonsDataFrame +library(sf) + +## First convert 'states' dataframe from geojsonsf pkg to json + +nc_sf %>% + sf_geojson() |> + ms_erase(bbox = c(-80, 35, -79, 35.5)) |> # Cut a big hole in the middle + ms_dissolve() |> # Dissolve county borders + ms_simplify(keep_shapes = TRUE, explode = TRUE) |> # Simplify polygon + geojson_sf() |> # Convert to sf object plot(col = "blue") # plot ``` -![](tools/readme/unnamed-chunk-6-1.png) +![](tools/readme/unnamed-chunk-8-1.png) ### Using the system mapshaper @@ -210,38 +178,37 @@ First make sure you have mapshaper installed: ``` r check_sys_mapshaper() -#> mapshaper version 0.5.88 is installed and on your PATH -#> mapshaper-xl -#> "/usr/local/bin/mapshaper-xl" +#> mapshaper version 0.6.25 is installed and on your PATH +#> mapshaper-xl +#> "/opt/homebrew/bin/mapshaper-xl" ``` If you get an error, you will need to install mapshaper. First install -node () and then install mapshaper with: +node () and then install mapshaper in a command +prompt with: - npm install -g mapshaper + $ npm install -g mapshaper Then you can use the `sys` argument in any rmapshaper function: ``` r -states_simp_internal <- ms_simplify(states_sf) -states_simp_sys <- ms_simplify(states_sf, sys = TRUE, sys_mem=8) #sys_mem specifies the amout of memory to use in Gb. It defaults to 8 if omitted. +nc_simp_internal <- ms_simplify(nc_sf) +nc_simp_sys <- ms_simplify(nc_sf, sys = TRUE, sys_mem=8) #sys_mem specifies the amount of memory to use in Gb. It defaults to 8 if omitted. par(mfrow = c(1,2)) -plot(st_geometry(states_simp_internal), main = "internal") -plot(st_geometry(states_simp_sys), main = "system") +plot(st_geometry(nc_simp_internal), main = "internal") +plot(st_geometry(nc_simp_sys), main = "system") ``` -![](tools/readme/unnamed-chunk-8-1.png) +![](tools/readme/unnamed-chunk-10-1.png) ### Thanks This package uses the [V8](https://cran.r-project.org/package=V8) package to provide an environment in which to run mapshaper’s javascript code in R. It relies heavily on all of the great spatial packages that -already exist (especially `sp` and `rgdal`), the `geojsonio` package for -converting between `geo_list`, `geo_json`, and `sf` and `Spatial` -objects, and the `jsonlite` package for converting between json strings -and R objects. +already exist (especially `sf`), and the `geojsonio` and the `geojsonsf` +packages for converting between `geojson`, `sf` and `Spatial` object. Thanks to [timelyportfolio](https://github.com/timelyportfolio) for helping me wrangle the javascript to the point where it works in V8. He diff --git a/codecov.yml b/codecov.yml index 8f36b6c..04c5585 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,7 +6,9 @@ coverage: default: target: auto threshold: 1% + informational: true patch: default: target: auto threshold: 1% + informational: true diff --git a/man/apply_mapshaper_commands.Rd b/man/apply_mapshaper_commands.Rd index d15c4e4..518482d 100644 --- a/man/apply_mapshaper_commands.Rd +++ b/man/apply_mapshaper_commands.Rd @@ -9,28 +9,31 @@ apply_mapshaper_commands( command, force_FC = TRUE, sys = FALSE, - sys_mem = 8 + sys_mem = getOption("mapshaper.sys_mem", default = 8), + quiet = getOption("mapshaper.sys_quiet", default = FALSE) ) } \arguments{ -\item{data}{geojson object or path to geojson file. If a file path, \code{sys} -must be true} +\item{data}{character containing geojson or path to geojson file. +If a file path, \code{sys} must be true.} \item{command}{valid mapshaper command string} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} +\item{force_FC}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} \item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} \item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + +\item{quiet}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} } \value{ geojson @@ -38,3 +41,9 @@ geojson \description{ Apply a mapshaper command string to a geojson object } +\examples{ + +nc <- sf::read_sf(system.file("gpkg/nc.gpkg", package = "sf")) +rmapshaper::apply_mapshaper_commands(geojsonsf::sf_geojson(nc), "-clean") + +} diff --git a/man/drop_null_geometries.Rd b/man/drop_null_geometries.Rd index 2798c8c..2827b7e 100644 --- a/man/drop_null_geometries.Rd +++ b/man/drop_null_geometries.Rd @@ -2,17 +2,17 @@ % Please edit documentation in R/drop_null_geometries.R \name{drop_null_geometries} \alias{drop_null_geometries} -\title{Drop features from a \code{geo_list} or \code{geo_json} FeatureCollection with null geometries} +\title{Drop features from a \code{geo_json} FeatureCollection with null geometries} \usage{ drop_null_geometries(x) } \arguments{ -\item{x}{a \code{geo_list} or \code{geo_json} FeatureCollection} +\item{x}{a \code{geo_json} FeatureCollection} } \value{ -a \code{geo_list} or \code{geo_json} FeatureCollection with Features with null geometries +a \code{geo_json} FeatureCollection with Features with null geometries removed } \description{ -Drop features from a \code{geo_list} or \code{geo_json} FeatureCollection with null geometries +Drop features from a \code{geo_json} FeatureCollection with null geometries } diff --git a/man/ms_clip.Rd b/man/ms_clip.Rd index 16e2e32..cc75e11 100644 --- a/man/ms_clip.Rd +++ b/man/ms_clip.Rd @@ -4,21 +4,12 @@ \alias{ms_clip} \title{Remove features or portions of features that fall outside a clipping area.} \usage{ -ms_clip( - target, - clip = NULL, - bbox = NULL, - remove_slivers = FALSE, - force_FC = TRUE, - sys = FALSE, - sys_mem = 8 -) +ms_clip(target, clip = NULL, bbox = NULL, remove_slivers = FALSE, ...) } \arguments{ \item{target}{the target layer from which to remove portions. One of: \itemize{ \item \code{geo_json} or \code{character} points, lines, or polygons; -\item \code{geo_list} points, lines, or polygons; \item \code{SpatialPolygons}, \code{SpatialLines}, \code{SpatialPoints}; \item \code{sf} or \code{sfc} points, lines, or polygons object }} @@ -26,7 +17,6 @@ ms_clip( \item{clip}{the clipping layer (polygon). One of: \itemize{ \item \code{geo_json} or \code{character} polygons; -\item \code{geo_list} polygons; \item \code{SpatialPolygons*}; \item \code{sf} or \code{sfc} polygons object }} @@ -36,19 +26,22 @@ the target layer. Supply as a numeric vector: \code{c(minX, minY, maxX, maxY)}.} \item{remove_slivers}{Remove tiny sliver polygons created by clipping. (Default \code{FALSE})} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ clipped target in the same class as the input target @@ -59,8 +52,8 @@ Removes portions of the target layer that fall outside the clipping layer or bou \examples{ if (rmapshaper:::check_v8_major_version() >= 6L) { - library(geojsonio, quietly = TRUE) - library(sp) + library(geojsonsf, quietly = TRUE) + library(sf) poly <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\",\"properties\":{}, @@ -72,8 +65,8 @@ if (rmapshaper:::check_v8_major_version() >= 6L) { [52.0329,-49.5677],[50.1747,-52.1814],[49.0098,-52.3641], [52.7068,-45.7639],[43.2278,-47.1908],[48.4755,-45.1388], [50.327,-43.5207],[48.0804,-41.2784],[49.6307,-40.6159], - [52.8658,-44.7219]]]}}]}", class = c("json", "geo_json")) - poly <- geojson_sp(poly) + [52.8658,-44.7219]]]}}]}", class = c("geojson", "json")) + poly <- geojson_sf(poly) plot(poly) clip_poly <- structure('{ @@ -91,8 +84,8 @@ if (rmapshaper:::check_v8_major_version() >= 6L) { ] ] } - }', class = c("json", "geo_json")) - clip_poly <- geojson_sp(clip_poly) + }', class = c("geojson", "json")) + clip_poly <- geojson_sf(clip_poly) plot(clip_poly) out <- ms_clip(poly, clip_poly) diff --git a/man/ms_dissolve.Rd b/man/ms_dissolve.Rd index 7319104..973d72a 100644 --- a/man/ms_dissolve.Rd +++ b/man/ms_dissolve.Rd @@ -11,16 +11,13 @@ ms_dissolve( copy_fields = NULL, weight = NULL, snap = TRUE, - force_FC = TRUE, - sys = FALSE, - sys_mem = 8 + ... ) } \arguments{ \item{input}{spatial object to dissolve. One of: \itemize{ \item \code{geo_json} or \code{character} points or polygons; -\item \code{geo_list} points or polygons; \item \code{SpatialPolygons}, or \code{SpatialPoints} }} @@ -36,19 +33,22 @@ copied to the aggregated feature.} \item{snap}{Snap together vertices within a small distance threshold to fix small coordinate misalignment in adjacent polygons. Default \code{TRUE}.} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ the same class as the input @@ -58,8 +58,8 @@ Aggregates using specified field, or all shapes if no field is given. For point replaces a group of points with their centroid. } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) poly <- structure('{"type":"FeatureCollection", "features":[ @@ -72,21 +72,21 @@ poly <- structure('{"type":"FeatureCollection", "properties":{"a": 5, "b": 3}, "geometry":{"type":"Polygon","coordinates":[[ [100,0],[100,1],[101,1],[101,0],[100,0] - ]]}}]}', class = c("json", "geo_json")) -poly <- geojson_sp(poly) + ]]}}]}', class = c("geojson", "json")) +poly <- geojson_sf(poly) plot(poly) length(poly) -poly@data +poly # Dissolve the polygon out <- ms_dissolve(poly) plot(out) length(out) -out@data +out # Dissolve and summing columns out <- ms_dissolve(poly, sum_fields = c("a", "b")) plot(out) -out@data +out } diff --git a/man/ms_erase.Rd b/man/ms_erase.Rd index ec4fb1f..725c28f 100644 --- a/man/ms_erase.Rd +++ b/man/ms_erase.Rd @@ -4,28 +4,18 @@ \alias{ms_erase} \title{Remove features or portions of features that fall inside a specified area} \usage{ -ms_erase( - target, - erase = NULL, - bbox = NULL, - remove_slivers = FALSE, - force_FC = TRUE, - sys = FALSE, - sys_mem = 8 -) +ms_erase(target, erase = NULL, bbox = NULL, remove_slivers = FALSE, ...) } \arguments{ \item{target}{the target layer from which to remove portions. One of: \itemize{ \item \code{geo_json} or \code{character} points, lines, or polygons; -\item \code{geo_list} points, lines, or polygons; \item \code{SpatialPolygons}, \code{SpatialLines}, \code{SpatialPoints} }} \item{erase}{the erase layer (polygon). One of: \itemize{ \item \code{geo_json} or \code{character} polygons; -\item \code{geo_list} polygons; \item \code{SpatialPolygons*} }} @@ -34,19 +24,22 @@ the target layer. Supply as a numeric vector: \code{c(minX, minY, maxX, maxY)}.} \item{remove_slivers}{Remove tiny sliver polygons created by erasing. (Default \code{FALSE})} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ erased target in the same format as the input target @@ -56,8 +49,8 @@ Removes portions of the target layer that fall inside the erasing layer or bound } \examples{ if (rmapshaper:::check_v8_major_version() >= 6L) { - library(geojsonio, quietly = TRUE) - library(sp) + library(geojsonsf, quietly = TRUE) + library(sf) points <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\",\"properties\":{}, @@ -74,8 +67,8 @@ if (rmapshaper:::check_v8_major_version() >= 6L) { {\"type\":\"Point\",\"coordinates\":[61.0835,-40.7529]}}, {\"type\":\"Feature\",\"properties\":{},\"geometry\": {\"type\":\"Point\",\"coordinates\":[58.0202,-43.634]}}]}", - class = c("json", "geo_json")) - points <- geojson_sp(points) + class = c("geojson", "json")) + points <- geojson_sf(points) plot(points) erase_poly <- structure('{ @@ -93,8 +86,8 @@ if (rmapshaper:::check_v8_major_version() >= 6L) { ] ] } - }', class = c("json", "geo_json")) - erase_poly <- geojson_sp(erase_poly) + }', class = c("geojson", "json")) + erase_poly <- geojson_sf(erase_poly) out <- ms_erase(points, erase_poly) plot(out, add = TRUE) diff --git a/man/ms_explode.Rd b/man/ms_explode.Rd index 912fcf1..784980c 100644 --- a/man/ms_explode.Rd +++ b/man/ms_explode.Rd @@ -4,30 +4,32 @@ \alias{ms_explode} \title{Convert multipart lines or polygons to singlepart} \usage{ -ms_explode(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) +ms_explode(input, ...) } \arguments{ \item{input}{One of: \itemize{ \item \code{geo_json} or \code{character} multipart lines, or polygons; -\item \code{geo_list} multipart lines, or polygons; \item multipart \code{SpatialPolygons}, \code{SpatialLines}; \item \code{sf} or \code{sfc} multipart lines, or polygons object }} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ same class as input @@ -40,27 +42,24 @@ you may find it faster to use \code{sp::disaggregate}. There is currently no method for SpatialMultiPoints } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) -poly <- structure("{\"type\":\"FeatureCollection\",\"crs\": - {\"type\":\"name\",\"properties\":{\"name\": - \"urn:ogc:def:crs:OGC:1.3:CRS84\"}},\"features\": +poly <- "{\"type\":\"FeatureCollection\",\"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\": \"MultiPolygon\",\"coordinates\":[[[[102,2],[102,3], [103,3],[103,2],[102,2]]],[[[100,0],[100,1],[101,1], - [101,0],[100,0]]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", - class = c("json", "geo_json")) + [101,0],[100,0]]]]},\"properties\":{\"a\":0}}\n]}" -poly <- geojson_sp(poly) +poly <- geojson_sf(poly) plot(poly) length(poly) -poly@data +poly # Explode the polygon out <- ms_explode(poly) plot(out) length(out) -out@data +out } diff --git a/man/ms_filter_fields.Rd b/man/ms_filter_fields.Rd index 5708cd3..112cf87 100644 --- a/man/ms_filter_fields.Rd +++ b/man/ms_filter_fields.Rd @@ -4,25 +4,30 @@ \alias{ms_filter_fields} \title{Delete fields in the attribute table} \usage{ -ms_filter_fields(input, fields, sys = FALSE, sys_mem = 8) +ms_filter_fields(input, fields, ...) } \arguments{ \item{input}{spatial object to filter fields on. One of: \itemize{ \item \code{geo_json} or \code{character} points, lines, or polygons; -\item \code{geo_list} points, lines, or polygons; \item \code{SpatialPolygonsDataFrame}, \code{SpatialLinesDataFrame}, \code{SpatialPointsDataFrame}; \item \code{sf} object }} \item{fields}{character vector of fields to retain.} -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ object with only specified attributes retained, in the same class as @@ -32,20 +37,20 @@ the input Removes all fields except those listed in the \code{fields} parameter } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) poly <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\", \"properties\":{\"a\": 1, \"b\":2, \"c\": 3}, \"geometry\":{\"type\":\"Polygon\", \"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]}}]}", - class = c("json", "geo_json")) -poly <- geojson_sp(poly) -poly@data + class = c("geojson", "json")) +poly <- geojson_sf(poly) +poly # Filter (keep) fields a and b, drop c out <- ms_filter_fields(poly, c("a", "b")) -out@data +out } diff --git a/man/ms_filter_islands.Rd b/man/ms_filter_islands.Rd index ac2df1d..ba01ed8 100644 --- a/man/ms_filter_islands.Rd +++ b/man/ms_filter_islands.Rd @@ -9,16 +9,13 @@ ms_filter_islands( min_area = NULL, min_vertices = NULL, drop_null_geometries = TRUE, - force_FC = TRUE, - sys = FALSE, - sys_mem = 8 + ... ) } \arguments{ \item{input}{spatial object to filter. One of: \itemize{ \item \code{geo_json} or \code{character} polygons; -\item \code{geo_list} polygons; \item \code{SpatialPolygons*}; \item \code{sf} or \code{sfc} polygons object }} @@ -33,19 +30,22 @@ calculated using spherical geometry in units of square meters.} Default \code{TRUE}. Ignored for \code{SpatialPolyons*}, as it is always \code{TRUE}.} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ object with only specified features retained, in the same class as @@ -56,8 +56,8 @@ Remove small detached polygons, keeping those with a minimum area and/or a minimum number of vertices. Optionally remove null geometries. } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) poly <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\",\"properties\":{}, @@ -69,9 +69,9 @@ poly <- structure("{\"type\":\"FeatureCollection\", {\"type\":\"Feature\",\"properties\":{}, \"geometry\":{\"type\":\"Polygon\", \"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}}]}", - class = c("json", "geo_json")) + class = c("geojson", "json")) -poly <- geojson_sp(poly) +poly <- geojson_sf(poly) plot(poly) out <- ms_filter_islands(poly, min_area = 12391399903) diff --git a/man/ms_innerlines.Rd b/man/ms_innerlines.Rd index c218389..ac42726 100644 --- a/man/ms_innerlines.Rd +++ b/man/ms_innerlines.Rd @@ -4,30 +4,32 @@ \alias{ms_innerlines} \title{Create a line layer consisting of shared boundaries with no attribute data} \usage{ -ms_innerlines(input, force_FC = TRUE, sys = FALSE, sys_mem = 8) +ms_innerlines(input, ...) } \arguments{ \item{input}{input polygons object to convert to inner lines. One of: \itemize{ \item \code{geo_json} or \code{character} polygons; -\item \code{geo_list} polygons; \item \code{SpatialPolygons*}; \item \code{sf} or \code{sfc} polygons object }} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ lines in the same class as the input layer, but without attributes @@ -36,8 +38,8 @@ lines in the same class as the input layer, but without attributes Create a line layer consisting of shared boundaries with no attribute data } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) poly <- structure('{"type":"FeatureCollection", "features":[ @@ -60,9 +62,9 @@ poly <- structure('{"type":"FeatureCollection", "properties":{"foo": "b"}, "geometry":{"type":"Polygon","coordinates":[[ [103,1],[103,2],[104,2],[104,1],[103,1] - ]]}}]}', class = c("json", "geo_json")) + ]]}}]}', class = c("geojson", "json")) -poly <- geojson_sp(poly) +poly <- geojson_sf(poly) plot(poly) out <- ms_innerlines(poly) diff --git a/man/ms_lines.Rd b/man/ms_lines.Rd index eb77127..3bf6dc1 100644 --- a/man/ms_lines.Rd +++ b/man/ms_lines.Rd @@ -4,13 +4,12 @@ \alias{ms_lines} \title{Convert polygons to topological boundaries (lines)} \usage{ -ms_lines(input, fields = NULL, force_FC = TRUE, sys = FALSE, sys_mem = 8) +ms_lines(input, fields = NULL, ...) } \arguments{ \item{input}{input polygons object to convert to inner lines. One of: \itemize{ \item \code{geo_json} or \code{character} polygons; -\item \code{geo_list} polygons; \item \code{SpatialPolygons*}; \item \code{sf} or \code{sfc} polygons object }} @@ -22,19 +21,22 @@ intermediate level of hierarchy at TYPE 1, with the lowest-level internal boundaries set to TYPE 2. Supplying a character vector of field names adds additional levels of hierarchy.} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ topological boundaries as lines, in the same class as the input @@ -44,8 +46,8 @@ Convert polygons to topological boundaries (lines) } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) poly <- structure('{"type":"FeatureCollection", "features":[ @@ -63,9 +65,9 @@ poly <- structure('{"type":"FeatureCollection", "properties":{"foo": "b"}, "geometry":{"type":"Polygon","coordinates":[[ [102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1] - ]]}}]}', class = c("json", "geo_json")) + ]]}}]}', class = c("geojson", "json")) -poly <- geojson_sp(poly) +poly <- geojson_sf(poly) summary(poly) plot(poly) diff --git a/man/ms_points.Rd b/man/ms_points.Rd index 9e80e42..81838d6 100644 --- a/man/ms_points.Rd +++ b/man/ms_points.Rd @@ -4,21 +4,12 @@ \alias{ms_points} \title{Create points from a polygon layer} \usage{ -ms_points( - input, - location = NULL, - x = NULL, - y = NULL, - force_FC = TRUE, - sys = FALSE, - sys_mem = 8 -) +ms_points(input, location = NULL, x = NULL, y = NULL, ...) } \arguments{ \item{input}{input polygons object to convert to points. One of: \itemize{ \item \code{geo_json} or \code{character} polygons; -\item \code{geo_list} polygons; \item \code{SpatialPolygons*}; \item \code{sf} or \code{sfc} polygons object }} @@ -36,19 +27,22 @@ specified. If left as \code{NULL} (default), will use centroids.} \item{y}{name of field containing y coordinate values. Must be \code{NULL} if \code{location} is specified.} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ points in the same class as the input. @@ -59,8 +53,8 @@ Can be generated from the polygons by specifying \code{location} to be attributes of the layer containing \code{x} and \code{y} coordinates. } \examples{ -library(geojsonio) -library(sp) +library(geojsonsf) +library(sf) poly <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\",\"properties\": @@ -73,9 +67,9 @@ poly <- structure("{\"type\":\"FeatureCollection\", {\"type\":\"Feature\",\"properties\":{\"x_pos\": 5, \"y_pos\": 6}, \"geometry\":{\"type\":\"Polygon\", \"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}}]}", - class = c("json", "geo_json")) + class = c("geojson", "json")) -poly <- geojson_sp(poly) +poly <- geojson_sf(poly) summary(poly) plot(poly) diff --git a/man/ms_simplify.Rd b/man/ms_simplify.Rd index db7d3e2..16e39d4 100644 --- a/man/ms_simplify.Rd +++ b/man/ms_simplify.Rd @@ -13,18 +13,15 @@ ms_simplify( no_repair = FALSE, snap = TRUE, explode = FALSE, - force_FC = TRUE, drop_null_geometries = TRUE, snap_interval = NULL, - sys = FALSE, - sys_mem = 8 + ... ) } \arguments{ \item{input}{spatial object to simplify. One of: \itemize{ \item \code{geo_json} or \code{character} polygons or lines; -\item \code{geo_list} polygons or lines; \item \code{SpatialPolygons*} or \code{SpatialLines*}; \item \code{sf} or \code{sfc} polygons or lines object }} @@ -56,25 +53,28 @@ small coordinate misalignment in adjacent polygons. Default \code{TRUE}.} This prevents small shapes from disappearing during simplification if \code{keep_shapes = TRUE}. Default \code{FALSE}} -\item{force_FC}{should the output be forced to be a FeatureCollection (or -Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. -FeatureCollections are more compatible with rgdal::readOGR and -geojsonio::geojson_sp. If FALSE and there are no attributes associated with -the geometries, a GeometryCollection (or Spatial object with no dataframe) -will be output.} - \item{drop_null_geometries}{should Features with null geometries be dropped? Ignored for \code{Spatial*} objects, as it is always \code{TRUE}.} \item{snap_interval}{Specify snapping distance in source units, must be a numeric. Default \code{NULL}} -\item{sys}{Should the system mapshaper be used instead of the bundled mapshaper? Gives +\item{...}{ + Arguments passed on to \code{\link[=apply_mapshaper_commands]{apply_mapshaper_commands}} + \describe{ + \item{\code{force_FC}}{should the output be forced to be a FeatureCollection (or sf object or +Spatial*DataFrame) even if there are no attributes? Default \code{TRUE}. If FALSE and +there are no attributes associated with the geometries, a +GeometryCollection (or Spatial object with no dataframe, or sfc) will be output.} + \item{\code{sys}}{Should the system mapshaper be used instead of the bundled mapshaper? Gives better performance on large files. Requires the mapshaper node package to be installed and on the PATH.} - -\item{sys_mem}{How much memory (in GB) should be allocated if using the system -mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}.} + \item{\code{sys_mem}}{How much memory (in GB) should be allocated if using the system +mapshaper (\code{sys = TRUE})? Default 8. Ignored if \code{sys = FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_mem"}} + \item{\code{quiet}}{If \code{sys = TRUE}, should the mapshaper messages be silenced? Default \code{FALSE}. +This can also be set globally with the option \code{"mapshaper.sys_quiet"}} + }} } \value{ a simplified representation of the geometry in the same class as the @@ -114,13 +114,13 @@ poly <- structure('{ [-70.603637, -33.399918] ]] } -}', class = c("json", "geo_json")) +}', class = c("geojson", "json")) ms_simplify(poly, keep = 0.1) -# With a SpatialPolygonsDataFrame: +# With an sf object -poly_sp <- geojsonio::geojson_sp(poly) -ms_simplify(poly_sp, keep = 0.5) +poly_sf <- geojsonsf::geojson_sf(poly) +ms_simplify(poly_sf, keep = 0.5) } diff --git a/man/rmapshaper.Rd b/man/rmapshaper.Rd index af13740..6109596 100644 --- a/man/rmapshaper.Rd +++ b/man/rmapshaper.Rd @@ -10,9 +10,6 @@ This is wrapper around the 'mapshaper' 'javascript' library by Matthew Bloch \url{https://github.com/mbloch/mapshaper/} to perform topologically-aware polygon simplification, as well as other operations such as clipping, erasing, dissolving, and converting 'multi-part' to 'single-part' geometries. -It relies on the 'geojsonio' package for working with 'geojson' objects, the 'sf' -package for working with 'sf' objects, and the 'sp' and 'rgdal' packages for -working with 'Spatial' objects. } \section{rmapshaper functions}{ diff --git a/rmapshaper.Rproj b/rmapshaper.Rproj index 7fa7884..9a2c6bf 100755 --- a/rmapshaper.Rproj +++ b/rmapshaper.Rproj @@ -16,7 +16,6 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes -PackageCleanBeforeInstall: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageCheckArgs: --as-cran PackageRoxygenize: rd,collate,namespace diff --git a/scratch/fedora-test/Dockerfile b/scratch/docker/fedora-test/Dockerfile similarity index 100% rename from scratch/fedora-test/Dockerfile rename to scratch/docker/fedora-test/Dockerfile diff --git a/scratch/fedora-test/README.Rmd b/scratch/docker/fedora-test/README.Rmd similarity index 100% rename from scratch/fedora-test/README.Rmd rename to scratch/docker/fedora-test/README.Rmd diff --git a/scratch/fedora-test/README.md b/scratch/docker/fedora-test/README.md similarity index 100% rename from scratch/fedora-test/README.md rename to scratch/docker/fedora-test/README.md diff --git a/scratch/docker/fedora-test/poly.geojson b/scratch/docker/fedora-test/poly.geojson new file mode 100644 index 0000000..55466c4 Binary files /dev/null and b/scratch/docker/fedora-test/poly.geojson differ diff --git a/scratch/docker/fedora-test/poly_simp.geojson b/scratch/docker/fedora-test/poly_simp.geojson new file mode 100644 index 0000000..2da66bf --- /dev/null +++ b/scratch/docker/fedora-test/poly_simp.geojson @@ -0,0 +1,3 @@ +{"type":"FeatureCollection", "features": [ +{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[1745493.1964,6001802.1687],[1688395.3701,5989681.2008],[1611887.8264,6107105.9235],[1684932.9873,6121315.2794],[1745493.1964,6001802.1687]]]},"properties":{"foo":"bar"}} +]} \ No newline at end of file diff --git a/scratch/docker/fedora-test/regional-council-2018-clipped-generalised.gpkg b/scratch/docker/fedora-test/regional-council-2018-clipped-generalised.gpkg new file mode 100644 index 0000000..1aa6d2b Binary files /dev/null and b/scratch/docker/fedora-test/regional-council-2018-clipped-generalised.gpkg differ diff --git a/scratch/docker/fedora-test/statsnzregional-council-2018-clipped-generalised-GPKG.zip b/scratch/docker/fedora-test/statsnzregional-council-2018-clipped-generalised-GPKG.zip new file mode 100644 index 0000000..c53be7b Binary files /dev/null and b/scratch/docker/fedora-test/statsnzregional-council-2018-clipped-generalised-GPKG.zip differ diff --git a/scratch/fedora-test/test.R b/scratch/docker/fedora-test/test.R similarity index 100% rename from scratch/fedora-test/test.R rename to scratch/docker/fedora-test/test.R diff --git a/scratch/docker/linux-check/Dockerfile b/scratch/docker/linux-check/Dockerfile new file mode 100644 index 0000000..ea01389 --- /dev/null +++ b/scratch/docker/linux-check/Dockerfile @@ -0,0 +1,9 @@ +FROM jakubnowosad/rspatial_proj7:latest + +# RUN sudo apt-get update && apt-get install -y curl + +# RUN curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash - \ +# && sudo apt-get install -y libnode-dev libnode64 nodejs \ +# && npm install -g mapshaper + +RUN Rscript -e "install.packages(c('devtools', 'sf', 'V8', 'sp', 'geojsonsf', 'readr', 'geojsonlint', 'rgdal', 'geojsonio'))" diff --git a/scratch/docker/linux-check/README.md b/scratch/docker/linux-check/README.md new file mode 100644 index 0000000..27df2fc --- /dev/null +++ b/scratch/docker/linux-check/README.md @@ -0,0 +1,12 @@ +``` +docker build --tag deb-test . +docker run -d -p 8787:8787 -e PASSWORD=hello deb-test:latest +``` + +Go to localhost:8787 and log in with rstudio/hello. In terminal in Rstudio: + +``` +git clone https://github.com/ateucher/rmapshaper.git +``` + +Then open the `rmapshaper.Rproj` and run checks etc. diff --git a/scratch/test_conversion_performance/test_conversion_performance.R b/scratch/test_conversion_performance/test_conversion_performance.R new file mode 100644 index 0000000..ff43d6f --- /dev/null +++ b/scratch/test_conversion_performance/test_conversion_performance.R @@ -0,0 +1,54 @@ +#' --- +#' output: github_document +#' --- + +library(sf) +library(geojsonio) +library(sp) +library(rgdal) +devtools::load_all() + +u = "https://borders.ukdataservice.ac.uk/ukborders/easy_download/prebuilt/shape/England_caswa_2001_clipped.zip" +# download.file(u, destfile = "zipped_shapefile.zip") +unzip("zipped_shapefile.zip") +f = list.files(pattern = ".shp") + +# sf +res = sf::st_read(f) + +res_simp <- ms_simplify(res, sys = TRUE) + +## Test converting sf to geojson object +system.time(js_gjio <- geojson_json(res_simp)) + +system.time(js_int <- sf_to_GeoJSON(res_simp)) + +all.equal(as.character(js_gjio), as.character(js_int)) + +## Test writing sf to geojson file +system.time(st_write(res, tempfile(fileext = ".geojson"))) + +system.time(sf_sp_to_tempfile(res)) + +system.time( + jsonlite::write_json(unclass(geojson_list(res)), path = tempfile(fileext = ".geojson"), + auto_unbox = TRUE, digits = 7) +) + +# sp +res_sp <- as(res, "Spatial") +res_sp_simp <- as(res_simp, "Spatial") + +## Test converting sf to geojson object +system.time(js_gjio <- geojson_json(res_sp_simp)) + +system.time(js_int <- sp_to_GeoJSON(res_sp_simp)) + +## Test writing sp to geojson file +f <- tempfile() +system.time( + writeOGR(res_sp, paste0(f, ".geojson"), basename(f), driver = "GeoJSON", + check_exists = FALSE) +) + +system.time(sf_sp_to_tempfile(res_sp)) diff --git a/scratch/test_conversion_performance/test_conversion_performance.html b/scratch/test_conversion_performance/test_conversion_performance.html new file mode 100644 index 0000000..123c565 --- /dev/null +++ b/scratch/test_conversion_performance/test_conversion_performance.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + +

test_conversion_performance.R

+

ateucher
+Mon Feb 5 15:02:19 2018

+
library(sf)
+
## Linking to GEOS 3.6.2, GDAL 2.2.3, proj.4 4.9.3
+
library(geojsonio)
+
## 
+## Attaching package: 'geojsonio'
+
+## The following object is masked from 'package:devtools':
+## 
+##     lint
+
+## The following object is masked from 'package:base':
+## 
+##     pretty
+
library(sp)
+library(rgdal)
+
## rgdal: version: 1.2-16, (SVN revision 701)
+##  Geospatial Data Abstraction Library extensions to R successfully loaded
+##  Loaded GDAL runtime: GDAL 2.2.3, released 2017/11/20
+##  Path to GDAL shared files: /usr/local/Cellar/gdal2/2.2.3/share/gdal
+##  GDAL binary built with GEOS: TRUE 
+##  Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
+##  Path to PROJ.4 shared files: (autodetected)
+##  Linking to sp version: 1.2-6
+
devtools::load_all()
+
## Loading rmapshaper
+
u = "https://borders.ukdataservice.ac.uk/ukborders/easy_download/prebuilt/shape/England_caswa_2001_clipped.zip"
+# download.file(u, destfile = "zipped_shapefile.zip")
+unzip("zipped_shapefile.zip")
+f = list.files(pattern = ".shp")
+
+# sf
+res = sf::st_read(f)
+
## Reading layer `england_caswa_2001_clipped' from data source `/Users/ateucher/dev/rmapshaper/scratch/england_caswa_2001_clipped.shp' using driver `ESRI Shapefile'
+## Simple feature collection with 6930 features and 5 fields
+## geometry type:  MULTIPOLYGON
+## dimension:      XY
+## bbox:           xmin: 85665 ymin: 7054 xmax: 655604 ymax: 657534.1
+## epsg (SRID):    NA
+## proj4string:    +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs
+
res_simp <- ms_simplify(res, sys = TRUE)
+
+## Test converting sf to geojson object
+system.time(js_gjio <- geojson_json(res_simp))
+
##    user  system elapsed 
+##  15.235   1.107  16.350
+
system.time(js_int <- sf_to_GeoJSON(res_simp))
+
##    user  system elapsed 
+##   7.270   0.052   7.324
+
all.equal(as.character(js_gjio), as.character(js_int))
+
## [1] TRUE
+
## Test writing sf to geojson file
+system.time(st_write(res, tempfile(fileext = ".geojson")))
+
## Writing layer `filed86d3f87d11c' to data source `/var/folders/2w/x5wq73f93yzgm7hjr_b_54q00000gp/T//RtmpaXfErf/filed86d3f87d11c.geojson' using driver `GeoJSON'
+## features:       6930
+## fields:         5
+## geometry type:  Multi Polygon
+
+##    user  system elapsed 
+##  44.657   2.041  46.906
+
system.time(sf_sp_to_tempfile(res))
+
##    user  system elapsed 
+##  43.767   1.725  45.781
+
system.time(
+  jsonlite::write_json(unclass(geojson_list(res)), path = tempfile(fileext = ".geojson"),
+                       auto_unbox = TRUE, digits = 7)
+)
+
##    user  system elapsed 
+##  44.125   1.766  46.140
+
# sp
+res_sp <- as(res, "Spatial")
+res_sp_simp <- as(res_simp, "Spatial")
+
+## Test converting sf to geojson object
+system.time(js_gjio <- geojson_json(res_sp_simp))
+
##    user  system elapsed 
+##  16.464   1.268  17.822
+
system.time(js_int <- sp_to_GeoJSON(res_sp_simp))
+
##    user  system elapsed 
+##   2.862   0.159   3.040
+
## Test writing sp to geojson file
+f <- tempfile()
+system.time(
+  writeOGR(res_sp, paste0(f, ".geojson"), basename(f), driver = "GeoJSON",
+                  check_exists = FALSE)
+)
+
##    user  system elapsed 
+##  45.190   2.118  47.674
+
system.time(sf_sp_to_tempfile(res_sp))
+
##    user  system elapsed 
+##  45.972   3.306  49.761
+ + + diff --git a/scratch/test_conversion_performance/test_conversion_performance.md b/scratch/test_conversion_performance/test_conversion_performance.md new file mode 100644 index 0000000..e8ce55d --- /dev/null +++ b/scratch/test_conversion_performance/test_conversion_performance.md @@ -0,0 +1,154 @@ +test\_conversion\_performance.R +================ +ateucher +Mon Feb 5 15:02:19 2018 + +``` r +library(sf) +``` + + ## Linking to GEOS 3.6.2, GDAL 2.2.3, proj.4 4.9.3 + +``` r +library(geojsonio) +``` + + ## + ## Attaching package: 'geojsonio' + + ## The following object is masked from 'package:devtools': + ## + ## lint + + ## The following object is masked from 'package:base': + ## + ## pretty + +``` r +library(sp) +library(rgdal) +``` + + ## rgdal: version: 1.2-16, (SVN revision 701) + ## Geospatial Data Abstraction Library extensions to R successfully loaded + ## Loaded GDAL runtime: GDAL 2.2.3, released 2017/11/20 + ## Path to GDAL shared files: /usr/local/Cellar/gdal2/2.2.3/share/gdal + ## GDAL binary built with GEOS: TRUE + ## Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493] + ## Path to PROJ.4 shared files: (autodetected) + ## Linking to sp version: 1.2-6 + +``` r +devtools::load_all() +``` + + ## Loading rmapshaper + +``` r +u = "https://borders.ukdataservice.ac.uk/ukborders/easy_download/prebuilt/shape/England_caswa_2001_clipped.zip" +# download.file(u, destfile = "zipped_shapefile.zip") +unzip("zipped_shapefile.zip") +f = list.files(pattern = ".shp") + +# sf +res = sf::st_read(f) +``` + + ## Reading layer `england_caswa_2001_clipped' from data source `/Users/ateucher/dev/rmapshaper/scratch/england_caswa_2001_clipped.shp' using driver `ESRI Shapefile' + ## Simple feature collection with 6930 features and 5 fields + ## geometry type: MULTIPOLYGON + ## dimension: XY + ## bbox: xmin: 85665 ymin: 7054 xmax: 655604 ymax: 657534.1 + ## epsg (SRID): NA + ## proj4string: +proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +datum=OSGB36 +units=m +no_defs + +``` r +res_simp <- ms_simplify(res, sys = TRUE) + +## Test converting sf to geojson object +system.time(js_gjio <- geojson_json(res_simp)) +``` + + ## user system elapsed + ## 15.235 1.107 16.350 + +``` r +system.time(js_int <- sf_to_GeoJSON(res_simp)) +``` + + ## user system elapsed + ## 7.270 0.052 7.324 + +``` r +all.equal(as.character(js_gjio), as.character(js_int)) +``` + + ## [1] TRUE + +``` r +## Test writing sf to geojson file +system.time(st_write(res, tempfile(fileext = ".geojson"))) +``` + + ## Writing layer `filed86d3f87d11c' to data source `/var/folders/2w/x5wq73f93yzgm7hjr_b_54q00000gp/T//RtmpaXfErf/filed86d3f87d11c.geojson' using driver `GeoJSON' + ## features: 6930 + ## fields: 5 + ## geometry type: Multi Polygon + + ## user system elapsed + ## 44.657 2.041 46.906 + +``` r +system.time(sf_sp_to_tempfile(res)) +``` + + ## user system elapsed + ## 43.767 1.725 45.781 + +``` r +system.time( + jsonlite::write_json(unclass(geojson_list(res)), path = tempfile(fileext = ".geojson"), + auto_unbox = TRUE, digits = 7) +) +``` + + ## user system elapsed + ## 44.125 1.766 46.140 + +``` r +# sp +res_sp <- as(res, "Spatial") +res_sp_simp <- as(res_simp, "Spatial") + +## Test converting sf to geojson object +system.time(js_gjio <- geojson_json(res_sp_simp)) +``` + + ## user system elapsed + ## 16.464 1.268 17.822 + +``` r +system.time(js_int <- sp_to_GeoJSON(res_sp_simp)) +``` + + ## user system elapsed + ## 2.862 0.159 3.040 + +``` r +## Test writing sp to geojson file +f <- tempfile() +system.time( + writeOGR(res_sp, paste0(f, ".geojson"), basename(f), driver = "GeoJSON", + check_exists = FALSE) +) +``` + + ## user system elapsed + ## 45.190 2.118 47.674 + +``` r +system.time(sf_sp_to_tempfile(res_sp)) +``` + + ## user system elapsed + ## 45.972 3.306 49.761 diff --git a/tests/testthat/helper-tests.R b/tests/testthat/helper-tests.R index 429df74..16bb5c3 100644 --- a/tests/testthat/helper-tests.R +++ b/tests/testthat/helper-tests.R @@ -10,3 +10,17 @@ skip_on_old_v8 <- function() { } } +expect_equivalent_json <- function(object, expected, ...) { + testthat::expect_equivalent( + unclass(clean_ws(object)), + unclass(clean_ws(expected)), + ... + ) +} + +expect_equivalent_sfp <- function(object, expected, ...) { + object <- object[, sort(names(object)), drop = FALSE] + expected <- expected[, sort(names(expected)), drop = FALSE] + + testthat::expect_equivalent(object, expected, ...) +} diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 0000000..a398208 --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1,3 @@ +original_options <- options() +options("rgdal_show_exportToProj4_warnings" = "none") +options("mapshaper.sys_quiet" = TRUE) \ No newline at end of file diff --git a/tests/testthat/teardown.R b/tests/testthat/teardown.R new file mode 100644 index 0000000..414248b --- /dev/null +++ b/tests/testthat/teardown.R @@ -0,0 +1 @@ +options(original_options) diff --git a/tests/testthat/test-clip_erase.R b/tests/testthat/test-clip_erase.R index 2de03db..3904326 100644 --- a/tests/testthat/test-clip_erase.R +++ b/tests/testthat/test-clip_erase.R @@ -1,6 +1,6 @@ context("ms_clip_erase") suppressPackageStartupMessages({ - library("geojsonlint", quietly = TRUE) + library("jsonify", quietly = TRUE) library("geojsonio", quietly = TRUE) library("sf", quietly = TRUE) }) @@ -20,7 +20,7 @@ poly <- structure('{"type":"FeatureCollection","features":[{ ] ] }}] -}', class = c("json", "geo_json")) +}', class = c("geojson", "json")) line <- structure('{"type":"FeatureCollection","features":[ { "type": "Feature", @@ -32,9 +32,9 @@ line <- structure('{"type":"FeatureCollection","features":[ }, "properties": {} }] -}', class = c("json", "geo_json")) +}', class = c("geojson", "json")) -points <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[53,-42]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[57,-42]},"properties":{}}]}', class = c("json", "geo_json")) +points <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[53,-42]},"properties":{}},{"type":"Feature","geometry":{"type":"Point","coordinates":[57,-42]},"properties":{}}]}', class = c("geojson", "json")) clip_poly <- structure('{ "type": "Feature", @@ -51,62 +51,59 @@ clip_poly <- structure('{ ] ] } -}', class = c("json", "geo_json")) +}', class = c("geojson", "json")) poly_spdf <- geojsonio::geojson_sp(poly) poly_sp <- as(poly_spdf, "SpatialPolygons") -line_list <- geojson_list(line) line_spdf <- geojson_sp(line) line_sp <- as(line_spdf, "SpatialLines") -points_list <- geojson_list(points) points_spdf <- geojson_sp(points) points_sp <- as(points_spdf, "SpatialPoints") clip_poly_spdf <- geojsonio::geojson_sp(clip_poly) -test_that("ms_clip.geo_json works", { +test_that("ms_clip.geojson works", { skip_on_old_v8() default_clip_json <- ms_clip(poly, clip_poly) - expect_is(default_clip_json, "geo_json") - # expect_equivalent(default_clip_json, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[52.8658,-44.7219],[53.7702,-40.4873],[54.02807275892674,-40],[55,-40],[55,-45],[51,-45],[51,-42.353820249760446],[52.8658,-44.7219]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json", "geo_json"))) - expect_true(geojsonlint::geojson_validate(default_clip_json)) + expect_is(default_clip_json, "geojson") + expect_equivalent_json(default_clip_json, "{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[53,-42],[55,-42],[55,-45],[53,-45],[53,-42]]]},\"properties\":{\"rmapshaperid\":0}}\n]}") + expect_true(jsonify::validate_json(default_clip_json)) skip_if_not(has_sys_mapshaper()) - expect_is(ms_clip(poly, clip_poly, sys = TRUE), "geo_json") - expect_is(ms_clip(poly, clip_poly, sys = TRUE, sys_mem = 2), "geo_json") + expect_is(ms_clip(poly, clip_poly, sys = TRUE), "geojson") }) test_that("ms_clip.character works", { skip_on_old_v8() default_clip_json <- ms_clip(unclass(poly), unclass(clip_poly)) - expect_is(default_clip_json, "geo_json") - #expect_equivalent(default_clip_json, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[52.8658,-44.7219],[53.7702,-40.4873],[54.02807275892674,-40],[55,-40],[55,-45],[51,-45],[51,-42.353820249760446],[52.8658,-44.7219]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json", "geo_json"))) - expect_true(geojsonlint::geojson_validate(default_clip_json)) + expect_is(default_clip_json, "geojson") + expect_equivalent_json(default_clip_json, "{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[53,-42],[55,-42],[55,-45],[53,-45],[53,-42]]]},\"properties\":{\"rmapshaperid\":0}}\n]}") + expect_true(jsonify::validate_json(default_clip_json)) }) -test_that("ms_erase.geo_json works", { +test_that("ms_erase.geojson works", { skip_on_old_v8() default_erase_json <- ms_erase(poly, clip_poly) - expect_is(default_erase_json, "geo_json") - #expect_equivalent(default_erase_json, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[54.02807275892674,-40],[55.3204,-37.5579],[56.2757,-37.917],[56.184,-40.6443],[61.0835,-40.7529],[58.0202,-43.634],[61.6699,-45.0678],[62.737,-46.2841],[55.7763,-46.2637],[54.9742,-49.1184],[52.799,-45.9386],[52.0329,-49.5677],[50.1747,-52.1814],[49.0098,-52.3641],[52.7068,-45.7639],[43.2278,-47.1908],[48.4755,-45.1388],[50.327,-43.5207],[48.0804,-41.2784],[49.6307,-40.6159],[51,-42.353820249760446],[51,-45],[55,-45],[55,-40],[54.02807275892674,-40]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json", "geo_json"))) - expect_true(geojsonlint::geojson_validate(default_erase_json)) + expect_is(default_erase_json, "geojson") + expect_equivalent_json(default_erase_json, "{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[55,-42],[57,-42],[57,-47],[53,-47],[53,-45],[55,-45],[55,-42]]]},\"properties\":{\"rmapshaperid\":0}}\n]}") + expect_true(jsonify::validate_json(default_erase_json)) skip_if_not(has_sys_mapshaper()) - expect_is(ms_erase(poly, clip_poly, sys = TRUE), "geo_json") + expect_is(ms_erase(poly, clip_poly, sys = TRUE), "geojson") }) test_that("ms_erase.character works", { skip_on_old_v8() default_erase_json <- ms_erase(unclass(poly), unclass(clip_poly)) - expect_is(default_erase_json, "geo_json") - #expect_equivalent(default_erase_json, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[54.02807275892674,-40],[55.3204,-37.5579],[56.2757,-37.917],[56.184,-40.6443],[61.0835,-40.7529],[58.0202,-43.634],[61.6699,-45.0678],[62.737,-46.2841],[55.7763,-46.2637],[54.9742,-49.1184],[52.799,-45.9386],[52.0329,-49.5677],[50.1747,-52.1814],[49.0098,-52.3641],[52.7068,-45.7639],[43.2278,-47.1908],[48.4755,-45.1388],[50.327,-43.5207],[48.0804,-41.2784],[49.6307,-40.6159],[51,-42.353820249760446],[51,-45],[55,-45],[55,-40],[54.02807275892674,-40]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json", "geo_json"))) - expect_true(geojsonlint::geojson_validate(default_erase_json)) + expect_is(default_erase_json, "geojson") + expect_equivalent_json(default_erase_json, "{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[55,-42],[57,-42],[57,-47],[53,-47],[53,-45],[55,-45],[55,-42]]]},\"properties\":{\"rmapshaperid\":0}}\n]}") + expect_true(jsonify::validate_json(default_erase_json)) }) ## Spatial Classes @@ -116,7 +113,7 @@ test_that("ms_clip.SpatialPolygons works", { expect_is(default_clip_spdf, "SpatialPolygonsDataFrame") expect_equivalent(sapply(default_clip_spdf@polygons[[1]]@Polygons, function(x) length(x@coords)), 10) - expect_true(rgeos::gIsValid(default_clip_spdf)) + expect_true(sf::st_is_valid(sf::st_as_sf(default_clip_spdf))) default_clip_sp <- ms_clip(poly_sp, clip_poly_spdf) expect_equivalent(as(default_clip_spdf, "SpatialPolygons"), default_clip_sp) @@ -131,7 +128,7 @@ test_that("ms_erase.SpatialPolygons works", { expect_is(default_erase_spdf, "SpatialPolygonsDataFrame") expect_equivalent(sapply(default_erase_spdf@polygons[[1]]@Polygons, function(x) length(x@coords)), 14) - expect_true(rgeos::gIsValid(default_erase_spdf)) + expect_true(sf::st_is_valid(sf::st_as_sf(default_erase_spdf))) default_erase_sp <- ms_erase(poly_sp, clip_poly_spdf) expect_equivalent(as(default_erase_spdf, "SpatialPolygons"), default_erase_sp) @@ -140,25 +137,16 @@ test_that("ms_erase.SpatialPolygons works", { expect_is(ms_erase(poly_spdf, clip_poly_spdf, sys = TRUE), "SpatialPolygonsDataFrame") }) -test_that("warning occurs when non-identical CRS", { - skip_on_old_v8() - diff_crs <- sp::spTransform(clip_poly_spdf, sp::CRS("+init=epsg:3005")) - expect_warning(ms_clip(poly_spdf, diff_crs), "target and clip do not have identical CRS") - expect_warning(ms_erase(poly_spdf, diff_crs), "target and erase do not have identical CRS") -}) - test_that("ms_clip works with lines", { skip_on_old_v8() expected_out <- structure('{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"LineString","coordinates":[[55,-40.125],[52,-42]]},"properties":{"rmapshaperid":0}} -]}', class = c("json","geo_json")) +]}', class = c("json","geojson")) - expect_equivalent(clean_ws(ms_clip(line, clip_poly)), clean_ws(expected_out)) - expect_equivalent(ms_clip(line_list, geojson_list(clip_poly)), geojson_list(expected_out)) + expect_equivalent_json(ms_clip(line, clip_poly), expected_out) expect_equivalent(ms_clip(line_spdf, geojson_sp(clip_poly)), geojson_sp(expected_out)) expect_equivalent(ms_clip(line_sp, geojson_sp(clip_poly)), as(geojson_sp(expected_out), "SpatialLines")) - expect_equivalent(clean_ws(ms_clip(line, bbox = c(51, -45, 55, -40))), clean_ws(expected_out)) - expect_equivalent(ms_clip(line_list, bbox = c(51, -45, 55, -40)), geojson_list(expected_out)) + expect_equivalent_json(ms_clip(line, bbox = c(51, -45, 55, -40)), expected_out) expect_equivalent(ms_clip(line_spdf, bbox = c(51, -45, 55, -40)), geojson_sp(expected_out)) expect_equivalent(ms_clip(line_sp, bbox = c(51, -45, 55, -40)), as(geojson_sp(expected_out), "SpatialLines")) }) @@ -167,14 +155,12 @@ test_that("ms_erase works with lines", { skip_on_old_v8() expected_out <- structure('{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"LineString","coordinates":[[60,-37],[55,-40.125]]},"properties":{"rmapshaperid":0}} -]} ', class = c("json", "geo_json")) +]} ', class = c("geojson", "json")) - expect_equivalent(clean_ws(ms_erase(line, clip_poly)), clean_ws(expected_out)) - expect_equivalent(ms_erase(line_list, geojson_list(clip_poly)), geojson_list(expected_out)) + expect_equivalent_json(ms_erase(line, clip_poly), expected_out) expect_equivalent(ms_erase(line_spdf, geojson_sp(clip_poly)), geojson_sp(expected_out)) expect_equivalent(ms_erase(line_sp, geojson_sp(clip_poly)), as(geojson_sp(expected_out), "SpatialLines")) - expect_equivalent(clean_ws(ms_erase(line, bbox = c(51, -45, 55, -40))), clean_ws(expected_out)) - expect_equivalent(ms_erase(line_list, bbox = c(51, -45, 55, -40)), geojson_list(expected_out)) + expect_equivalent_json(ms_erase(line, bbox = c(51, -45, 55, -40)), expected_out) expect_equivalent(ms_erase(line_spdf, bbox = c(51, -45, 55, -40)), geojson_sp(expected_out)) expect_equivalent(ms_erase(line_sp, bbox = c(51, -45, 55, -40)), as(geojson_sp(expected_out), "SpatialLines")) }) @@ -183,13 +169,12 @@ test_that("ms_clip works with points", { skip_on_old_v8() expected_out <- structure('{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Point","coordinates":[53,-42]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")) - expect_equivalent(clean_ws(ms_clip(points, clip_poly)), clean_ws(expected_out)) - expect_equivalent(ms_clip(points_list, geojson_list(clip_poly)), geojson_list(expected_out)) +]}', class = c("geojson", "json")) + + expect_equivalent_json(ms_clip(points, clip_poly), expected_out) expect_equivalent(ms_clip(points_spdf, geojson_sp(clip_poly)), geojson_sp(expected_out)) expect_equivalent(ms_clip(points_sp, geojson_sp(clip_poly)), as(geojson_sp(expected_out), "SpatialPoints")) - expect_equivalent(clean_ws(ms_clip(points, bbox = c(51, -45, 55, -40))), clean_ws(expected_out)) - expect_equivalent(ms_clip(points_list, bbox = c(51, -45, 55, -40)), geojson_list(expected_out)) + expect_equivalent_json(ms_clip(points, bbox = c(51, -45, 55, -40)), expected_out) expect_equivalent(ms_clip(points_spdf, bbox = c(51, -45, 55, -40)), geojson_sp(expected_out)) expect_equivalent(ms_clip(points_sp, bbox = c(51, -45, 55, -40)), as(geojson_sp(expected_out), "SpatialPoints")) }) @@ -198,13 +183,12 @@ test_that("ms_erase works with points", { skip_on_old_v8() expected_out <- structure('{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Point","coordinates":[57,-42]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")) - expect_equivalent(clean_ws(ms_erase(points, clip_poly)), clean_ws(expected_out)) - expect_equivalent(ms_erase(points_list, geojson_list(clip_poly)), geojson_list(expected_out)) +]}', class = c("geojson", "json")) + + expect_equivalent_json(ms_erase(points, clip_poly), expected_out) expect_equivalent(ms_erase(points_spdf, geojson_sp(clip_poly)), geojson_sp(expected_out)) expect_equivalent(ms_erase(points_sp, geojson_sp(clip_poly)), as(geojson_sp(expected_out), "SpatialPoints")) - expect_equivalent(clean_ws(ms_erase(points, bbox = c(51, -45, 55, -40))), clean_ws(expected_out)) - expect_equivalent(ms_erase(points_list, bbox = c(51, -45, 55, -40)), geojson_list(expected_out)) + expect_equivalent_json(ms_erase(points, bbox = c(51, -45, 55, -40)), expected_out) expect_equivalent(ms_erase(points_spdf, bbox = c(51, -45, 55, -40)), geojson_sp(expected_out)) expect_equivalent(ms_erase(points_sp, bbox = c(51, -45, 55, -40)), as(geojson_sp(expected_out), "SpatialPoints")) }) @@ -212,15 +196,15 @@ test_that("ms_erase works with points", { test_that("bbox works", { skip_on_old_v8() out <- ms_erase(poly, bbox = c(51, -45, 55, -40)) - expect_is(out, "geo_json") - expect_equivalent(clean_ws(out), clean_ws(structure('{"type":"FeatureCollection", "features": [ + expect_is(out, "geojson") + expect_equivalent_json(out, '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[55,-42],[57,-42],[57,-47],[53,-47],[53,-45],[55,-45],[55,-42]]]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")))) +]}') out <- ms_clip(poly, bbox = c(51, -45, 55, -40)) - expect_is(out, "geo_json") - expect_equivalent(clean_ws(out), clean_ws(structure('{"type":"FeatureCollection", "features": [ + expect_is(out, "geojson") + expect_equivalent_json(out, '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[53,-42],[55,-42],[55,-45],[53,-45],[53,-42]]]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")))) +]}') expect_error(ms_erase(poly), "You must specificy either a bounding box") expect_error(ms_erase(poly, "foo", c(1,2,3,4)), "Please only specify either a bounding box") @@ -228,8 +212,8 @@ test_that("bbox works", { expect_error(ms_clip(poly, bbox = c("a","b","c", "d")), "bbox must be a numeric vector of length 4") skip_if_not(has_sys_mapshaper()) - expect_is(ms_clip(poly, bbox = c(51, -45, 55, -40), sys = TRUE), "geo_json") - expect_is(ms_erase(poly, bbox = c(51, -45, 55, -40), sys = TRUE), "geo_json") + expect_is(ms_clip(poly, bbox = c(51, -45, 55, -40), sys = TRUE), "geojson") + expect_is(ms_erase(poly, bbox = c(51, -45, 55, -40), sys = TRUE), "geojson") }) ## test sf classes @@ -281,3 +265,10 @@ test_that("ms_clip and ms_erase fail with old v8", { expect_error(ms_clip(poly, clip_poly)) expect_error(ms_erase(poly, clip_poly)) }) + +test_that("error occurs when non-identical crs in sf", { + skip_on_old_v8() + diff_crs <- sf::st_transform(clip_sf, 3005) + expect_error(ms_clip(poly_sf, diff_crs), "target and clip do not have identical CRS") + expect_error(ms_erase(poly_sf, diff_crs), "target and erase do not have identical CRS") +}) diff --git a/tests/testthat/test-dissolve.R b/tests/testthat/test-dissolve.R index 801c843..ca544df 100644 --- a/tests/testthat/test-dissolve.R +++ b/tests/testthat/test-dissolve.R @@ -16,7 +16,7 @@ poly <- structure('{"type":"FeatureCollection", "properties":{}, "geometry":{"type":"Polygon","coordinates":[[ [100,0],[100,1],[101,1],[101,0],[100,0] - ]]}}]}', class = c("json", "geo_json")) + ]]}}]}', class = c("geojson", "json")) poly_attr <- structure('{"type":"FeatureCollection", "features":[ @@ -29,70 +29,43 @@ poly_attr <- structure('{"type":"FeatureCollection", "properties":{"a": 5, "b": 3}, "geometry":{"type":"Polygon","coordinates":[[ [100,0],[100,1],[101,1],[101,0],[100,0] - ]]}}]}', class = c("json", "geo_json")) + ]]}}]}', class = c("geojson", "json")) -points <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"foo\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"foo\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"foo\":2}}\n]}", class = c("json", - "geo_json")) - -poly_list <- geojson_list(poly) +points <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"foo\":0}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"foo\":1}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"foo\":2}}]}", class = c("json", + "geojson")) poly_spdf <- geojson_sp(poly) poly_sp <- as(poly_spdf, "SpatialPolygons") -points_list <- geojson_list(points) points_spdf <- geojson_sp(points) points_sp <- as(points_spdf, "SpatialPoints") -test_that("ms_dissolve.geo_json works", { +test_that("ms_dissolve.geojson works", { out_poly <- ms_dissolve(poly) - expect_is(out_poly, "geo_json") - expect_equal(length(geojson_list(out_poly)$features), 1) - #expect_equal(out_poly, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[102,2],[102,3],[103,3],[103,2],[102,2]]],[[[100,0],[100,1],[101,1],[101,0],[100,0]]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json","geo_json"))) + expect_is(out_poly, "geojson") + expect_equal(nrow(geojson_sf(out_poly)), 1) + expect_equivalent_json(out_poly, "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[102,2],[102,3],[103,3],[103,2],[102,2]]],[[[100,0],[100,1],[101,1],[101,0],[100,0]]]]},\"properties\":{\"rmapshaperid\":0}}]}") out_points <- ms_dissolve(points) - expect_is(out_points, "geo_json") - # expect_equal(out_points, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-95.89715805641582,56.33174194239571]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json","geo_json"))) + expect_is(out_points, "geojson") + expect_equivalent_json(out_points, "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-95.89715805641582,56.33174194239571]},\"properties\":{\"rmapshaperid\":0}}]}") skip_if_not(has_sys_mapshaper()) - expect_is(ms_dissolve(points, sys = TRUE), "geo_json") + expect_is(ms_dissolve(points, sys = TRUE), "geojson") }) -test_that("ms_dissolve.geo_json errors correctly", { +test_that("ms_dissolve.geojson errors correctly", { expect_error(ms_dissolve('{foo: "bar"}'), "Input is not valid geojson") }) test_that("ms_dissolve.character works", { out_poly <- ms_dissolve(unclass(poly)) - expect_is(out_poly, "geo_json") - expect_equal(length(geojson_list(out_poly)$features), 1) - # expect_equal(out_poly, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[102,2],[102,3],[103,3],[103,2],[102,2]]],[[[100,0],[100,1],[101,1],[101,0],[100,0]]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json", "geo_json"))) + expect_is(out_poly, "geojson") + expect_equal(nrow(geojson_sf(out_poly)), 1) + expect_equivalent_json(out_poly, "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"MultiPolygon\",\"coordinates\":[[[[102,2],[102,3],[103,3],[103,2],[102,2]]],[[[100,0],[100,1],[101,1],[101,0],[100,0]]]]},\"properties\":{\"rmapshaperid\":0}}]}") out_points <- ms_dissolve(unclass(points)) - expect_is(out_points, "geo_json") - # expect_equal(out_points, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-95.89715805641582,56.33174194239571]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json","geo_json"))) - -}) + expect_is(out_points, "geojson") + expect_equivalent_json(out_points, "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-95.89715805641582,56.33174194239571]},\"properties\":{\"rmapshaperid\":0}}]}") -test_that("ms_dissolve.geo_list works", { - out_poly <- ms_dissolve(poly_list) - expect_is(out_poly, "geo_list") - expect_equal(length(out_poly$features), 1) - expect_equal(out_poly, structure(list(type = "FeatureCollection", features = list(structure(list( - type = "Feature", geometry = structure(list(type = "MultiPolygon", - coordinates = list(list(list(list(102L, 2L), list(102L, - 3L), list(103L, 3L), list(103L, 2L), list(102L, 2L))), - list(list(list(100L, 0L), list(100L, 1L), list(101L, - 1L), list(101L, 0L), list(100L, 0L))))), .Names = c("type", - "coordinates")), properties = structure(list(rmapshaperid = 0L), .Names = "rmapshaperid")), .Names = c("type", - "geometry", "properties")))), .Names = c("type", "features"), class = "geo_list", from = "json")) - - out_points <- ms_dissolve(points_list) - expect_equal(out_points, structure(list(type = "FeatureCollection", features = list(structure(list( - type = "Feature", geometry = structure(list(type = "Point", - coordinates = list(-95.8971580564158, 56.3317419423957)), .Names = c("type", - "coordinates")), properties = structure(list(rmapshaperid = 0L), .Names = "rmapshaperid")), .Names = c("type", - "geometry", "properties")))), .Names = c("type", "features"), class = "geo_list", from = "json")) - - skip_if_not(has_sys_mapshaper()) - expect_is(ms_dissolve(poly_list, sys = TRUE), "geo_list") }) test_that("ms_dissolve.SpatialPolygons works", { @@ -117,10 +90,10 @@ test_that("ms_dissolve.SpatialPolygons works", { }) test_that("copy_fields and sum_fields works", { - expect_equal(geojson_list(ms_dissolve(poly_attr, copy_fields = c("a", "b")))$features[[1]]$properties, + expect_equal(as.list(geojson_sf(ms_dissolve(poly_attr, copy_fields = c("a", "b"))))[1:3], list(a = 1L, b = 2L, rmapshaperid = 0L)) - expect_equal(geojson_list(ms_dissolve(poly_attr, sum_fields = c("a", "b")))$features[[1]]$properties, + expect_equal(as.list(geojson_sf(ms_dissolve(poly_attr, sum_fields = c("a", "b"))))[1:3], list(a = 6L, b = 5L, rmapshaperid = 0L)) }) diff --git a/tests/testthat/test-drop_null_geometries.R b/tests/testthat/test-drop_null_geometries.R index 2a3b9f5..2351470 100644 --- a/tests/testthat/test-drop_null_geometries.R +++ b/tests/testthat/test-drop_null_geometries.R @@ -1,16 +1,10 @@ context("drop_null_geometries") suppressPackageStartupMessages(library("geojsonio")) -remove_ws <- function(x) gsub("\\s+", "", x) - -poly_geo_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-76.3,-49.68],[-75.53,-51.13],[-74.71,-56.89],[-84.11,-57.09],[-77.9,-50.62],[-84.12,-49.59],[-76.3,-49.68]]]},\"properties\":{\"x\":-78,\"y\":-53}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-68.77,69.82],[-66.26,62.96],[-74.22,60.87],[-74.12,65.22],[-74.55,65.81],[-75.66,67.03],[-68.77,69.82]]]},\"properties\":{\"x\":-71,\"y\":65}},{\"type\":\"Feature\",\"geometry\":{},\"properties\":{\"x\":135,\"y\":65}}]}", class = c("json", "geo_json")) - -poly_geo_list <- geojson_list(poly_geo_json) +poly_geojson <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-76.3,-49.68],[-75.53,-51.13],[-74.71,-56.89],[-84.11,-57.09],[-77.9,-50.62],[-84.12,-49.59],[-76.3,-49.68]]]},\"properties\":{\"x\":-78,\"y\":-53}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-68.77,69.82],[-66.26,62.96],[-74.22,60.87],[-74.12,65.22],[-74.55,65.81],[-75.66,67.03],[-68.77,69.82]]]},\"properties\":{\"x\":-71,\"y\":65}},{\"type\":\"Feature\",\"geometry\":{},\"properties\":{\"x\":135,\"y\":65}}]}", class = c("geojson", "json")) test_that("drop_null_geometries_works", { - expected_out <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-76.3,-49.68],[-75.53,-51.13],[-74.71,-56.89],[-84.11,-57.09],[-77.9,-50.62],[-84.12,-49.59],[-76.3,-49.68]]]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-68.77,69.82],[-66.26,62.96],[-74.22,60.87],[-74.12,65.22],[-74.55,65.81],[-75.66,67.03],[-68.77,69.82]]]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}}]}", class = c("json","geo_json")) + expected_out <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-76.3,-49.68],[-75.53,-51.13],[-74.71,-56.89],[-84.11,-57.09],[-77.9,-50.62],[-84.12,-49.59],[-76.3,-49.68]]]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-68.77,69.82],[-66.26,62.96],[-74.22,60.87],[-74.12,65.22],[-74.55,65.81],[-75.66,67.03],[-68.77,69.82]]]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}}]}", class = c("json","geojson")) - expect_equal(remove_ws(drop_null_geometries(poly_geo_json)), expected_out) - out_list <- drop_null_geometries(poly_geo_list) - expect_equal(length(out_list$features), 2) + expect_equivalent_json(drop_null_geometries(poly_geojson), expected_out) }) diff --git a/tests/testthat/test-explode.R b/tests/testthat/test-explode.R index d310f52..10d841b 100644 --- a/tests/testthat/test-explode.R +++ b/tests/testthat/test-explode.R @@ -9,56 +9,42 @@ js <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","prop "coordinates": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]] -}}]}', class = c("json", "geo_json")) +}}]}', class = c("geojson", "json")) multi_line <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{ "type": "MultiLineString", "coordinates": [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0]], [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0]]] -}}]}', class = c("json", "geo_json")) +}}]}', class = c("geojson", "json")) -multi_point <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type": "MultiPoint","coordinates": [ [100.0, 0.0], [101.0, 1.0] ]}}]}', class = c("json", "geo_json")) +multi_point <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type": "MultiPoint","coordinates": [ [100.0, 0.0], [101.0, 1.0] ]}}]}', class = c("geojson", "json")) -test_that("ms_explode.geo_json works", { +test_that("ms_explode.geojson works", { out <- ms_explode(js) - expect_is(out, "geo_json") - expect_equal(length(geojson_list(out)$features), 2) - # expect_equal(out, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json"))) + expect_is(out, "geojson") + expect_equal(nrow(geojson_sf(out)), 2) + expect_equivalent_json(out, "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}") skip_if_not(has_sys_mapshaper()) - expect_is(ms_explode(js, sys = TRUE), "geo_json") + expect_is(ms_explode(js, sys = TRUE), "geojson") }) -test_that("ms_explode.geo_json errors correctly", { +test_that("ms_explode.geojson errors correctly", { expect_error(ms_explode('{foo: "bar"}'), "Input is not valid geojson") }) test_that("ms_explode.character works", { out <- ms_explode(unclass(js)) - expect_is(out, "geo_json") - expect_equal(length(geojson_list(out)$features), 2) - # expect_equal(out, structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json"))) -}) - -test_that("ms_explode.geo_list works", { - js_list <- geojson_list(js) - out <- ms_explode(js_list) - expect_is(out, "geo_list") - expect_equal(length(out$features), 2) - expect_equal(out, structure(list(type = "FeatureCollection", features = list(structure(list(type = "Feature", geometry = structure(list(type = "Polygon", coordinates = list(list(list(102L, 2L), list(102L, 3L), list(103L, 3L), list(103L, 2L), list(102L, 2L)))), .Names = c("type", "coordinates")), properties = structure(list(rmapshaperid = 0L), .Names = "rmapshaperid")), .Names = c("type", "geometry", "properties")), structure(list(type = "Feature", geometry = structure(list(type = "Polygon", coordinates = list(list(list(100L, 0L), list(100L, 1L), list(101L, 1L), list(101L, 0L), list(100L, 0L)))), .Names = c("type", "coordinates")), properties = structure(list(rmapshaperid = 1L), .Names = "rmapshaperid")), .Names = c("type", "geometry", "properties")))), .Names = c("type", "features"), class = "geo_list", from = "json")) - skip_if_not(has_sys_mapshaper()) - expect_is(ms_explode(js_list, sys = TRUE), "geo_list") + expect_is(out, "geojson") + expect_equal(nrow(geojson_sf(out)), 2) + expect_equivalent_json(out, "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}") }) test_that("ms_explode.SpatialPolygonsDataFrame works", { spdf <- geojsonio::geojson_sp(js) - out <- ms_explode(spdf, force_FC = TRUE) + out <- ms_explode(spdf) expect_is(out, "SpatialPolygonsDataFrame") # Temporarily remove due to bug in GDAL 2.1.0 expect_equal(length(out@polygons), 2) - sp_dis <- sp::disaggregate(spdf) - # Temporarily remove due to bug in GDAL 2.1.0 - expect_equivalent(lapply(out@polygons, function(x) x@Polygons[[1]]@coords), - lapply(sp_dis@polygons, function(x) x@Polygons[[1]]@coords)) skip_if_not(has_sys_mapshaper()) expect_is(ms_explode(spdf, sys = TRUE), "SpatialPolygonsDataFrame") }) @@ -67,10 +53,9 @@ test_that("ms_explode works with lines", { multi_line_exploded <- structure('{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"LineString","coordinates":[[102,2],[103,2],[103,3],[102,3]]},"properties":{"rmapshaperid":0}}, {"type":"Feature","geometry":{"type":"LineString","coordinates":[[100,0],[101,0],[101,1],[100,1]]},"properties":{"rmapshaperid":1}} -]} ', class = c("json", "geo_json")) +]} ', class = c("geojson", "json")) - # expect_equal(ms_explode(multi_line), multi_line_exploded) - expect_equal(ms_explode(geojson_list(multi_line)), geojson_list(multi_line_exploded)) + expect_equivalent_json(ms_explode(multi_line), multi_line_exploded) sp_lines <- geojsonio::geojson_sp(multi_line_exploded) out <- ms_explode(sp_lines) @@ -82,9 +67,8 @@ test_that("ms_explode works with lines", { }) test_that("ms_explode works with points", { - multi_point_exploded <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[100,0]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[101,1]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json")) - expect_equal(clean_ws(ms_explode(multi_point)), clean_ws(multi_point_exploded)) - expect_equal(ms_explode(geojson_list(multi_point)), geojson_list(multi_point_exploded)) + multi_point_exploded <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[100,0]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[101,1]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("geojson", "json")) + expect_equivalent_json(ms_explode(multi_point), multi_point_exploded) }) diff --git a/tests/testthat/test-filter_fields.R b/tests/testthat/test-filter_fields.R index 4f06e4e..6aa67d1 100644 --- a/tests/testthat/test-filter_fields.R +++ b/tests/testthat/test-filter_fields.R @@ -9,41 +9,41 @@ poly <- structure("{\"type\":\"FeatureCollection\", \"properties\":{\"a\": 1, \"b\":2, \"c\": 3}, \"geometry\":{\"type\":\"Polygon\", \"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]}}]}", - class = c("json", "geo_json")) + class = c("geojson", "json")) pts <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\", \"properties\":{\"a\":1,\"b\":2,\"c\":3}, \"geometry\":{\"type\":\"Point\", -\"coordinates\":[103,3]}}]}", class = c("json", "geo_json")) +\"coordinates\":[103,3]}}]}", class = c("geojson", "json")) lines <- structure("{\"type\":\"FeatureCollection\", \"features\":[{\"type\":\"Feature\", \"properties\":{\"a\":1,\"b\":2,\"c\":3}, \"geometry\":{\"type\":\"LineString\", \"coordinates\":[[102,2],[102,4],[104,4],[104,2],[102,2]]}}]}", - class = c("json", "geo_json")) + class = c("geojson", "json")) test_that("ms_filter_fields works with polygons", { expected_out <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"a\":1,\"b\":2}}\n]}", class = c("json", - "geo_json")) - expect_is(ms_filter_fields(poly, c("a", "b")), "geo_json") - expect_is(ms_filter_fields(unclass(poly), c("a", "b")), "geo_json") - expect_equal(ms_filter_fields(geojson_list(poly), c("a", "b")), geojson_list(expected_out)) + "geojson")) + expect_is(ms_filter_fields(poly, c("a", "b")), "geojson") + expect_is(ms_filter_fields(unclass(poly), c("a", "b")), "geojson") + expect_equivalent_json(ms_filter_fields(poly, c("a", "b")), expected_out) out_sp <- ms_filter_fields(geojson_sp(poly), c("a", "b")) expect_is(out_sp, "SpatialPolygonsDataFrame") expect_equal(out_sp@data, data.frame(a = 1, b = 2, row.names = 1L)) skip_if_not(has_sys_mapshaper()) - expect_is(ms_filter_fields(poly, c("a", "b"), sys = TRUE), "geo_json") + expect_is(ms_filter_fields(poly, c("a", "b"), sys = TRUE), "geojson") expect_is(ms_filter_fields(geojson_sp(poly), c("a", "b"), sys = TRUE), "SpatialPolygonsDataFrame") }) test_that("ms_filter_fields works with points", { expected_out <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[103,3]},\"properties\":{\"a\":1,\"b\":2}}\n]}", class = c("json", - "geo_json")) - expect_is(ms_filter_fields(pts, c("a", "b")), "geo_json") - expect_equal(ms_filter_fields(geojson_list(pts), c("a", "b")), geojson_list(expected_out)) + "geojson")) + expect_is(ms_filter_fields(pts, c("a", "b")), "geojson") + expect_equivalent_json(ms_filter_fields(pts, c("a", "b")), expected_out) out_sp <- ms_filter_fields(geojson_sp(pts), c("a", "b")) expect_is(out_sp, "SpatialPointsDataFrame") expect_equal(out_sp@data, data.frame(a = 1, b = 2)) @@ -51,9 +51,9 @@ test_that("ms_filter_fields works with points", { test_that("ms_filter_fields works with lines", { expected_out <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[102,2],[102,4],[104,4],[104,2],[102,2]]},\"properties\":{\"a\":1,\"b\":2}}\n]}", class = c("json", - "geo_json")) - expect_is(ms_filter_fields(lines, c("a", "b")), "geo_json") - expect_equal(ms_filter_fields(geojson_list(lines), c("a", "b")), geojson_list(expected_out)) + "geojson")) + expect_is(ms_filter_fields(lines, c("a", "b")), "geojson") + expect_equivalent_json(ms_filter_fields(lines, c("a", "b")), expected_out) out_sp <- ms_filter_fields(geojson_sp(lines), c("a", "b")) expect_is(out_sp, "SpatialLinesDataFrame") expect_equal(out_sp@data, data.frame(a = 1, b = 2, row.names = 1L)) diff --git a/tests/testthat/test-filter_islands.R b/tests/testthat/test-filter_islands.R index dca8ee6..b047360 100644 --- a/tests/testthat/test-filter_islands.R +++ b/tests/testthat/test-filter_islands.R @@ -1,6 +1,5 @@ context("ms_filter_islands") suppressPackageStartupMessages({ - library("geojsonio") library("sf", quietly = TRUE) }) @@ -14,7 +13,7 @@ poly <- structure("{\"type\":\"FeatureCollection\", {\"type\":\"Feature\",\"properties\":{}, \"geometry\":{\"type\":\"Polygon\", \"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]}}]}", - class = c("json", "geo_json")) + class = c("geojson", "json")) poly_spdf <- geojson_sp(poly) poly_sp <- as(poly_spdf, "SpatialPolygons") @@ -24,12 +23,10 @@ poly_sf <- st_as_sf(poly_spdf) poly_sfc <- st_geometry(poly_sf) test_that("ms_filter_islands works with min_area", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,2],[98,4],[101.5,4],[100,2]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,2],[98,4],[101.5,4],[100,2]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("geojson", "json")) - expect_is(ms_filter_islands(poly, min_area = 12391399903), "geo_json") - expect_is(ms_filter_islands(unclass(poly), min_area = 12391399903), "geo_json") - expect_equal(ms_filter_islands(geojson_list(poly), min_area = 12391399903), - geojson_list(expected_json)) + expect_is(ms_filter_islands(poly, min_area = 12391399903), "geojson") + expect_is(ms_filter_islands(unclass(poly), min_area = 12391399903), "geojson") out_spdf <- ms_filter_islands(poly_spdf, min_area = 12391399903) out_sp <- ms_filter_islands(poly_sp, min_area = 12391399903) expect_equal(out_spdf@polygons, out_sp@polygons) @@ -47,11 +44,9 @@ test_that("ms_filter_islands works with min_area", { }) test_that("ms_filter_islands works with min_vertoces", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json")) - expect_is(ms_filter_islands(poly, min_vertices = 4), "geo_json") - expect_is(ms_filter_islands(unclass(poly), min_vertices = 4), "geo_json") - expect_equal(ms_filter_islands(geojson_list(poly), min_vertices = 4), - geojson_list(expected_json)) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("geojson", "json")) + expect_is(ms_filter_islands(poly, min_vertices = 4), "geojson") + expect_is(ms_filter_islands(unclass(poly), min_vertices = 4), "geojson") out_spdf <- ms_filter_islands(poly_spdf, min_vertices = 4) expect_equal(length(out_spdf@polygons[[1]]@Polygons), 1) expect_equal(out_spdf@polygons[[1]]@Polygons[[1]]@coords, @@ -62,11 +57,9 @@ test_that("ms_filter_islands works with min_vertoces", { }) test_that("ms_filter_islands works drop_null_geometries = FALSE", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":null,\"properties\":{\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":null,\"properties\":{\"rmapshaperid\":2}}\n]}", class = c("json", "geo_json")) - expect_is(ms_filter_islands(poly, min_area = 43310462718, drop_null_geometries = FALSE), "geo_json") - expect_is(ms_filter_islands(unclass(poly), min_area = 43310462718, drop_null_geometries = FALSE), "geo_json") - expect_equal(ms_filter_islands(geojson_list(poly), min_area = 43310462718, drop_null_geometries = FALSE), - geojson_list(expected_json)) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,4],[104,4],[104,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":null,\"properties\":{\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":null,\"properties\":{\"rmapshaperid\":2}}\n]}", class = c("geojson", "json")) + expect_is(ms_filter_islands(poly, min_area = 43310462718, drop_null_geometries = FALSE), "geojson") + expect_is(ms_filter_islands(unclass(poly), min_area = 43310462718, drop_null_geometries = FALSE), "geojson") out_spdf <- ms_filter_islands(poly_spdf, min_area = 43310462718, drop_null_geometries = FALSE) expect_equal(length(out_spdf@polygons[[1]]@Polygons), 1) expect_equal(out_spdf@polygons[[1]]@Polygons[[1]]@coords, @@ -88,8 +81,7 @@ test_that("ms_filter_islands fails correctly", { test_that("ms_filter_islands works with sys = TRUE", { skip_if_not(has_sys_mapshaper()) - expect_is(ms_filter_islands(poly, min_area = 12391399902, sys = TRUE), "geo_json") - expect_is(ms_filter_islands(geojson_list(poly), min_area = 12391399902, sys = TRUE), "geo_list") + expect_is(ms_filter_islands(poly, min_area = 12391399902, sys = TRUE), "geojson") expect_is(ms_filter_islands(poly_spdf, min_area = 12391399902, sys = TRUE), "SpatialPolygonsDataFrame") expect_is(ms_filter_islands(poly_sf, min_area = 12391399902, sys = TRUE), "sf") }) diff --git a/tests/testthat/test-innerlines.R b/tests/testthat/test-innerlines.R index 0af7c01..add5778 100644 --- a/tests/testthat/test-innerlines.R +++ b/tests/testthat/test-innerlines.R @@ -4,7 +4,7 @@ suppressPackageStartupMessages({ library("sf", quietly = TRUE) }) -poly_geo_json <- structure('{"type":"FeatureCollection", +poly_geojson <- structure('{"type":"FeatureCollection", "features":[ {"type":"Feature", "properties":{"foo": "a"}, @@ -25,25 +25,22 @@ poly_geo_json <- structure('{"type":"FeatureCollection", "properties":{"foo": "b"}, "geometry":{"type":"Polygon","coordinates":[[ [103,1],[103,2],[104,2],[104,1],[103,1] -]]}}]}', class = c("json", "geo_json")) +]]}}]}', class = c("geojson", "json")) -poly_geo_list <- geojson_list(poly_geo_json) - -poly_spdf <- geojson_sp(poly_geo_json) +poly_spdf <- geojson_sp(poly_geojson) poly_sp <- as(poly_spdf, "SpatialPolygons") - -poly_sf <- read_sf(unclass(poly_geo_json)) +poly_sf <- read_sf(unclass(poly_geojson)) poly_sfc <- st_geometry(poly_sf) test_that("ms_innerlines works with all classes", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[103,2]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[102,2]]},\"properties\":{\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[104,2],[103,2]]},\"properties\":{\"rmapshaperid\":2}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[103,1]]},\"properties\":{\"rmapshaperid\":3}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[103,2]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[102,2]]},\"properties\":{\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[104,2],[103,2]]},\"properties\":{\"rmapshaperid\":2}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[103,1]]},\"properties\":{\"rmapshaperid\":3}}\n]}", class = c("geojson", "json")) expected_sp <- as(geojson_sp(expected_json), "SpatialLines") - expect_is(ms_innerlines(unclass(poly_geo_json)), "geo_json") - expect_is(ms_innerlines(poly_geo_json), "geo_json") - expect_equivalent(ms_innerlines(poly_geo_list), geojson_list(expected_json)) + expect_is(ms_innerlines(unclass(poly_geojson)), "geojson") + expect_is(ms_innerlines(poly_geojson), "geojson") + expect_equivalent(ms_innerlines(poly_spdf), expected_sp) expect_equivalent(ms_innerlines(poly_sp), expected_sp) @@ -54,15 +51,14 @@ test_that("ms_innerlines works with all classes", { test_that("ms_innerlines errors correctly", { expect_error(ms_innerlines('{foo: "bar"}'), "Input is not valid geojson") - expect_error(ms_innerlines(poly_geo_json, force_FC = "true"), "force_FC must be TRUE or FALSE") + expect_error(ms_innerlines(poly_geojson, force_FC = "true"), "force_FC must be TRUE or FALSE") # Don't test this as the V8 error throws a warning - expect_error(ms_innerlines(ms_lines(poly_geo_json)), class = "std::runtime_error") + expect_error(ms_innerlines(ms_lines(poly_geojson)), class = "std::runtime_error") }) test_that("ms_innerlines works with sys = TRUE", { skip_if_not(has_sys_mapshaper()) - expect_is(ms_innerlines(poly_geo_json, sys = TRUE), "geo_json") - expect_is(ms_innerlines(poly_geo_list, sys = TRUE), "geo_list") + expect_is(ms_innerlines(poly_geojson, sys = TRUE), "geojson") expect_is(ms_innerlines(poly_spdf, sys = TRUE), "SpatialLines") expect_is(ms_innerlines(poly_sf, sys = TRUE), "sfc") }) diff --git a/tests/testthat/test-lines.R b/tests/testthat/test-lines.R index aaa285a..b599569 100644 --- a/tests/testthat/test-lines.R +++ b/tests/testthat/test-lines.R @@ -4,7 +4,7 @@ suppressPackageStartupMessages({ library("sf", quietly = TRUE) }) -poly_geo_json <- structure('{"type":"FeatureCollection", +poly_geojson <- structure('{"type":"FeatureCollection", "features":[ {"type":"Feature", "properties":{"foo": "a"}, @@ -20,28 +20,26 @@ poly_geo_json <- structure('{"type":"FeatureCollection", "properties":{"foo": "b"}, "geometry":{"type":"Polygon","coordinates":[[ [102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1] - ]]}}]}', class = c("json", "geo_json")) + ]]}}]}', class = c("geojson", "json")) -poly_geo_list <- geojson_list(poly_geo_json) - -poly_spdf <- geojson_sp(poly_geo_json) +poly_spdf <- geojson_sp(poly_geojson) poly_sp <- as(poly_spdf, "SpatialPolygons") -poly_sf <- read_sf(unclass(poly_geo_json)) +poly_sf <- read_sf(unclass(poly_geojson)) poly_sfc <- st_geometry(poly_sf) test_that("ms_lines works with all classes", { - expected_json <- structure(structure("{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[103,2]]},\"properties\":{\"RANK\":1,\"TYPE\":\"inner\",\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[102,2],[102,3],[103,3]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[104,3],[104,2],[103,2]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":2}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":3}}\n]}", class = c("json", "geo_json"))) + expected_json <- structure(structure("{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[103,2]]},\"properties\":{\"RANK\":1,\"TYPE\":\"inner\",\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[102,2],[102,3],[103,3]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[104,3],[104,2],[103,2]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":2}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":3}}\n]}", class = c("geojson", "json"))) expected_sp <- geojson_sp(expected_json, stringsAsFactors = FALSE) expected_sp <- expected_sp[, setdiff(names(expected_sp), "rmapshaperid")] - expect_is(ms_lines(unclass(poly_geo_json)), "geo_json") - expect_is(ms_lines(poly_geo_json), "geo_json") - expect_equivalent(ms_lines(poly_geo_list), geojson_list(expected_json)) - expect_equivalent(ms_lines(poly_spdf), expected_sp) + expect_is(ms_lines(unclass(poly_geojson)), "geojson") + expect_is(ms_lines(poly_geojson), "geojson") + + expect_equivalent_sfp(ms_lines(poly_spdf), expected_sp) expect_equivalent(ms_lines(poly_sp), as(expected_sp, "SpatialLines")) @@ -54,13 +52,13 @@ test_that("ms_lines works with all classes", { }) test_that("ms_lines works with fields specified", { - expected_json <- structure("{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[103,2]]},\"properties\":{\"RANK\":2,\"TYPE\":\"inner\",\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[102,2],[102,3],[103,3]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[104,3],[104,2],[103,2]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":2}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":3}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\", \"features\": [\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[103,2]]},\"properties\":{\"RANK\":2,\"TYPE\":\"inner\",\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,2],[102,2],[102,3],[103,3]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[103,3],[104,3],[104,2],[103,2]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":2}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[102.5,1],[102.5,2],[103.5,2],[103.5,1],[102.5,1]]},\"properties\":{\"RANK\":0,\"TYPE\":\"outer\",\"rmapshaperid\":3}}\n]}", class = c("geojson", "json")) expected_sp <- geojson_sp(expected_json, stringsAsFactors = FALSE) - expect_is(ms_lines(poly_geo_json, "foo"), "geo_json") - expect_equivalent(ms_lines(poly_geo_list, "foo"), geojson_list(expected_json)) - expect_equivalent(ms_lines(poly_spdf, "foo"), + expect_is(ms_lines(poly_geojson, "foo"), "geojson") + + expect_equivalent_sfp(ms_lines(poly_spdf, "foo"), expected_sp[, setdiff(names(expected_sp), "rmapshaperid")]) expect_equivalent(ms_lines(poly_sf, "foo")$RANK, c(2L,0L,0L,0L)) @@ -69,18 +67,17 @@ test_that("ms_lines works with fields specified", { test_that("ms_lines errors correctly", { expect_error(ms_lines('{foo: "bar"}'), "Input is not valid geojson") # Don't test this as the V8 error throws a warning - expect_error(ms_lines(poly_geo_json, "bar"), class = "std::runtime_error") + expect_error(ms_lines(poly_geojson, "bar"), class = "std::runtime_error") expect_error(ms_lines(poly_spdf, "bar"), "not all fields specified exist in input data") - expect_error(ms_lines(poly_geo_json, 1), "fields must be a character vector") - expect_error(ms_lines(poly_geo_json, force_FC = "true"), "force_FC must be TRUE or FALSE") + expect_error(ms_lines(poly_geojson, 1), "fields must be a character vector") + expect_error(ms_lines(poly_geojson, force_FC = "true"), "force_FC must be TRUE or FALSE") expect_error(ms_lines(poly_sfc, "foo"), "Do not specify fields for sfc classes") }) test_that("ms_innerlines works with sys = TRUE", { skip_if_not(has_sys_mapshaper()) - expect_is(ms_lines(poly_geo_json, sys = TRUE), "geo_json") - expect_is(ms_lines(poly_geo_list, sys = TRUE), "geo_list") + expect_is(ms_lines(poly_geojson, sys = TRUE), "geojson") expect_is(ms_lines(poly_spdf, sys = TRUE), "SpatialLinesDataFrame") expect_is(ms_lines(poly_sf, sys = TRUE), "sf") }) diff --git a/tests/testthat/test-points.R b/tests/testthat/test-points.R index dcad05e..02eed62 100644 --- a/tests/testthat/test-points.R +++ b/tests/testthat/test-points.R @@ -3,10 +3,9 @@ suppressPackageStartupMessages({ library("sf", quietly = TRUE) }) -poly_geo_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-76.3,-49.68],[-75.53,-51.13],[-74.71,-56.89],[-84.11,-57.09],[-77.9,-50.62],[-84.12,-49.59],[-76.3,-49.68]]]},\"properties\":{\"x\": -78, \"y\": -53}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-68.77,69.82],[-66.26,62.96],[-74.22,60.87],[-74.12,65.22],[-74.55,65.81],[-75.66,67.03],[-68.77,69.82]]]},\"properties\":{\"x\": -71, \"y\": 65}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[136.27,65.8],[137.78,64.03],[140.03,59.56],[139.48,56.48],[133.64,62.44],[129.67,69.6],[136.27,65.8]]]},\"properties\":{\"x\": 135, \"y\": 65}}]}", class = c("json", "geo_json")) +poly_geojson <- structure("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-76.3,-49.68],[-75.53,-51.13],[-74.71,-56.89],[-84.11,-57.09],[-77.9,-50.62],[-84.12,-49.59],[-76.3,-49.68]]]},\"properties\":{\"x\": -78, \"y\": -53}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[-68.77,69.82],[-66.26,62.96],[-74.22,60.87],[-74.12,65.22],[-74.55,65.81],[-75.66,67.03],[-68.77,69.82]]]},\"properties\":{\"x\": -71, \"y\": 65}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[136.27,65.8],[137.78,64.03],[140.03,59.56],[139.48,56.48],[133.64,62.44],[129.67,69.6],[136.27,65.8]]]},\"properties\":{\"x\": 135, \"y\": 65}}]}", class = c("geojson", "json")) -poly_geo_list <- geojson_list(poly_geo_json) -poly_spdf <- geojson_sp(poly_geo_json) +poly_spdf <- geojson_sp(poly_geojson) poly_sp <- as(poly_spdf, "SpatialPolygons") @@ -14,29 +13,27 @@ poly_sf <- st_as_sf(poly_spdf) poly_sfc <- st_geometry(poly_sf) test_that("ms_points works with defaults", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("geojson", "json")) expected_sp <- geojson_sp(expected_json) expected_sp <- expected_sp[, setdiff(names(expected_sp), "rmapshaperid")] - expect_is(ms_points(poly_geo_json), "geo_json") - expect_is(ms_points(unclass(poly_geo_json)), "geo_json") - expect_equivalent(ms_points(poly_geo_list), geojson_list(expected_json)) - expect_equivalent(ms_points(poly_spdf), expected_sp) + expect_is(ms_points(poly_geojson), "geojson") + expect_is(ms_points(unclass(poly_geojson)), "geojson") + expect_equivalent_sfp(ms_points(poly_spdf), expected_sp) expect_equivalent(ms_points(poly_sp), as(expected_sp, "SpatialPoints")) skip_if_not(has_sys_mapshaper()) - expect_is(ms_points(poly_geo_json, sys = TRUE), "geo_json") - expect_is(ms_points(unclass(poly_geo_json), sys = TRUE), "geo_json") - expect_is(ms_points(poly_geo_list, sys = TRUE), "geo_list") + expect_is(ms_points(poly_geojson, sys = TRUE), "geojson") + expect_is(ms_points(unclass(poly_geojson), sys = TRUE), "geojson") expect_is(ms_points(poly_spdf, sys = TRUE), "SpatialPointsDataFrame") expect_is(ms_points(poly_sp, sys = TRUE), "SpatialPoints") }) test_that("ms_points works with defaults with sf", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("geojson", "json")) expected_sf <- st_read(expected_json, quiet = TRUE, stringsAsFactors = FALSE)[1:2] - expect_equivalent(ms_points(poly_sf), expected_sf) + expect_equivalent_sfp(ms_points(poly_sf), expected_sf) expect_equivalent(ms_points(poly_sfc), st_geometry(expected_sf)) skip_if_not(has_sys_mapshaper()) @@ -45,60 +42,57 @@ test_that("ms_points works with defaults with sf", { }) test_that("ms_points works with location=centroid", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78.4154562738861,-53.95000746272258]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.8687480648099,65.19505422895163]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.65518268439885,63.10517782011297]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("geojson", "json")) expected_sp <- geojson_sp(expected_json) expected_sp <- expected_sp[, setdiff(names(expected_sp), "rmapshaperid")] - expect_equivalent(ms_points(poly_geo_json, location = "centroid"), ms_points(poly_geo_json)) - expect_is(ms_points(poly_geo_json, location = "centroid"), "geo_json") - expect_equivalent(ms_points(poly_geo_list, location = "centroid"), geojson_list(expected_json)) - expect_equivalent(ms_points(poly_spdf, location = "centroid"), expected_sp) + expect_equivalent(ms_points(poly_geojson, location = "centroid"), ms_points(poly_geojson)) + expect_is(ms_points(poly_geojson, location = "centroid"), "geojson") + expect_equivalent_sfp(ms_points(poly_spdf, location = "centroid"), expected_sp) expected_sf <- st_read(expected_json, quiet = TRUE, stringsAsFactors = FALSE)[1:2] - expect_equivalent(ms_points(poly_sf, location = "centroid"), expected_sf) + expect_equivalent_sfp(ms_points(poly_sf, location = "centroid"), expected_sf) expect_equivalent(ms_points(poly_sfc, location = "centroid"), st_geometry(expected_sf)) }) test_that("ms_points works with location=inner", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-77.94495627388609,-54.35054796472695]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.7792242552861,65.38990758263705]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.73366753288371,63.20605469121952]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-77.94495627388609,-54.35054796472695]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-70.7792242552861,65.38990758263705]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135.73366753288371,63.20605469121952]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("geojson", "json")) expected_sp <- geojson_sp(expected_json) expected_sp <- expected_sp[, setdiff(names(expected_sp), "rmapshaperid")] - expect_is(ms_points(poly_geo_json, location = "inner"), "geo_json") - expect_equivalent(ms_points(poly_geo_list, location = "inner"), geojson_list(expected_json)) - expect_equivalent(ms_points(poly_spdf, location = "inner"), expected_sp) + expect_is(ms_points(poly_geojson, location = "inner"), "geojson") + expect_equivalent_sfp(ms_points(poly_spdf, location = "inner"), expected_sp) expected_sf <- st_read(expected_json, quiet = TRUE, stringsAsFactors = FALSE)[1:2] - expect_equivalent(ms_points(poly_sf, location = "inner"), expected_sf, tolerance = 0.0001) + expect_equivalent_sfp(ms_points(poly_sf, location = "inner"), expected_sf, tolerance = 0.0001) expect_equivalent(ms_points(poly_sfc, location = "inner"), st_geometry(expected_sf), tolerance = 0.0001) }) test_that("ms_points works with x and y", { - expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78,-53]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-71,65]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135,65]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("json", "geo_json")) + expected_json <- structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78,-53]},\"properties\":{\"x\":-78,\"y\":-53,\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-71,65]},\"properties\":{\"x\":-71,\"y\":65,\"rmapshaperid\":1}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[135,65]},\"properties\":{\"x\":135,\"y\":65,\"rmapshaperid\":2}}\n]}", class = c("geojson", "json")) expected_sp <- geojson_sp(expected_json) expected_sp <- expected_sp[, setdiff(names(expected_sp), "rmapshaperid")] - expect_is(ms_points(poly_geo_json, x = "x", y = "y"), "geo_json") - expect_equivalent(ms_points(poly_geo_list, x = "x", y = "y"), geojson_list(expected_json)) - expect_equivalent(ms_points(poly_spdf, x = "x", y = "y"), expected_sp) + expect_is(ms_points(poly_geojson, x = "x", y = "y"), "geojson") + expect_equivalent_sfp(ms_points(poly_spdf, x = "x", y = "y"), expected_sp) - expect_equivalent(ms_points(poly_sf, x = "x", y = "y"), + expect_equivalent_sfp(ms_points(poly_sf, x = "x", y = "y"), st_read(expected_json, quiet = TRUE, stringsAsFactors = FALSE)[1:2]) }) test_that("ms_points fails correctly", { - expect_error(ms_points(poly_geo_json, location = "foo"), "location must be 'centroid' or 'inner'") - expect_error(ms_points(poly_geo_json, location = "inner", x = "x", y = "y"), + expect_error(ms_points(poly_geojson, location = "foo"), "location must be 'centroid' or 'inner'") + expect_error(ms_points(poly_geojson, location = "inner", x = "x", y = "y"), "You have specified both a location and x/y for point placement") - expect_error(ms_points(poly_geo_json, location = "inner", x = "x"), + expect_error(ms_points(poly_geojson, location = "inner", x = "x"), "You have specified both a location and x/y for point placement") - expect_error(ms_points(poly_geo_json, location = "inner", y = "y"), + expect_error(ms_points(poly_geojson, location = "inner", y = "y"), "You have specified both a location and x/y for point placement") - expect_error(ms_points(poly_geo_json, x = "x"), "Only one of x/y pair found") - expect_error(ms_points(poly_geo_json, y = "y"), "Only one of x/y pair found") - expect_error(ms_points(poly_geo_json, force_FC = "true"), + expect_error(ms_points(poly_geojson, x = "x"), "Only one of x/y pair found") + expect_error(ms_points(poly_geojson, y = "y"), "Only one of x/y pair found") + expect_error(ms_points(poly_geojson, force_FC = "true"), "force_FC must be TRUE or FALSE") }) diff --git a/tests/testthat/test-simplify.R b/tests/testthat/test-simplify.R index ce37c8f..2830ec3 100644 --- a/tests/testthat/test-simplify.R +++ b/tests/testthat/test-simplify.R @@ -2,15 +2,13 @@ context("ms_simplify") suppressPackageStartupMessages({ library("geojsonio") - library("geojsonlint") + library("jsonify") library("sf") }) -poly <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-7.1549869,45.4449053],[-7.6245498,37.9890775],[-7.5290969,38.0423402],[-3.3235845,40.588151],[-7.344442,37.6863061],[1.8042184,41.0097841],[3.7578538,38.7756389],[1.8629117,35.5400723],[-6.3787009,28.8026166],[-8.3144042,35.6271496],[-9.3413257,34.4122375],[-7.8818739,37.2784218],[-10.970619,35.0652943],[-7.855486,37.303094],[-17.6800154,33.0680873],[-11.4987062,37.7759151],[-16.8542278,41.7896373],[-9.6292336,41.0325088],[-8.3619054,39.5168442],[-8.1027301,39.7855456],[-7.1549869,45.4449053]]]},"properties":{}}]}', class = c("json", "geo_json")) +poly <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-7.1549869,45.4449053],[-7.6245498,37.9890775],[-7.5290969,38.0423402],[-3.3235845,40.588151],[-7.344442,37.6863061],[1.8042184,41.0097841],[3.7578538,38.7756389],[1.8629117,35.5400723],[-6.3787009,28.8026166],[-8.3144042,35.6271496],[-9.3413257,34.4122375],[-7.8818739,37.2784218],[-10.970619,35.0652943],[-7.855486,37.303094],[-17.6800154,33.0680873],[-11.4987062,37.7759151],[-16.8542278,41.7896373],[-9.6292336,41.0325088],[-8.3619054,39.5168442],[-8.1027301,39.7855456],[-7.1549869,45.4449053]]]},"properties":{}}]}', class = c("geojson", "json")) -line <- structure('{"type":"FeatureCollection", "features": [{"type":"Feature","geometry":{"type":"LineString","coordinates":[[-146.030845,-17.697398],[-138.8493372,-17.938697],[-137.5671055,-18.9589785],[-146.3153242,-20.8865269],[-142.9518755,-24.359833],[-147.6422817,-20.9477376],[-146.6957993,-24.7101963],[-147.696223,-21.1162469],[-156.2250727,-21.2045764],[-150.6399109,-15.4286993],[-146.030845,-17.697398]]},"properties":{}}]}', class = c("json", "geo_json")) - -line_list <- structure(geojson_list(line), class = "geo_list") +line <- structure('{"type":"FeatureCollection", "features": [{"type":"Feature","geometry":{"type":"LineString","coordinates":[[-146.030845,-17.697398],[-138.8493372,-17.938697],[-137.5671055,-18.9589785],[-146.3153242,-20.8865269],[-142.9518755,-24.359833],[-147.6422817,-20.9477376],[-146.6957993,-24.7101963],[-147.696223,-21.1162469],[-156.2250727,-21.2045764],[-150.6399109,-15.4286993],[-146.030845,-17.697398]]},"properties":{}}]}', class = c("geojson", "json")) line_spdf <- geojson_sp(line) line_sp <- as(line_spdf, "SpatialLines") @@ -18,66 +16,35 @@ line_sp <- as(line_spdf, "SpatialLines") poly_spdf <- geojson_sp(poly) poly_sp <- as(poly_spdf, "SpatialPolygons") -poly_list <- structure(geojson_list(poly), class = "geo_list") - -test_that("ms_simplify.geo_json and character works with defaults", { +test_that("ms_simplify.geojson and character works with defaults", { default_simplify_json <- ms_simplify(poly) - expect_is(default_simplify_json, "geo_json") - expect_equal(clean_ws(default_simplify_json), - clean_ws(structure('{"type":"FeatureCollection", "features": [ + expect_is(default_simplify_json, "geojson") + expect_equivalent_json(default_simplify_json, + '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-7.1549869,45.4449053],[-7.344442,37.6863061],[1.8629117,35.5400723],[-6.3787009,28.8026166],[-9.6292336,41.0325088],[-7.1549869,45.4449053]]]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")))) - expect_equal(default_simplify_json, ms_simplify(unclass(poly))) # character - expect_true(geojsonlint::geojson_validate(default_simplify_json)) +]}') + expect_equivalent_json(default_simplify_json, ms_simplify(unclass(poly))) # character + expect_true(jsonify::validate_json(default_simplify_json)) skip_if_not(has_sys_mapshaper()) - expect_is(ms_simplify(poly, sys = TRUE), "geo_json") + expect_is(ms_simplify(poly, sys = TRUE), "geojson") }) -test_that("ms_simplify.geo_json with keep=1 returns same as input", { - expect_equal(geojson_list(poly)$features[[1]]$geometry, - geojson_list(ms_simplify(poly, keep = 1))$features[[1]]$geometry) -}) - -test_that("ms_simplify.geo_json works with different methods", { +test_that("ms_simplify.geojson works with different methods", { vis_simplify_json <- ms_simplify(poly, method = "vis", weighting = 0) dp_simplify_json <- ms_simplify(poly, method = "dp") - expect_is(vis_simplify_json, "geo_json") - expect_equal(clean_ws(vis_simplify_json), - clean_ws(structure('{"type":"FeatureCollection", "features": [ + expect_is(vis_simplify_json, "geojson") + expect_equivalent_json(vis_simplify_json, + '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-7.1549869,45.4449053],[1.8629117,35.5400723],[-6.3787009,28.8026166],[-7.1549869,45.4449053]]]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")))) - expect_is(dp_simplify_json, "geo_json") - expect_equal(clean_ws(dp_simplify_json), - clean_ws(structure('{"type":"FeatureCollection", "features": [ +]}') + expect_is(dp_simplify_json, "geojson") + expect_equivalent_json(dp_simplify_json, + '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-7.1549869,45.4449053],[-6.3787009,28.8026166],[-17.6800154,33.0680873],[-7.1549869,45.4449053]]]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")))) -}) - -test_that("ms_simplify.geo_list works with defaults", { - default_simplify_geo_list <- ms_simplify(poly_list) - - expect_is(default_simplify_geo_list, "geo_list") - expect_equal(default_simplify_geo_list$features, - list(structure(list(type = "Feature", geometry = structure(list( - type = "Polygon", - coordinates = list( - list(list(-7.1549869, 45.4449053), - list(-7.344442, 37.6863061), - list(1.8629117, 35.5400723), - list(-6.3787009, 28.8026166), - list(-9.6292336, 41.0325088), - list(-7.1549869, 45.4449053)))), - .Names = c("type", "coordinates")), - properties = structure(list(rmapshaperid = 0L), - .Names = "rmapshaperid")), - .Names = c("type", "geometry", "properties"))) - ) - - skip_if_not(has_sys_mapshaper()) - expect_is(ms_simplify(poly_list, sys = TRUE), "geo_list") +]}') }) test_that("ms_simplify.SpatialPolygons works with defaults", { @@ -91,7 +58,7 @@ test_that("ms_simplify.SpatialPolygons works with defaults", { structure(c(-7.1549869, -7.344442, 1.8629117, -6.3787009, -9.6292336, -7.1549869, 45.4449053, 37.6863061, 35.5400723, 28.8026166, 41.0325088, 45.4449053), .Dim = c(6L, 2L))) - expect_true(rgeos::gIsValid(default_simplify_spdf)) + expect_true(sf::st_is_valid(sf::st_as_sf(default_simplify_spdf))) skip_if_not(has_sys_mapshaper()) expect_is(ms_simplify(poly_spdf, sys = TRUE), "SpatialPolygonsDataFrame") @@ -106,14 +73,14 @@ test_that("simplify.SpatialPolygonsDataFrame works with other methods", { expect_equal(vis_simplify_spdf@polygons[[1]]@Polygons[[1]]@coords, structure(c(-7.1549869, 1.8629117, -6.3787009, -7.1549869, 45.4449053, 35.5400723, 28.8026166, 45.4449053), .Dim = c(4L, 2L))) - expect_true(rgeos::gIsValid(vis_simplify_spdf)) + expect_true(sf::st_is_valid(sf::st_as_sf(vis_simplify_spdf))) expect_is(dp_simplify_spdf, "SpatialPolygonsDataFrame") expect_equal(dp_simplify_spdf@polygons[[1]]@Polygons[[1]]@coords, structure(c(-7.1549869, -6.3787009, -17.6800154, -7.1549869, 45.4449053, 28.8026166, 33.0680873, 45.4449053), .Dim = c(4L, 2L))) - expect_true(rgeos::gIsValid(dp_simplify_spdf)) + expect_true(sf::st_is_valid(sf::st_as_sf(dp_simplify_spdf))) }) multipoly <- structure('{ @@ -121,16 +88,16 @@ multipoly <- structure('{ "coordinates": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]] -} ', class = c("json", "geo_json")) +} ', class = c("geojson", "json")) multi_spdf <- geojsonio::geojson_sp(multipoly) -test_that("exploding works with geo_json", { +test_that("exploding works with geojson", { out <- ms_simplify(multipoly, keep_shapes = TRUE, explode = FALSE) - expect_equal(clean_ws(out), - clean_ws(structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}}\n]}", class = c("json", "geo_json")))) + expect_equivalent_json(out, + "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}}\n]}") out <- ms_simplify(multipoly, keep_shapes = TRUE, explode = TRUE) - expect_equal(clean_ws(out), - clean_ws(structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json")))) + expect_equivalent_json(out, + "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[102,2],[102,3],[103,3],[103,2],[102,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[100,0],[100,1],[101,1],[101,0],[100,0]]]},\"properties\":{\"rmapshaperid\":1}}\n]}") }) test_that("exploding works with SpatialPolygonsDataFrame", { @@ -141,7 +108,7 @@ test_that("exploding works with SpatialPolygonsDataFrame", { expect_equal(length(out@polygons), 2) }) -test_that("ms_simplify fails with invalid geo_json", { +test_that("ms_simplify fails with invalid geojson", { expect_error(ms_simplify('{foo: "bar"}'), "Input is not valid geojson") }) @@ -184,24 +151,23 @@ multipoly <- structure(' "properties": {} } ] - }', class = c("json", "geo_json")) + }', class = c("geojson", "json")) -multipoly_list <- geojson_list(multipoly) multipoly_spdf <- geojson_sp(multipoly) test_that("ms_simplify works with drop_null_geometries", { out_drop <- ms_simplify(multipoly, keep_shapes = FALSE, drop_null_geometries = TRUE) - expect_equal(clean_ws(out_drop), - clean_ws(structure('{"type":"FeatureCollection", "features": [ + expect_equivalent_json(out_drop, + '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-152.3433185,-51.1400329],[-142.1320457,-59.7694693],[-153.3221574,-65.1360902],[-168.2902719,-61.7109737],[-152.3433185,-51.1400329]]]},"properties":{"rmapshaperid":0}} -]}', class = c("json", "geo_json")))) +]}') out_nodrop <- ms_simplify(multipoly, keep_shapes = FALSE, drop_null_geometries = FALSE) - expect_equal(clean_ws(out_nodrop), - clean_ws(structure('{"type":"FeatureCollection", "features": [ + expect_equivalent_json(out_nodrop, + '{"type":"FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-152.3433185,-51.1400329],[-142.1320457,-59.7694693],[-153.3221574,-65.1360902],[-168.2902719,-61.7109737],[-152.3433185,-51.1400329]]]},"properties":{"rmapshaperid":0}}, {"type":"Feature","geometry":null,"properties":{"rmapshaperid":1}}, {"type":"Feature","geometry":null,"properties":{"rmapshaperid":2}} -]} ', class = c("json", "geo_json")))) +]} ') }) test_that("ms_simplify.SpatialPolygonsDataFrame works keep_shapes = FALSE and ignores drop_null_geometries", { @@ -212,19 +178,17 @@ test_that("ms_simplify.SpatialPolygonsDataFrame works keep_shapes = FALSE and ig }) test_that("ms_simplify works with lines", { - expected_json <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"LineString","coordinates":[[-146.030845,-17.697398],[-147.696223,-21.1162469],[-156.2250727,-21.2045764],[-150.6399109,-15.4286993],[-146.030845,-17.697398]]},"properties":{"rmapshaperid":0}}]}', class = c("json", "geo_json")) + expected_json <- structure('{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"LineString","coordinates":[[-146.030845,-17.697398],[-147.696223,-21.1162469],[-156.2250727,-21.2045764],[-150.6399109,-15.4286993],[-146.030845,-17.697398]]},"properties":{"rmapshaperid":0}}]}', class = c("geojson", "json")) - expect_equal(clean_ws(ms_simplify(line, keep = 0.1)), clean_ws(expected_json)) - expect_equal(ms_simplify(line_list, keep = 0.1), geojson_list(expected_json), tolerance = 1e-7) + expect_equivalent_json(ms_simplify(line, keep = 0.1), expected_json) expect_equivalent(ms_simplify(line_spdf, keep = 0.1), geojson_sp(expected_json)) expect_equivalent(ms_simplify(line_sp, keep = 0.1), as(ms_simplify(line_spdf, keep = 0.1), "SpatialLines")) }) test_that("ms_simplify works correctly when all geometries are dropped", { expect_error(ms_simplify(multipoly_spdf, keep = 0.001), "Cannot convert result to a Spatial\\* object") - expect_equal(clean_ws(ms_simplify(multipoly, keep = 0.001)), - clean_ws(structure("{\"type\":\"GeometryCollection\",\"geometries\":[\n\n]}", class = c("json", "geo_json")))) - expect_equal(ms_simplify(multipoly_list, keep = 0.001), structure(list(type = "GeometryCollection", geometries = list()), .Names = c("type", "geometries"), class = "geo_list", from = "json")) + expect_equivalent_json(ms_simplify(multipoly, keep = 0.001), + "{\"type\":\"GeometryCollection\",\"geometries\":[\n\n]}") }) test_that("snap_interval works", { @@ -239,18 +203,18 @@ test_that("snap_interval works", { "properties":{}, "geometry":{"type":"Polygon","coordinates":[[ [101,1],[101,2],[102,1.9],[103,2],[103,1],[101,1] - ]]}}]}', class = c("json", "geo_json")) + ]]}}]}', class = c("geojson", "json")) poly_not_snapped <- ms_simplify(poly, keep = 0.8, snap = TRUE, snap_interval = 0.09) - expect_equal(clean_ws(poly_not_snapped), - clean_ws(structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[101,3],[103,3],[103,2],[102,2],[101,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[102,1.9],[103,2],[103,1],[101,1],[101,2]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json", "geo_json")))) + expect_equivalent_json(poly_not_snapped, + "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[101,3],[103,3],[103,2],[102,2],[101,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[102,1.9],[103,2],[103,1],[101,1],[101,2]]]},\"properties\":{\"rmapshaperid\":1}}\n]}") poly_snapped <- ms_simplify(poly, keep = 0.8, snap = TRUE, snap_interval = 0.11) - expect_equal(clean_ws(poly_snapped), clean_ws(structure("{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[101,3],[103,3],[103,2],[102,2],[101,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[102,2],[103,2],[103,1],[101,1],[101,2]]]},\"properties\":{\"rmapshaperid\":1}}\n]}", class = c("json","geo_json")))) + expect_equivalent_json(poly_snapped, "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[101,3],[103,3],[103,2],[102,2],[101,2]]]},\"properties\":{\"rmapshaperid\":0}},\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[101,2],[102,2],[103,2],[103,1],[101,1],[101,2]]]},\"properties\":{\"rmapshaperid\":1}}\n]}") }) test_that("ms_simplify works with very small values of 'keep", { - expect_s3_class(ms_simplify(poly, keep = 0.0001), "geo_json") + expect_s3_class(ms_simplify(poly, keep = 0.0001), "geojson") }) @@ -287,10 +251,10 @@ test_that("ms_simplify works with various column types", { nr <- dim(xsf)[1] various_types <- list( date = Sys.Date() + seq_len(nr), - time = Sys.time() + seq_len(nr), - cpx = complex(nr), - # rw = raw(nr), - lst = replicate(nr, "a", simplify = FALSE) + time = Sys.time() + seq_len(nr) + # complex(nr), + # rw = raw(nr), + # lst = replicate(nr, "a", simplify = FALSE) ) for (itype in seq_along(various_types)) { xsf$check_me <- various_types[[itype]] @@ -298,12 +262,12 @@ test_that("ms_simplify works with various column types", { simp_xsf <- ms_simplify(xsf) expect_is(simp_xsf, c("sf", "data.frame")) ## not currently working for POSIXct - #expect_identical(simp_xsf$check_me, various_types[[itype]]) + expect_equal(simp_xsf$check_me, various_types[[itype]], tolerance = 1) } ## raw special case - xsf$check_me <- raw(nr) - expect_warning(simp_xsf <- ms_simplify(xsf), "NAs introduced by coercion") + # xsf$check_me <- raw(nr) + # expect_warning(simp_xsf <- ms_simplify(xsf), "NAs introduced by coercion") }) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 06eabdb..b8be432 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1,5 +1,5 @@ context("Utilites") -suppressPackageStartupMessages(library(jsonlite)) +library(jsonlite) test_df <- data.frame(char = letters[1:3], dbl = c(1.1, 2.2, 3.3), int = c(1L, 2L, 3L), fct = factor(LETTERS[4:6]), @@ -8,8 +8,9 @@ test_df <- data.frame(char = letters[1:3], dbl = c(1.1, 2.2, 3.3), posix_ct = as.POSIXct(c("2016-01-25 11:25:03", "2017-01-27 23:24:56", "1979-03-09 10:25:15")), - stringsAsFactors = FALSE) -test_df$posix_lt <- as.POSIXlt(test_df$posix_ct) + "column 6" = 1:3, + stringsAsFactors = FALSE, + check.names = FALSE) test_that("Restore column works", { cls <- col_classes(test_df) @@ -19,10 +20,13 @@ test_that("Restore column works", { back_in <- fromJSON(toJSON(test_df)) expect_equal(unname(sapply(back_in, class)), c("character", "numeric", "integer", "character", "character", - "character", "character", "character")) + "character", "character", "integer")) restored <- restore_classes(df = back_in, classes = cls) expect_equal(lapply(test_df, class), lapply(restored, class)) + expect_equal(names(test_df), names(restored)) # retain special names, https://github.com/ateucher/rmapshaper/issues/91 expect_equal(test_df, restored) + test_df$posix_lt <- as.POSIXlt(test_df$posix_ct) + expect_error(col_classes(test_df), "POSIXlt classes not supported") }) test_that("Restore columns works with rmapshaperid column", { @@ -34,11 +38,24 @@ test_that("Restore columns works with rmapshaperid column", { }) test_that("dealing with encoding works", { - pts <- "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78,-53]},\"properties\":{\"x\":\"Provence-Alpes-Côte d'Azur\"}}]}" + test_name <- "örtchen" + test_value <- "Münster" + Encoding(test_name) <- "UTF-8" + Encoding(test_value) <- "UTF-8" + + pts <- "{\"type\":\"FeatureCollection\",\"features\":[\n{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-78,-53]},\"properties\":{\"örtchen\":\"Münster\"}}]}" Encoding(pts) <- "UTF-8" + out <- GeoJSON_to_sp(pts) expect_is(out, "SpatialPointsDataFrame") - expect_equal(out$x[1], "Provence-Alpes-Côte d'Azur") + expect_equal(out[1][[1]], test_value) + expect_equal(names(out), test_name) + + + out <- GeoJSON_to_sf(pts) + expect_is(out, "sf") + expect_equal(out[1][[1]], test_value) + expect_equal(names(out)[1], test_name) }) test_that("geometry column name is preserved", { @@ -49,7 +66,7 @@ test_that("geometry column name is preserved", { expect_equal(attr(out, "sf_column"), "SHAPE") }) -test_that("NA values dealt with in sf_to_GeoJSON", { +test_that("NA values dealt with in sf_to_GeoJSON and GeoJSON_to_sf", { sf_obj <- sf::st_sf( a = c(1.0, NA_real_), sf::st_as_sfc(c("POINT(0 0)", "POINT(1 1)")) @@ -57,18 +74,25 @@ test_that("NA values dealt with in sf_to_GeoJSON", { geojson <- sf_to_GeoJSON(sf_obj) expect_equivalent(geojson, structure( - "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"a\":1},\"geometry\":{\"type\":\"Point\",\"coordinates\":[0,0]}},{\"type\":\"Feature\",\"properties\":{\"a\":null},\"geometry\":{\"type\":\"Point\",\"coordinates\":[1,1]}}]}", - class = "json" + "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"a\":1.0},\"geometry\":{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}},{\"type\":\"Feature\",\"properties\":{\"a\":null},\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.0,1.0]}}]}", + class = "geojson" ) ) - back_to_sf <- GeoJSON_to_sf(geojson, proj = attr(geojson, "proj")) - expect_equivalent(sf_obj, back_to_sf) + back_to_sf <- GeoJSON_to_sf(geojson, crs = attr(geojson, "crs")) + expect_equivalent_sfp(sf_obj, back_to_sf) }) test_that("utilities for checking v8 engine work", { expect_is(check_v8_major_version(), "integer") }) + +test_that("an sf data frame with no columns works", { + points <- geojsonsf::geojson_sf("{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Point\",\"coordinates\":[53.7702,-40.4873]}},{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"Point\",\"coordinates\":[58.0202,-43.634]}}]}") + expect_silent(ms_sf(points, "-info")) + expect_true(grepl("\"type\":\"FeatureCollection\"", sf_to_GeoJSON(points))) +}) + test_that("sys_mapshaper works with spaces in path (#107)", { skip_if_not(has_sys_mapshaper()) geojson <- "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"a\":1},\"geometry\":{\"type\":\"Point\",\"coordinates\":[0,0]}},{\"type\":\"Feature\",\"properties\":{\"a\":null},\"geometry\":{\"type\":\"Point\",\"coordinates\":[1,1]}}]}" diff --git a/tools/readme/unnamed-chunk-10-1.png b/tools/readme/unnamed-chunk-10-1.png new file mode 100644 index 0000000..424e820 Binary files /dev/null and b/tools/readme/unnamed-chunk-10-1.png differ diff --git a/tools/readme/unnamed-chunk-3-1.png b/tools/readme/unnamed-chunk-3-1.png index 68b7f89..f40df55 100644 Binary files a/tools/readme/unnamed-chunk-3-1.png and b/tools/readme/unnamed-chunk-3-1.png differ diff --git a/tools/readme/unnamed-chunk-4-1.png b/tools/readme/unnamed-chunk-4-1.png index 9a085c9..ab1bc8e 100644 Binary files a/tools/readme/unnamed-chunk-4-1.png and b/tools/readme/unnamed-chunk-4-1.png differ diff --git a/tools/readme/unnamed-chunk-5-1.png b/tools/readme/unnamed-chunk-5-1.png index f94162d..9554d5f 100644 Binary files a/tools/readme/unnamed-chunk-5-1.png and b/tools/readme/unnamed-chunk-5-1.png differ diff --git a/tools/readme/unnamed-chunk-6-1.png b/tools/readme/unnamed-chunk-6-1.png index 5259edb..a2d84be 100644 Binary files a/tools/readme/unnamed-chunk-6-1.png and b/tools/readme/unnamed-chunk-6-1.png differ diff --git a/tools/readme/unnamed-chunk-7-1.png b/tools/readme/unnamed-chunk-7-1.png new file mode 100644 index 0000000..cf0c29f Binary files /dev/null and b/tools/readme/unnamed-chunk-7-1.png differ diff --git a/tools/readme/unnamed-chunk-8-1.png b/tools/readme/unnamed-chunk-8-1.png index 791d2c3..61702ae 100644 Binary files a/tools/readme/unnamed-chunk-8-1.png and b/tools/readme/unnamed-chunk-8-1.png differ diff --git a/vignettes/rmapshaper.Rmd b/vignettes/rmapshaper.Rmd index 5f2811b..4a190bb 100644 --- a/vignettes/rmapshaper.Rmd +++ b/vignettes/rmapshaper.Rmd @@ -19,7 +19,7 @@ At this time, `rmapshaper` provides the following functions: - `ms_clip` - clip an area out of a layer using a polygon layer or a bounding box. Works on polygons, lines, and points - `ms_erase` - erase an area from a layer using a polygon layer or a bounding box. Works on polygons, lines, and points - `ms_dissolve` - aggregate polygon features, optionally specifying a field to aggregate on. If no field is specified, will merge all polygons into one. -- `ms_explode` - convert multipart shapes to single part. Works with polygons, lines, and points in geojson format, but currently only with polygons and lines in the `Spatial` classes (not `SpatialMultiPoints` and `SpatialMultiPointsDataFrame`). +- `ms_explode` - convert multipart shapes to single part. Works with polygons, lines, and points. - `ms_lines` - convert polygons to topological boundaries (lines) - `ms_innerlines` - convert polygons to shared inner boundaries (lines) - `ms_points` - create points from a polygon layer @@ -30,83 +30,76 @@ This short vignette focuses on simplifying polygons with the `ms_simplify` funct ### Usage -rmapshaper works with geojson strings (character objects of class `geo_json`) and `list` -geojson objects of class `geo_list`. These classes are defined in the `geojsonio` -package. It also works with `Spatial` classes from the `sp` package. +rmapshaper works with `sf` objects as well as geojson strings (character objects of class `geo_json`). It also works with `Spatial` classes from the `sp` package, though this will likely be retired in the future; users are encouraged to use the more modern `sf` package. -We will use the `states` dataset from the `geojsonio` package and first turn it -into a `geo_json` object: +We will use the `nc.gpkg` file (North Carolina county boundaries) +from the `sf` package and read it in as an `sf` object: ```{r} -library(geojsonio) library(rmapshaper) -library(sp) +library(sf) -states_json <- geojson_json(states, geometry = "polygon", group = "group") +file <- system.file("gpkg/nc.gpkg", package = "sf") +nc_sf <- read_sf(file) ``` -For ease of illustration via plotting, we will convert to a `SpatialPolygonsDataFrame`: -```{r} -states_sp <- geojson_sp(states_json) +Plot the original: -## Plot the original -plot(states_sp) +```{r} +plot(nc_sf["FIPS"]) ``` -Now simplify using default parameters, then plot the simplified states +Now simplify using default parameters, then plot the simplified North Carolina counties: ```{r} -states_simp <- ms_simplify(states_sp) -plot(states_simp) +nc_simp <- ms_simplify(nc_sf) +plot(nc_simp["FIPS"]) ``` You can see that even at very high levels of simplification, the mapshaper -simplification algorithm preserves the topology, including shared boundaries: +simplification algorithm preserves the topology, including shared boundaries. The `keep` +parameter specifies what proportion of vertices to keep: ```{r} -states_very_simp <- ms_simplify(states_sp, keep = 0.001) -plot(states_very_simp) +nc_very_simp <- ms_simplify(nc_sf, keep = 0.001) +plot(nc_very_simp["FIPS"]) ``` -Compare this to the output using `rgeos::gSimplify`, where overlaps and gaps are evident: +Compare this to the output using `sf::st_simplify`, where overlaps and gaps are evident: ```{r} -library(rgeos) -states_gsimp <- gSimplify(states_sp, tol = 1, topologyPreserve = TRUE) -plot(states_gsimp) + +nc_stsimp <- st_simplify(nc_sf, preserveTopology = TRUE, dTolerance = 10000) # dTolerance specified in meters +plot(nc_stsimp["FIPS"]) ``` -The package also works with `sf` objects. This time we'll demonstrate the `ms_innerlines` function: +This time we'll demonstrate the `ms_innerlines` function: ```{r} -library(sf) -states_sf <- st_as_sf(states_sp) -states_sf_innerlines <- ms_innerlines(states_sf) -plot(states_sf_innerlines) +nc_sf_innerlines <- ms_innerlines(nc_sf) +plot(nc_sf_innerlines) ``` -All of the functions are quite fast with `geo_json` character objects and `geo_list` -list objects. They are slower with the `Spatial` classes due to internal conversion -to/from json. If you are going to do multiple operations on large `Spatial` objects, -it's recommended to first convert to json using `geojson_list` or `geojson_json` from -the `geojsonio` package. All of the functions have the input object as the first argument, +All of the functions are quite fast with `geojson` character objects. They are slower with the `sf` and +`Spatial` classes due to internal conversion to/from json. If you are going to do multiple +operations on large `sf` objects, +it's recommended to first convert to json using `geojsonsf::sf_geojson()`, or `geojsonio::geojson_json()`. +All of the functions have the input object as the first argument, and return the same class of object as the input. As such, they can be chained together. -For a totally contrived example, using `states_sp` as created above: +For a totally contrived example, using `nc_sf` as created above: ```{r eval=rmapshaper:::check_v8_major_version() >= 6} -library(geojsonio) +library(geojsonsf) library(rmapshaper) -library(sp) -library(magrittr) - -## First convert 'states' dataframe from geojsonio pkg to json -states_json <- geojson_json(states, lat = "lat", lon = "long", group = "group", - geometry = "polygon") - -states_json %>% - ms_erase(bbox = c(-107, 36, -101, 42)) %>% # Cut a big hole in the middle - ms_dissolve() %>% # Dissolve state borders - ms_simplify(keep_shapes = TRUE, explode = TRUE) %>% # Simplify polygon - geojson_sp() %>% # Convert to SpatialPolygonsDataFrame +library(sf) + +## First convert 'states' dataframe from geojsonsf pkg to json + +nc_sf %>% + sf_geojson() |> + ms_erase(bbox = c(-80, 35, -79, 35.5)) |> # Cut a big hole in the middle + ms_dissolve() |> # Dissolve county borders + ms_simplify(keep_shapes = TRUE, explode = TRUE) |> # Simplify polygon + geojson_sf() |> # Convert to sf object plot(col = "blue") # plot ``` @@ -133,7 +126,7 @@ $ npm install -g mapshaper Then you can use the `sys` argument in any rmapshaper function: ```{r eval=nzchar(Sys.which("mapshaper"))} -states_simp_sys <- ms_simplify(states_sf, sys = TRUE) +nc_simp_sys <- ms_simplify(nc_sf, sys = TRUE) -plot(states_simp_sys[, "region"]) +plot(nc_simp_sys[, "FIPS"]) ```