leaflet.tilelayer.glcolorscale
Version:
Custom Leaflet TileLayer using WebGL to colorize floating-point pixels according to a specified color scale
648 lines (622 loc) • 70.5 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var L = require('leaflet');
var lodashEs = require('lodash-es');
var REGL = _interopDefault(require('regl'));
var upngJs = require('upng-js');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css = ".gl-tilelayer-tile {\n -ms-interpolation-mode: nearest-neighbor;\n image-rendering: -moz-crisp-edges;\n image-rendering: pixelated;\n image-rendering: crisp-edges;\n}\n";
styleInject(css);
var SCALE_MAX_LENGTH = 16;
var SENTINEL_MAX_LENGTH = 16;
var CLEAR_COLOR = [0, 0, 0, 0];
var DEFAULT_COLOR_STOP = {
color: CLEAR_COLOR,
offset: 0,
};
var vertDouble = "#define GLSLIFY 1\nuniform mat4 transformMatrix;\n\nattribute vec2 position;\nattribute vec2 texCoordA;\nattribute vec2 texCoordB;\n\nvarying vec2 vTexCoordA;\nvarying vec2 vTexCoordB;\n\nvoid main() {\n vTexCoordA = texCoordA;\n vTexCoordB = texCoordB;\n gl_Position = transformMatrix * vec4(position, 0.0, 1.0);\n}\n"; // eslint-disable-line
var vertSingle = "#define GLSLIFY 1\nuniform mat4 transformMatrix;\n\nattribute vec2 position;\nattribute vec2 texCoord;\n\nvarying vec2 vTexCoord;\n\nvoid main() {\n vTexCoord = texCoord;\n gl_Position = transformMatrix * vec4(position, 0.0, 1.0);\n}\n"; // eslint-disable-line
var fragInterpolateColor = "#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#define GLSLIFY 1\n#endif\n\n#define TRANSPARENT vec4(0.0)\n\n// Denormalize 8-bit color channels to integers in the range 0 to 255.\nivec4 floatsToBytes(vec4 inputFloats, bool littleEndian) {\n ivec4 bytes = ivec4(inputFloats * 255.0);\n return (\n littleEndian\n ? bytes.abgr\n : bytes\n );\n}\n\n// Break the four bytes down into an array of 32 bits.\nvoid bytesToBits(const in ivec4 bytes, out bool bits[32]) {\n for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {\n float acc = float(bytes[channelIndex]);\n for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {\n float powerOfTwo = exp2(float(indexInByte));\n bool bit = acc >= powerOfTwo;\n bits[channelIndex * 8 + (7 - indexInByte)] = bit;\n acc = mod(acc, powerOfTwo);\n }\n }\n}\n\n// Compute the exponent of the 32-bit float.\nfloat getExponent(bool bits[32]) {\n const int startIndex = 1;\n const int bitStringLength = 8;\n const int endBeforeIndex = startIndex + bitStringLength;\n float acc = 0.0;\n int pow2 = bitStringLength - 1;\n for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {\n acc += float(bits[bitIndex]) * exp2(float(pow2--));\n }\n return acc;\n}\n\n// Compute the mantissa of the 32-bit float.\nfloat getMantissa(bool bits[32], bool subnormal) {\n const int startIndex = 9;\n const int bitStringLength = 23;\n const int endBeforeIndex = startIndex + bitStringLength;\n // Leading/implicit/hidden bit convention:\n // If the number is not subnormal (with exponent 0), we add a leading 1 digit.\n float acc = float(!subnormal) * exp2(float(bitStringLength));\n int pow2 = bitStringLength - 1;\n for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {\n acc += float(bits[bitIndex]) * exp2(float(pow2--));\n }\n return acc;\n}\n\n// Parse the float from its 32 bits.\nfloat bitsToFloat(bool bits[32]) {\n float signBit = float(bits[0]) * -2.0 + 1.0;\n float exponent = getExponent(bits);\n bool subnormal = abs(exponent - 0.0) < 0.01;\n float mantissa = getMantissa(bits, subnormal);\n float exponentBias = 127.0;\n return signBit * mantissa * exp2(exponent - exponentBias - 23.0);\n}\n\n// Decode a 32-bit float from the RGBA color channels of a texel.\nfloat rgbaToFloat(vec4 texelRGBA, bool littleEndian) {\n ivec4 rgbaBytes = floatsToBytes(texelRGBA, littleEndian);\n bool bits[32];\n bytesToBits(rgbaBytes, bits);\n return bitsToFloat(bits);\n}\n\n#ifndef RELATIVE_TOLERANCE\n#define RELATIVE_TOLERANCE 0.0001\n#endif\n\nbool isCloseEnough(float a, float b) {\n return abs(a - b) <= max(abs(a), abs(b)) * RELATIVE_TOLERANCE;\n}\n\nstruct ScaleStop {\n float offset;\n vec4 color;\n};\n\n#ifndef DEFAULT_COLOR\n#define DEFAULT_COLOR vec4(0.0)\n#endif\n\n#ifndef SCALE_MAX_LENGTH\n#define SCALE_MAX_LENGTH 16\n#endif\n\n#ifndef SENTINEL_MAX_LENGTH\n#define SENTINEL_MAX_LENGTH 16\n#endif\n\nvec4 computeColor(\n float inputVal,\n ScaleStop colorScale[SCALE_MAX_LENGTH],\n ScaleStop sentinelValues[SENTINEL_MAX_LENGTH],\n int colorScaleLength,\n int sentinelValuesLength\n) {\n // Compare the value against any sentinel values, if defined.\n if (sentinelValuesLength > 0) {\n for (int i_0 = 0; i_0 < SENTINEL_MAX_LENGTH; ++i_0) {\n if (i_0 == sentinelValuesLength) {\n break;\n }\n ScaleStop sentinel = sentinelValues[i_0];\n if (isCloseEnough(inputVal, sentinel.offset)) {\n return sentinel.color;\n }\n }\n }\n\n // Do linear interpolation using the color scale, if defined.\n if (colorScaleLength > 0) {\n // If value below color scale range, clamp to lowest color stop.\n if (inputVal < colorScale[0].offset) {\n return colorScale[0].color;\n } else {\n for (int i = 0; i < SCALE_MAX_LENGTH; ++i) {\n // If value above color scale range, clamp to highest color stop.\n if (i == colorScaleLength) {\n return colorScale[i - 1].color;\n } else if (inputVal <= colorScale[i + 1].offset) {\n float percent = (inputVal - colorScale[i].offset)\n / (colorScale[i + 1].offset - colorScale[i].offset);\n return mix(colorScale[i].color, colorScale[i + 1].color, percent);\n }\n }\n }\n }\n\n return DEFAULT_COLOR;\n}\n\nuniform sampler2D textureA;\nuniform ScaleStop colorScaleA[SCALE_MAX_LENGTH];\nuniform int colorScaleLengthA;\nuniform ScaleStop sentinelValuesA[SENTINEL_MAX_LENGTH];\nuniform int sentinelValuesLengthA;\n\nuniform sampler2D textureB;\nuniform ScaleStop colorScaleB[SCALE_MAX_LENGTH];\nuniform int colorScaleLengthB;\nuniform ScaleStop sentinelValuesB[SENTINEL_MAX_LENGTH];\nuniform int sentinelValuesLengthB;\n\nuniform float nodataValue;\nuniform bool littleEndian;\nuniform float interpolationFraction;\n\nvarying vec2 vTexCoordA;\nvarying vec2 vTexCoordB;\n\nvoid main() {\n if (interpolationFraction <= 0.0) {\n vec4 rgbaFloats = texture2D(textureA, vTexCoordA);\n float pixelFloatValue = rgbaToFloat(rgbaFloats, littleEndian);\n if (isCloseEnough(pixelFloatValue, nodataValue)) {\n discard;\n }\n gl_FragColor = computeColor(pixelFloatValue, colorScaleA, sentinelValuesA, colorScaleLengthA, sentinelValuesLengthA);\n } else if (interpolationFraction >= 1.0) {\n vec4 rgbaFloats = texture2D(textureB, vTexCoordB);\n float pixelFloatValue = rgbaToFloat(rgbaFloats, littleEndian);\n if (isCloseEnough(pixelFloatValue, nodataValue)) {\n discard;\n }\n gl_FragColor = computeColor(pixelFloatValue, colorScaleB, sentinelValuesB, colorScaleLengthB, sentinelValuesLengthB);\n } else {\n vec4 rgbaFloatsA = texture2D(textureA, vTexCoordA);\n float pixelFloatValueA = rgbaToFloat(rgbaFloatsA, littleEndian);\n vec4 rgbaFloatsB = texture2D(textureB, vTexCoordB);\n float pixelFloatValueB = rgbaToFloat(rgbaFloatsB, littleEndian);\n vec4 colorA = (\n isCloseEnough(pixelFloatValueA, nodataValue)\n ? TRANSPARENT\n : computeColor(pixelFloatValueA, colorScaleA, sentinelValuesA, colorScaleLengthA, sentinelValuesLengthA)\n );\n vec4 colorB = (\n isCloseEnough(pixelFloatValueB, nodataValue)\n ? TRANSPARENT\n : computeColor(pixelFloatValueB, colorScaleB, sentinelValuesB, colorScaleLengthB, sentinelValuesLengthB)\n );\n gl_FragColor = mix(colorA, colorB, interpolationFraction);\n }\n}\n"; // eslint-disable-line
var fragInterpolateValue = "#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#define GLSLIFY 1\n#endif\n\n#define TRANSPARENT vec4(0.0)\n\n// Denormalize 8-bit color channels to integers in the range 0 to 255.\nivec4 floatsToBytes(vec4 inputFloats, bool littleEndian) {\n ivec4 bytes = ivec4(inputFloats * 255.0);\n return (\n littleEndian\n ? bytes.abgr\n : bytes\n );\n}\n\n// Break the four bytes down into an array of 32 bits.\nvoid bytesToBits(const in ivec4 bytes, out bool bits[32]) {\n for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {\n float acc = float(bytes[channelIndex]);\n for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {\n float powerOfTwo = exp2(float(indexInByte));\n bool bit = acc >= powerOfTwo;\n bits[channelIndex * 8 + (7 - indexInByte)] = bit;\n acc = mod(acc, powerOfTwo);\n }\n }\n}\n\n// Compute the exponent of the 32-bit float.\nfloat getExponent(bool bits[32]) {\n const int startIndex = 1;\n const int bitStringLength = 8;\n const int endBeforeIndex = startIndex + bitStringLength;\n float acc = 0.0;\n int pow2 = bitStringLength - 1;\n for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {\n acc += float(bits[bitIndex]) * exp2(float(pow2--));\n }\n return acc;\n}\n\n// Compute the mantissa of the 32-bit float.\nfloat getMantissa(bool bits[32], bool subnormal) {\n const int startIndex = 9;\n const int bitStringLength = 23;\n const int endBeforeIndex = startIndex + bitStringLength;\n // Leading/implicit/hidden bit convention:\n // If the number is not subnormal (with exponent 0), we add a leading 1 digit.\n float acc = float(!subnormal) * exp2(float(bitStringLength));\n int pow2 = bitStringLength - 1;\n for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {\n acc += float(bits[bitIndex]) * exp2(float(pow2--));\n }\n return acc;\n}\n\n// Parse the float from its 32 bits.\nfloat bitsToFloat(bool bits[32]) {\n float signBit = float(bits[0]) * -2.0 + 1.0;\n float exponent = getExponent(bits);\n bool subnormal = abs(exponent - 0.0) < 0.01;\n float mantissa = getMantissa(bits, subnormal);\n float exponentBias = 127.0;\n return signBit * mantissa * exp2(exponent - exponentBias - 23.0);\n}\n\n// Decode a 32-bit float from the RGBA color channels of a texel.\nfloat rgbaToFloat(vec4 texelRGBA, bool littleEndian) {\n ivec4 rgbaBytes = floatsToBytes(texelRGBA, littleEndian);\n bool bits[32];\n bytesToBits(rgbaBytes, bits);\n return bitsToFloat(bits);\n}\n\n#ifndef RELATIVE_TOLERANCE\n#define RELATIVE_TOLERANCE 0.0001\n#endif\n\nbool isCloseEnough(float a, float b) {\n return abs(a - b) <= max(abs(a), abs(b)) * RELATIVE_TOLERANCE;\n}\n\nstruct ScaleStop {\n float offset;\n vec4 color;\n};\n\n#ifndef DEFAULT_COLOR\n#define DEFAULT_COLOR vec4(0.0)\n#endif\n\n#ifndef SCALE_MAX_LENGTH\n#define SCALE_MAX_LENGTH 16\n#endif\n\n#ifndef SENTINEL_MAX_LENGTH\n#define SENTINEL_MAX_LENGTH 16\n#endif\n\nvec4 computeColor(\n float inputVal,\n ScaleStop colorScale[SCALE_MAX_LENGTH],\n ScaleStop sentinelValues[SENTINEL_MAX_LENGTH],\n int colorScaleLength_0,\n int sentinelValuesLength_0\n) {\n // Compare the value against any sentinel values, if defined.\n if (sentinelValuesLength_0 > 0) {\n for (int i_0 = 0; i_0 < SENTINEL_MAX_LENGTH; ++i_0) {\n if (i_0 == sentinelValuesLength_0) {\n break;\n }\n ScaleStop sentinel = sentinelValues[i_0];\n if (isCloseEnough(inputVal, sentinel.offset)) {\n return sentinel.color;\n }\n }\n }\n\n // Do linear interpolation using the color scale, if defined.\n if (colorScaleLength_0 > 0) {\n // If value below color scale range, clamp to lowest color stop.\n if (inputVal < colorScale[0].offset) {\n return colorScale[0].color;\n } else {\n for (int i = 0; i < SCALE_MAX_LENGTH; ++i) {\n // If value above color scale range, clamp to highest color stop.\n if (i == colorScaleLength_0) {\n return colorScale[i - 1].color;\n } else if (inputVal <= colorScale[i + 1].offset) {\n float percent = (inputVal - colorScale[i].offset)\n / (colorScale[i + 1].offset - colorScale[i].offset);\n return mix(colorScale[i].color, colorScale[i + 1].color, percent);\n }\n }\n }\n }\n\n return DEFAULT_COLOR;\n}\n\nuniform ScaleStop colorScale[SCALE_MAX_LENGTH];\nuniform int colorScaleLength;\n\nuniform ScaleStop sentinelValues[SENTINEL_MAX_LENGTH];\nuniform int sentinelValuesLength;\n\nuniform float nodataValue;\nuniform sampler2D textureA;\nuniform sampler2D textureB;\nuniform bool littleEndian;\nuniform float interpolationFraction;\n\nvarying vec2 vTexCoordA;\nvarying vec2 vTexCoordB;\n\nbool isSentinelValue(ScaleStop sentinelValues[SENTINEL_MAX_LENGTH], int len, float value) {\n for (int i = 0; i < SENTINEL_MAX_LENGTH; ++i) {\n if (i == len) {\n break;\n }\n if (isCloseEnough(sentinelValues[i].offset, value)) {\n return true;\n }\n }\n return false;\n}\n\nvoid main() {\n if (interpolationFraction <= 0.0) {\n vec4 rgbaFloats = texture2D(textureA, vTexCoordA);\n float pixelFloatValue = rgbaToFloat(rgbaFloats, littleEndian);\n if (isCloseEnough(pixelFloatValue, nodataValue)) {\n discard;\n }\n gl_FragColor = computeColor(pixelFloatValue, colorScale, sentinelValues, colorScaleLength, sentinelValuesLength);\n } else if (interpolationFraction >= 1.0) {\n vec4 rgbaFloats = texture2D(textureB, vTexCoordB);\n float pixelFloatValue = rgbaToFloat(rgbaFloats, littleEndian);\n if (isCloseEnough(pixelFloatValue, nodataValue)) {\n discard;\n }\n gl_FragColor = computeColor(pixelFloatValue, colorScale, sentinelValues, colorScaleLength, sentinelValuesLength);\n } else {\n vec4 rgbaFloatsA = texture2D(textureA, vTexCoordA);\n float pixelFloatValueA = rgbaToFloat(rgbaFloatsA, littleEndian);\n vec4 rgbaFloatsB = texture2D(textureB, vTexCoordB);\n float pixelFloatValueB = rgbaToFloat(rgbaFloatsB, littleEndian);\n bool aIsNodata = isCloseEnough(pixelFloatValueA, nodataValue);\n bool bIsNodata = isCloseEnough(pixelFloatValueB, nodataValue);\n if (aIsNodata && bIsNodata) {\n discard;\n } else if (\n aIsNodata\n || bIsNodata\n || colorScaleLength == 0\n || isSentinelValue(sentinelValues, sentinelValuesLength, pixelFloatValueA)\n || isSentinelValue(sentinelValues, sentinelValuesLength, pixelFloatValueB)\n ) {\n vec4 colorA = (\n aIsNodata\n ? TRANSPARENT\n : computeColor(pixelFloatValueA, colorScale, sentinelValues, colorScaleLength, sentinelValuesLength)\n );\n vec4 colorB = (\n bIsNodata\n ? TRANSPARENT\n : computeColor(pixelFloatValueB, colorScale, sentinelValues, colorScaleLength, sentinelValuesLength)\n );\n gl_FragColor = mix(colorA, colorB, interpolationFraction);\n } else {\n float interpolated = mix(pixelFloatValueA, pixelFloatValueB, interpolationFraction);\n gl_FragColor = computeColor(interpolated, colorScale, sentinelValues, colorScaleLength, sentinelValuesLength);\n }\n }\n}\n"; // eslint-disable-line
var fragSingle = "#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#define GLSLIFY 1\n#endif\n\n// Denormalize 8-bit color channels to integers in the range 0 to 255.\nivec4 floatsToBytes(vec4 inputFloats, bool littleEndian) {\n ivec4 bytes = ivec4(inputFloats * 255.0);\n return (\n littleEndian\n ? bytes.abgr\n : bytes\n );\n}\n\n// Break the four bytes down into an array of 32 bits.\nvoid bytesToBits(const in ivec4 bytes, out bool bits[32]) {\n for (int channelIndex = 0; channelIndex < 4; ++channelIndex) {\n float acc = float(bytes[channelIndex]);\n for (int indexInByte = 7; indexInByte >= 0; --indexInByte) {\n float powerOfTwo = exp2(float(indexInByte));\n bool bit = acc >= powerOfTwo;\n bits[channelIndex * 8 + (7 - indexInByte)] = bit;\n acc = mod(acc, powerOfTwo);\n }\n }\n}\n\n// Compute the exponent of the 32-bit float.\nfloat getExponent(bool bits[32]) {\n const int startIndex = 1;\n const int bitStringLength = 8;\n const int endBeforeIndex = startIndex + bitStringLength;\n float acc = 0.0;\n int pow2 = bitStringLength - 1;\n for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {\n acc += float(bits[bitIndex]) * exp2(float(pow2--));\n }\n return acc;\n}\n\n// Compute the mantissa of the 32-bit float.\nfloat getMantissa(bool bits[32], bool subnormal) {\n const int startIndex = 9;\n const int bitStringLength = 23;\n const int endBeforeIndex = startIndex + bitStringLength;\n // Leading/implicit/hidden bit convention:\n // If the number is not subnormal (with exponent 0), we add a leading 1 digit.\n float acc = float(!subnormal) * exp2(float(bitStringLength));\n int pow2 = bitStringLength - 1;\n for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) {\n acc += float(bits[bitIndex]) * exp2(float(pow2--));\n }\n return acc;\n}\n\n// Parse the float from its 32 bits.\nfloat bitsToFloat(bool bits[32]) {\n float signBit = float(bits[0]) * -2.0 + 1.0;\n float exponent = getExponent(bits);\n bool subnormal = abs(exponent - 0.0) < 0.01;\n float mantissa = getMantissa(bits, subnormal);\n float exponentBias = 127.0;\n return signBit * mantissa * exp2(exponent - exponentBias - 23.0);\n}\n\n// Decode a 32-bit float from the RGBA color channels of a texel.\nfloat rgbaToFloat(vec4 texelRGBA, bool littleEndian) {\n ivec4 rgbaBytes = floatsToBytes(texelRGBA, littleEndian);\n bool bits[32];\n bytesToBits(rgbaBytes, bits);\n return bitsToFloat(bits);\n}\n\n#ifndef RELATIVE_TOLERANCE\n#define RELATIVE_TOLERANCE 0.0001\n#endif\n\nbool isCloseEnough(float a, float b) {\n return abs(a - b) <= max(abs(a), abs(b)) * RELATIVE_TOLERANCE;\n}\n\nstruct ScaleStop {\n float offset;\n vec4 color;\n};\n\n#ifndef DEFAULT_COLOR\n#define DEFAULT_COLOR vec4(0.0)\n#endif\n\n#ifndef SCALE_MAX_LENGTH\n#define SCALE_MAX_LENGTH 16\n#endif\n\n#ifndef SENTINEL_MAX_LENGTH\n#define SENTINEL_MAX_LENGTH 16\n#endif\n\nvec4 computeColor(\n float inputVal,\n ScaleStop colorScale[SCALE_MAX_LENGTH],\n ScaleStop sentinelValues[SENTINEL_MAX_LENGTH],\n int colorScaleLength_0,\n int sentinelValuesLength_0\n) {\n // Compare the value against any sentinel values, if defined.\n if (sentinelValuesLength_0 > 0) {\n for (int i_0 = 0; i_0 < SENTINEL_MAX_LENGTH; ++i_0) {\n if (i_0 == sentinelValuesLength_0) {\n break;\n }\n ScaleStop sentinel = sentinelValues[i_0];\n if (isCloseEnough(inputVal, sentinel.offset)) {\n return sentinel.color;\n }\n }\n }\n\n // Do linear interpolation using the color scale, if defined.\n if (colorScaleLength_0 > 0) {\n // If value below color scale range, clamp to lowest color stop.\n if (inputVal < colorScale[0].offset) {\n return colorScale[0].color;\n } else {\n for (int i = 0; i < SCALE_MAX_LENGTH; ++i) {\n // If value above color scale range, clamp to highest color stop.\n if (i == colorScaleLength_0) {\n return colorScale[i - 1].color;\n } else if (inputVal <= colorScale[i + 1].offset) {\n float percent = (inputVal - colorScale[i].offset)\n / (colorScale[i + 1].offset - colorScale[i].offset);\n return mix(colorScale[i].color, colorScale[i + 1].color, percent);\n }\n }\n }\n }\n\n return DEFAULT_COLOR;\n}\n\nuniform ScaleStop colorScale[SCALE_MAX_LENGTH];\nuniform int colorScaleLength;\n\nuniform ScaleStop sentinelValues[SENTINEL_MAX_LENGTH];\nuniform int sentinelValuesLength;\n\nuniform float nodataValue;\nuniform sampler2D texture;\nuniform bool littleEndian;\n\nvarying vec2 vTexCoord;\n\nvoid main() {\n vec4 rgbaFloats = texture2D(texture, vTexCoord);\n float pixelFloatValue = rgbaToFloat(rgbaFloats, littleEndian);\n if (isCloseEnough(pixelFloatValue, nodataValue)) {\n discard;\n }\n gl_FragColor = computeColor(pixelFloatValue, colorScale, sentinelValues, colorScaleLength, sentinelValuesLength);\n}\n"; // eslint-disable-line
function convertColorScale(colorScale) {
return colorScale.map(function (_a) {
var color = _a.color, offset = _a.offset;
return ({
color: colorStringToWebGLFloats(color),
offset: offset,
});
});
}
var RGB_REGEX = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/;
function colorStringToWebGLFloats(rgb) {
if (rgb === 'transparent') {
return [1, 1, 1, 0];
}
var match = rgb.match(RGB_REGEX);
if (match === null) {
throw new Error("'" + rgb + "' is not a valid RGB color expression.");
}
var r = match[1], g = match[2], b = match[3];
return [+r / 255, +g / 255, +b / 255, 1];
}
function bindStructArray(structPropertyNames, defaultValue, maxArrayLength, glslIdentifier, propName) {
if (propName === void 0) { propName = glslIdentifier; }
var output = {};
var _loop_1 = function (i) {
var _loop_2 = function (key) {
output[glslIdentifier + "[" + i + "]." + key] = function (_, props) {
var inputArray = props[propName];
return (i < inputArray.length
? inputArray[i][key]
: defaultValue[key]);
};
};
for (var _i = 0, structPropertyNames_1 = structPropertyNames; _i < structPropertyNames_1.length; _i++) {
var key = structPropertyNames_1[_i];
_loop_2(key);
}
};
for (var i = 0; i < maxArrayLength; ++i) {
_loop_1(i);
}
return output;
}
function machineIsLittleEndian() {
var uint8Array = new Uint8Array([0xAA, 0xBB]);
var uint16array = new Uint16Array(uint8Array.buffer);
return uint16array[0] === 0xBBAA;
}
function range() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (args.length === 1) {
var until = args[0];
return new Array(until).fill(undefined).map(function (_, i) { return i; });
}
else {
var from = args[0], until = args[1], _a = args[2], step = _a === void 0 ? 1 : _a;
if (step === 0) {
throw new Error('Argument step must be nonzero.');
}
var output = [];
for (var val = from; (step > 0) ? val < until : val > until; val += step) {
output.push(val);
}
return output;
}
}
function fetchPNGData(url, nodataValue, tileDimension) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.addEventListener('load', function () {
resolve(xhr.response);
});
xhr.addEventListener('error', reject);
xhr.send(null);
}).then(function (data) {
return new Uint8Array(upngJs.decode(data).data);
}).catch(function () { return createNoDataTile(nodataValue, tileDimension); })];
});
});
}
function getTransformMatrix(drawingBufferWidth, drawingBufferHeight) {
var sx = 2 / drawingBufferWidth;
var sy = -2 / drawingBufferHeight;
var tx = -1;
var ty = 1;
return [
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, 1, 0,
tx, ty, 0, 1,
];
}
function getTexCoordVertices(textureBounds) {
var _a = textureBounds[0], left = _a.x, top = _a.y, _b = textureBounds[1], right = _b.x, bottom = _b.y;
return [
[left, top],
[right, top],
[left, bottom],
[right, bottom],
];
}
function Timer(duration) {
return new Promise(function (resolve) { return setTimeout(resolve, duration); });
}
function compareTileCoordinates(a, b) {
var z = a.z - b.z;
var x = a.x - b.x;
var y = a.y - b.y;
if (z !== 0) {
return z;
}
else if (x !== 0) {
return x;
}
else {
return y;
}
}
function sameTiles(a, b) {
return (a.length === b.length
&& a.every(function (tileA, index) { return compareTileCoordinates(tileA, b[index]) === 0; }));
}
var createNoDataTile = lodashEs.memoize(function (nodataValue, tileDimension) {
if (tileDimension === void 0) { tileDimension = 256; }
var float32Tile = new Float32Array(tileDimension * tileDimension);
float32Tile.fill(nodataValue);
return new Uint8Array(float32Tile.buffer);
});
function staticCast(val) {
return val;
}
function defineMacros(src, macros) {
var defs = Object.keys(macros).map(function (key) { return "#define " + key + " " + macros[key] + "\n"; }).join('');
return defs + "\n" + src;
}
var littleEndian = machineIsLittleEndian();
var bindStructArray$1 = bindStructArray.bind(null, ['color', 'offset'], DEFAULT_COLOR_STOP);
var colorScaleUniforms = bindStructArray$1(SCALE_MAX_LENGTH, 'colorScale');
var sentinelValuesUniforms = bindStructArray$1(SENTINEL_MAX_LENGTH, 'sentinelValues');
var fragMacros = {
SCALE_MAX_LENGTH: SCALE_MAX_LENGTH,
SENTINEL_MAX_LENGTH: SENTINEL_MAX_LENGTH,
};
function getCommonDrawConfiguration(tileSize, nodataValue) {
return {
uniforms: {
nodataValue: nodataValue,
littleEndian: littleEndian,
transformMatrix: function (_a) {
var viewportWidth = _a.viewportWidth, viewportHeight = _a.viewportHeight;
return (getTransformMatrix(viewportWidth, viewportHeight));
},
},
attributes: {
position: function (_, _a) {
var canvasCoordinates = _a.canvasCoordinates;
var left = canvasCoordinates[0], top = canvasCoordinates[1];
var _b = [left + tileSize, top + tileSize], right = _b[0], bottom = _b[1];
return [
[left, top],
[right, top],
[left, bottom],
[right, bottom],
];
},
},
depth: { enable: false },
primitive: 'triangle strip',
count: 4,
viewport: function (_, _a) {
var _b = _a.canvasSize, width = _b[0], height = _b[1];
return ({ width: width, height: height });
},
};
}
function createDrawTileCommand(regl, commonConfig) {
return regl(__assign({}, commonConfig, { vert: vertSingle, frag: defineMacros(fragSingle, fragMacros), uniforms: __assign({}, commonConfig.uniforms, colorScaleUniforms, sentinelValuesUniforms, { colorScaleLength: function (_, _a) {
var colorScale = _a.colorScale;
return colorScale.length;
}, sentinelValuesLength: function (_, _a) {
var sentinelValues = _a.sentinelValues;
return sentinelValues.length;
}, texture: function (_, _a) {
var texture = _a.texture;
return texture;
} }), attributes: __assign({}, commonConfig.attributes, { texCoord: function (_, _a) {
var textureBounds = _a.textureBounds;
return getTexCoordVertices(textureBounds);
} }) }));
}
function createDrawTileInterpolateValueCommand(regl, commonConfig) {
return regl(__assign({}, commonConfig, { vert: vertDouble, frag: defineMacros(fragInterpolateValue, fragMacros), uniforms: __assign({}, commonConfig.uniforms, colorScaleUniforms, sentinelValuesUniforms, { colorScaleLength: function (_, _a) {
var colorScale = _a.colorScale;
return colorScale.length;
}, sentinelValuesLength: function (_, _a) {
var sentinelValues = _a.sentinelValues;
return sentinelValues.length;
}, textureA: function (_, _a) {
var textureA = _a.textureA;
return textureA;
}, textureB: function (_, _a) {
var textureB = _a.textureB;
return textureB;
}, interpolationFraction: function (_, _a) {
var interpolationFraction = _a.interpolationFraction;
return interpolationFraction;
} }), attributes: __assign({}, commonConfig.attributes, { texCoordA: function (_, _a) {
var textureBoundsA = _a.textureBoundsA;
return getTexCoordVertices(textureBoundsA);
}, texCoordB: function (_, _a) {
var textureBoundsB = _a.textureBoundsB;
return getTexCoordVertices(textureBoundsB);
} }) }));
}
function createDrawTileInterpolateColorCommand(regl, commonConfig) {
return regl(__assign({}, commonConfig, { vert: vertDouble, frag: defineMacros(fragInterpolateColor, fragMacros), uniforms: __assign({}, commonConfig.uniforms, bindStructArray$1(SCALE_MAX_LENGTH, 'colorScaleA'), bindStructArray$1(SCALE_MAX_LENGTH, 'colorScaleB'), bindStructArray$1(SENTINEL_MAX_LENGTH, 'sentinelValuesA'), bindStructArray$1(SENTINEL_MAX_LENGTH, 'sentinelValuesB'), { colorScaleLengthA: function (_, _a) {
var colorScaleA = _a.colorScaleA;
return colorScaleA.length;
}, colorScaleLengthB: function (_, _a) {
var colorScaleB = _a.colorScaleB;
return colorScaleB.length;
}, sentinelValuesLengthA: function (_, _a) {
var sentinelValuesA = _a.sentinelValuesA;
return sentinelValuesA.length;
}, sentinelValuesLengthB: function (_, _a) {
var sentinelValuesB = _a.sentinelValuesB;
return sentinelValuesB.length;
}, textureA: function (_, _a) {
var textureA = _a.textureA;
return textureA;
}, textureB: function (_, _a) {
var textureB = _a.textureB;
return textureB;
}, interpolationFraction: function (_, _a) {
var interpolationFraction = _a.interpolationFraction;
return interpolationFraction;
} }), attributes: __assign({}, commonConfig.attributes, { texCoordA: function (_, _a) {
var textureBoundsA = _a.textureBoundsA;
return getTexCoordVertices(textureBoundsA);
}, texCoordB: function (_, _a) {
var textureBoundsB = _a.textureBoundsB;
return getTexCoordVertices(textureBoundsB);
} }) }));
}
var MAX_TEXTURE_DIMENSION = 1024;
var TextureManager = (function () {
function TextureManager(regl, tileSize) {
var tilesAcross = Math.floor(MAX_TEXTURE_DIMENSION / tileSize);
var pixelsAcross = tilesAcross * tileSize;
var tileCapacity = tilesAcross * tilesAcross;
var texture = regl.texture({
width: pixelsAcross,
height: pixelsAcross,
format: 'rgba',
type: 'uint8',
});
var contents = new Map();
var available = this.allTextureCoordinates(tilesAcross, tileSize);
Object.assign(this, {
tileSize: tileSize,
tilesAcross: tilesAcross,
pixelsAcross: pixelsAcross,
tileCapacity: tileCapacity,
texture: texture,
contents: contents,
available: available,
});
}
TextureManager.prototype.addTile = function (tileCoordinates, data) {
var _a = this, available = _a.available, contents = _a.contents, texture = _a.texture, tileSize = _a.tileSize;
var hashKey = this.hashTileCoordinates(tileCoordinates);
if (contents.has(hashKey)) {
var textureCoordinates_1 = contents.get(hashKey);
contents.delete(hashKey);
contents.set(hashKey, textureCoordinates_1);
return this.formatOutputTextureCoordinates(textureCoordinates_1);
}
if (lodashEs.isEmpty(available)) {
var firstInsertedKey = contents.keys().next().value;
this.removeByHashKey(firstInsertedKey);
}
var textureCoordinates = available.pop();
contents.set(hashKey, textureCoordinates);
var textureX = textureCoordinates.x, textureY = textureCoordinates.y;
texture.subimage({
data: data,
width: tileSize,
height: tileSize,
}, textureX, textureY);
return this.formatOutputTextureCoordinates(textureCoordinates);
};
TextureManager.prototype.removeTile = function (tileCoordinates) {
this.removeByHashKey(this.hashTileCoordinates(tileCoordinates));
};
TextureManager.prototype.clearTiles = function () {
for (var _i = 0, _a = Array.from(this.contents.keys()); _i < _a.length; _i++) {
var hashKey = _a[_i];
this.removeByHashKey(hashKey);
}
};
TextureManager.prototype.destroy = function () {
this.texture.destroy();
};
TextureManager.prototype.removeByHashKey = function (hashKey) {
if (this.contents.has(hashKey)) {
var textureCoordinates = this.contents.get(hashKey);
this.contents.delete(hashKey);
this.available.push(textureCoordinates);
}
};
TextureManager.prototype.formatOutputTextureCoordinates = function (textureCoordinates) {
var x = textureCoordinates.x, y = textureCoordinates.y;
var _a = this, pixelsAcross = _a.pixelsAcross, tileSize = _a.tileSize;
return [
{
x: x / pixelsAcross,
y: y / pixelsAcross,
},
{
x: (x + tileSize) / pixelsAcross,
y: (y + tileSize) / pixelsAcross,
},
];
};
TextureManager.prototype.hashTileCoordinates = function (_a) {
var x = _a.x, y = _a.y, z = _a.z;
return x + ":" + y + ":" + z;
};
TextureManager.prototype.allTextureCoordinates = function (tilesAcross, tileSize) {
return lodashEs.flatMap(range(tilesAcross), function (x) {
return range(tilesAcross).map(function (y) { return ({
x: x * tileSize,
y: y * tileSize,
}); });
});
};
return TextureManager;
}());
var Renderer = (function () {
function Renderer(tileSize, nodataValue) {
var canvas = L.DomUtil.create('canvas');
var regl = REGL(canvas);
var commonDrawConfig = getCommonDrawConfiguration(tileSize, nodataValue);
Object.assign(this, {
canvas: canvas,
regl: regl,
tileSize: tileSize,
textureManager: new TextureManager(regl, tileSize),
drawTile: createDrawTileCommand(regl, commonDrawConfig),
drawTileInterpolateColor: createDrawTileInterpolateColorCommand(regl, commonDrawConfig),
drawTileInterpolateValue: createDrawTileInterpolateValueCommand(regl, commonDrawConfig),
});
}
Renderer.prototype.renderTile = function (_a, colorScale, sentinelValues) {
var coords = _a.coords, pixelData = _a.pixelData;
var _b = this, regl = _b.regl, textureManager = _b.textureManager, tileSize = _b.tileSize;
this.setCanvasSize(tileSize, tileSize);
var textureBounds = textureManager.addTile(coords, pixelData);
regl.clear({ color: CLEAR_COLOR });
this.drawTile({
colorScale: convertColorScale(colorScale),
sentinelValues: convertColorScale(sentinelValues),
canvasSize: [tileSize, tileSize],
canvasCoordinates: [0, 0],
textureBounds: textureBounds,
texture: textureManager.texture,
});
return [0, 0];
};
Renderer.prototype.renderTiles = function (tiles, colorScale, sentinelValues) {
var _a = this, regl = _a.regl, textureManager = _a.textureManager;
var _b = this.computeRequiredCanvasDimensions(tiles.length), canvasWidth = _b[0], canvasHeight = _b[1];
this.setCanvasSize(canvasWidth, canvasHeight);
var canvasCoordinates = this.getCanvasCoordinates(canvasWidth, canvasHeight, tiles.length);
var tilesWithCanvasCoordinates = lodashEs.zipWith(tiles, canvasCoordinates, function (tile, canvasCoords) { return (__assign({}, tile, { canvasCoords: canvasCoords })); });
var webGLColorScale = convertColorScale(colorScale);
var webGLSentinelValues = convertColorScale(sentinelValues);
var canvasSize = [canvasWidth, canvasHeight];
textureManager.clearTiles();
regl.clear({ color: CLEAR_COLOR });
var chunks = lodashEs.chunk(tilesWithCanvasCoordinates, textureManager.tileCapacity);
var _loop_1 = function (chunk_1) {
var textureBounds = chunk_1.map(function (_a) {
var coords = _a.coords, pixelData = _a.pixelData;
return textureManager.addTile(coords, pixelData);
});
this_1.drawTile(chunk_1.map(function (_a, index) {
var canvasCoords = _a.canvasCoords;
return ({
colorScale: webGLColorScale,
sentinelValues: webGLSentinelValues,
canvasSize: canvasSize,
canvasCoordinates: canvasCoords,
textureBounds: textureBounds[index],
texture: textureManager.texture,
});
}));
};
var this_1 = this;
for (var _i = 0, chunks_1 = chunks; _i < chunks_1.length; _i++) {
var chunk_1 = chunks_1[_i];
_loop_1(chunk_1);
}
return canvasCoordinates;
};
Renderer.prototype.renderTilesWithTransition = function (oldTiles, newTiles, colorScale, sentinelValues, transitionDurationMs, onFrameRendered) {
return __awaiter(this, void 0, void 0, function () {
var _a, regl, textureManager, tileSize, canvasSize, canvasWidth, canvasHeight, canvasCoordinates, tilesWithCanvasCoordinates, newTextureManager, webGLColorScale, webGLSentinelValues, transitionStart, renderFrame, animationHandle;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = this, regl = _a.regl, textureManager = _a.textureManager, tileSize = _a.tileSize;
canvasSize = this.computeRequiredCanvasDimensions(oldTiles.length);
canvasWidth = canvasSize[0], canvasHeight = canvasSize[1];
this.setCanvasSize(canvasWidth, canvasHeight);
canvasCoordinates = this.getCanvasCoordinates(canvasWidth, canvasHeight, oldTiles.length);
tilesWithCanvasCoordinates = lodashEs.zipWith(oldTiles, newTiles, canvasCoordinates, function (oldTile, newTile, canvasCoords) { return ({
coords: oldTile.coords,
oldPixelData: oldTile.pixelData,
newPixelData: newTile.pixelData,
canvasCoords: canvasCoords,
}); });
newTextureManager = new TextureManager(regl, tileSize);
webGLColorScale = convertColorScale(colorScale);
webGLSentinelValues = convertColorScale(sentinelValues);
transitionStart = regl.now();
renderFrame = function (interpolationFraction) {
var chunks = lodashEs.chunk(tilesWithCanvasCoordinates, textureManager.tileCapacity);
regl.clear({ color: CLEAR_COLOR });
var _loop_2 = function (chunk_2) {
var oldTextureBounds = chunk_2.map(function (_a) {
var coords = _a.coords, oldPixelData = _a.oldPixelData;
return textureManager.addTile(coords, oldPixelData);
});
var newTextureBounds = chunk_2.map(function (_a) {
var coords = _a.coords, newPixelData = _a.newPixelData;
return newTextureManager.addTile(coords, newPixelData);
});
_this.drawTileInterpolateValue(chunk_2.map(function (_a, index) {
var canvasCoords = _a.canvasCoords;
return ({
colorScale: webGLColorScale,
sentinelValues: webGLSentinelValues,
canvasSize: canvasSize,
canvasCoordinates: canvasCoords,
textureA: textureManager.texture,
textureB: newTextureManager.texture,
textureBoundsA: oldTextureBounds[index],
textureBoundsB: newTextureBounds[index],
interpolationFraction: interpolationFraction,
});
}));
};
for (var _i = 0, chunks_2 = chunks; _i < chunks_2.length; _i++) {
var chunk_2 = chunks_2[_i];
_loop_2(chunk_2);
}
onFrameRendered(canvasCoordinates);
};
animationHandle = regl.frame(function (_a) {
var time = _a.time;
var elapsedTimeMs = (time - transitionStart) * 1000;
var interpolationFraction = elapsedTimeMs / transitionDurationMs;
renderFrame(interpolationFraction);
});
return [4, Timer(transitionDurationMs)];
case 1:
_b.sent();
animationHandle.cancel();
renderFrame(1);
this.textureManager.destroy();
this.textureManager = newTextureManager;
return [2];
}
});
});
};
Renderer.prototype.renderTilesWithTransitionAndNewColorScale = function (oldTiles, newTiles, oldColorScale, newColorScale, oldSentinelValues, newSentinelValues, transitionDurationMs, onFrameRendered) {
return __awaiter(this, void 0, void 0, function () {
var _a, regl, textureManager, tileSize, canvasSize, canvasWidth, canvasHeight, canvasCoordinates, tilesWithCanvasCoordinates, newTextureManager, colorScaleA, colorScaleB, sentinelValuesA, sentinelValuesB, transitionStart, renderFrame, animationHandle;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = this, regl = _a.regl, textureManager = _a.textureManager, tileSize = _a.tileSize;
canvasSize = this.computeRequiredCanvasDimensions(oldTiles.length);
canvasWidth = canvasSize[0], canvasHeight = canvasSize[1];
this.setCanvasSize(canvasWidth, canvasHeight);
canvasCoordinates = this.getCanvasCoordinates(canvasWidth, canvasHeight, oldTiles.length);
tilesWithCanvasCoordinates = lodashEs.zipWith(oldTiles, newTiles, canvasCoordinates, function (oldTile, newTile, canvasCoords) { return ({
coords: oldTile.coords,
oldPixelData: oldTile.pixelData,
newPixelData: newTile.pixelData,
canvasCoords: canvasCoords,
}); });
newTextureManager = new TextureManager(regl, tileSize);
colorScaleA = convertColorScale(oldColorScale);
colorScaleB = convertColorScale(newColorScale);
sentinelValuesA = convertColorScale(oldSentinelValues);
sentinelValuesB = convertColorScale(newSentinelValues);
transitionStart = regl.now();
renderFrame = function (interpolationFraction) {
var chunks = lodashEs.chunk(tilesWithCanvasCoordinates, textureMana