diff --git a/demos/heatmap.html b/demos/heatmap.html
new file mode 100644
index 0000000000..44f99033ef
--- /dev/null
+++ b/demos/heatmap.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+ heatmap
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/engine/renderpass.js b/src/core/engine/renderpass.js
index 9a19c47b35..70311302be 100644
--- a/src/core/engine/renderpass.js
+++ b/src/core/engine/renderpass.js
@@ -1,25 +1,28 @@
import * as THREE from '../three';
-export class RenderPass {
+export default class RenderPass {
constructor(cfg) {
this.scene;
this.camera = cfg.camera;
this.renderer = cfg.renderer;
this.clearColor = cfg.clear.clearColor;
this.clearAlpha = cfg.clear.clearAlpha;
+ this.size = cfg.size ? cfg.size : cfg.renderer.getSize();
this._init(cfg);
}
_init() {
this.scene = new THREE.Scene();
- const parameters = { minFilter: THREE.NearestFilter,
- magFilter: THREE.NearestFilter,
+ const parameters = {
+ // minFilter: THREE.NearestFilter,
+ // magFilter: THREE.NearestFilter,
+ minFilter: THREE.LinearFilter,
+ magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: false,
depthBuffer: false
};
- const size = this.renderer.getSize();
- this.pass = new THREE.WebGLRenderTarget(size.width, size.height, parameters);
+ this.pass = new THREE.WebGLRenderTarget(this.size.width, this.size.height, parameters);
this.originClearColor = this.renderer.getClearColor();
this.originClearAlpha = this.renderer.getClearAlpha();
this.texture = this.pass.texture;
@@ -35,7 +38,8 @@ export class RenderPass {
render() {
this.renderer.setClearColor(this.clearColor, this.clearAlpha);
- this.renderer.render(this.scene, this.camera, this.pass, true); // this.pass,true
- this.renderer.setClearColor(this.clearColor, this.clearAlpha);
+ this.renderer.render(this.scene, this.camera, this.pass, true);
+ this.renderer.setClearColor(this.originClearColor, this.originClearAlpha);
+ this.texture = this.pass.texture;
}
}
diff --git a/src/core/three.js b/src/core/three.js
index 75cbe58b5d..3c05ab2d13 100644
--- a/src/core/three.js
+++ b/src/core/three.js
@@ -11,7 +11,9 @@ export { Mesh } from 'three/src/objects/Mesh.js';
export { Texture } from 'three/src/textures/Texture.js';
export { WebGLRenderTarget } from 'three/src/renderers/WebGLRenderTarget.js';
export { PerspectiveCamera } from 'three/src/cameras/PerspectiveCamera.js';
+export { OrthographicCamera } from 'three/src/cameras/OrthographicCamera.js';
export { BufferGeometry } from 'three/src/core/BufferGeometry.js';
+export { PlaneBufferGeometry } from 'three/src/geometries/PlaneGeometry.js';
export { Raycaster } from 'three/src/core/Raycaster.js';
export { Matrix4 } from 'three/src/math/Matrix4.js';
export { Matrix3 } from 'three/src/math/Matrix3.js';
@@ -20,7 +22,9 @@ export { Vector4 } from 'three/src/math/Vector4.js';
export { Vector3 } from 'three/src/math/Vector3.js';
export { Vector2 } from 'three/src/math/Vector2.js';
export { ShaderMaterial } from 'three/src/materials/ShaderMaterial.js';
+export { MeshBasicMaterial } from 'three/src/materials/MeshBasicMaterial.js';
export { DataTexture } from 'three/src/textures/DataTexture.js';
+export { Color } from 'three/src/math/Color.js';
export {
Float64BufferAttribute,
Float32BufferAttribute,
diff --git a/src/geom/buffer/heatmap.js b/src/geom/buffer/heatmap.js
new file mode 100644
index 0000000000..c52c6b58ae
--- /dev/null
+++ b/src/geom/buffer/heatmap.js
@@ -0,0 +1,105 @@
+import BufferBase from './bufferBase';
+import { colorScales } from '../../attr/colorscales';
+import * as THREE from '../../core/three';
+
+
+export default class HeatmapBuffer extends BufferBase {
+ geometryBuffer() {
+ const coordinates = this.get('coordinates');
+ const properties = this.get('properties');
+ const positions = [];
+ const dirs = [];
+ const weights = [];
+ const indices = [];
+
+ // 组织顶点数据
+ coordinates.forEach((geo, index) => {
+ const totalIndex = index * 4;
+ const props = properties[index];
+ const radius = props.size;
+ /* const tl = this._addVertex(geo, -1, -1);
+ const tr = this._addVertex(geo, 1, -1);
+ const bl = this._addVertex(geo, -1, 1);
+ const br = this._addVertex(geo, 1, 1);*/
+ const dir = this._addDir(-1, 1);
+ const dir1 = this._addDir(1, 1);
+ const dir2 = this._addDir(-1, -1);
+ const dir3 = this._addDir(1, -1);
+ positions.push(...geo, ...geo, ...geo, ...geo, ...geo, ...geo);
+ dirs.push(...dir, ...dir2, ...dir3, ...dir1, ...dir, ...dir3);
+ // dirs.push(...this._addDir(-1, 1), ...this._addDir(1, 1), ...this._addDir(-1, -1), ...this._addDir(1, -1));
+ weights.push(radius, radius, radius, radius, radius, radius);
+ indices.push(totalIndex, totalIndex + 2, totalIndex + 3, totalIndex, totalIndex + 3, totalIndex + 1);
+ });
+
+ this.attributes = {
+ vertices: positions,
+ indices,
+ dirs,
+ weights
+ };
+ }
+
+ _addVertex(position, dirX, dirY) {
+ const x = (position[0] * 2) + ((dirX + 1) / 2);
+ const y = (position[1] * 2) + ((dirY + 1) / 2);
+ const z = position[2];
+ return [ x, y, z ];
+ }
+
+ _addDir(dirX, dirY) {
+ const x = (dirX + 1) / 2;
+ const y = (dirY + 1) / 2;
+ return [ x, y ];
+ }
+
+}
+
+export function createColorRamp(colors) {
+ const colorImageData = getColorRamp(colors);
+ const colorTexture = getTexture(colorImageData);
+ return colorTexture;
+}
+
+function getColorRamp(name) {
+ let colorscale = name;
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ canvas.width = 256;
+ canvas.height = 1;
+ const gradient = ctx.createLinearGradient(0, 0, 256, 0);
+ let data = null;
+ if (typeof (colorscale) === 'string') {
+ colorscale = colorScales[name];
+ }
+ if (Object.prototype.toString.call(colorscale) === '[object Object]') {
+ const min = colorscale.positions[0];
+ const max = colorscale.positions[colorscale.positions.length - 1];
+
+ for (let i = 0; i < colorscale.colors.length; ++i) {
+ const value = (colorscale.positions[i] - min) / (max - min);
+ gradient.addColorStop(value, colorscale.colors[i]);
+ }
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, 256, 1);
+ data = new Uint8ClampedArray(ctx.getImageData(0, 0, 256, 1).data);
+ }
+ if (Object.prototype.toString.call(colorscale) === '[object Uint8Array]') {
+ data = ctx.createImageData(256, 1);
+ }
+
+ return new ImageData(data, 16, 16);
+
+}
+
+function getTexture(image) {
+ const texture = new THREE.Texture(image);
+ texture.magFilter = THREE.LinearFilter;
+ texture.minFilter = THREE.LinearFilter;
+ texture.format = THREE.RGBAFormat;
+ texture.type = THREE.UnsignedByteType;
+ texture.needsUpdate = true;
+ return texture;
+}
+
diff --git a/src/geom/material/heatmapMateial.js b/src/geom/material/heatmapMateial.js
new file mode 100644
index 0000000000..31e1064c37
--- /dev/null
+++ b/src/geom/material/heatmapMateial.js
@@ -0,0 +1,33 @@
+import * as THREE from '../../core/three';
+import Material from './material';
+import heatmap_intensity_vert from '../shader/heatmap_intensity_vert.glsl';
+import heatmap_intensity_frag from '../shader/heatmap_intensity_frag.glsl';
+import heatmap_colorize_vert from '../shader/heatmap_colorize_vert.glsl';
+import heatmap_colorize_frag from '../shader/heatmap_colorize_frag.glsl';
+
+export function HeatmapIntensityMaterial(opt) {
+ const material = new Material({
+ uniforms: {
+ u_intensity: { value: opt.intensity },
+ u_radius: { value: opt.radius },
+ u_zoom: { value: opt.zoom }
+ },
+ vertexShader: heatmap_intensity_vert,
+ fragmentShader: heatmap_intensity_frag,
+ transparent: true,
+ blending: THREE.AdditiveBlending
+ });
+ return material;
+}
+
+export function HeatmapColorizeMaterial(opt) {
+ const material = new Material({
+ uniforms: {
+ u_texture: { value: opt.texture },
+ u_colorRamp: { value: opt.colorRamp }
+ },
+ vertexShader: heatmap_colorize_vert,
+ fragmentShader: heatmap_colorize_frag
+ });
+ return material;
+}
diff --git a/src/geom/shader/heatmap_colorize_frag.glsl b/src/geom/shader/heatmap_colorize_frag.glsl
new file mode 100644
index 0000000000..e408ca3852
--- /dev/null
+++ b/src/geom/shader/heatmap_colorize_frag.glsl
@@ -0,0 +1,9 @@
+uniform sampler2D u_texture;
+uniform sampler2D u_colorRamp;
+varying vec2 v_uv;
+
+void main(){
+ float intensity = texture2D(u_texture,v_uv).r;
+ vec4 color = texture2D(u_colorRamp,vec2(0.5,1.0-intensity));
+ gl_FragColor = color * intensity;
+}
\ No newline at end of file
diff --git a/src/geom/shader/heatmap_colorize_vert.glsl b/src/geom/shader/heatmap_colorize_vert.glsl
new file mode 100644
index 0000000000..fb7bcf8ff1
--- /dev/null
+++ b/src/geom/shader/heatmap_colorize_vert.glsl
@@ -0,0 +1,5 @@
+varying vec2 v_uv;
+void main(){
+ v_uv = uv;
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+}
\ No newline at end of file
diff --git a/src/geom/shader/heatmap_intensity_frag.glsl b/src/geom/shader/heatmap_intensity_frag.glsl
new file mode 100644
index 0000000000..a70f8dd848
--- /dev/null
+++ b/src/geom/shader/heatmap_intensity_frag.glsl
@@ -0,0 +1,11 @@
+precision highp float;
+varying float v_weight;
+varying vec2 v_extrude;
+
+
+void main(){
+ float GAUSS_COEF = 0.3989422804014327;
+ float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude);
+ float val = v_weight * 10.0 * GAUSS_COEF * exp(d);
+ gl_FragColor = vec4(val, val, val, 1.0);
+}
diff --git a/src/geom/shader/heatmap_intensity_vert.glsl b/src/geom/shader/heatmap_intensity_vert.glsl
new file mode 100644
index 0000000000..504355316b
--- /dev/null
+++ b/src/geom/shader/heatmap_intensity_vert.glsl
@@ -0,0 +1,22 @@
+precision highp float;
+attribute float a_weight;
+attribute vec2 a_dir;
+uniform float u_intensity;
+uniform float u_radius;
+uniform float u_zoom;
+varying vec2 v_extrude;
+varying float v_weight;
+
+void main(){
+ v_weight = a_weight;
+ float GAUSS_COEF = 0.3989422804014327;
+ float ZERO = 1.0 / 255.0 / 16.0;
+ float extrude_x = a_dir.x * 2.0 -1.0;
+ float extrude_y = a_dir.y * 2.0 -1.0;
+ vec2 extrude_dir = normalize(vec2(extrude_x,extrude_y));
+ float S = sqrt(-2.0 * log(ZERO / a_weight / u_intensity / GAUSS_COEF)) / 3.0;
+ v_extrude = extrude_dir * S;
+ vec2 extrude = v_extrude * u_radius * pow(2.0,20.0-min(u_zoom,9.0));
+ vec4 pos = vec4( position.xy+ extrude, 0.0, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * pos;
+}
\ No newline at end of file
diff --git a/src/layer/heatmapLayer.js b/src/layer/heatmapLayer.js
new file mode 100644
index 0000000000..420a99ba97
--- /dev/null
+++ b/src/layer/heatmapLayer.js
@@ -0,0 +1,113 @@
+import Layer from '../core/layer';
+import HeatmapBuffer from '../geom/buffer/heatmap';
+import { createColorRamp } from '../geom/buffer/heatmap';
+import { HeatmapIntensityMaterial, HeatmapColorizeMaterial } from '../geom/material/heatmapMateial';
+import Renderpass from '../core/engine/renderpass';
+import * as THREE from '../core/three';
+
+export default class HeatmapLayer extends Layer {
+
+ render() {
+ this.init();
+ const bbox = this._calBoundingBox(this.layerSource.geoData);
+ const colors = this.get('styleOptions').rampColors;
+ this.colorRamp = createColorRamp(colors);
+ this._createIntensityPass(bbox);
+ this._createColorizePass(bbox);
+ }
+
+ _createIntensityPass(bbox) {
+ const source = this.layerSource;
+ const style = this.get('styleOptions');
+ const StyleData = this.StyleData;
+ // get attributes data
+ const buffer = new HeatmapBuffer({
+ coordinates: source.geoData,
+ properties: StyleData
+ });
+ const attributes = buffer.attributes;
+ // create geometery
+ const geometry = new THREE.BufferGeometry();
+ // geometry.setIndex(attributes.indices);
+ geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3));
+ geometry.addAttribute('a_dir', new THREE.Float32BufferAttribute(attributes.dirs, 2));
+ geometry.addAttribute('a_weight', new THREE.Float32BufferAttribute(attributes.weights, 1));
+ // set material
+ const material = new HeatmapIntensityMaterial({
+ intensity: style.intensity,
+ radius: style.radius,
+ zoom: this.scene.getZoom()
+ });
+ const mesh = new THREE.Mesh(geometry, material);
+ // set camera
+ const passOrth = new THREE.OrthographicCamera(bbox.width / -2, bbox.width / 2, bbox.height / 2, bbox.height / -2, 1, 10000);
+ passOrth.position.set(bbox.minX + bbox.width / 2, bbox.minY + bbox.height / 2, 1000);
+ // renderpass
+ const renderpass = new Renderpass({
+ renderer: this.scene._engine._renderer,
+ camera: passOrth,
+ size: {
+ width: 10000,
+ height: 10000 * (bbox.height / bbox.width)
+ },
+ clear: {
+ clearColor: 0x000000,
+ clearAlpha: 1.0
+ }
+ });
+ renderpass.add(mesh);
+ renderpass.render();
+ this.intensityPass = renderpass;
+ const scene = this.scene;
+ render();
+ function render() {
+ requestAnimationFrame(render);
+ renderpass.render();
+ mesh.material.uniforms.u_zoom.value = scene.getZoom();
+ }
+ }
+ _createColorizePass(bbox) {
+ // create plane geometry
+ const geometery = new THREE.PlaneBufferGeometry(bbox.width, bbox.height);
+ const material = new HeatmapColorizeMaterial({
+ texture: this.intensityPass.texture,
+ colorRamp: this.colorRamp
+ });
+ const mesh = new THREE.Mesh(geometery, material);
+ mesh.position.set(bbox.minX + bbox.width / 2, bbox.minY + bbox.height / 2, 0.0);
+ this.add(mesh);
+ }
+
+ _calBoundingBox(positions) {
+ let minX = Infinity;
+ let minY = Infinity;
+ let maxX = -Infinity;
+ let maxY = -Infinity;
+ for (let i = 0; i < positions.length; i++) {
+ const p = positions[i];
+ if (p[0] < minX) {
+ minX = p[0];
+ } else if (p[0] > maxX) {
+ maxX = p[0];
+ }
+ if (p[1] < minY) {
+ minY = p[1];
+ } else if (p[1] > maxY) {
+ maxY = p[1];
+ }
+ }
+ const width = maxX - minX;
+ const height = maxY - minY;
+
+ return {
+ minX,
+ maxX,
+ minY,
+ maxY,
+ width,
+ height
+ };
+ }
+
+
+}
diff --git a/src/layer/index.js b/src/layer/index.js
index 5e8fef27c6..8f5c3622ef 100644
--- a/src/layer/index.js
+++ b/src/layer/index.js
@@ -4,12 +4,14 @@ import PointLayer from './pointLayer';
import LineLayer from './lineLayer';
import ImageLayer from './imageLayer';
import RasterLayer from './rasterLayer';
+import HeatmapLayer from './heatmapLayer';
registerLayer('PolygonLayer', PolygonLayer);
registerLayer('PointLayer', PointLayer);
registerLayer('LineLayer', LineLayer);
registerLayer('ImageLayer', ImageLayer);
registerLayer('RasterLayer', RasterLayer);
+registerLayer('HeatmapLayer', HeatmapLayer);
export { LAYER_MAP } from './factory';
export { default as PolygonLayer } from './polygonLayer';
@@ -17,4 +19,5 @@ export { default as PointLayer } from './pointLayer';
export { default as LineLayer } from './lineLayer';
export { default as ImageLayer } from './imageLayer';
export { default as RasterLayer } from './rasterLayer';
+export { default as HeatmapLayer } from './heatmapLayer';
diff --git a/src/layer/lineLayer.js b/src/layer/lineLayer.js
index 4d40755100..fd103aa549 100644
--- a/src/layer/lineLayer.js
+++ b/src/layer/lineLayer.js
@@ -1,4 +1,3 @@
-
import Layer from '../core/layer';
import * as THREE from '../core/three';
import { LineBuffer } from '../geom/buffer/index';
diff --git a/src/layer/pointLayer.js b/src/layer/pointLayer.js
index a21a80b4a7..5eabb8bfad 100644
--- a/src/layer/pointLayer.js
+++ b/src/layer/pointLayer.js
@@ -13,7 +13,6 @@ import * as PointBuffer from '../geom/buffer/point/index';
* shape 3d cube,column, sphere
* shape Model ,自定义
* image
- *
*/
export default class PointLayer extends Layer {
diff --git a/src/layer/rasterLayer.js b/src/layer/rasterLayer.js
index 7a68dc8530..0c5cd44089 100644
--- a/src/layer/rasterLayer.js
+++ b/src/layer/rasterLayer.js
@@ -37,7 +37,6 @@ export default class RasterLayer extends Layer {
const rasterMesh = new THREE.Mesh(this.geometry, material);
this.add(rasterMesh);
return this;
-
}
animateFunc() {