ChMoe ChMoe - 2 months ago 21
R Question

Filter data points on a map using R Leaflet and Shiny (multiple criteria)

I am trying to create a filter map based on two categories "material" and "application". The user can select between 4 different materials and 4 different applications. Some data points have multiple values (i.e. "water,sand").
I am struggling with two issues:


  1. Two points (id=2 and 3) appear on the map if I select application=c and material=sand instead of one point (id=3) only. I somehow have to check whether both filter criteria apply for each data point (id) separately but I cannot find information how to do this.

  2. It depends on the order of checking the boxes if points are selected or not.



I will be very happy if anyone can give me some hints.

Here is my minimum example:

library(data.table)
library(shiny)
library(dplyr)
library(leaflet)

mydat <- data.table( id=c(1,2,3,4),
londd=c(20, 38, 96, 32),
latdd=c(60, 56, 30, 31),
material=c("stone", "water,sand", "sand", "wood"),
application=c("a","b","c","d"))

#Set up ui
ui <- shinyUI(fluidPage(
sidebarPanel(h5("", width=2),
checkboxGroupInput(inputId="MatFlag",label=h4("Material"),
choices=setNames(object=c("stone","water","sand", "wood"),
nm=c("stone", "water", "sand", "wood")),
),
checkboxGroupInput(inputId="AppFlag",label=h4("Application"),
choices=setNames(object=c("a","b","c","d"),
nm=c("a","b","c","d")),
),
position="left"),

#App mainPanel content and styles
mainPanel(fluidRow(leafletOutput(outputId="lmap")))
))

#Set up server
server <- function(input, output){
#Build leaflet map
lmap <- leaflet(data=mydat)%>%
addProviderTiles("Stamen.TonerLite",
options =providerTileOptions(noWrap = TRUE)) %>%
fitBounds(~min(londd), ~min(latdd), ~max(londd), ~max(latdd))

#Filter data
datFilt <- reactive({
filterName <- ifelse(length(input$MatFlag) == 0, 'none', input$MatFlag)
mydat[grepl(filterName,material) & input$AppFlag%in%application]
})

#Add markers based on selected flags
observe({
if(nrow(datFilt())==0) {
print("Nothing selected")
leafletProxy("lmap") %>%
clearShapes()}
else{ #print(paste0("Selected: ", unique(input$InFlags&input$InFlags2)))
leafletProxy("lmap", data=datFilt()) %>% clearShapes() %>%
addCircleMarkers(lng=~londd, lat=~latdd,
clusterOptions=markerClusterOptions(), weight=3,
color="#33CC33", opacity=1, fillColor="#FF9900",
fillOpacity=0.8) %>% clearShapes()
}
})

output$lmap <- renderLeaflet(lmap)
}

#Run app
shinyApp(ui = ui, server = server)

Answer

Okay, a couple of issues here:

Grepl does not work the way you think it does - it cannot compare two vectors together like %in% does. You need to first concantenate using | (regex OR), and then use grepl. datFilt should be:

  datFilt <- reactive({
    MatSearch <- paste0(c('xxx',input$MatFlag),collapse = "|")
    MatSearch <- gsub(",","|",MatSearch)
    mydat[grepl(MatSearch,material) & application %in% input$AppFlag]
  })

The next issue is with clearShapes. First of all, there is no reason for the second clearShapes() call in addCircleMarkers. If clearShapes() worked as expected, this would actually just clear any map you created. However, there seems to be a bug in how clearShapes works with MarkerClusters - contrary to documentation it can't clear them. Using clearMarkerClusters() does work:

  observe({
    if(nrow(datFilt())==0) {
      print("Nothing selected")
      leafletProxy("lmap") %>%
        clearMarkerClusters()
    } else { #print(paste0("Selected: ", unique(input$InFlags&input$InFlags2)))
      print("Something Selected")
      leafletProxy("lmap", data=datFilt()) %>% 
        clearMarkerClusters() %>% 
        addCircleMarkers(lng=~londd, lat=~latdd,
                         clusterOptions=markerClusterOptions(), weight=3,
                         color="#33CC33", opacity=1, fillColor="#FF9900", 
                         fillOpacity=0.8)
    }
  })

Subbing this into your code allows for the double filtering you are looking for