Search code examples
rrestrasterrgdalplumber

REST API with R Plumber - GeoJSON as Input


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


Solution

  • 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")
    }