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

Raster colorization via "raster-color" #12368

Closed
wants to merge 14 commits into from
310 changes: 310 additions & 0 deletions debug/raster-color.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../dist/mapbox-gl.css" />
<style>
body { margin: 0; padding: 0; }
html, body { height: 100%; }
#map { height: calc(100% - 40px); }
#controls {
position: fixed;
z-index: 1;
top: 5px;
left: 5px;
}
#gradientbg, #gradient {
position: fixed;
bottom: 0;
width: 100%;
height: 40px;
}
#gradientbg {
z-index: 1;
background: repeating-conic-gradient(#ccc 0% 25%, white 0% 50%) 50% / 20px 20px;
}
#gradient {
transition: all 0.3s ease-in-out;
}
</style>
</head>

<body>
<div id="map"></div>
<div id="controls"></div>
<div id="gradientbg"><div id="gradient"></div></div>

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source

Script loaded from content delivery network with no integrity check.
<script type="text/javascript" src="https://unpkg.com/d3@7.6.1/dist/d3.min.js"></script>
<script src="../dist/mapbox-gl-dev.js"></script>
<script src="../debug/access_token_generated.js"></script>
<script>

/*global d3*/

const presets = {
Satellite: {
tileset: "mapbox.satellite",
rasterColor: "Cubehelix",
rasterColorMix: "luminosity",
rasterColorRange: "[0, 1]"
},
Terrain: {
tileset: "mapbox.terrain-rgb",
rasterColor: "Hypsometric",
rasterColorMix: "decode terrain-rgb",
rasterColorRange: "[0, 8848]",
forcePngraw: true
},
"Population log-density": {
tileset: "rreusser.796swou6 (pop log-density)",
rasterColor: "PopDensity",
rasterColorMix: "decode population log-density",
rasterColorRange: "[-3, 4]",
forbid2x: true,
forcePngraw: true
},
'Clouds (GMGSI LWIR)': {
tileset: "rreusser.dv2uv1dw (GMGSI clouds)",
rasterColor: "Transparent white",
rasterColorMix: "luminosity",
rasterColorRange: "[0, 1]"
},
'Radar composite reflectivity (GFS)': {
tileset: "rreusser.b3c8wj9i (GFS refc)",
rasterColor: "Radar",
rasterColorMix: "luminosity",
rasterColorRange: "[0, 1]"
},
'Radar composite reflectivity (HRRR)': {
tileset: "rreusser.5vzfcdjn (HRRR refc)",
rasterColor: "Radar",
rasterColorMix: "luminosity",
rasterColorRange: "[0, 1]"
},
};

function GUIParams () {
this.preset = "Satellite";
this.opacity = 1;
Object.assign(this, presets[this.preset]);
}

const guiParams = new GUIParams();

const mixes = {
luminosity: [ 0.2126, 0.7152, 0.0722, 0 ],
average: [1 / 3, 1 / 3, 1 / 3, 0],
red: [1, 0, 0, 0],
green: [0, 1, 0, 0],
blue: [0, 0, 1, 0],
"decode terrain-rgb": [
256 * 256 * 256 * 0.1,
256 * 256 * 0.1,
256 * 0.1,
-10000
],
"decode population log-density": [
256 * 256 * 256 * 8.348993603394451e-07,
256 * 256 * 8.348993603394451e-07,
256 * 8.348993603394451e-07,
-9
],
};

const ranges = {
"[0, 1]": [0, 1],
"[0, 8848]": [0, 8848],
"[-3, 4]": [-3, 4],
};

const tilesets = {
"mapbox.terrain-rgb": "mapbox.terrain-rgb",
"mapbox.satellite": "mapbox.satellite",
"rreusser.796swou6 (pop log-density)": "rreusser.796swou6",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Included the sake of illustration and debugging, but we should probably remove tilesets on my account from the final PR.

"rreusser.dv2uv1dw (GMGSI clouds)": "rreusser.dv2uv1dw",
"rreusser.5vzfcdjn (HRRR refc)": "rreusser.5vzfcdjn",
"rreusser.b3c8wj9i (GFS refc)": "rreusser.b3c8wj9i",
};

const quantize = (interpolator, opac = x => 1, n = 100) => d3.quantize(interpolator, n).map((c, i) => {
const col = d3.rgb(c);
const t = i / (n - 1);
return [ t, `rgba(${col.r},${col.g},${col.b},${col.opacity * opac(t)})` ];
});

const colorScales = {
Viridis: quantize(d3.interpolateViridis),
Greys: quantize(x => d3.interpolateGreys(1 - x)),
Turbo: quantize(d3.interpolateTurbo),
Inferno: quantize(d3.interpolateInferno),
Magma: quantize(d3.interpolateMagma),
Plasma: quantize(d3.interpolatePlasma),
Cividis: quantize(d3.interpolateCividis),
Cool: quantize(d3.interpolateCool),
Warm: quantize(d3.interpolateWarm),
Cubehelix: quantize(d3.interpolateCubehelixDefault),
RdPu: quantize(x => d3.interpolateRdPu(1 - x)),
YlGnBu: quantize(x => d3.interpolateYlGnBu(1 - x)),
Rainbow: quantize(d3.interpolateRainbow),
Sinebow: quantize(d3.interpolateSinebow),
RdBu: quantize(d3.interpolateRdBu),
PopDensity: quantize(d3.interpolateMagma, t => 3 * t * t - 2 * t * t * t),
Hypsometric: [
[ 0.004, "rgb(48, 167, 228)" ],
[ 0.005, "rgb(57, 143, 83)" ],
[ 0.031, "rgb(116, 166, 129)" ],
[ 0.055, "rgb(178, 205, 174)" ],
[ 0.076, "rgb(188, 195, 169)" ],
[ 0.108, "rgb(221, 207, 153)" ],
[ 0.153, "rgb(211, 174, 114)" ],
[ 0.205, "rgb(207, 155, 103)" ],
[ 0.277, "rgb(179, 120, 85)" ],
[ 0.375, "rgb(227, 210, 197)" ],
[ 0.660, "rgb(255, 255, 255)" ]
],
'Transparent white': [
[ 0.0, "rgba(255, 255, 255, 0)" ],
[ 1.0, "rgba(255, 255, 255, 1)" ],
],
Radar: [
[ 0.0000, "rgba(35,23,27,0)" ],
[ 0.0714, "rgba(68, 188, 116, 0.1)" ],
[ 0.1428, "rgba(30, 183, 66, 0.3)" ],
[ 0.2142, "rgba(45, 193, 81, 0.5)" ],
[ 0.2857, "rgba(38, 208, 43, 0.7)" ],
[ 0.3571, "rgba(58, 237, 55, 0.9)" ],
[ 0.4285, "rgb(143, 252, 95)" ],
[ 0.5000, "rgb(190, 251, 81)" ],
[ 0.5714, "rgb(200, 235, 26)" ],
[ 0.6428, "rgb(245,199,43)" ],
[ 0.7142, "rgb(255,155,33)" ],
[ 0.7857, "rgb(251,105,25)" ],
[ 0.8571, "rgb(214,57,15)" ],
[ 0.9285, "rgb(168,22,4)" ],
[ 1.0000, "rgb(183, 44, 204)" ]
]
};

function setStyle (tileset) {
map.setStyle({
version: 8,
sources: {
mapbox: {
type: "raster",
url: "mapbox://mapbox.satellite",
tileSize: 256
},
raster: {
type: "raster",
tiles: [ `https://api.mapbox.com/v4/${tilesets[tileset]}/{z}/{x}/{y}@2x.webp` ],
maxzoom: 14,
tileSize: 256
}
},
layers: [{
"id": "background",
"type": "background",
"paint": {
"background-color": "rgb(4,7,14)"
}
}, {
"id": "satellite",
"type": "raster",
"source": "mapbox",
"source-layer": "mapbox_satellite_full",
"paint": {
"raster-saturation": -0.3,
"raster-brightness-max": 0.5,
}
}, {
"type": "raster",
"source": "raster",
"id": "raster",
"paint": {
"raster-opacity": guiParams.opacity
}
}]
});
}

var map = window.map = new mapboxgl.Map({
container: "map",
style: {version: 8, layers: [], sources: {}},
hash: true,
transformRequest (url, type) {
if (type !== "Tile") return;
const preset = presets[guiParams.preset];
if (preset.forbid2x) url = url.replace('@2x', '');
if (preset.forcePngraw) url = url.replace('.webp', '.pngraw');
return {url};
}
});

map.once("load", function () {
var gui = window.gui = new dat.GUI(); // eslint-disable-line

const preset = gui.add(guiParams, "preset", Object.keys(presets));
const tileset = gui.add(guiParams, "tileset", Object.keys(tilesets));
const scale = gui.add(guiParams, "rasterColor", Object.keys(colorScales));
const mix = gui.add(guiParams, "rasterColorMix", Object.keys(mixes));
const range = gui.add(guiParams, "rasterColorRange", Object.keys(ranges));
const opacity = gui.add(guiParams, "opacity", 0, 1);

function setColorScale(scale) {
const range = ranges[guiParams.rasterColorRange];
const cs = colorScales[scale];
map.setPaintProperty("raster", "raster-color", [
"interpolate",
["linear"],
["raster-value"],
...cs.map(([pos, col]) => [
range[0] + (range[1] - range[0]) * pos,
col
]).flat(),
]);
document.getElementById("gradient").style.background = `linear-gradient(90deg, ${cs.map(([pos, col]) => `${col} ${(pos * 100).toFixed(1)}%`).join(",")})`;
document.getElementById("gradient").style.opacity = guiParams.opacity;
}

function setColorMix(mix) {
map.setPaintProperty("raster", "raster-color-mix", mixes[mix]);
}

function setColorRange(range) {
map.setPaintProperty("raster", "raster-color-range", ranges[range]);
setColorScale(guiParams.rasterColor);
}

function setTileset(range) {
setStyle(guiParams.tileset);
setColorMix(guiParams.rasterColorMix);
setColorScale(guiParams.rasterColor);
setColorRange(guiParams.rasterColorRange);
}

function setPreset(presetName) {
const preset = presets[presetName];
Object.assign(guiParams, preset);
setTileset(guiParams.tileset);
}

mix.onChange(setColorMix).listen();
scale.onChange(setColorScale).listen();
range.onChange(setColorRange).listen();
tileset.onChange(setTileset).listen();
preset.onChange(setPreset);

opacity.onChange(opac => {
map.setPaintProperty("raster", "raster-opacity", opac);
document.getElementById("gradient").style.opacity = opac;
});

setTileset(guiParams.tileset);
});

</script>
</body>
</html>
29 changes: 27 additions & 2 deletions src/render/draw_raster.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import ImageSource from '../source/image_source.js';
import StencilMode from '../gl/stencil_mode.js';
import DepthMode from '../gl/depth_mode.js';
import CullFaceMode from '../gl/cull_face_mode.js';
import Texture from './texture.js';
import {rasterUniformValues} from './program/raster_program.js';

import type Context from '../gl/context.js';
import type Painter from './painter.js';
import type SourceCache from '../source/source_cache.js';
import type RasterStyleLayer from '../style/style_layer/raster_style_layer.js';
Expand All @@ -14,6 +16,8 @@ import rasterFade from './raster_fade.js';

export default drawRaster;

const RASTER_COLOR_TEXTURE_UNIT = 2;

function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterStyleLayer, tileIDs: Array<OverscaledTileID>, variableOffsets: any, isInitialLoad: boolean) {
if (painter.renderPass !== 'translucent') return;
if (layer.paint.get('raster-opacity') === 0) return;
Expand All @@ -22,7 +26,10 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty
const context = painter.context;
const gl = context.gl;
const source = sourceCache.getSource();
const program = painter.useProgram('raster');

const rasterColor = configureRasterColor(layer, context, gl);

const program = painter.useProgram('raster', null, rasterColor.defines);

const colorMode = painter.colorModeForRenderPass();

Expand Down Expand Up @@ -79,7 +86,7 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty
}

const perspectiveTransform = source instanceof ImageSource ? source.perspectiveTransform : [0, 0];
const uniformValues = rasterUniformValues(projMatrix, parentTL || [0, 0], parentScaleBy || 1, fade, layer, perspectiveTransform);
const uniformValues = rasterUniformValues(projMatrix, parentTL || [0, 0], parentScaleBy || 1, fade, layer, perspectiveTransform, RASTER_COLOR_TEXTURE_UNIT, rasterColor.mix, rasterColor.range);

painter.prepareDrawProgram(context, program, unwrappedTileID);

Expand All @@ -100,3 +107,21 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty
painter.resetStencilClippingMasks();
}

function configureRasterColor (layer: RasterStyleLayer, context: Context, gl: WebGLRenderingContext) {
const defines = [];
let mix;
let range;

if (layer.paint.get('raster-color')) {
defines.push('RASTER_COLOR');
mix = layer.paint.get('raster-color-mix');
range = layer.paint.get('raster-color-range');

// Allocate a texture if not allocated
context.activeTexture.set(gl.TEXTURE2);
let tex = layer.colorRampTexture;
if (!tex) tex = layer.colorRampTexture = new Texture(context, layer.colorRamp, gl.RGBA);
tex.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
}
return {mix, range, defines};
}
3 changes: 2 additions & 1 deletion src/render/program/program_uniforms.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow

import type {CircleDefinesType} from './circle_program.js';
import type {RasterDefinesType} from './raster_program.js';
import type {SymbolDefinesType} from './symbol_program.js';
import type {LineDefinesType} from './line_program.js';
import {fillExtrusionUniforms, fillExtrusionPatternUniforms} from './fill_extrusion_program.js';
Expand All @@ -23,7 +24,7 @@ import type {HeatmapDefinesType} from './heatmap_program.js';
import type {DebugDefinesType} from './debug_program.js';
import type {GlobeDefinesType} from '../../terrain/globe_raster_program.js';

export type DynamicDefinesType = CircleDefinesType | SymbolDefinesType | LineDefinesType | HeatmapDefinesType | DebugDefinesType | GlobeDefinesType;
export type DynamicDefinesType = CircleDefinesType | SymbolDefinesType | LineDefinesType | HeatmapDefinesType | DebugDefinesType | GlobeDefinesType | RasterDefinesType;

export const programUniforms = {
fillExtrusion: fillExtrusionUniforms,
Expand Down
Loading