Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add changeColors() for changing the color of a map layer on the fly #67

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

dfriend21
Copy link
Contributor

This PR adds a function to re-map the colors of a leaflet layer on the fly. I had two closely related reasons for creating it:

  1. I had generated some map tiles and wanted the ability to easily change the color scheme without having to regenerate them all (as they took a long time to generate)
  2. I wanted the ability to be able to change the colors of tiles added via WMS

It uses the gradientmaps JavaScript library to change the colors. For an example, here's a WMS layer as-is:

Screen Shot 2023-05-20 at 7 39 57 PM

And here I've changed the colors using the changeColors() function:

Screen Shot 2023-05-20 at 7 40 27 PM

Here's the code to recreate the previous example:

l <- leaflet() %>%
  setView(-112.56, 40.66, 5) %>%
  addTiles() %>%
  addWMSTiles("https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Bare_Ground_Shrubland_Fractional_Component/ows?SERVICE=WMS&",
              layers = "NLCD_2016_Bare_Ground_Shrubland_Fractional_Component",
              options = WMSTileOptions(className = "bare_ground", transparent = TRUE, format = "image/png"))
l
l %>% changeColors("bare_ground", terrain.colors(20))

I thought it might be a good fit for the leafem package, and it seems like something that could be useful for others as well. And from a more selfish point of view, it'd be nice for me if it was part of an already-existing package rather than its own tiny package, which is how I have it right now.

No worries if you'd rather not include it in the package. Also, I'm happy to make any changes to the code and/or documentation.

@dfriend21 dfriend21 changed the title Add change colors Add changeColors() for changing the color of a map layer on the fly May 21, 2023
@tim-salabim
Copy link
Member

Hi @dfriend21,
sorry for the long silence. This is quite an interesting feature! There are, however, a couple of things I don't quite understand (yet):

  1. how do we know how many colors we need to supply so that the map doesn't look awful? This applies to both baseLayers (when applying changeColor() to e.g. addTiles()) and overLayLayers such as the WMS example you give above (supplying 2000 instead of 20 terrain.colors makes the WMS layer appear all white)
l <- leaflet() %>%
  setView(-112.56, 40.66, 5) %>%
  addProviderTiles("CartoDB.DarkMatter") %>%
  addWMSTiles("https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2016_Bare_Ground_Shrubland_Fractional_Component/ows?SERVICE=WMS&",
              layers = "NLCD_2016_Bare_Ground_Shrubland_Fractional_Component",
              options = WMSTileOptions(className = "bare_ground", transparent = TRUE, format = "image/png"))
l

image

l %>% changeColors("bare_ground", terrain.colors(2000))

image

  1. what about the legends, if e.g. we want to apply this change to an addRasterImage() layer?
library(raster)
r <- raster(xmn = -2.8, xmx = -2.79, ymn = 54.04, ymx = 54.05, nrows = 30, ncols = 30)
values(r) <- matrix(1:900, nrow(r), ncol(r), byrow = TRUE)
crs(r) <- CRS("+init=epsg:4326")

pal <- colorNumeric("Spectral", domain = c(0, 1000))
leaflet() %>% addTiles() %>%
  addRasterImage(r, colors = pal, opacity = 0.8, options = tileOptions(className = "base")) %>%
  addLegend(pal = pal, values = c(0, 1000))

image

leaflet() %>% addTiles() %>%
  addRasterImage(r, colors = pal, opacity = 0.8, options = tileOptions(className = "base")) %>%
  addLegend(pal = pal, values = c(0, 1000)) |>
  changeColors("base", colorRampPalette(RColorBrewer::brewer.pal(11, "Blues"))(50))

image

Is there any way to infer/clarify the number of colors for 1.?
And is there a way to have the legend change according to the layer for 2.?

@dfriend21
Copy link
Contributor Author

Hi @tim-salabim,
Thanks for finding that bug with the number of colors - I hadn't noticed that. It appears to occur when there are 202 or more elements in the colors vector. The problem is in the underlying gradientmaps javascript code - I might be able to dig through that code and figure out exactly what's causing the problem, but for now I've modified changeColors() to resample the color vector to 201 colors if there are more than 201 colors - that way the problem is avoided.

I made some changes to allow for changeColors() to work with a legend. To make it work, you need to supply a class to the legend via the className parameter of addLegend() and then add an additional call to changeColors() with legend = TRUE:

r <- raster::raster(xmn = -2.8, xmx = -2.79, ymn = 54.04, ymx = 54.05, 
                    nrows = 30, ncols = 30, crs = "EPSG:4326", vals = 1:900)
old_pal <- colorNumeric(topo.colors(50), c(0, 1000))
new_pal <- heat.colors(50)
leaflet() |>
  addTiles() |>
  addRasterImage(r, colors = old_pal, opacity = 0.8,
                 options = tileOptions(className = "base")) |>
  addLegend(pal = old_pal, values = c(0, 1000),
            className = "info legend base-legend") |>
  changeColors("base", new_pal) |>
  changeColors("base-legend", new_pal, legend = TRUE)
Screenshot 2024-11-23 at 9 49 19 PM

It's a bit clunky, but I couldn't find a more streamlined way to do it.

Also, I've made some changes and bug fixes to this function in the time since I made the pull request, so I added those changes in there (for example, the original version didn't work with leafletProxy(), and now it does.).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants