Creating Leaflet Geo-Spatial Plots in R - r

Recently, I have started learning about the R package "leaflet". This package allows you to identify points on a map provided their latitude and longitude. I created some fake data and I was able to successfully make a leaflet plot (with popup options that display properties of each point):
library(dplyr)
library(leaflet)
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ),
"Job" = c("Economist", "Economist", "Teacher", "Teacher", "Lawyer"),
"First_Name" = c("John","James", "Jack", "Jason", "Jim"),
"Last_Name" = c("Smith","Charles", "Henry", "David", "Robert"),
"vehicle" = c("car", "van", "car", "none", "car"))
map_data %>% leaflet() %>% addTiles() %>% addMarkers(clusterOption=markerClusterOptions(), lng = map_data$Long, lat = map_data$Lat, popup = paste("Job", map_data$Job, "<br>", "First_Name:", map_data$First_Name, "<br>", "Last_Name:", map_data$Last_Name, "<br>", "vehicle:", map_data$vehicle, "<br>" ))
Now, I wanted to take this a few steps further and color code the points based on the "job" of each individual. I found this github tutorial over here that I want to follow: https://poldham.github.io/abs/mapgbif.html
Using the same data I previously created, I tried to re-create a similar plot (slightly modifying it by adding my own popup options). However, when I run my code, I now get an empty map. Can someone please tell me what I am doing wrong? I have attached my code below:
library(dplyr)
library(leaflet)
library(RColorBrewer)
#create map data
map_data <- data.frame("Lati" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Longi" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ),
"Job" = c("Economist", "Economist", "Teacher", "Teacher", "Lawyer"),
"First_Name" = c("John","James", "Jack", "Jason", "Jim"),
"Last_Name" = c("Smith","Charles", "Henry", "David", "Robert"),
"vehicle" = c("car", "van", "car", "none", "car"))
#create initial map
map <- leaflet::leaflet(map_data) %>%
addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(~Longi, ~Lati, popup = map_data$Job)
#add markers
map <- leaflet(map_data) %>% addTiles() %>% addCircleMarkers(~Longi,
~Lati, popup = map_data$Job, radius = 1, fillOpacity = 0.5, clusterOptions = markerClusterOptions())
map <- leaflet(map_data) %>% addTiles() %>% addCircleMarkers(~Longi, ~Lati,
popup = map_data$Job, radius = 1, weight = 2, opacity = 0.5, fill = TRUE,
fillOpacity = 0.2)
#create colors (is it possible to make the colors Red, Blue and Green?)
previewColors(colorFactor("Paired", domain = NULL), LETTERS[1:3])
kingdom <- c("Economist", "Lawyer", "Teacher")
my_palette <- brewer.pal(3, "Paired")
previewColors(colorFactor(my_palette, levels = kingdom), kingdom)
factpal <- colorFactor(my_palette, levels = kingdom)
species_circle <- leaflet(map_data) %>% addTiles() %>% addCircleMarkers(~Longi,
~Lati, popup = map_data$Job, radius = 1, weight = 2, opacity = 0.5,
fill = TRUE, fillOpacity = 0.2, color = ~factpal(kingdom))
species_circle
groups = unique(map_data$Job)
#finalize map
map = leaflet(map_data) %>% addTiles(group = "OpenStreetMap")
for (i in groups) {
data = map_data[map_data$kingdom == i, ]
map = map %>% addCircleMarkers(data = data, ~Longi, ~Lati, radius = 1,
weight = 2, opacity = 0.5, fill = TRUE, fillOpacity = 0.2, color = ~factpal(kingdom),
group = i)
}
map %>% addLayersControl(overlayGroups = groups, options = layersControlOptions(collapsed = FALSE)
%>% addTiles() %>% # Add default OpenStreetMap map tiles
addMarkers(lng = map_data$Long, lat = map_data$Lat,
popup = paste("Job", map_data$Job, "<br>",
"First_Name:", map_data$First_Name, "<br>",
"Last_Name:", map_data$Last_Name, "<br>", "vehicle:", map_data$vehicle, "<br>" ))
Thanks!

The main issue with your code is that you filter on variable map_data$kingdom (which is no column of map_data) in the for loop instead of on variablr map_data$Job. Hence data is always an empty df and you don't get any markers.
Besides that:
You don't need the for loop
To get the right assigments for colors map ~factpal(Job) on color instead of ~factpal(kingdom).
You don't need to refer to variables in your data with map_data$. Simply use the name of the variable. Leaflet by default will look for these variables in the dataset you provided with leaflet(map_data)
I reduced all the unnecessary code to focus on the main map code. For illustration purposes I increased the radius of the cirleMarkers to 10 and set the opacity to 1.
library(dplyr)
library(leaflet)
library(RColorBrewer)
# create map data
map_data <- data.frame(
"Lati" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Longi" = c(-79.3871, -79.3860, -79.3807, -79.3806, -79.3957),
"Job" = c("Economist", "Economist", "Teacher", "Teacher", "Lawyer"),
"First_Name" = c("John", "James", "Jack", "Jason", "Jim"),
"Last_Name" = c("Smith", "Charles", "Henry", "David", "Robert"),
"vehicle" = c("car", "van", "car", "none", "car")
)
kingdom <- c("Economist", "Lawyer", "Teacher")
my_palette <- brewer.pal(3, "Paired")
factpal <- colorFactor(my_palette, levels = kingdom)
groups <- unique(map_data$Job)
# finalize map
map <- leaflet(map_data) %>%
addTiles(group = "OpenStreetMap") %>%
addCircleMarkers(~Longi, ~Lati, popup = ~Job,
radius = 10, weight = 2, opacity = 1, color = ~factpal(Job),
fill = TRUE, fillOpacity = 1, group = ~Job
)
map %>%
addLayersControl(overlayGroups = groups, options = layersControlOptions(collapsed = FALSE)) %>%
addTiles() %>%
addMarkers(lng = ~Longi,
lat = ~Lati,
popup = ~paste("Job", Job, "<br>",
"First_Name:", First_Name, "<br>",
"Last_Name:", Last_Name, "<br>", "vehicle:", vehicle, "<br>"))
EDIT To achieve the features you mentioned in your comment you could do:
If you want different colors you can simply pass any color vector to colorFactor, e.g.
my_palette <- c("red", "green", "blue")
factpal <- colorFactor(my_palette, levels = kingdom)
To remove the pins drop the line starting with addMarkers. In that case you have to move the code for the popup into addCircleMarkers (but be aware that there is already a popup=~Job in addCircleMarkers, i.e. overwrite this one). Another option would be to add group=~Job to addMarkers. Doing so only the pins for checked jobs will appear on the map.

Related

R: Making More "Noticeable" Labels in R Leaflet map

I made the following map in R (from a data frame with 5 points ordered from "1" to "5"):
library(dplyr)
library(leaflet)
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), type = c(1,2,3,4,5))
map_data$type = as.factor(map_data$type)
leaflet(map_data) %>% addTiles() %>% addCircleMarkers(stroke = FALSE, label = ~type,
labelOptions = labelOptions(noHide = TRUE, offset=c(0,-12), fill = TRUE, opacity = 10, weight = 10, textOnly = TRUE))
But the problem is, you can barely see the "labels" for each city:
I would like to make the "labels" a lot more noticeable, something like this:
I tried to play around with the "weight" and "opacity" arguments, but this does not seem to be working.
Can someone please show me how to do this?
Note: I do not want to do this in R SHINY, just using LEAFLET in R.
Reference: Print label on circle markers in leaflet in Rshiny
fillOpacity = before labelOptions:
library(dplyr)
library(leaflet)
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), type = c(1,2,3,4,5))
map_data$type = as.factor(map_data$type)
leaflet(map_data) %>%
addTiles() %>%
addCircleMarkers(stroke = FALSE,
label = ~type,
fillOpacity = 0.9,
labelOptions = labelOptions(noHide = TRUE, fill = FALSE, offset = c(5,2), textsize = "18px", textOnly = TRUE))

R: Connecting Dots on a Map

I am working with the R programming language.
Using the "leaflet" library, I made the following map for these 5 cities:
library(dplyr)
library(leaflet)
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), type = c(1,2,3,4,5))
map_data$type = as.factor(map_data$type)
leaflet(map_data) %>%
addTiles() %>% addCircleMarkers(stroke = FALSE, label = ~type,fillOpacity = 0.8, labelOptions = labelOptions(direction = "center",style = list('color' = "white"),noHide = TRUE, offset=c(0,0), fill = TRUE, opacity = 1, weight = 10, textOnly = TRUE))
On this above map that I have created, I would now like to "connect" all these "points" (i.e. cities) on the map (in a route) based on their "number" (e.g. connect 1 with 2, 2 with 3, 3 with 4, 4 with 5, 5 with 1), and output the "total distance" of the route. I found a previous post that shows how to do this: How to show path and distance on map with leaflet, shiny apps?
I tried to adapt the code from this post to suit my question:
library(osrm)
route = osrmRoute(c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), overview = 'full')
route_summary = osrmRoute(c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), overview = FALSE)
leaflet() %>% addTiles() %>%
addCircleMarkers(c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957 ), c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629), stroke = FALSE, label = ~type,fillOpacity = 0.8,
labelOptions = labelOptions(direction = "center",style = list('color' = "white"),noHide = TRUE, offset=c(0,0), fill = TRUE, opacity = 1, weight = 10, textOnly = TRUE)) %>%
addPolylines(route$lon,route$lat,
label = paste(round(route_summary[1]/60), 'hr - ', round(route_summary[2]), 'km'),
labelOptions = labelOptions(noHide = TRUE))
But this returns the following error:
Error in UseMethod("metaData") :
no applicable method for 'metaData' applied to an object of class "NULL"
Can someone please show me how to fix this problem?
I would like to do this using "leaflet" and not using "rshiny". In the end, I would like the final map to look something like this (this is supposed to represent a "single path" from a Travelling Salesman Problem) :
[![enter image description here][2]][2]
Note: I am starting to think that problem might be that the "osrmRoute()" function might not be able to work for more than 2 points?
https://github.com/riatelab/osrm/issues/41
https://rdrr.io/cran/osrm/man/osrmTrip.html
One way is for you to make API call:
https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md
I'll just outline how can you do it:
data
df <- data.frame(
lon = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957),
lat = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629)
)
url call
root <- "http://router.project-osrm.org/route/v1/driving/"
options <- c(
continue_straight = "true",
overview = "full",
annotations = "true",
steps = "true"
)
coords <- df %>%
slice(c(seq_len(n()), 1)) %>%
pmap_chr(str_c, sep = ",") %>% str_c(collapse = ";")
options <- options %>%
imap_chr(~str_c(.y, "=", .x)) %>%
str_c(collapse = "&") %>%
str_c("?", .)
res <- rjson::fromJSON(file = str_c(root, coords, options))
Note that I've added first point as 6th row to make circle route.
map
res$routes[[1]]$geometry %>%
googlePolylines::decode() %>%
.[[1]] %>%
leaflet() %>%
addTiles() %>%
addPolylines(lng = ~lon, lat = ~lat) %>%
addCircleMarkers(
data = df,
stroke = FALSE,
label = seq_len(nrow(df)),
fillOpacity = 0.8,
labelOptions = labelOptions(
direction = "center",
style = list('color' = "white"),
noHide = TRUE,
offset=c(0,0),
fill = TRUE,
opacity = 1,
weight = 10,
textOnly = TRUE
)
)
distance
res$routes[[1]]$distance
This is in meters (documentation)
EDIT
There probably is better way of labeling polyline but I don't have time now:
library(sf)
segment_df <- df %>% rbind(df[1,])
d <- segment_df %>%
st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
{st_distance(.[-6,], .[-1,], by_element = TRUE)} %>%
as.vector() %>%
round()
m <- leaflet() %>% addTiles()
for(i in seq_len(nrow(segment_df) - 1))
m <- m %>% addPolylines(
data = segment_df[i:(i+1),],
lng = ~lon, lat = ~lat, color = "red", label = paste(d[[i]], "m"),
labelOptions(noHide = TRUE, direction = 'top')
)
m <- m %>% addCircleMarkers(
data = df,
stroke = FALSE,
label = seq_len(nrow(df)),
fillOpacity = 0.8,
labelOptions = labelOptions(
direction = "center",
style = list('color' = "white"),
noHide = TRUE,
offset=c(0,0),
fill = TRUE,
opacity = 1,
weight = 10,
textOnly = TRUE
)
)
If you want only to show total distance then that is easier and does not require loop, just replace loop with:
segment_df %>%
leaflet() %>%
addTiles() %>%
addPolylines(
lng = ~lon, lat = ~lat, color = "red",
label = paste(sum(d), "m"),
labelOptions = labelOptions(noHide = TRUE, direction = 'top')
)
I hope you understand (and see from map) that this is not drivable.
Here is an answer I tried based on #det's answer:
library(sf)
library(geosphere)
library(dplyr)
library(leaflet)
library(data.table)
library(VPF)
#add a 6th row that is equal to the 1st row - so that the path loops back
map_data <- data.frame("Lat" = c(43.6426, 43.6424, 43.6544, 43.6452, 43.6629, 43.6426), "Long" = c(-79.3871, -79.3860, -79.3807, -79.3806,-79.3957, -79.3871 ), type = c(1,2,3,4,5,1))
map_data$type = as.factor(map_data$type)
m1 = leaflet(map_data) %>% addTiles() %>% addCircleMarkers(stroke = FALSE, label = ~type,fillOpacity = 0.8,
color = ~ifelse(type==1,"red","blue"), labelOptions = labelOptions(direction = "center",style = list('color' = "white"),
noHide = TRUE, offset=c(0,0), fill = TRUE, opacity = 1, weight = 10, textOnly = TRUE))
m1 %>% addTiles() %>%
addPolylines(data = map_data, lng = ~Long, lat = ~Lat, group = ~type)
Now, I want to calculate the total distance of the trip and have it displayed on the map:
#distances (https://stackoverflow.com/questions/42119438/calculate-distance-between-two-long-lat-coordinates-in-a-dataframe)
result = rbind(
cbind(map_data[1:nrow(map_data)-1,c(1,2)], map_data[-1,c(1,2)]),
cbind(map_data[nrow(map_data), c(1,2)], map_data[1,c(1,2)])
)
colnames(result) <- c("start_lat", "start_long", "end_lat", "end_long")
result$id = as.factor(c(1,2,3,4,5,1))
result = data.frame(result)
for (i in 1:nrow(result)) {
a<-result$start_long[i]
b<-result$start_lat[i]
c<-result$end_long[i]
d<-result$end_lat[i]
result$distance[i]<-distm(c(a,b),c(c,d), fun = distHaversine)
}
#total distance of trip in meters
d = result$distance
total_d = signif(sum(d),3)
m1 %>% addPolylines(
data = map_data,
lng = ~Long, lat = ~Lat, color = "blue", label = paste0(total_d, " meters"),
labelOptions(noHide = TRUE, direction = 'top')
)
I think I finally got it - thanks so much # Det!

Data mismatch between dataset and value in the leaflet map

I'm trying to create a choropleth map in county level using leaflet R package and I actually do it. But when I check my data file and the hover text of any county I find that the values are not correct. For example check the Conejos county. Any explanation? Or a better way to process tha data and create this map without the mismatches?
Code:
library(raster)
library(leaflet)
library(tidyverse)
# Get USA polygon data
USA <- getData("GADM", country = "usa", level = 2)
### Get data
mydata <- read.csv("https://www.betydb.org/miscanthus_county_avg_yield.csv",
stringsAsFactors = FALSE) %>%
dplyr::select(COUNTY_NAME, Avg_yield)
### Check counties that exist in USA, but not in mydata
### Create a dummy data frame and bind it with mydata
mydata <- data.frame(COUNTY_NAME = setdiff(USA$NAME_2, mydata$COUNTY_NAME),
Avg_yield = NA,
stringsAsFactors = FALSE) %>%
bind_rows(mydata)
### Create a color palette
mypal <- colorNumeric(palette = "viridis", domain = mydata$Avg_yield)
leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
setView(lat = 39.8283, lng = -98.5795, zoom = 4) %>%
addPolygons(data = USA, stroke = FALSE, smoothFactor = 0.2, fillOpacity = 0.3,
fillColor = ~mypal(mydata$Avg_yield),
popup = paste("Region: ", USA$NAME_2, "<br>",
"Avg_yield: ", mydata$Avg_yield, "<br>")) %>%
addLegend(position = "bottomleft", pal = mypal, values = mydata$Avg_yield,
title = "Avg_yield",
opacity = 1)

renderLeaflet: legend is not rendered in shiny

Using the renderLeaflet (following codes), I am trying to create an interactive map within shiny. I want the map to be updated based on users selections of age group, sex, and year. When age group=60 and sex=both sexes and year= 2010 are selected, everything looks great (please see this screen shot).
However, when the other age groups are selected, the legend remains unchanged and is not rendered (Please see the second screen shot).
Here is my code:
mapdata_ <- reactive ({
nhmap$Per <- round(nhmap$Per, 1)
out_map <- nhmap %>%
filter (
Age_Group %in% input$Age_Group_map,
Sex %in% input$sex_map,
Year %in% input$Year_map)
return(out_map)
})
output$int_map <- renderLeaflet ({
leaflet (mapdata_(),
pal8 <- c("#FFFFE5", "#D9F0A3", "#78C679", "#006837") ,
pal <- colorBin(palette = pal8, domain = nhmap$Per, bins=4, right =FALSE, na.color = "#808080", alpha = FALSE, reverse = F)
) %>%
addProviderTiles("CartoDB.Positron") %>%
clearControls() %>%
clearShapes()%>%
addPolygons(fillColor = ~pal(Per),
stroke=T,
weight=1,
smoothFactor=0.2,
fillOpacity = 1,
color="black",
popup=~paste(NAME,"<br>",input$sex_map,
input$Age_Group_map,"=",Per,"%"),
highlightOptions = highlightOptions(color = "red",
weight = T,
bringToFront = T),
label=~NAME) %>%
addTiles() %>%
setView(-82.706838, 40.358615, zoom=7) %>%
addLegend(position = "bottomright",
values = ~pal(Per),
pal = pal,
title = (paste("%",input$Age_Group_map, input$sex_map, "in", input$Year_map)) ,
labFormat = labelFormat())
})

How to assign popup on map polygon that corresponds with the country R leaflet

I am trying to write a script that would allow people to click on a country then a popup would appear and show the country's name and corresponding value. I have successfully created the map and the boundaries of each country, however when you click on, for example Russia, the popup would show India. Below is a simplified version of the script:
library(leaflet)
library(maps)
countries <- c("Australia", "South Africa", "India", "Mexico", "USA", "Russia")
values <- c(1,2,3,4,5,6)
bounds <- map("world", countries, fill = TRUE, plot = FALSE)
map <- leaflet(data) %>%
addTiles() %>%
addPolygons(data = bounds, group = "Countries",
color = "red",
weight = 2,
popup = paste("Country: ", countries, "<br>", "Value: ", values, "<br>"),
fillOpacity = 0.1,
highlightOptions = highlightOptions(color = "black",
weight = 2,
bringToFront = TRUE))
map
The map is generated using the leaflet package in R. Any solutions or advice would be welcome, I am sure it is a simple error that I am making somewhere.
I leave two options for you. If you just want to show the country names as they are in bounds (e.g., Australia:Melville Island), you need to change popup = countries to popup = ~names.
map1 <- leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
addPolygons(data = bounds, group = "Countries",
color = "red",
weight = 2,
popup = ~names,
fillOpacity = 0.1,
highlightOptions = highlightOptions(color = "black",
weight = 2,
bringToFront = TRUE))
If you want to just have the country names as you specified in countries, you want to manipulate names using gsub(), for example. Here I removed all characters from : to the end for each name.
map2 <- leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
addPolygons(data = bounds, group = "Countries",
color = "red",
weight = 2,
popup = ~gsub(x = names, pattern = ":.*$", replacement = ""),
fillOpacity = 0.1,
highlightOptions = highlightOptions(color = "black",
weight = 2,
bringToFront = TRUE))
EXTRA
The OP added one more thing to his question. Here is my idea. If you have two things to show in popups, you can do the following. You add value to bounds and create popups.
# Add values to bounds.
set.seed(111)
bounds$value <- sample.int(n = 1000, size = 301, replace = TRUE)
map3 <- leaflet() %>%
addProviderTiles("OpenStreetMap.Mapnik") %>%
addPolygons(data = bounds, group = "Countries",
color = "red",
weight = 2,
popup = paste("Country: ", bounds$names, "<br>",
"Value: ", bounds$value, "<br>"),
fillOpacity = 0.1,
highlightOptions = highlightOptions(color = "black",
weight = 2,
bringToFront = TRUE))

Resources