I have a leaflet map, which has points that overlap when zoomed out. I have therefore used markerClusterOtions() so that as the user zooms in, the overlapping points that were grouped into clusters will separate.
This is all working fine, but my problem is that I would like to apply a zoom limit. I don't want the users to be able to zoom right in to street level, because then they could see the exact address of my markers (which in this case would be a data protection issue). I want the markers to freeze when the user reaches the max zoom limit, but until then, they should keep separating out as the user zooms in.
I thought this could be achieved by setting the maxZoom in tileOptions, and freezeAtZoom in markerClusterOptions, but it doesn't work and I keep getting a strange error.
Here is my code:
# Create the interactive map:
myleaflet <- leaflet() %>%
# Add open street map (default base layer)
addTiles(tileOptions(minZoom = 1, maxZoom = 12)) %>%
# Add transformed shapefile of regions
addPolygons(data = regions, weight = 5, col = "black") %>%
# Add markers for case residence with descriptive labels:
addMarkers(lng = mydata$home_long,
lat = mydata$home_lat,
popup = paste("ID: ", mydata$id, "<br/>",
"Link: ", mydata$links, "<br/>",
"Clade: ", mydata$wgs_cluster),
clusterOptions = markerClusterOptions(freezeAtZoom = "max"))
# View the map:
myleaflet
And here is the error I get (it appears when I try to view the map, not when running the code to create it):
Error in dirname(to) : path too long
If I remove the zoom options, I can view the map, but then the zoom is not limited.
I have looked at posts regarding this error, and I can see that it is associated with Windows OS and paths that are too long. I am using windows, and this is part of an R markdown chunk, but the chunk name is 24 characters (so within the limit) and the R markdown file name is short - also, the error only occurs when I run the above code, which is just part of the code in that chunk. I can't see how adding the zoom options makes something too long, since they are supposed to be valid arguments?
Any insights as to what I'm doing wrong would be much appreciated.
Update:
It seems my mistake was not specifying the argument name for tileOptions - also freezeAtZoom is not necessary to specify when the maximum zoom limit is already specified for the map tiles (in fact if both are specified, the unclustered markers also show up on the map).
The code below works:
# Create the interactive map:
myleaflet <- leaflet() %>%
# Add open street map (default base layer)
addTiles(options = tileOptions(maxZoom = 12)) %>%
# Add transformed shapefile of regions
addPolygons(data = regions, weight = 5, col = "black") %>%
# Add markers for case residence with descriptive labels:
addMarkers(lng = mydata$home_long,
lat = mydata$home_lat,
popup = paste("ID: ", mydata$id, "<br/>",
"Link: ", mydata$links, "<br/>",
"Clade: ", mydata$wgs_cluster),
clusterOptions = markerClusterOptions())
# View the map:
myleaflet
Since the first option in addTiles() is map I suppose that it was previously interpreting the tileOptions argument as a file path to a map, but I would be interested to know if there are other interpretations, since the traceback showed the problem with the file path length was in saving to html, not importing / reading a file.
Related
I am creating maps for offline use using Leaflet for R and OpenStreetMap, adding information such as points of interest, and saving them as png files. This works well, but I would like to be able to adjust the font size on the OpenStreetMap basemap layer. EDIT This is because for very large maps (one I am working on is 4,500 pixels square) the text is so small as to be unreadable, even though the road network is visible. For example, this code:
require(leaflet)
require(mapview)
require(webshot)
m <- leaflet() %>%
setView(lng = -0.134509, lat = 51.509898, zoom = 17) %>%
addTiles(group = "OpenStreetMap")
mapshot(m, file = file.path("c:\\Temp\\stack1.png"), vwidth = 500, vheight = 500)
produces this output:
In this image, I want to be able to change (say) the size of the font used to display "Jermyn Street". I have experimented with zoomOffset and tileSize, like this:
m <- leaflet() %>%
setView(lng = -0.134509, lat = 51.509898, zoom = 17) %>%
addTiles(group = "OpenStreetMap",
tileOptions(tileSize = 512,
zoomOffset = -1))
So far, all combinations I have tried result in blank output, like the image below.
Is it even possible to change the basemap font on such tiles?
(I would be open to using a different tile provider, if that helped.)
Not by passing arguments to a tile server you can’t. You’ll have to run your own tile server configured with larger text size or look for a map style/theme with larger text size.
You can refer to https://help.openstreetmap.org/questions/29621/how-to-increase-font-size-when-viewing-osm
Making a leaflet map with clustered markers.
Is there a parameter in markerClusterOptions() that controls the number of groups based on the zoom level? For example, at default zoom my map shows just 3 clusters for 1800 observations. However it would be nice if there were at least twice as many clusters at default zoom.
I don't want to use freezeAtZoom because then users won't be able to zoom in and see each individual observation.
Data procured from: https://data.sccgov.org/Public-Safety/Crime-Reports/n9u6-aijz/data
library(tidyverse);library(leaflet);library(lubridate)
crime_reports <- read_csv("Crime_Reports.csv")
car_burglaries <- crime_reports %>% filter(incident_type_primary=="VEHICLE BURGLARY") %>%
mutate(datetime=mdy_hms(incident_datetime)) %>%
arrange(datetime)
leaflet(car_burglaries) %>% addTiles() %>%
addMarkers(
popup = ~paste(as.character(incident_datetime),",","Case number",as.character(case_number)),
label = ~as.character(address_1),
clusterOptions = markerClusterOptions()
)
The R leaflet package docs mention that clustering is done via a plugin for the Leaflet javascript library called Leaflet.markercluster, documented here. Many functions in the R leaflet package mention that additional arguments in ... will be passed to the underlying javascript libraries, but to use these, it's helpful to know your way around the javascript docs. For clustering, you were right to notice that markerClusterOptions will pass on more advanced options.
The markercluster docs say there's an argument maxClusterRadius:
The maximum radius that a cluster will cover from the central marker (in pixels). Default 80. Decreasing will make more, smaller clusters. You can also use a function that accepts the current map zoom and returns the maximum cluster radius in pixels.
So I messed around with a couple options. Screenshots from my RStudio View pane:
The default radius of 80 pixels:
leaflet(car_burglaries) %>%
addTiles() %>%
addMarkers(
popup = ~paste(as.character(incident_datetime),",","Case number",as.character(case_number)),
label = ~as.character(address_1),
clusterOptions = markerClusterOptions()
)
Smaller radius = more clusters:
leaflet(car_burglaries) %>%
addTiles() %>%
addMarkers(
popup = ~paste(as.character(incident_datetime),",","Case number",as.character(case_number)),
label = ~as.character(address_1),
clusterOptions = markerClusterOptions(maxClusterRadius = 50)
)
Larger radius = fewer clusters:
leaflet(car_burglaries) %>%
addTiles() %>%
addMarkers(
popup = ~paste(as.character(incident_datetime),",","Case number",as.character(case_number)),
label = ~as.character(address_1),
clusterOptions = markerClusterOptions(maxClusterRadius = 200)
)
If you have the default setting of showing the coverage area when hovering over a marker, it should help with figuring out the radius you want.
Worth noting that the radius is measured in pixels as the map is currently being displayed. That means that the number of clusters shown will depend on how big the map is, in pixels. If you need something more complicated, like to set the radius to scale with window size so users can resize the window while keeping the number of clusters approximately constant, you could write a function to pass that radius in as a variable, or possibly write it in javascript and pass it in somehow. I'll leave that for another post.
I want to make a Shiny app where the colouring of a choropleth is based on a numeric value of one of many possible quantitative variables that a user can select from. In simple cases, this is straightforward, but I'm unsure of the best practices when we have 20+ variables, with quite detailed shape files (~2300 polygons).
It might or might not be relevant that the variables might be completely independent to each other such as 'Total Population' or 'Average Temperature' but some of them will have a temporal relationship such as 'Total Population' at 3 or more points in time.
One of the main shapefiles I am using is the ABS Statistical Area 2. Below I give the population density (total population/area) for Australia and a zoomed in view of Sydney to better convey the level of detail I'm interested in.
Australia
Sydney
I have read the shapefile in to R and greatly reduced the complexity/number of points using the ms_simplify() function in the rmapshaper package.
Now as far as Shiny and leaflet go, this is what I have been doing:
Before the server object is defined in server.R, I build a primary map object with all the desired 'layers'. That is, a leaflet with numerous addPolygon() calls to define the colouring of each 'layer' (group).
# Create main map
primary_map <- leaflet() %>%
addProviderTiles(
providers$OpenStreetMap.BlackAndWhite,
options = providerTileOptions(opacity = 0.60)
) %>%
# Layer 0 (blank)
addPolygons(
data = aus_sa2_areas,
group = "blank"
) %>%
# Layer 1
addPolygons(
data = aus_sa2_areas,
fillColor = ~palette_layer_1(aus_sa2_areas$var_1),
smoothFactor = 0.5,
group = "layer_1"
) %>%
...
# Layer N
addPolygons(
data = aus_sa2_areas,
fillColor = ~palette_layer_n(aus_sa2_areas$var_n),
smoothFactor = 0.5,
group = "layer_n"
) %>% ...
All bar the first layer is then hidden using hideGroup() so that the initial rendering of the map doesn't look silly.
hideGroup("layer_1") %>%
hideGroup("layer_2") %>%
...
hideGroup("layer_n")
In the Shiny app, using radio buttons (layer_selection), the user can select the 'layer' they'd like to see. I use observeEvent(input$layer_selection, {}) to watch the status of the radio button options.
To update the plot, I use leafletProxy() and hideGroup() to hide all the groups and then showGroup() to unhide the selected layer.
I apologize for the lack of reproducible example.
Questions
How can I optimise my code? I am eager to make it more performant and/or easy to work with. I've found using hideGroup()'s/showGroup() for each layer selection is far faster than using addPolygon() to a blank map, but this causes the app to take a very significant amount of time to load.
Can I change the variable I am colouring the polygons by, without redrawing or adding those polygons again? To clarify, if I have 2 different variables to plot, both using the same shape data, do I have to do 2 distinct addPolygon() calls?
Is there a more automatic way to sensibly colour the polygons for each layer according to a desired palette (from the viridis package?). Right now I'm finding defining a new palette for each variable, rather cumbersome, eg:
palette_layer_n <- colorNumeric(
palette = "viridis",
domain = aus_sa2_areas$aus_sa2_areas$var_n
)
Side Question
How does this map on the ABS website work? It can be incredibly detailed and yet extremely responsive. Compare the Mesh Block detail to the SA2 (2310 polygons), example below:
Since you haven't gotten any answers yet, I'll post a few things that can maybe help you, based on a simple example.
It would of course be easier if yours was reproducible; and I suppose from looking around you have already seen that there are several related issues / requests (about re-coloring polygons), whereas it doesn't seem that a real solution has made it into any release (of leaflet) yet.
With the below work-around you should be able to avoid multiple addPolygons and can cover an arbitrary number of variables (for now I have just hard-coded a single variable into the modFillCol call though).
library(leaflet)
library(maps)
library(viridis)
mapStates = map("state", fill = TRUE, plot = FALSE)
# regarding Question 3 - the way you set the domain it looks equivalent
# to just not setting it up front, i.e. domain = NULL
myPalette <- colorNumeric(
palette = "viridis",
domain = NULL
)
mp <- leaflet(data = mapStates) %>%
addTiles() %>%
addPolygons(fillColor = topo.colors(10, alpha = NULL), stroke = FALSE)
# utility function to change fill color
modFillCol <- function(x, var_x) {
cls <- lapply(x$x$calls, function(cl) {
if (cl$method == "addPolygons") {
cl$args[[4]]$fillColor <- myPalette(var_x)
}
cl
})
x$x$calls <- cls
x
}
# modify fill color depending on the variable, in this simple example
# I just use the number of characters of the state-names
mp %>%
modFillCol(nchar(mapStates$names))
I have recently discovered the r package “leaflet” and found a great blog with some basic instruction for creating an interactive map (found here) http://mhermans.net/hiking-gpx-r-leaflet.html.
I have not, however been successful at adding a scale bar on the map. That is, I’d like to add a graphic feature that scales distance as you zoom in and out of the map (e.g. a bar at the bottom of the map that represents 1km). The leaflet site (found here) http://leafletjs.com/reference.html#control-scale-l.control.scale discusses this feature: L.control,scale().
Does anyone know how to add a scale bar??
This is the code for my map so far (The “Add Scale Bar” Does NOT work):
# A map of Track data
Mymap <- leaflet() %>% addTiles() %>%
addPolylines(data=Dofu1) %>%
addPolylines(data=Zak1) %>%
addProviderTiles("Esri.WorldImagery")
# Add a legend
Mymap %>%
addLegend(position = 'topright',
colors = "blue",
labels = 'Buruku Tracks', opacity = 0.5,
title = '')
# Add a Scale Bar
Mymap %>%
addControl(Mymap, "Scale",
position = c("topright"),
layerID = NULL,
className = "Scale",
data - getMapData(Mymap))
This feature has been added to the development version of the leaflet package. See Add support for scale bar. Also, the second argument to addControl expects html as either a character string or html generated from Shiny or htmltools tags. Finally, I think you have a typo in addControl: data - getMapData(Mymap) should be data = getMapData(Mymap)
the scale bar function is implemented and can be added with 'addScaleBar'
leaflet() %>%
addTiles() %>%
addScaleBar()
I would like to ask some help with regard to the leaflet package. When you draw an interactive map, you can do something like this.
library(leaflet)
library(magrittr)
m <- leaflet() %>%
setView(lng = -71.0589, lat = 42.3601, zoom = 8) %>%
addTiles()
m
If you want to add a third-party tile, you can do that too. The following link offers options for third-party tiles (http://leaflet-extras.github.io/leaflet-providers/preview/index.html) The following image is created with OpenWeatherMap.Precipitation.
### They work
m %>% addProviderTiles("MtbMap")
m %>% addProviderTiles("HikeBike.HikeBike")
m %>% addProviderTiles("OpenWeatherMap.Precipitation")
Some of the tile options in the link include tiles by NASA. I wanted to use one of them. So I tried the following codes. None of them unfortunately worked.
### The default map appears, then a black layer appears on top of the default layer.
m %>% addProviderTiles("NASAGIBS.ModisTerraTRUEColorCR")
m %>% addProviderTiles("NASAGIBS.ModisTerraBands367CR")
The only option which is working is the following.
m %>% addProviderTiles("NASAGIBS.ViirsEarthAtNight2012")
My next attempt was to use custom URL template with addTiles(). The URL is from the link above. But, this was not successful either; no error message appeared, but no change in tile.
m %>%addTiles(urlTemplate = "http://map1.vis.earthdata.nasa.gov/wmts-webmerc/MODIS_Terra_CorrectedReflectance_Bands367/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}",
tileOptions(minZoom = 1, maxZoom = 8))
My final attempt was the following. This showed the default map, but an additional tile did not appear either.
leaflet() %>%
addTiles() %>%
setView(lng = -71.0589, lat = 42.3601, zoom = 8) %>%
addTiles(urlTemplate = "http://map1.vis.earthdata.nasa.gov/wmts-webmerc/MODIS_Terra_CorrectedReflectance_Bands367/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}",
tileOptions(minZoom = 1, maxZoom = 8))
My question is whether this is a potential bug specifically with NASA tiles. Alternatively, what revision do I need in these scripts? Thank you for your help in advance.
UPDATE
I found a website which uses same NASA tiles. I specified NASAGIBS.ModisTerraTRUEColorCR and got the following image. The image is showing how a mail traveled from the US to Sweden. As you see, there is no image for both US and Europe. I think this could be the reason why I saw a black tile. I would like to know if anybody knows some details of NASA tiles. I chose the area which I can see the NASA image. But, I had no luck.
### I expected to see Japan area this time.
foo <- leaflet() %>%
setView(lng = 137.37, lat = 35.93, zoom = 5) %>%
addTiles()
foo %>% addProviderTiles("NASAGIBS.ModisTerraTRUEColorCR")
UPDATE 2
Today, I gave one more shot. At this moment, I managed to get the following image. I zoomed out a bit when I captured it. In UPDATE, I provided a map which you cannot see the US and Europe. In the new image, you see West coast of the States is in black. Given all observations, it seems to me that one may not get NASA images of a location all the time. Depending on when you ask NASA tiles, you may/may not have an image you want.
m <- leaflet() %>%
setView(lng = -71.0589, lat = 42.3601, zoom = 8) %>%
addTiles()
m %>% addProviderTiles("NASAGIBS.ModisTerraBands367CR")
Your final conclusion is correct: depending on what location you request imagery for and on the time of the request, the satellite may or may not yet have acquired the image. So you may get an actual image or just an empty one. (This is also stated in the GIBS API documentation.)
However, you can specify what day to request the image for via the 'time' option for addProviderTiles(). By specifying a date in the near past, you can get non-empty images for all locations if that is what you prefer.
This is the syntax:
> library(leaflet)
> library(magrittr)
> m <- leaflet() %>%
setView(lng = 4.5, lat = 51, zoom = 1) %>%
addTiles() %>%
addProviderTiles("NASAGIBS.ModisTerraTrueColorCR",
options = providerTileOptions(time = "2015-08-31", opacity = 0.5))
> m
At the time of writing (2015-08-31) I get this result:
It's cloudy in England, who would have guessed?
Most data happens to be there already, but there is no imagery for Alaska yet. If on the other hand, I specify yesterday's date
options = providerTileOptions(time = "2015-08-30", opacity = 0.5)
we get the full image:
Finally, the reason why
m %>% addProviderTiles("NASAGIBS.ModisTerraTRUEColorCR")
didn't work was probably because of a typo. It should be
m %>% addProviderTiles("NASAGIBS.ModisTerraTrueColorCR")