UNPKG

leaflet.webgl-temperature-map-forked

Version:

Minimalist heat map Leaflet.js library using WebGL inspired by [temperature-map-gl](https://github.com/ham-systems/temperature-map-gl)

394 lines (350 loc) 15.7 kB
/* eslint-disable eol-last */ var vertex_shader_source = '\ attribute vec2 position;\ \ void main(void) {\ gl_Position = vec4(position.x*2.0-1.0, position.y*2.0-1.0, 1.0, 1.0);\ }\ '; var vertex_shader_stencil_poly = '\ attribute vec2 position;\ \ uniform float scale;\ uniform vec2 translation;\ \ void main(void) {\ gl_Position = vec4((position.x*scale-translation.x)*2.0-1.0, 1.0-(position.y*scale-translation.y)*2.0, 1.0, 1.0);\ }\ '; var fragment_shader_stencil_poly = '\ \ void main(void) {\ \ gl_FragColor = vec4(1.0, 0, 0, 1.0);\ }\ '; var computation_fragment_shader_source = '\ precision lowp float;\ \ uniform float ui;\ uniform vec2 xi;\ uniform float p;\ uniform float scale;\ uniform float range_factor;\ uniform vec2 screen_size;\ uniform vec2 translation;\ void main(void) {\ vec2 x = vec2((gl_FragCoord.x+translation.x)/screen_size.x, (gl_FragCoord.y+translation.y)/screen_size.y);\ float dist = distance(x, xi*scale);\ float wi = 1.0/pow(dist, p);\ gl_FragColor = vec4(ui*wi*range_factor, wi*range_factor, 0.0, 1.0);\ }\ '; var get_draw_fragment_shader_source = function get_draw_fragment_shader_source(_temp) { var _ref = _temp === void 0 ? {} : _temp, isNullColorized = _ref.isNullColorized; return " precision lowp float; uniform sampler2D computation_texture; uniform vec2 screen_size; uniform float gamma; void main(void) { vec4 data = texture2D(computation_texture, vec2(gl_FragCoord.x/screen_size.x, 1.0-gl_FragCoord.y/screen_size.y)); float val = data.x/data.y; vec3 color = vec3(max((val-0.5)*2.0, 0.0), " + (isNullColorized ? '1.0 - 2.0*abs(val - 0.3)' : '0.0') + ", max((0.5-val)*2.0, 0.0)); gl_FragColor.rgb = pow(color, vec3(1.0/gamma)); gl_FragColor.a = " + (isNullColorized ? '1.0' : 'max(abs((0.5 - val)*2.0), abs(max((val-0.5)*2.0, 0.0)))') + "; }"; }; function get_shader(gl, source, type) { if (type === 'fragment') { type = gl.FRAGMENT_SHADER; } else if (type === 'vertex') { type = gl.VERTEX_SHADER; } else { return null; } var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.log('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } function get_program(gl, vertex_shader, fragment_shader) { var program = gl.createProgram(); gl.attachShader(program, vertex_shader); gl.attachShader(program, fragment_shader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.log('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program)); } return program; } var default_options = { p: 5, canvas: null, zIndex: 10, opacity: 0.35, range_factor: 0.00390625, gamma: 2.2, debug_points: false, // work only for debug - not right position on zoom after move framebuffer_factor: 1, isNullColorized: true, point_text: function point_text(val) { var v; if (val < 1) v = val.toFixed(2);else if (val < 10) v = val.toFixed(1);else v = Math.round(val); return v; } }; var instance = 0; function TemperatureMapGl(options) { if (options === void 0) { options = {}; } var _options = {}; for (var k in default_options) { _options[k] = default_options[k]; } if (typeof options === 'object') { for (var _k in options) { _options[_k] = options[_k]; } } if (!_options.canvas) throw new Error("Don't set canvas"); instance++; var canvas = _options.canvas; canvas.style.position = 'absolute'; canvas.style.top = '0px'; canvas.style.left = '0px'; canvas.style.zIndex = _options.zIndex; canvas.style.opacity = _options.opacity; this.canvas = _options.canvas; this.context = this.canvas.getContext('webgl2'); if (!this.context) console.log('Your browser does not support webgl'); if (!this.context || !this.context.getExtension('OES_texture_half_float')) { console.log('Your browser does not support float textures'); this.context = null; } this.points = []; this.translated_points = []; this.square_vertices_buffer = null; this.computation_program = null; this.draw_program = null; this.position_attribute = null; this.computation_framebuffer = null; this.computation_texture = null; this.ui_uniform = null; this.xi_uniform = null; this.c_screen_size_uniform = null; this.d_screen_size_uniform = null; this.range_factor_uniform = null; this.p_uniform = null; this.computation_texture_uniform = null; this.gamma_uniform = null; this.point_text = _options.point_text; this.p = _options.p; this.range_factor = _options.range_factor; this.gamma = _options.gamma; this.isNullColorized = _options.isNullColorized; this.debug_points = _options.debug_points; this.unit = _options.unit; this.framebuffer_factor = _options.framebuffer_factor; this.computation_framebuffer_width = 0; this.computation_framebuffer_height = 0; this.init_shaders(); this.resize(this.canvas.clientWidth, this.canvas.clientHeight); } TemperatureMapGl.is_supported = function () { var canvas = document.createElement('canvas'); var context = canvas.getContext('webgl', { stencil: true }); return canvas && context && context.getExtension('OES_texture_half_float'); }; TemperatureMapGl.prototype.set_points = function (points, low_val, high_val, normal_val, _temp2) { var _ref2 = _temp2 === void 0 ? {} : _temp2, _ref2$mask = _ref2.mask, mask = _ref2$mask === void 0 ? false : _ref2$mask; this[mask ? 'mask_points' : 'points'] = points; if (!this.context) return; if (points.length) { var translated_points = []; var min = typeof low_val !== 'undefined' ? Math.min(points[0][2], low_val) : points[0][2]; var max = typeof high_val !== 'undefined' ? Math.max(points[0][2], high_val) : points[0][2]; var avg = typeof normal_val !== 'undefined' ? normal_val : 0; for (var i = 1; i < points.length; ++i) { var p = [points[i][0], points[i][1], points[i][2]]; if (p[2] > max) max = p[2]; if (p[2] < min) min = p[2]; if (typeof normal_val === 'undefined') avg += p[2] / points.length; } var w = this.canvas.width; var h = this.canvas.height; for (var _i = 0; _i < points.length; ++_i) { var _p = [points[_i][0], points[_i][1], points[_i][2]]; _p[0] = _p[0] / w; _p[1] = _p[1] / h; if (_p[2] > avg) _p[2] = max == avg ? 1.0 : 0.5 * (_p[2] - avg) / (max - avg) + 0.5;else _p[2] = min == avg ? 0.5 : -0.5 * (avg - _p[2]) / (avg - min) + 0.5; _p[2] = Math.max(Math.min(_p[2], 1.0), 0.0); if (_p[2] > max) max = _p[2]; if (_p[2] < min) min = _p[2]; translated_points.push(_p); } this[mask ? 'translated_mask_points' : 'translated_points'] = mask ? translated_points.reduce(function (result, _ref3) { var lat = _ref3[0], lng = _ref3[1]; return [].concat(result, [lat, lng]); }, []) : translated_points; } }; TemperatureMapGl.prototype.init_buffers = function () { if (!this.context) return; var gl = this.context; gl.getExtension('OES_texture_half_float'); this.square_vertices_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.square_vertices_buffer); var vertices = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(vertices), gl.STATIC_DRAW); this.stencil_vertices_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.stencil_vertices_buffer); var stencil_vertices = this.translated_mask_points || [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]; var pixelData = new Uint16Array(4096 * 2048 * 3); pixelData.fill(255); // Use lower precision: switch from float to unsigned byte for stencil vertices buffer gl.bufferData(gl.ARRAY_BUFFER, new Uint16Array(stencil_vertices), gl.STATIC_DRAW); this.computation_framebuffer_width = Math.ceil(this.canvas.width * this.framebuffer_factor); this.computation_framebuffer_height = Math.ceil(this.canvas.height * this.framebuffer_factor); this.computation_texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.computation_texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.computation_framebuffer_width, this.computation_framebuffer_height, 0, pixelData, gl.UNSIGNED_BYTE, null); this.computation_framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, this.computation_framebuffer); gl.viewport(0, 0, this.computation_framebuffer_width, this.computation_framebuffer_height); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.computation_texture, 0); gl.bindTexture(gl.TEXTURE_2D, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; TemperatureMapGl.prototype.init_shaders = function () { if (!this.context) return; var gl = this.context; gl.getExtension('OES_texture_half_float'); var vertex_shader = get_shader(gl, vertex_shader_source, 'vertex'); var stencil_vertex_shader = get_shader(gl, vertex_shader_stencil_poly, 'vertex'); var stencil_fragment_shader = get_shader(gl, fragment_shader_stencil_poly, 'fragment'); var computation_fragment_shader = get_shader(gl, computation_fragment_shader_source, 'fragment'); var draw_fragment_shader = get_shader(gl, get_draw_fragment_shader_source({ isNullColorized: this.isNullColorized }), 'fragment'); this.stencil_program = get_program(gl, stencil_vertex_shader, stencil_fragment_shader); this.stencil_position_attribute = gl.getAttribLocation(this.stencil_program, 'position'); this.stencil_scale_uniform = gl.getUniformLocation(this.stencil_program, 'scale'); this.stencil_translation_uniform = gl.getUniformLocation(this.stencil_program, 'translation'); this.computation_program = get_program(gl, vertex_shader, computation_fragment_shader); this.position_attribute = gl.getAttribLocation(this.computation_program, 'position'); this.ui_uniform = gl.getUniformLocation(this.computation_program, 'ui'); this.xi_uniform = gl.getUniformLocation(this.computation_program, 'xi'); this.c_screen_size_uniform = gl.getUniformLocation(this.computation_program, 'screen_size'); this.range_factor_uniform = gl.getUniformLocation(this.computation_program, 'range_factor'); this.p_uniform = gl.getUniformLocation(this.computation_program, 'p'); this.scale_uniform = gl.getUniformLocation(this.computation_program, 'scale'); this.translation_uniform = gl.getUniformLocation(this.computation_program, 'translation'); gl.enableVertexAttribArray(this.position_attribute); this.draw_program = get_program(gl, vertex_shader, draw_fragment_shader); this.d_screen_size_uniform = gl.getUniformLocation(this.draw_program, 'screen_size'); this.computation_texture_uniform = gl.getUniformLocation(this.draw_program, 'computation_texture'); this.gamma_uniform = gl.getUniformLocation(this.draw_program, 'gamma'); }; TemperatureMapGl.prototype.draw = function (_temp3) { var _ref4 = _temp3 === void 0 ? {} : _temp3, _ref4$scale = _ref4.scale, scale = _ref4$scale === void 0 ? 1.0 : _ref4$scale, _ref4$transform = _ref4.transform, transform = _ref4$transform === void 0 ? [0.0, 0.0] : _ref4$transform; if (!this.context) return; var w = this.canvas.width; var h = this.canvas.height; var gl = this.context; gl.disable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.enable(gl.STENCIL_TEST); gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.SRC_ALPHA, gl.SRC_ALPHA); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.useProgram(this.computation_program); gl.uniform2f(this.c_screen_size_uniform, this.computation_framebuffer_width, this.computation_framebuffer_height); gl.uniform1f(this.p_uniform, this.p); gl.uniform1f(this.range_factor_uniform, this.range_factor); gl.uniform1f(this.scale_uniform, scale); gl.uniform2f(this.translation_uniform, transform[0], transform[1]); gl.bindFramebuffer(gl.FRAMEBUFFER, this.computation_framebuffer); gl.viewport(0, 0, this.computation_framebuffer_width, this.computation_framebuffer_height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); for (var i = 0; i < this.translated_points.length; ++i) { var p = this.translated_points[i]; gl.uniform2f(this.xi_uniform, p[0], p[1]); gl.uniform1f(this.ui_uniform, p[2]); gl.bindBuffer(gl.ARRAY_BUFFER, this.square_vertices_buffer); gl.vertexAttribPointer(this.position_attribute, 2, gl.FLOAT, false, 0, 0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.clearColor(0.0, 0.0, 0.0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); gl.stencilFunc(gl.ALWAYS, 1, 0xff); gl.useProgram(this.stencil_program); gl.uniform1f(this.stencil_scale_uniform, scale); gl.uniform2f(this.stencil_translation_uniform, transform[0] / w, transform[1] / h); gl.bindBuffer(gl.ARRAY_BUFFER, this.stencil_vertices_buffer); gl.vertexAttribPointer(this.stencil_position_attribute, 2, gl.FLOAT, false, 0, 0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.stencil_points_count || 4); gl.stencilFunc(gl.EQUAL, 1, 0xff); gl.useProgram(this.draw_program); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.computation_texture); gl.uniform1i(this.computation_texture_uniform, 0); gl.uniform1f(this.gamma_uniform, this.gamma); gl.uniform2f(this.d_screen_size_uniform, this.canvas.width, this.canvas.height); gl.viewport(0, 0, this.canvas.width, this.canvas.height); gl.bindBuffer(gl.ARRAY_BUFFER, this.square_vertices_buffer); gl.vertexAttribPointer(this.position_attribute, 2, gl.FLOAT, false, 0, 0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); if (this.debug_points) this.debug_points(scale, transform);else this.hide_points(); }; TemperatureMapGl.prototype.hide_points = function () { var elements = document.getElementsByClassName('tmg-point-' + instance); while (elements[0]) { elements[0].parentNode.removeChild(elements[0]); } }; var oldScale = 1; TemperatureMapGl.prototype.debug_points = function (scale, offset) { this.hide_points(); for (var i = 0; i < this.points.length; ++i) { var p = this.points[i]; var dot = document.createElement('p'); dot.style.position = 'absolute'; if (oldScale !== scale) { p[3] = p[0] - offset[0] / scale; p[4] = p[1] - offset[1] / scale; } dot.style.left = (p[3] || p[0]) * scale + 'px'; dot.style.top = (p[4] || p[1]) * scale + 'px'; dot.style.zIndex = '' + (Number(this.canvas.style.zIndex) + 1); dot.className = 'tmg-point ' + 'tmg-point-' + instance; dot.innerHTML = this.point_text(p[2]); this.canvas.parentNode.insertBefore(dot, this.canvas.nextSibling); } oldScale = scale; }; TemperatureMapGl.prototype.resize = function (width, height) { if (width === void 0) { width = this.canvas.clientWidth; } if (height === void 0) { height = this.canvas.clientHeight; } this.canvas.height = height; this.canvas.width = width; this.canvas.style.height = this.canvas.height + 'px'; this.canvas.style.width = this.canvas.width + 'px'; this.init_buffers(); }; export default TemperatureMapGl;