I'm trying to plot using leaflet with a somewhat sizable set of coordinates (~34K latitude/longitude pairs) however, using the code below it seems that leaflet is only plotting a small portion of these:
data <- read.csv("Food_inspections.csv", header = TRUE)
names(data) <- tolower(names(data))
data1 <- filter(data, risk == c("Risk 1 (High)","Risk 2 (Medium)","Risk 3 (Low)"))
data1$risk <- droplevels(data1$risk)
leaflet(data1) %>%
addTiles() %>%
addMarkers(lat = ~latitude, lng = ~longitude)
What I get back is a map like this:
This clearly does not contain all ~34K coordinates. Even if I use "addCircles" I get the same thing. Other mapping packages (RgoogleMaps for instance) seem to plot everything correctly. Does leaflet round the coordinates it takes as input before plotting because I could see that making several of the coordinates appear to overlap in the plot.
The points are there, you have to zoom in to see them. But... at least in my browser... anything more than 80 points or so, and it takes super long to zoom.
url <- "http://data.cityofchicago.org/api/views/4ijn-s7e5/rows.csv?accessType=DOWNLOAD"
data <- read.csv(url, header = TRUE) # takes a minute...
names(data) <- tolower(names(data))
data1 <- subset(data, risk %in% c("Risk 1 (High)","Risk 2 (Medium)","Risk 3 (Low)"))
data1$risk <- droplevels(data1$risk)
data1 <- data1[1:50,]
library(leaflet)
leaflet(data1) %>%
addTiles() %>%
addMarkers(lat = ~latitude, lng = ~longitude)
Related
I need to work with a 10minutes (1/12th degree) global grid of all land areas. The grid is generated in R using package (sf). The grid is to be limited to land areas of the world. Grid ID is needed for further down-stream analysis. Code generating the grid is below:
library(tidyverse)
library(sf)
sf_use_s2(F)
birds <- st_read('BOTW_breeding_valid_union.gpkg') ## This is just an examplary shapefile I use to set a bbox.
bbox <- st_bbox(birds)
bbox[1] <- -180
bbox[2] <- -90
bbox[3] <- 180
bbox[4] <- 90
bbox <- bbox %>% st_as_sfc
grid <- st_make_grid(bbox, cellsize = 1/12) %>% st_as_sf() %>% mutate(grid_ID = row_number())
land <- st_read('ne_10m_land.shp')
land_grids <- st_intersects(grid, land) %>% as.data.frame() %>% rename(grid_ID = row.id)
grid <- grid %>% left_join(land_grids, by = "grid_ID") %>% filter(col.id == "1") %>% select(grid_ID) %>%
st_write('global_10m_grid.gpkg')
Now I need to plot it to inspect it and for further data mapping (the grids will have values). I use package tmap:
grid <- st_read('global_10m_grid.gpkg')
bitmap('test_grid.png')
tm_shape(grid) + tm_fill(col = 'red')
dev.off()
However, I am struggling due to the size either on a personal machine (takes incredibly long time to load [or so I hope as it hasn't loaded in principle just yet]) or on a cluster with interactive shell (dev.off produced an empty file).
Is there a way to plot this more efficiently?
Yes, rasterising was indeed the solution. Maintaining the resolution of the vector in raster results in files just under 11 mb, perfectly openable in a normal RStudio setting on my Desktop.
For future references, the code looks like this:
g <- st_read('yourfile.gpkg')
library(stars)
g %>% left_join(df, by = 'grp') %>% select(value) %>% st_rasterize(n = 2773927) %>% write_stars('filename.tif')
In the actual problem this variable is created by a script filtering the coordinates specified by the user, so sometimes variables return NULL. After specifying the coordinates I want to run the rest of the script without editing any part of it.
For simplicity I have used the breweries dataset to represent this.
##load required package
library(mapview)
library(leaflet)
##create variable with points
breweries_A <- breweries
##create variable with no points
breweries_B <- NULL
##create a leaflet plot
breweries_plot <- leaflet() %>%
addProviderTiles('CartoDB.Positron') %>%
addCircleMarkers(data = breweries_A) %>%
addCircleMarkers(data = breweries_B)
The result is an error because breweries B has no data.
Therefore it would be really helpful if there was a way to make leaflet ignore NULL objects, or dataframes with no rows?
You can add the data conditionally to the map:
breweries_plot <- leaflet() %>%
addProviderTiles('CartoDB.Positron')
if (!is.null(breweries_A))
breweries_plot <- breweries_plot %>% addCircleMarkers(data = breweries_A)
if (!is.null(breweries_B))
breweries_plot <- breweries_plot %>% addCircleMarkers(data = breweries_B)
My question is similar (basically the same) to the previous asked one: Draw All Lines Between Points
But I would like to have a solution using leaflet in R. Is it possible to do this using leaflet? The addPolylines() function works as connecting all of the consecutive points in a dataframe. As the example dataset below, it is easy to connect all points by sequence, but how to draw all the possible segments between those five points?
I would like to see a general solution with leaflet that I can apply on scenarios with more points. Many thanks!
locations <- data.frame(Long = c(76,75,73,72,74,76), Lat = c(43,40,40,43,45,43))
leaflet("locations") %>%
addTiles() %>%
addPolylines(lng = locations$Long,
lat = locations$Lat)
It can be broken down into 2 steps:
Create a data frame which contains all combinations between coordinates.
Draw lines between each pair of coordinates. See How Do I connect two coordinates with a line using Leaflet in R
Step 1
library(leaflet)
library(tidyr)
library(dplyr)
library(purrr)
locations <- data.frame(Long = c(76,75,73,72,74,76), Lat = c(43,40,40,43,45,43))
# get unique coordinates
locations_distinct <- locations %>%
distinct_all()
# get all combinations between any two points
combs <- combn(1:nrow(locations_distinct),2,simplify = FALSE)
# get pairs of coordinates
locations_paris <- map_df(combs,function(x){
df <- bind_cols(
locations_distinct[x[1],],locations_distinct[x[2],]
)
colnames(df) <- c("Long","Lat","Long1","Lat1")
return(df)
})
Step 2
How Do I connect two coordinates with a line using Leaflet in R
map <- leaflet(locations_paris) %>%
addTiles()
for(i in 1 : nrow(locations_paris)){
map <- map %>%
addPolylines(
lng = c(locations_paris$Long[i],locations_paris$Long1[i]),
lat = c(locations_paris$Lat[i],locations_paris$Lat1[i])
)
}
map
I have a dataset with lat long coordinates. Each coordinate belongs to a group and I want to connect them with polylines using leaflet. I am trying to give different colors to different line segments. I am fairly new to leaflet, so I don't know if I'm approaching this problem the right way.
I have tried the solution provided here, but I only get a single color as a result.
I have simulated a dataset to illustrate my problem:
#Example dataframe
lat <- runif(10,-10.8544921875,2.021484375)
long <- runif(10,49.82380908513249,59.478568831926395)
group <- factor(c(1,1,1,1,2,2,2,1,1,1))
header <- c("lat", "long", "group")
df <- data.frame(lat, long, group)
colnames(df) <- header
I have tried the following:
#Color palette
pal <- colorFactor(palette = c('navy', 'red'),
levels = levels(df$group))
#Graph
leaflet(df) %>%
addTiles() %>%
addPolylines(lng = ~as.numeric(df$long),
lat = ~as.numeric(df$lat), stroke = pal(df$group),
opacity = 0.1)
I want that the polyline between points that belong to group 1 and 2 shows red, and the rest just blue (or any combination of 2 colors, for that matter). However, I get back only one color. Opacity doesn't seem to match either (I have defined an opacity of 0.1 but the value of resulting polyline has certainly a value of 1).
Can anyone give me some pointers as to what I'm doing wrong?
Also, (with the larger dataset) I notice that once I add colors to the polylines of my dataset, the process becomes computationally very intensive. Can anyone give me guidelines to optimize the process?
Any help is very much appreciated. Thank you in advance!!
You mentioned performance was an issue, for which I recommend using mapdeck to do the plotting
In this example I'm plotting 100,000 lines, coloured by a group variable.
Setting up the data
For the data I'm putting the origin & destination coordinates in the same row. And using data.table to make it.
library(data.table)
lons <- seq(-180, 180, by = 0.0001)
lats <- seq(-90, 90,by = 0.0001)
n <- 1e5
dt <- data.table(
lon = sample(lons, size = n)
, lat = sample(lats, size = n)
, group = sample(c(1,2), replace = T, size = n)
)
dt[
, `:=`(
lon_to = shift(lon, type = "lead")
, lat_to = shift(lat, type = "lead")
)
]
Plot
You need a Mapbox API key for the underlying base map
Use the add_line() function to create a coloured line between a given origin & destination
mapdeck() %>%
add_line(
data = dt
, origin = c("lon","lat")
, destination = c("lon_to", "lat_to")
, stroke_colour = "group"
)
It's a mess because I've just sampled random points, but you get the idea.
Each color needs added as a separate layer to the map.
library(leaflet)
library(dplyr)
lat <- runif(10,-10.8544921875,2.021484375)
long <- runif(10,49.82380908513249,59.478568831926395)
group <- factor(c(1,1,1,1,2,2,2,1,1,1))
header <- c("lat", "long", "group")
col<-mapvalues(group,from=c(1,2),to=c("#00FF00","#FF0000")) #mannually add colors
#col<-mapvalues(group,from=c(1,2),to=substr(rainbow(2),1,7)) # programatically; looks like addpolylines wants hex not 8-digit colors
df <- data.frame(lat, long, group,col)
#colnames(df) <- header #you dont need this
map <- leaflet(df)
map <- addTiles(map)
for( group in levels(df$group)){
map <- addPolylines(map, lng=~long,lat=~lat,data=df[df$group==group,], color=~col)
}
map
I don't think arguments like color and stroke will read unique levels of the vector for each point of the line if you have multiple unique ones for each layer, whcih is why you need the for. you can see this if you reverse the order of the colors, eg from
col<-mapvalues(group,from=c(1,2),to=c("#00FF00","#FF0000"))
to
col<-mapvalues(group,from=c(1,2),to=c("#FF0000","#00FF00"))
It only picks up whatever the first color is.
Regarding my performance question, the following code snippet was very helpful:
map <- leaflet(df, options = leafletOptions(preferCanvas = TRUE))
I am trying to layer on tooltips to a map of the US, but wherever I hover... it displays the same data. In addition, the data is wrong. I'm thinking that it is passing through the factor values and not the character value. I tried taking tips from the movie explorer example - http://shiny.rstudio.com/gallery/movie-explorer.html - but, it's not working as I hoped. Any hints or clues I should look into?
Update: I've determined that you can only pass through arguments that are being called into the ggvis function. So, if my tooltip function included region, long, & lat, all of them would appear in the tooltip. Since Population and Income do not appear anywhere in the function, it is not passing them through. I'm still lost on how to proceed, but any ideas would be awesome! :)
library(ggplot2)
library(shiny)
library(ggvis)
library(dplyr)
shinyApp(
ui = fluidPage(
#numericInput("n", "n", 1),
ggvisOutput("map")
),
server = function(input, output) {
statesData <- reactive({
states <- data.frame(state.x77)
states$region <- row.names(state.x77) %>% tolower
row.names(states) <- NULL
all_states <- map_data("state") %>%
mutate(region = tolower(region)) %>%
left_join(states)
all_states_unique <- all_states %>%
select(region, Population, Income, Illiteracy, Life.Exp, Murder, HS.Grad, Frost, Area) %>%
unique
states_tooltip <- function(x) {
if (is.null(x)) return(NULL)
if (is.null(x$region)) return(NULL)
# Pick out the movie with this ID
allStates <- isolate(all_states_unique)
state <- allStates[allStates$region == x$region, ]
paste0("<b>", state$region, "</b><br>",
state$Population, "<br>",
state$Income
)
}
all_states %>%
arrange(group, order) %>%
ggvis(x = ~long, y = ~lat) %>%
layer_paths(fill = ~region, stroke := .2) %>%
add_tooltip(states_tooltip, "hover")
})
statesData %>% bind_shiny('map')
}
)
Add an index to the dataframe you want to pull the tooltip data from:
state$id <- 1:nrow(state)
ggvis takes a "key" argument to facilitate this kind of tooltip:
ggvis(x = ~long, y = ~lat, key := ~id) %>%
I tried figuring out that movie example and didn't find it very helpful. This always works for me:
add_tooltip(function(x) {
row <- state[state$id == x$key,]
paste0("<b>", row[,"region"], "</b><br>",
row[,"Population"], "<br>",
row[,"Income"]
)})
As for the issue w/ the tooltip always coming up the same, I don't know for sure but think it's due to the order of your layers in the ggvis command. Had a similar problem where I had some polygons layered on top of a scatterplot. It kept trying to draw the tooltip for the polygons (which covered the whole chart) when what I wanted was the individual points to display the tooltip. By reversing their order in the ggvis command (ie layer_points() %>% layer_shapes()) I got it to work.
I realise this is quite late but for future reference and others that stumble across this page. If your dataframe has been converted using fortify and has the group variable then this may be equivalent to the State level. Group can then be used to filter for the tooltip as it is in the ggvis command. That then allowed me to gain access to other variables I wanted.
In my problem I couldn't use the key solution because I was creating the plot to react to numerous years. So to change what you have above states_tooltip would become:
states_tooltip <- function(x){
row <- allstates[allstates$group==x$group,] %>%
select(region, Population, Income) %>% unique
paste0("<b>", row[,"region"], "</b><br>",
row[,"Population"], "<br>",
row[,"Income"]
)})