REST API with R Plumber - GeoJSON as Input

689 views Asked by At

I am trying to wrap a function into REST API using plumber R Package. As a Input , function takes a shape file and returns GeoJSON as Output after the transformation.

#* Return Spatial Polygon Object in form of GeoJSON
#* @param dsn:character Path to Shapefile with Name
#* @param design:character one or two
#* @post /sprayermap

sprayer_map <-
  function(dsn,
       design = c("one", "two")) {
# library
require(rgeos)
require(rgdal)
require(sp)
require(raster)
require(cleangeo)

#Import Shapefile
a_shape <- raster::shapefile(dsn)

result <-
  list("success" = F,
       additional_info = NULL ,
       GeoJSON = NULL)

if (class(a_shape) == "SpatialPolygonsDataFrame") {
  a_shape <- tryCatch (
    rgeos::gBuffer(a_shape, byid = TRUE, width = 0),
    error = function(err) {
      return(paste("sprayer map : ", err))

    }
  )

  if (design == "one") {
    sprayer_map <- tryCatch (
      aggregate(a_shape, "Rx"),
      error = function(err) {
        return(paste("sprayer map : ", err))

      }
    )

    sprayer_map@data$Rx <- as.integer(sprayer_map@data$Rx)

  } else if (design == "two") {
    return(paste0("Design Two !"))

  }

  temppath <- file.path(tempdir(), "sprayermap.GeoJSON")
  rgdal::writeOGR(
    sprayer_map,
    dsn = temppath,
    layer = "geojson",
    driver = "GeoJSON",
    overwrite_layer = TRUE
  )

  if (file.exists(temppath)) {
    GeoJSON <- readLines(temppath)
    result$success <- T
    result$GeoJSON = GeoJSON
    return(result)
  } else {
    return(paste0("GeoJSON Creation Failed!"))
  }

} else {
  return(paste0("Please provide spatial polygon object !"))

}
  }

Now to make REST API more generic in terms of implementation and use , Input of the REST API need to change into GeoJSON as request body (req$postBody) instead of shape file path import method. Looking for guidance how to achieve the same in this case. Test Input Shape file as well as GeoJSON

1

There are 1 answers

4
Bruno Tremblay On BEST ANSWER

With plumber 1.0

Just need to load librairies once. Take them out of your endpoint.

First create a parser and a serializer to parse geojson content and return geojson response. This could be done inside your endpoint too. It just makes it more reusable.

Parser are what handles request body content. Serializer encode the API response.

I repurposed parser_rds, and serializer_rds, just replaced with GeoJSON functions.

Then you do your endpoint using the just created parser and serializer.

Don't hesitate if you have any question.

Edit : Added a zipping folder serializer.

library(rgeos)
library(rgdal)
library(sp)
library(raster)
library(cleangeo)
library(plumber)

parser_geojson <- function(...) {
  parser_read_file(function(tmpfile) {
    rgdal::readOGR(tmpfile, ...)
  })
}
register_parser("geojson", parser_geojson, fixed = c("application/geo+json", "application/vnd.geo+json", "application/octet-stream"))

serializer_geojson <- function(type = "application/geo+json") {
  serializer_write_file(
    fileext = ".GeoJSON",
    type = type,
    write_fn = function(val, tmpfile) {
      rgdal::writeOGR(val, dsn = tmpfile, layer = "geojson", driver = "GeoJSON", overwrite_layer = TRUE)
    }
  )
}
register_serializer("geojson", serializer_geojson)

serializer_shapezip <- function(type = "application/zip") {
  serializer_content_type(type, function(val) {
    tmpdir <- file.path(tempdir(), "output")
    dir.create(tmpdir)
    on.exit({
      if (dir.exists(tmpdir)) {
        unlink(tmpdir, recursive = TRUE)
      }
    }, add = TRUE)
    raster::shapefile(val, file.path(tmpdir, "shapefile"))
    tmpfile <- file.path(tmpdir, "shapefile.zip")
    zip(tmpfile, file.path(tmpdir, dir(tmpdir)), extras = "-j")
    readBin(tmpfile, what = "raw", n = file.info(tmpfile)$size)
  })
}
register_serializer("shapezip", serializer_shapezip)

dothething <- function(a_shape, design) {
  if (!class(a_shape) == "SpatialPolygonsDataFrame") stop("Please provide spatial polygon object !")
  a_shape <- rgeos::gBuffer(a_shape, byid = TRUE, width = 0)

  if (design == "one") {
    sprayer_map <- aggregate(a_shape, "Rx")
    sprayer_map@data$Rx <- as.integer(sprayer_map@data$Rx)
  } else if (design == "two") {
    # This should return a geojson too since endpoint should have predictable outputs
    stop(paste0("Design Two !"))
  }

  sprayer_map
}

#* Return Spatial Polygon Object in form of GeoJSON
#* @param dsn:file A GeoJSON file
#* @param design:character one or two
#* @parser multi
#* @parser geojson
#* @serializer geojson
#* @post /sprayermap_geojson
function(dsn, design = c("one", "two")) {
  a_shape <- dsn[[1]]
  as_attachment(dothething(a_shape, design), "response.GeoJSON")
}

#* Return Spatial Polygon Object in form of GeoJSON
#* @param dsn:character Path to Shapefile with Name
#* @param design:character one or two
#* @serializer shapezip
#* @post /sprayermap_shapefile
function(dsn, design = c("one", "two")) {
  # Import Shapefile
  a_shape <- raster::shapefile(dsn)
  as_attachment(dothething(a_shape, design), "response.zip")
}