r-spatial / mapview

Interactive viewing of spatial data in R
https://r-spatial.github.io/mapview/
GNU General Public License v3.0
515 stars 91 forks source link

Quick query - color table #208

Closed rsbivand closed 5 years ago

rsbivand commented 5 years ago

Is there a way of using a color table such as the csv file with RGB, land cover category codes and names in this zip archive:

LUC3.zip

to register against the values in a raster/SpatialGrid? By default, the raster layer and SGDF methods treat the values as continuous, is there a way around? The archive also includes a small subset of three annual land cover maps from LandGIS, using the codes.

rsbivand commented 5 years ago

@edzer - is colour table support feasible in the stars setting?

tim-salabim commented 5 years ago

@rsbivand currently not possible in mapview. It would be nice to have though. In case you have any ideas on/experience with how to implement such a thing, I'd love to hear them. I'll have a look at the file you link on the weekend

edzer commented 5 years ago

Like in:

library(stars)
luc = read_stars("LUC3.tif")
rgb = read.csv("ESACCI-LC-Legend.csv", header = TRUE, sep = ";")
plot(luc, rgb = rgb, axes = TRUE)

x

edzer commented 5 years ago

So the current hack simply assumes that the rgb table is as the one supplied here: col 1 index, col 2 label, col 3-5 R, G B. Is there some precedence (raster?) on how to do this more flexibly? Use named columns? allow for alpha? cater for the case where there are no labels? etc.

Also: no legend yet...

rsbivand commented 5 years ago

There was code in rgdal for reading colour tables from raster files, but matching integer codes to the colour tables was tricky. GRASS has a list of colour tables to apply to raster layers. The data then look like factors, with the colours as levels, to avoid replication. But from stars it seems to be available (@edzer posted while I was thinking. In rgdal it uses the GDALColorTableH ctab = GDALCreateColorTable(GPI_RGB) and similar.

edzer commented 5 years ago

Does ggspatial deal with color tables, and if yes, how @paleolimbot?

rsbivand commented 5 years ago

Which stars version? See also https://github.com/bergen-ml/2019-02-19-bivand - I'm subsetting from the file downloaded from the link to LandGIS ... https://storage.googleapis.com/cci-lc-v207/ESACCI-LC-L4-LCCS-Map-300m-P1Y-1992_2015-v2.0.7.zip

paleolimbot commented 5 years ago

I don't do anything with color tables but should...I'd like anything that is supposed to be an image display like one without any user input. It would be lovely if the details were implemented somewhere else, since I know little about this...

rsbivand commented 5 years ago

OK, wrong subsetting in my code, works now with stars master.

edzer commented 5 years ago

Looks like the downsampling algorithm doesn't work well, yet: x

tim-salabim commented 5 years ago

@rsbivand the following works but needs a little work to set up the color lookup properly:

library(mapview)
library(raster)

rst = raster("/home/timpanse/Downloads/LUC3/LUC3.tif")
rst = as.factor(rst)
leg = read.csv2("/home/timpanse/Downloads/LUC3/ESACCI-LC-Legend.csv")

hex = grDevices::rgb(leg$R, leg$G, leg$B, maxColorValue = 255, names = leg$NB_LAB)

# we need to match the hex colors that actually exist in the data!
hex = hex[names(hex) %in% as.character(levels(rst)[[1]]$ID)]

mapview(rst, col.regions = hex)

screenshot from 2019-02-16 12-01-23

The crucial bit is to a) convert the raster to a factor layer and b) to make sure the color vector only contains values actually present in the layer.

rsbivand commented 5 years ago

Thanks, I needed to stack after as.factor for the factors to feed through for input bands. Optically, the key looks off also because of the alpha=0.8 default - can alpha for the key be adjusted to match the image?

tim-salabim commented 5 years ago

For me it works without the call to stack.

Regarding the key, opacity is hardwired to 1 for legends, see https://github.com/r-spatial/mapview/blob/develop/R/legend.R#L321. If you set layer opacity to 1 via

mapview(rst, col.regions = hex, alpha.regions = 1)

the colors should match (I checked with Gpick)

Should we set legend opacity to the same value as layer opacity ba default?

rsbivand commented 5 years ago

Maybe matching the key alpha might make sense (do you have contacts to set up some tests - like click on objects matching key values for matched and unmatched key alpha? Group of cartography students maybe?).

I needed multiple input bands to be able to toggle to contrast LU/LC changes.

tim-salabim commented 5 years ago

Unfortunately I've left academia so access to free work-force is limited now :-)

Colour values of map and legend can only ever match when alpha is set to 1, as otherwise the values will depend on what is located on the basemap underneath the layer region of a certain colour vs. what is underneath the legend entry of that same colour. This will be "not so bad" for the default grey background map with moderate alpha value, but can be significant when using low alpha on top of a colourful map background like the satellite view or openstreetmap. This is why I have so far opted to at least keep the legend at alpha = 1.

I fear there is no ideal solution here. Ideally, I'd love to bind layer and legend opacity to a mouse gesture such as click and hold Alt-key and use mouse wheel to increase/decrease opacity (which, apart from being a little out of my javascript league, has other implications, e.g. working with a touchpad).

Just thinking out loud...

tim-salabim commented 5 years ago

I found leaflet.opacity by @be-marc which lets us add an opacity slider to a leaflet map. Although it is currently not very customisable (e.g. you currently cannot control slider placement), it may be useful for demonstration purposes.

library(mapview)
library(raster)
library(leaflet.opacity)
library(leaflet)

rst = raster("/home/timpanse/Downloads/LUC3/LUC3.tif")
rst = as.factor(rst)
leg = read.csv2("/home/timpanse/Downloads/LUC3/ESACCI-LC-Legend.csv")

hex = grDevices::rgb(leg$R, leg$G, leg$B, maxColorValue = 255, names = leg$NB_LAB)

# we need to match the hex colors that actually exist in the data!
hex = hex[names(hex) %in% as.character(levels(rst)[[1]]$ID)]

mapview(rst, col.regions = hex, alpha.regions = 1)@map %>%
  addOpacitySlider("rst")

peek 2019-02-17 11-32

@be-marc do you think we could expand functionality of leaflet.opacity so that we can

?

Also, does it also work for vector layers? Can we use it with multiple layers?

tim-salabim commented 5 years ago

Ok, so I've had some success to enable opacity control via key strokes. This gist enables the use of keys 'o' (opaque) and 't' (transparent) to increase/decrease the layer opacity without adding a slider to the map.

Even though this sidetracked quite a bit, I am grateful for what this issue has turned out to be. Thanks @rsbivand for sending me down this rabbit hole :-)

I think we can close this issue here. Feel free to reopen if you disagree. Anything further related to opacity controls in mapview should be opened in a new dedicated issue.