Search code examples
shinyterrar-leaflet

Interactive Leafpm editing in Shiny App doesn't recognize dataframe depending on session


I have a perplexing issue that must be related to some difference in my RStudio sessions. I am attempting to build an interactive shiny app that allows users to draw a polygon on a "leaflet" map using the "leafpm" package, and then extract and summarize raster data using terra::extract() function. I am able to make my app work as expected on one machine, but on a different machine, I get the following error:

Warning: Error in cbind: inherits(y, "data.frame") is not TRUE
  3: runApp
  2: print.shiny.appobj
  1: <Anonymous>

I have been able to isolate the offending line of code to this one (see the end of my post for the full reproducible example):

bio_extract<-reactive({coords$dtf<-data.frame(do.call(rbind, do.call(cbind,input$MAP_draw_new_feature$geometry$coordinates)))

This is the line of code that parses the coordinates for all of the vertices that the user drew, and concatenates them into a dataframe that I later pass to sf::st_as_sf() to create a simple feature polygon.

Here is the session info for the machine it is working properly on:

R version 4.2.2 (2022-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8    LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                           LC_TIME=English_United States.utf8    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] dplyr_1.1.0          sf_1.0-9             shinydashboard_0.7.2 terra_1.6-47         leafpm_0.1.0        
[6] leaflet_2.1.1        shiny_1.7.4         

loaded via a namespace (and not attached):
 [1] tidyselect_1.2.0    bslib_0.4.2         listenv_0.9.0       vctrs_0.5.2         generics_0.1.3      htmltools_0.5.4    
 [7] yaml_2.3.7          utf8_1.2.2          blob_1.2.3          rlang_1.0.6         e1071_1.7-12        later_1.3.0        
[13] pillar_1.8.1        jquerylib_0.1.4     glue_1.6.2          withr_2.5.0         DBI_1.1.3           bit64_4.0.5        
[19] lifecycle_1.0.3     fontawesome_0.5.0   future_1.30.0       htmlwidgets_1.6.1   memoise_2.0.1       codetools_0.2-18   
[25] fastmap_1.1.0       httpuv_1.6.9        crosstalk_1.2.0     parallel_4.2.2      class_7.3-20        fansi_1.0.4        
[31] Rcpp_1.0.10         KernSmooth_2.23-20  xtable_1.8-4        promises_1.2.0.1    classInt_0.4-8      cachem_1.0.6       
[37] jsonlite_1.8.4      mime_0.12           parallelly_1.34.0   bit_4.0.5           hms_1.1.2           digest_0.6.31      
[43] grid_4.2.2          cli_3.6.0           odbc_1.3.4          tools_4.2.2         magrittr_2.0.3      sass_0.4.5         
[49] proxy_0.4-27        tibble_3.1.8        future.apply_1.10.0 pkgconfig_2.0.3     ellipsis_0.3.2      rstudioapi_0.14    
[55] R6_2.5.1            globals_0.16.2      units_0.8-1         compiler_4.2.2    

And here is the session info for the machine that throws the error:

R version 4.3.1 (2023-06-16 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19045)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8  LC_CTYPE=English_United States.utf8    LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                           LC_TIME=English_United States.utf8    

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] dplyr_1.1.3          sf_1.0-15            shinydashboard_0.7.2 terra_1.7-73         leafpm_0.1.0         shiny_1.7.5.1        leaflet_2.2.0       

loaded via a namespace (and not attached):
 [1] sass_0.4.7         utf8_1.2.4         generics_0.1.3     class_7.3-22       KernSmooth_2.23-22 digest_0.6.33      magrittr_2.0.3    
 [8] grid_4.3.1         fastmap_1.1.1      jsonlite_1.8.7     e1071_1.7-13       DBI_1.1.3          promises_1.2.1     fansi_1.0.5       
[15] crosstalk_1.2.0    codetools_0.2-19   jquerylib_0.1.4    cli_3.6.1          rlang_1.1.1        crayon_1.5.2       units_0.8-4       
[22] ellipsis_0.3.2     cachem_1.0.8       tools_4.3.1        memoise_2.0.1      httpuv_1.6.12      vctrs_0.6.4        R6_2.5.1          
[29] mime_0.12          proxy_0.4-27       lifecycle_1.0.3    classInt_0.4-10    htmlwidgets_1.6.2  fontawesome_0.5.2  pkgconfig_2.0.3   
[36] pillar_1.9.0       bslib_0.5.1        later_1.3.1        glue_1.6.2         Rcpp_1.0.12        tibble_3.2.1       tidyselect_1.2.0  
[43] rstudioapi_0.15.0  xtable_1.8-4       htmltools_0.5.6.1  compiler_4.3.1  

Here is the full reproducible example: (N.B. When drawing the polygon, attempt to draw in Tillamook, Washington, or Lincoln Counties, as I am 100% sure that the fake raster will cover those areas. The counties will display on the map tiles I call).

library(leaflet)
library(shiny)
library(leafpm)
library(terra)
library(shinydashboard)
library(sf)
library(dplyr)


fake<-rast(xmin= -124.0, xmax=-121.0, ymin=44.0, ymax=46.0, nrows=500, ncols=500, crs="epsg:4326")
values(fake)<-rnorm(250000, 1000, 200)

rastpath<-tempdir()

if(!dir.exists(rastpath)){
  dir.create(tempdir)
}

writeRaster(fake, paste(rastpath, "fake.tif", sep="/"), overwrite=TRUE)


ui<-dashboardPage(
  title = "EFI Interactive Mapper",# Start Dashboard Page
  header = dashboardHeader(
    tags$li(class = "dropdown",
            tags$style(".main-header {max-height: 100px}"),
            tags$style(".main-header .logo {height: 100px} .primary-title height {100px}"),
            tags$style(".sidebar-toggle {height: 20px; padding-top: 150px !important;}"),
            tags$style(".navbar {min-height:20px !important}")
    ),
    titleWidth='100%',
    title = span(
      tags$img(src="Rlogo.png", width = '5%', align='right'), 
      column(12, class="title-box", 
             tags$h1(class="primary-title", style='margin-top:5px;', 'EFI Interactive Mapper')
      ))),#End Header
  dashboardSidebar(tags$style(".left-side, .main-sidebar {padding-top: 150px} "),selectInput(inputId = "BIO", "Select a Biometric", 
                                                                                             choices = c("Fake Biometric", "All Biometrics"),
                                                                                             selected = "Fake Biometric"),
                   downloadButton('downloadData', 'Download Polygon')
  ),
  dashboardBody(
    leafletOutput(outputId = "MAP", "100%", 800),
    verbatimTextOutput("debug_out")
  )
)

server<-function(input, output, session){
  
  Bios<-c("Fake Biometric", "All Biometrics")
  
  tifnames<-c('fake.tif')
  
  inpath<-rastpath
  rastdf<-data.frame(Bio=Bios[order(Bios)],  paths=paste(inpath, tifnames, sep="/"))
  
  
  #addResourcePath("maptiles", path)
  
  output$MAP<-renderLeaflet({
    leaflet() %>% addTiles() %>% 
      setView(lng=-123.5, lat=45.5, zoom = 10) %>% 
      #addTiles(urlTemplate = "/maptiles/{z}/{x}/{y}.png", options=list(tms=TRUE, crs=6557, minZoom=7, maxZoom=16)) %>% 
      addWMSTiles(
        "https://gis.odf.oregon.gov/ags3/rest/services/Basemaps/ProtectionMap/MapServer/tile/{z}/{y}/{x}",
        layers = "0",
        options = WMSTileOptions(format = "image/png", transparent = TRUE),
        attribution = "") %>% 
      
      addPmToolbar(
        toolbarOptions = pmToolbarOptions(drawPolygon = T,
                                          drawCircle = F,
                                          drawPolyline = F,
                                          drawRectangle = F,
                                          editMode = T,
                                          cutPolygon = T,
                                          removalMode = F,
                                          position="topleft"))
  })
  
  proxy <- leafletProxy("MAP", session)
  
  poly<-reactiveValues()
  coords<-reactiveValues()
  bio_ras<-reactiveValues()
  observeEvent(input$MAP_draw_new_feature, {
    req(input$BIO)
    
    bio_extract<-reactive({coords$dtf<-data.frame(do.call(rbind, do.call(cbind,input$MAP_draw_new_feature$geometry$coordinates)))
    colnames(coords$dtf)<-c("long", "lat")
    sfp <- coords$dtf %>%
      st_as_sf(coords = c("long", "lat"), crs = 4326) %>%
      summarise(geometry = st_combine(geometry)) %>%
      st_cast("POLYGON") %>% st_transform(crs=6557)
    
    #coords$df %>% 
    #st_as_sf(coords = c("long", "lat"), crs = 4326) %>%
    #summarise(geometry = st_combine(geometry)) %>%
    #st_cast("POLYGON") %>% st_transform(crs=6557)
    
    if(input$BIO=="All Biometrics"){
      tmp<-lapply(rastdf$paths, rast)
      bio_ras$biom<-rast(tmp)
    } else{ 
      bio_ras$biom<-rast(rastdf$paths[rastdf$Bio==input$BIO])
    }  
    fnx<-function(x){
      themean<-mean(x, na.rm=TRUE)
      thesd<-sqrt(var(x, na.rm=TRUE))
      out<-list(mean=round(themean,2), sd=round(thesd,2))
      return(out)
    }
    shapevect<-sfp
    st_as_sf(extract(bio_ras$biom, shapevect, fnx, bind=TRUE))})
    poly$out<-bio_extract()
  })
  
  #  observeEvent(input$drawnPoly_deleted_features, { 
  
  #    f <- input$drawnPoly_deleted_features
  #    ids<-lapply(f$features, function(x){unlist(x$properties$layerId)})
  #    polys$out<-polys$out[!row.names(polys$out) %in% ids ,]
  
  #  }) 
  
  output$debug_out<-renderPrint({poly$out})
  
  output$downloadData<-downloadHandler(
    
    filename <- function(){
      fname<-input$BIO
      paste(fname, "Polygon.gpkg", sep="_")}, 
    
    content = function(file) {
      #   req(polys$out)
      if (length(Sys.glob(paste(input$BIO, "*", sep=".")))>0){
        file.remove(Sys.glob(paste(input$BIO, "*", sep=".")))
      }
      
      owd<-setwd(tempdir())
      on.exit(setwd(owd))
      polys_out<-poly$out
      st_write(polys_out, "polyExport.gpkg", "GPKG", append=FALSE)  
      file.rename("polyExport.gpkg", file)  
      
      if (length(Sys.glob("polyExport.*"))>0){
        file.remove(Sys.glob("polyExport.*"))
      }
    })
}

shinyApp(ui=ui, server=server)

Solution

  • The culprit is not the line you mention. The error is generated by

    extract(biom, shapevect, fnx, bind = TRUE)
    

    Here, biom is SpatRaster and shapevect is sf. Then extract does:

    y <- vect(y)
    extract(x, y, ......)
    

    Now y is SpatVector. In the extract method for such types, there is:

            if (bind) {
                if (nrow(e) == nrow(y)) {
                    e <- cbind(y, e[, -1, drop = FALSE])
                }
                ......
    

    And this is this call to cbind which throws the error. So it looks like a bug.

    I think (not sure) you can do:

    e <- extract(biom, shapevect, fnx, bind = FALSE)
    

    and then do

    cbind(shapevect, e[, -1, drop = FALSE])
    

    That should give the expected output of extract(biom, shapevect, fnx, bind = TRUE).

    But there are some problems in your code... Check your parentheses and your curly braces. There's also some bad coding, e.g. you define a reactive inside an observer.