Search code examples
rshinyr-leafletggmap

How to filter data based on location of user input data?


I have a shiny app running here. It plots around 12 thousand apartments and rooms for rent on a leaflet interactive map and adds a marker on the map based on the address that the user inputs. Here's the code. Sorry for the if it is not well documented.

There are two different data frame objects: one is for apartments (df.apt) and the other is for rooms(df.quartos).

However, because of the amount of data the app loads, it is a bit slow. I want to add a resource that will plot the data only after the user inserts the address and also choose a proximity range (like, show only the apartments within 10km from the inputed address). How should I go about it?

library(leaflet)
library(shiny)
library(ggmap)


source("post4-prepararshiny.R") #loads data and helper functions


 ui = bootstrapPage(
   div(class = "outer",
       tags$head(
         # Include our custom CSS
         includeCSS("styles.css"),
         includeScript("gomap.js")
       ),

   tags$style(type = "text/css", "html, body {width:100%;height:100%}"),
   leafletOutput("mymap", width = "100%", height = "100%"),

   absolutePanel(id = "controls",# class = "panel panel-default",
                 fixed = TRUE,
                 draggable = TRUE,
                 top = 60, left = "auto", right = 20, bottom = "auto",
                 width = 330, height = "auto",

                 h2("Buscador OLX"),
                 textInput(inputId = "userlocation",
                           label = "Digite um endereço\n com pelo menos rua, número, bairro e cidade",
                           value = ""),
                 helpText("Exemplo: Rua Dias da Rocha, 85 - Copacabana, Rio de Janeiro - RJ"),

                 sliderInput(inputId = "distancia", label = "Escolha a distância em km:",
                             min = 0, max = 30, value = 15),

                 actionButton("go", "Buscar"),
                 helpText("Encontre imóveis para alugar perto de onde você quiser!"),

                 helpText("Cada ponto no mapa representa um imóvel para alugar.",
                          "A cor de um ponto é determinada pelo valor do aluguel.",
                          "Clique em um ponto para ter mais informações sobre o imóvel."),

                 helpText("Mais informações sobre este app em sillasgonzaga.github.io")

                 )

   ),
   tags$div(id="cite",
            'Dados extraídos do OLX em 12/11/2016.', ' Contato: sillasgonzaga.github.io'
   )
)

server.R

server = function(input, output, session){

  #browser()
  output$mymap <- renderLeaflet({
    map <- leaflet() %>%
    addTiles() %>%
    addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
    # coordenadas de um ponto em específico
    addMarkers(lat = -22.911872, lng = -43.230184,
               popup = "Estádio do Maracanã! <br> Apenas um exemplo!") %>%


    # plotar apartamentos
    addCircleMarkers(data = df.apt,
                     lng = ~lon, lat = ~lat,
                     color = ~vetorCoresApt(preco),
                     opacity = 1.5,
                     popup = textoPopup(df.apt, "apartamento"),
                     # Definir nome do grupo para ser usado na camada
                     group = "Apartamentos") %>%
    # plotar quartos
    addCircleMarkers(data = df.quartos,
                     lng = ~lon, lat = ~lat,
                     color = ~vetorCoresQuarto(preco),
                     opacity = 1.5,
                     popup = textoPopup(df.quartos, "quarto"),
                     group = "Quartos") %>%
    addLayersControl(
      overlayGroups = c("Apartamentos", "Quartos"),
      options = layersControlOptions(collapsed = FALSE),
      position = "bottomright"
    ) %>%
    addLegend(pal = vetorCoresApt, values = df.apt$preco,
              position = "bottomright")
    map
  })


   observeEvent(input$go, {
     v <- geocode(input$userlocation)
     leafletProxy('mymap', session) %>% addMarkers(lng = v$lon,lat = v$lat)
   })


}

I know that I can use the function geosphere::distm() to compute the distance between a matrix of data and a data point like:

coord <- matrix(data = c(df.apt$lon, df.apt$lat), ncol = 2)
distance_vector <- distm(x = coord, y = c(lon = -43.183447, lat = -22.913912), fun = distVincentySphere)
# insert vector into data frame
df.apt$distance <- distance_vector

However, how do I do this in an reactive way that will allow me to change the distance column every time the users click the button and change the sliderInput() that will be used to indicate the range of proximity?

P.S.: Sorry for code and comments in portuguese.

EDIT: SOLVED

I was able to come up with a solution after @HubertL reply. Here's what I did at server.R:

  distance_apt_reactive <- eventReactive(input$go, { 
    address_latlon <- geocode(input$userlocation)
    dist <- distm(x = matrix(data = c(df.apt$lon, df.apt$lat), ncol = 2), 
                  y = c(lon = address_latlon$lon, lat = address_latlon$lat), 
                  fun = distVincentySphere)
    dist <- dist/1000

  })

  apt_reactive <- reactive({df.apt[distance_reactive() < input$distancia,]})

  output$mymap <- renderLeaflet({
    map <- leaflet() %>%
      addTiles() %>%
      addProviderTiles("OpenStreetMap.BlackAndWhite") %>%
      setView(lng = mean(df.apt$lon), lat = mean(df.apt$lat), zoom = 11) %>%
      addLegend(pal = vetorCoresApt, values = df.apt$preco,
                position = "bottomright",
                layerId = "legend")

  map  
  })

     observe({
       leafletProxy("mymap") %>%
         clearMarkers() %>%
         #addMarkers(lng = myadress()$lon, lat = myadress()$lat) %>%
         addCircleMarkers(data = apt_reactive(),
                          lng = ~lon, lat = ~lat,
                          color = ~vetorCoresQuarto(preco),
                          opacity = 1.5,
                          # adicionar popup
                          popup = textoPopup(apt_reactive(), "apartamento"),
                          group = "Apartamentos")
    })

Solution

  • You could add a reactive that would filter your data.frame based on the distance to the address :

    apt_reactive <- reactive({ 
        address_latlon <- geocode(input$userlocation)
        dist <- distm(x = matrix(data = c(df.apt$lon, df.apt$lat), ncol = 2), 
                      y = c(lon = address_latlon$lon, lat = address_latlon$lat), 
                      fun = distVincentySphere)
        apt.df[dist < input$distancia,]
    })
    

    Then replace

    addCircleMarkers(data = df.apt
    

    by

    addCircleMarkers(data = apt_reactive()
    

    (and repeat same process with quartos_reactive for df.quartos)