Cleaned up the heatmap layer & use dynamic radius
This commit is contained in:
@@ -117,70 +117,18 @@ class Heatmap extends VectorLayer {
|
|||||||
|
|
||||||
this.setRadius(options.radius !== undefined ? options.radius : 8);
|
this.setRadius(options.radius !== undefined ? options.radius : 8);
|
||||||
|
|
||||||
listen(this,
|
|
||||||
getChangeEventType(Property.BLUR),
|
|
||||||
this.handleStyleChanged_, this);
|
|
||||||
listen(this,
|
|
||||||
getChangeEventType(Property.RADIUS),
|
|
||||||
this.handleStyleChanged_, this);
|
|
||||||
|
|
||||||
this.handleStyleChanged_();
|
|
||||||
|
|
||||||
const weight = options.weight ? options.weight : 'weight';
|
const weight = options.weight ? options.weight : 'weight';
|
||||||
let weightFunction;
|
|
||||||
if (typeof weight === 'string') {
|
if (typeof weight === 'string') {
|
||||||
weightFunction = function(feature) {
|
this.weightFunction_ = function(feature) {
|
||||||
return feature.get(weight);
|
return feature.get(weight);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
weightFunction = weight;
|
this.weightFunction_ = weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.setStyle(function(feature, resolution) {
|
|
||||||
// const weight = weightFunction(feature);
|
|
||||||
// const opacity = weight !== undefined ? clamp(weight, 0, 1) : 1;
|
|
||||||
// // cast to 8 bits
|
|
||||||
// const index = (255 * opacity) | 0;
|
|
||||||
// let style = this.styleCache_[index];
|
|
||||||
// if (!style) {
|
|
||||||
// style = [
|
|
||||||
// new Style({
|
|
||||||
// image: new Icon({
|
|
||||||
// opacity: opacity,
|
|
||||||
// src: this.circleImage_
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// ];
|
|
||||||
// this.styleCache_[index] = style;
|
|
||||||
// }
|
|
||||||
// return style;
|
|
||||||
// }.bind(this));
|
|
||||||
|
|
||||||
// For performance reasons, don't sort the features before rendering.
|
// For performance reasons, don't sort the features before rendering.
|
||||||
// The render order is not relevant for a heatmap representation.
|
// The render order is not relevant for a heatmap representation.
|
||||||
this.setRenderOrder(null);
|
this.setRenderOrder(null);
|
||||||
|
|
||||||
listen(this, RenderEventType.RENDER, this.handleRender_, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string} Data URL for a circle.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
createCircle_() {
|
|
||||||
// const radius = this.getRadius();
|
|
||||||
// const blur = this.getBlur();
|
|
||||||
// const halfSize = radius + blur + 1;
|
|
||||||
// const size = 2 * halfSize;
|
|
||||||
// const context = createCanvasContext2D(size, size);
|
|
||||||
// context.shadowOffsetX = context.shadowOffsetY = this.shadow_;
|
|
||||||
// context.shadowBlur = blur;
|
|
||||||
// context.shadowColor = '#000';
|
|
||||||
// context.beginPath();
|
|
||||||
// const center = halfSize - this.shadow_;
|
|
||||||
// context.arc(center, center, radius, 0, Math.PI * 2, true);
|
|
||||||
// context.fill();
|
|
||||||
// return context.canvas.toDataURL();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,35 +168,6 @@ class Heatmap extends VectorLayer {
|
|||||||
this.gradient_ = createGradient(this.getGradient());
|
this.gradient_ = createGradient(this.getGradient());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
handleStyleChanged_() {
|
|
||||||
this.circleImage_ = this.createCircle_();
|
|
||||||
this.styleCache_ = new Array(256);
|
|
||||||
this.changed();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("../render/Event.js").default} event Post compose event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
handleRender_(event) {
|
|
||||||
// const context = event.context;
|
|
||||||
// const canvas = context.canvas;
|
|
||||||
// const image = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
// const view8 = image.data;
|
|
||||||
// for (let i = 0, ii = view8.length; i < ii; i += 4) {
|
|
||||||
// const alpha = view8[i + 3] * 4;
|
|
||||||
// if (alpha) {
|
|
||||||
// view8[i] = this.gradient_[alpha];
|
|
||||||
// view8[i + 1] = this.gradient_[alpha + 1];
|
|
||||||
// view8[i + 2] = this.gradient_[alpha + 2];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// context.putImageData(image, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the blur size in pixels.
|
* Set the blur size in pixels.
|
||||||
* @param {number} blur Blur size in pixels.
|
* @param {number} blur Blur size in pixels.
|
||||||
@@ -284,6 +203,29 @@ class Heatmap extends VectorLayer {
|
|||||||
*/
|
*/
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
|
vertexShader: `
|
||||||
|
precision mediump float;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute vec2 a_texCoord;
|
||||||
|
attribute float a_rotateWithView;
|
||||||
|
attribute vec2 a_offsets;
|
||||||
|
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
|
uniform float u_size;
|
||||||
|
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
if (a_rotateWithView == 1.0) {
|
||||||
|
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
|
||||||
|
}
|
||||||
|
vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets * u_size;
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
}`,
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
uniform float u_opacity;
|
uniform float u_opacity;
|
||||||
@@ -300,6 +242,11 @@ class Heatmap extends VectorLayer {
|
|||||||
}
|
}
|
||||||
gl_FragColor.a = alpha * 0.1;
|
gl_FragColor.a = alpha * 0.1;
|
||||||
}`,
|
}`,
|
||||||
|
uniforms: {
|
||||||
|
u_size: function() {
|
||||||
|
return this.get(Property.RADIUS) * 10;
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
postProcesses: [
|
postProcesses: [
|
||||||
{
|
{
|
||||||
scaleRatio: 0.25
|
scaleRatio: 0.25
|
||||||
@@ -317,17 +264,16 @@ class Heatmap extends VectorLayer {
|
|||||||
void main() {
|
void main() {
|
||||||
vec4 color = texture2D(u_image, v_texCoord);
|
vec4 color = texture2D(u_image, v_texCoord);
|
||||||
gl_FragColor.rgb = texture2D(u_gradientTexture, vec2(0.5, color.a)).rgb;
|
gl_FragColor.rgb = texture2D(u_gradientTexture, vec2(0.5, color.a)).rgb;
|
||||||
// gl_FragColor.rgb = color.rgb;
|
|
||||||
gl_FragColor.a = smoothstep(0.0, 0.07, color.a);
|
gl_FragColor.a = smoothstep(0.0, 0.07, color.a);
|
||||||
}`,
|
}`,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
u_gradientTexture: this.gradient_
|
u_gradientTexture: this.gradient_,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
sizeCallback: function() {
|
sizeCallback: function(feature) {
|
||||||
return 50;
|
return this.weightFunction_(feature);
|
||||||
},
|
}.bind(this),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
|
|||||||
const options = opt_options || {};
|
const options = opt_options || {};
|
||||||
|
|
||||||
this.context_ = new WebGLHelper({
|
this.context_ = new WebGLHelper({
|
||||||
postProcesses: options.postProcesses
|
postProcesses: options.postProcesses,
|
||||||
|
uniforms: options.uniforms
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sourceRevision_ = -1;
|
this.sourceRevision_ = -1;
|
||||||
|
|||||||
@@ -147,6 +147,18 @@ class WebGLHelper extends Disposable {
|
|||||||
*/
|
*/
|
||||||
this.attribLocations_;
|
this.attribLocations_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds info about custom uniforms used in the post processing pass
|
||||||
|
* @type {Array<{value: *, texture?: WebGLTexture}>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.uniforms_ = [];
|
||||||
|
options.uniforms && Object.keys(options.uniforms).forEach(function(name) {
|
||||||
|
this.uniforms_.push({
|
||||||
|
name: name,
|
||||||
|
value: options.uniforms[name]
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
// initialize post processes from options
|
// initialize post processes from options
|
||||||
// if none given, use a default one
|
// if none given, use a default one
|
||||||
@@ -250,6 +262,8 @@ class WebGLHelper extends Disposable {
|
|||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
this.applyUniforms();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -462,6 +476,55 @@ class WebGLHelper extends Disposable {
|
|||||||
|
|
||||||
// TODO: shutdown program
|
// TODO: shutdown program
|
||||||
|
|
||||||
|
// todo
|
||||||
|
applyUniforms() {
|
||||||
|
const gl = this.getGL();
|
||||||
|
|
||||||
|
let value;
|
||||||
|
let textureSlot = 0;
|
||||||
|
this.uniforms_.forEach(function(uniform) {
|
||||||
|
value = typeof uniform.value === 'function' ? uniform.value() : uniform.value;
|
||||||
|
|
||||||
|
// apply value based on type
|
||||||
|
if (value instanceof HTMLCanvasElement || value instanceof ImageData) {
|
||||||
|
// create a texture & put data
|
||||||
|
if (!uniform.texture) {
|
||||||
|
uniform.texture = gl.createTexture();
|
||||||
|
}
|
||||||
|
gl.activeTexture(gl[`TEXTURE${textureSlot}`]);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, uniform.texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (value instanceof ImageData) {
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, value.width, value.height, 0,
|
||||||
|
gl.UNSIGNED_BYTE, new Uint8Array(value.data));
|
||||||
|
} else {
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill texture slots
|
||||||
|
gl.uniform1i(this.getUniformLocation(uniform.name), textureSlot++);
|
||||||
|
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
switch (value.length) {
|
||||||
|
case 2:
|
||||||
|
gl.uniform2f(this.getUniformLocation(uniform.name), value[0], value[1]);
|
||||||
|
return;
|
||||||
|
case 3:
|
||||||
|
gl.uniform3f(this.getUniformLocation(uniform.name), value[0], value[1], value[2]);
|
||||||
|
return;
|
||||||
|
case 4:
|
||||||
|
gl.uniform4f(this.getUniformLocation(uniform.name), value[0], value[1], value[2], value[3]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
gl.uniform1f(this.getUniformLocation(uniform.name), value);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number=} opt_wrapS wrapS.
|
* @param {number=} opt_wrapS wrapS.
|
||||||
* @param {number=} opt_wrapT wrapT.
|
* @param {number=} opt_wrapT wrapT.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class WebGLPostProcessingPass {
|
|||||||
this.renderTargetTextureLocation_ = gl.getUniformLocation(this.renderTargetProgram_, 'u_image');
|
this.renderTargetTextureLocation_ = gl.getUniformLocation(this.renderTargetProgram_, 'u_image');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds info about all the uniforms used in the post processing pass
|
* Holds info about custom uniforms used in the post processing pass
|
||||||
* @type {Array<{value: *, location: WebGLUniformLocation, texture?: WebGLTexture}>}
|
* @type {Array<{value: *, location: WebGLUniformLocation, texture?: WebGLTexture}>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user