iatowks iatowks - 16 days ago 9
R Question

Shiny: 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")
})

Answer

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)