mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.22k stars 2.23k forks source link

Add "raster-colorize" property #3889

Closed cebartling closed 10 months ago

cebartling commented 7 years ago

Motivation

Mapbox GL JS uses raster and vector tilesets as core pieces of making maps visible in the browser and relies heavily on both raster and vector tilesets to keep their maps fast and efficient. Our company will mostly use raster-based tilesets. Raster tilesets are created when raster images, in TIFF and GeoTIFF format, are uploaded and processed by Mapbox Studio. Our company currently uploads various types of mosaic raster images into Mapbox Studio for creating raster tilesets that can be layered onto maps rendered in the Mapbox GL JS library. Mapbox itself provides their satellite map imagery as a raster tileset.

Our company has some more complex requirements where certain raster tilesets will need to be colorized on the client-side using a data-driven approach. This colorization process needs to be performed client-side as the colorization scheme is manipulated by our users in our web UI. Therefore, our developers will need to be able to hook into the rendering pipeline of the Mapbox GL JS library and provide colorization information to the fragment shader used to transform raster image pixel color.

Design Alternatives

This need for client-side, data-driven rendering hooks has been cited by other customers. Ubilabs wrote a blog post about how they built this feature into a forked version of Mapbox GL JS. In that writeup, they build a colorization lookup texture in JavaScript that maps values in the range of 0 to 255 to a unique color. This colorization lookup texture is then passed to a custom fragment shader that transforms each pixel value of an input raster tile to a color mapped by the generated colorization lookup texture. The input raster tile is grayscale, thus locking all color channels, so only the red channel is used to determine pixel value. Those pixel values can range from 0 to 255.

This is exactly the solution our company needs for our colorization of mosaic images layered on the map. The downside of the Ubilabs solution is that they forked Mapbox GL JS to get their solution to work. Our company would like to avoid having to fork the Mapbox GL JS module to get this functionality.

lucaswoj commented 7 years ago

@cebartling Could you clarify what you mean by "client-side, data-driven rendering hooks" with an API mock up? Would a data-driven raster-colorize property meet your use case?

cebartling commented 7 years ago

@lucaswoj This blog post https://www.mapbox.com/blog/tilemill-raster-colorizer/ is very similar to what I would like to do with the Mapbox GL JS component. I like TileMill's raster-colorizer style API-it fits very well with what we're doing:

{
  raster-opacity:1;
  raster-colorizer-default-mode: linear;
  raster-colorizer-default-color: transparent;
  raster-colorizer-stops:
    stop(0,#47443e)
    stop(50, #77654a)
    stop(100, rgb(85,107,50))
    stop(200, rgb(187, 187, 120))
    stop(255, rgb(217,222,170));
}

A raster-colorize property on the map layer could be set with this style to colorize the raster image tiles. Setting the property would trigger a re-colorization and render of the raster image. Does that make sense?

averas commented 7 years ago

Interesting! Openlayers 3 has similar hooks to perform pixelwise operations on raster sources pre-rendering:

https://openlayers.org/en/latest/examples/raster.html https://openlayers.org/en/latest/examples/shaded-relief.html https://openlayers.org/en/latest/examples/region-growing.html

lucaswoj commented 7 years ago

That makes sense @cebartling. Thanks! I'm retitling this issue to focus discussion on the idea of a "raster-colorize" style spec property.

This might dovetail nicely with https://github.com/mapbox/mapbox-gl-js/issues/3605.

cebartling commented 7 years ago

@lucaswoj Great! Thanks for engaging on the enhancement request so quickly.

andrewharvey commented 7 years ago

The style spec issue for this is at https://github.com/mapbox/mapbox-gl-style-spec/issues/476

Scarysize commented 7 years ago

Nice to see a growing interest on the subject. Of course we (Ubilabs) would love to have this feature in mapbox-gl-js; we would still be maintaining our own fork (mainly for data-driven sdf icons), but this would lead to one thing less to worry about. I'm excited for your implementation! If you have questions about ours, we would love to help!

stevage commented 7 years ago

This would be really cool. I'm really hoping to port cycletour.org from Tilemill to Mapbox-GL-JS one of these days, and this (and slope shading) is pretty much the big blocker. There's really no equivalent in GL-JS. (There are equivalents for slope shading, but they're a fairly poor substitute IMHO.)

With this, some generous company could host elevation raster tiles for the whole world, and everyone could re-colorise them as they see fit.

averas commented 7 years ago

@stevage Some great company is already doing so ... https://www.mapbox.com/blog/terrain-rgb/

stevage commented 7 years ago

Oh nice! :)

Another example of client-side recoloring code: TerriaJS

cebartling commented 7 years ago

@lucaswoj Do you have a timetable in place yet for providing this feature. We're trying to do some planning here at our company, and depending on when this feature may show up natively in Mapbox GL JS, we may need to resort to a plan B implementation of a product feature.

lucaswoj commented 7 years ago

@cebartling per our public roadmap, this feature is not slated for implementation in the next few months. Your options:

lbud commented 7 years ago

The new canvas source type allows for integration with external raster manipulation. cc @lbud

Right — the canvas copies image data directly from an HTML canvas onto the map (the same mechanism used for image and video sources), so the canvas can be colorized as desired. Here's an example that reads pixels from PNGs onto a canvas and attaches that to the map, colorizing as defined by the user (takes a few seconds to load all the images): https://bl.ocks.org/lbud/ee919cb9cf265c635e2adc899b65dbbb

I've played around with reading data from a tiled raster source already added to Mapbox GL (reading pixels from webgl tile textures, placing them into a subset of a fullscreen canvas, colorizing as desired and copying back to the map) but the amount of roundtripping combined with the complications of accessing raster tiles' webgl textures makes this approach more or less unusable. To do this "right" (tiled raster layers with color manipulation) would require either aforementioned raster-colorizer property or a custom layer type (https://github.com/mapbox/mapbox-gl-js/issues/281).

cebartling commented 7 years ago

Thanks for the information @lbud! I believe we're leaning towards a spike solution on the canvas source.

sensoarltd commented 7 years ago

Any updates on the release of this feature?

SiggyF commented 6 years ago

If I understand this issue correct, the implementation would consist of the following:

andrewharvey commented 6 years ago

@SiggyF That plan looks okay, but I'm not confident enough to comment. Would be very keen to see a PR implementing this!

The only thing I'd add is it'd be great to have it working on raster-dem sources to enable #6245.

sk- commented 6 years ago

Note that openlayers Raster source is more powerful as it allows to process the whole image at once, not only mapping pixels. This allows you to use the elevation data to compute the slope, which can be used to generate hill shading as in one of the linked examples or to render whole areas with a slope over certain threshold, useful for mountain sports.

stevage commented 5 years ago

Just discovered that you can do a reasonable workaround using Mapbox Street's "contours" layer as a fill, and interpolating color across elevations. Sounds crazy, but it actually looks ok!

screenshot 2019-01-08 16 27 00 screenshot 2019-01-08 16 46 32

Downsides include:

shobhitg commented 5 years ago

Interesting! Openlayers 3 has similar hooks to perform pixelwise operations on raster sources pre-rendering:

https://openlayers.org/en/latest/examples/raster.html https://openlayers.org/en/latest/examples/shaded-relief.html https://openlayers.org/en/latest/examples/region-growing.html

If implemented, this pixelwise operation is exactly what would help in #8080.

shobhitg commented 5 years ago

Is there any progress on raster-colorize?

If there is a mock implementation of raster-colorize, and if I can the same results as raster-stretch (#8587) then I am interested in utilizing that branch.

andrewharvey commented 5 years ago

Is there any progress on raster-colorize?

No, it's open for anyone who wants to implement it.

shobhitg commented 5 years ago

{ raster-opacity:1; raster-colorizer-default-mode: linear; raster-colorizer-default-color: transparent; raster-colorizer-stops: stop(0,#47443e) stop(50, #77654a) stop(100, rgb(85,107,50)) stop(200, rgb(187, 187, 120)) stop(255, rgb(217,222,170)); }

Just saying that like most settings in Mapbox, and in the spirit of OpenGL, stops should be ranging from 0 to 1 instead of 0 to 255. like...

{
  raster-opacity:1;
  raster-colorizer-default-mode: linear;
  raster-colorizer-default-color: transparent;
  raster-colorizer-stops:
    stop(0,#47443e)
    stop(0.2, #77654a)
    stop(0.4, rgb(85,107,50))
    stop(0.6, rgb(187, 187, 120))
    stop(1, rgb(217,222,170));
}
andrewharvey commented 5 years ago

Just saying that like most settings in Mapbox, and in the spirit of OpenGL, stops should be ranging from 0 to 1 instead of 0 to 255.

Those stops are dependent on your source data, so 8 bit bands would range 0-255, 16 bit 0-65,535 etc.

I'm in favour of this proposed feature working with stops from the source data, since you need to support palatted, ie. value 0 is red, value 1 is purple, value 2 is yellow etc. which wouldn't work if you rescale to 0-1.

shobhitg commented 5 years ago

Can someone clarify what raster-colorizer-default-color: transparent; means specifically in terms of color ramp (visual examples would be appreciated)?

Also it would be awesome if someone could try out in TileMill and post a sample grayscale terrain image file, and a color ramp info, and the exact expected outcome image. It would be good to have this info in order to test any possible solutions.

andrewharvey commented 5 years ago

Can someone clarify what raster-colorizer-default-color: transparent; means specifically in terms of color ramp (visual examples would be appreciated)?

If you're using a mode/interpolation value of exact, and your source data has a value which doesn't exactly match one of the steps, then it would use the default color to fill that.

I'd suggest if you are planning on implementing this, to put together a design document with the proposed style spec syntax first. You don't have to, but it would help.

shobhitg commented 5 years ago

Those stops are dependent on your source data, so 8 bit bands would range 0-255, 16 bit 0-65,535 etc.

I'm in favour of this proposed feature working with stops from the source data, since you need to support palatted, ie. value 0 is red, value 1 is purple, value 2 is yellow etc. which wouldn't work if you rescale to 0-1.

Other than supporting paletted colors this doesn't make much sense to me. A color ramp is easier to reason about when thinking between 0 to 1 instead of 0 to 255. Paletted colorization isn't something that needs to be supported. Does TileMill's color rasterizer support it?

If you're using a mode/interpolation value of exact, and your source data has a value which doesn't exactly match one of the steps, then it would use the default color to fill that.

raster-colorizer-default-color option feels unnecessary to me because transparent is default behavior, and in theory any other different color can be specified via color ramp using a stop value of 0 or 1 for leading or trailing edges of the color ramp.

andrewharvey commented 5 years ago

Other than supporting paletted colors this doesn't make much sense to me. A color ramp is easier to reason about when thinking between 0 to 1 instead of 0 to 255. Paletted colorization isn't something that needs to be supported. Does TileMill's color rasterizer support it?

You're right that it makes the most sense for paletted colors (which is common for landcover datasets: 0 is water, 1 is trees, 2 is sand etc), but even for other use cases it still makes sense to think about the input classes or steps to be in native raster values (not 0-1). For example working with a DEM, values are typically in meters above sea level, and its much easier to think about it that way than 0-1.

raster-colorizer-default-color option feels unnecessary to me because transparent is default behavior, and in theory any other different color can be specified via color ramp using a stop value of 0 or 1 for leading or trailing edges of the color ramp.

Yeah you could always just specify those other default values, but having a default value would make it simpler.

I'm not suggesting we copy raster-colorizer from CartoCSS just for the sake of it, I think it's fair to determine from scratch what exactly we want to support in GL JS and what that syntax would be.

I think the first step should be to try to document what the feature would look like and the syntax for the style spec, in terms of layers, sources, paint and layout properties.

elfmanryan commented 5 years ago

You're right that it makes the most sense for paletted colors (which is common for landcover datasets: 0 is water, 1 is trees, 2 is sand etc), but even for other use cases it still makes sense to think about the input classes or steps to be in native raster values (not 0-1). For example working with a DEM, values are typically in meters above sea level, and its much easier to think about it that way than 0-1.

I would second that using the real pixel values is important for a couple of reasons. Firstly, the two main GIS software packages (ArcGIS and QGIS + GDAL) scale using ramps and palettes built off pixel values. This is what many users are familiar with for a start, but also means that if someone wants to replicate a colour scheme it will be straightforward if the colour stops are also built of the inherent raster values in the same context; conversely, using 0-1 scaling will force a user to try and rescale their colour scheme to match! this will cause no ends of of frustration and compromise. Secondly, continuous data (e.g. float) unlike class/cardinal data (e.g. int) often represents real world values, such as tC/m2, or density of people etc. So when it comes to colouring, users will have natural stops and thresholds in mind that are real world specific.

With respect to stops; perhaps a good GIS precedent is gdal; te specific tool is gdal_dem (dont be mislead by the term dem, it colourises any raster continuous, classified outwith terrain); and uses a table of stops, but later releases allowed to-from rgb stops aswell. An example: https://gis.stackexchange.com/questions/308458/colorize-singleband-geotiff-raster-using-python-gdal-with-discrete-interpolation

dazza-codes commented 5 years ago

Nice to see a growing interest on the subject. Of course we (Ubilabs) would love to have this feature in mapbox-gl-js; we would still be maintaining our own fork (mainly for data-driven sdf icons), but this would lead to one thing less to worry about. I'm excited for your implementation! If you have questions about ours, we would love to help!

korria commented 3 years ago

This would be a cool addition! Especially being able to colorize grayscale raster tiles.

korria commented 3 years ago

This would be a cool addition! Especially being able to colorize grayscale raster tiles.

Any update on this?

devgioele commented 2 years ago

Any updates? This feature would be truly awesome!

rreusser commented 2 years ago

Based on the above discussion, I've implemented a first cut of raster-color in https://github.com/mapbox/mapbox-gl-js/pull/12368. It's not set in stone, so any and all feedback is very much welcome!

ndroo commented 2 years ago

I have nothing productive to add other than i desperately want this and thank you for doing it! Excited for when it gets released.

rreusser commented 2 years ago

@ndroo Glad to hear it! I really want this as well! v2.11.0-beta.2 was released five days ago which means features are frozen for 2.11. Thus this will likely make it into 2.12 early in the new year, with studio support to follow.

ZehavaSayyes commented 1 year ago

Any updates? Will this feature be included in a release soon?

timbtimbtimb commented 10 months ago

Any news on this?

mourner commented 10 months ago

This was actually released as a part of GL JS v3 — see the changelog here: https://github.com/mapbox/mapbox-gl-js/releases/tag/v3.0.0 The docs for the new properties (raster-color, raster-color-mix, raster-color-range) are here: https://docs.mapbox.com/style-spec/reference/layers/#paint-raster-raster-color For more information on how the feature is designed, see #12368

@rreusser @underoot btw, would be really nice to add an example of the feature on our examples page.

stevage commented 9 months ago

This is very exciting.

Following up from my comment 7 years ago, here's a fairly quick and dirty attempt to recreate the style I was talking about (originally created in Tilemill, just over 10 years ago now).

image

(Mapbox GL JS on the left)