UNPKG

leaflet.tilelayer.glcolorscale

Version:

Custom Leaflet TileLayer using WebGL to colorize floating-point pixels according to a specified color scale

172 lines (160 loc) 6.25 kB
import REGL from 'regl'; import vertDouble from './shaders/double.vert.glsl'; import vertSingle from './shaders/single.vert.glsl'; import fragInterpolateColor from './shaders/interpolateColor.frag.glsl'; import fragInterpolateValue from './shaders/interpolateValue.frag.glsl'; import fragSingle from './shaders/single.frag.glsl'; import { DEFAULT_COLOR_STOP, SCALE_MAX_LENGTH, SENTINEL_MAX_LENGTH, } from './constants'; import { DrawCommon, DrawTile, DrawTileInterpolateColor, DrawTileInterpolateValue, } from './types'; import * as util from './util'; const littleEndian = util.machineIsLittleEndian(); const bindStructArray = util.bindStructArray.bind(null, ['color', 'offset'], DEFAULT_COLOR_STOP); const colorScaleUniforms = bindStructArray(SCALE_MAX_LENGTH, 'colorScale'); const sentinelValuesUniforms = bindStructArray(SENTINEL_MAX_LENGTH, 'sentinelValues'); const fragMacros = { SCALE_MAX_LENGTH, SENTINEL_MAX_LENGTH, }; /** * The object generated by this function should be merged into the DrawConfig for each Regl * DrawCommand in the application. */ export function getCommonDrawConfiguration( tileSize: number, nodataValue: number, ): REGL.DrawConfig<DrawCommon.Uniforms, DrawCommon.Attributes, DrawCommon.Props> { return { uniforms: { nodataValue, littleEndian, transformMatrix: ({ viewportWidth, viewportHeight }) => ( util.getTransformMatrix(viewportWidth, viewportHeight) ), }, attributes: { position: (_, { canvasCoordinates }) => { const [left, top] = canvasCoordinates; const [right, bottom] = [left + tileSize, top + tileSize]; return [ [left, top ], [right, top ], [left, bottom], [right, bottom], ]; }, }, // We don't need the depth buffer for 2D drawing. Leaving it enabled (and failing to clear it // between draw calls) results in visual artifacts. depth: { enable: false }, primitive: 'triangle strip', count: 4, viewport: (_, { canvasSize: [width, height] }) => ({ width, height }), }; } /** * The resulting Regl DrawCommand is used to draw a single tile. The fragment shader decodes the * Float32 value of a pixel and colorizes it with the given color scale (and/or sentinel values). */ export function createDrawTileCommand( regl: REGL.Regl, commonConfig: REGL.DrawConfig<DrawCommon.Uniforms, DrawCommon.Attributes, DrawCommon.Props>, ) { return regl<DrawTile.Uniforms, DrawTile.Attributes, DrawTile.Props>({ ...commonConfig, vert: vertSingle, frag: util.defineMacros(fragSingle, fragMacros), uniforms: { ...commonConfig.uniforms as DrawCommon.Uniforms, ...colorScaleUniforms, ...sentinelValuesUniforms, colorScaleLength: (_, { colorScale }) => colorScale.length, sentinelValuesLength: (_, { sentinelValues }) => sentinelValues.length, texture: (_, { texture }) => texture, }, attributes: { ...commonConfig.attributes as DrawCommon.Attributes, texCoord: (_, { textureBounds }) => util.getTexCoordVertices(textureBounds), }, }); } /** * The DrawCommand output by this function interpolates, for each pixel, between two values, one * from `textureA` and one from `textureB`. The same color scale / sentinel values are applied to * both. */ export function createDrawTileInterpolateValueCommand( regl: REGL.Regl, commonConfig: REGL.DrawConfig<DrawCommon.Uniforms, DrawCommon.Attributes, DrawCommon.Props>, ) { return regl< DrawTileInterpolateValue.Uniforms, DrawTileInterpolateValue.Attributes, DrawTileInterpolateValue.Props >({ ...commonConfig, vert: vertDouble, frag: util.defineMacros(fragInterpolateValue, fragMacros), uniforms: { ...commonConfig.uniforms as DrawCommon.Uniforms, ...colorScaleUniforms, ...sentinelValuesUniforms, colorScaleLength: (_, { colorScale }) => colorScale.length, sentinelValuesLength: (_, { sentinelValues }) => sentinelValues.length, textureA: (_, { textureA }) => textureA, textureB: (_, { textureB }) => textureB, interpolationFraction: (_, { interpolationFraction }) => interpolationFraction, }, attributes: { ...commonConfig.attributes as DrawCommon.Attributes, texCoordA: (_, { textureBoundsA }) => util.getTexCoordVertices(textureBoundsA), texCoordB: (_, { textureBoundsB }) => util.getTexCoordVertices(textureBoundsB), }, }); } /** * The behavior of this DrawCommand is similar to the one above, except that pixels from `textureA` * are colorized with one color scale / set of sentinel values, while pixels from `textureB` use a * different color scale / set of sentinel values. */ export function createDrawTileInterpolateColorCommand( regl: REGL.Regl, commonConfig: REGL.DrawConfig<DrawCommon.Uniforms, DrawCommon.Attributes, DrawCommon.Props>, ) { return regl< DrawTileInterpolateColor.Uniforms, DrawTileInterpolateColor.Attributes, DrawTileInterpolateColor.Props >({ ...commonConfig, vert: vertDouble, frag: util.defineMacros(fragInterpolateColor, fragMacros), uniforms: { ...commonConfig.uniforms as DrawCommon.Uniforms, ...bindStructArray(SCALE_MAX_LENGTH, 'colorScaleA'), ...bindStructArray(SCALE_MAX_LENGTH, 'colorScaleB'), ...bindStructArray(SENTINEL_MAX_LENGTH, 'sentinelValuesA'), ...bindStructArray(SENTINEL_MAX_LENGTH, 'sentinelValuesB'), colorScaleLengthA: (_, { colorScaleA }) => colorScaleA.length, colorScaleLengthB: (_, { colorScaleB }) => colorScaleB.length, sentinelValuesLengthA: (_, { sentinelValuesA }) => sentinelValuesA.length, sentinelValuesLengthB: (_, { sentinelValuesB }) => sentinelValuesB.length, textureA: (_, { textureA }) => textureA, textureB: (_, { textureB }) => textureB, interpolationFraction: (_, { interpolationFraction }) => interpolationFraction, }, attributes: { ...commonConfig.attributes as DrawCommon.Attributes, texCoordA: (_, { textureBoundsA }) => util.getTexCoordVertices(textureBoundsA), texCoordB: (_, { textureBoundsB }) => util.getTexCoordVertices(textureBoundsB), }, }); }