mirror of https://gitee.com/antv-l7/antv-l7
feat heatmap layer
This commit is contained in:
parent
712e983b3e
commit
6ed876befe
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="geometry" content="diagram">
|
||||
<link rel="stylesheet" href="./assets/common.css">
|
||||
<title>heatmap</title>
|
||||
<style>
|
||||
#map { position:absolute; top:0; bottom:0; width:100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
|
||||
<script src="./assets/jquery-3.2.1.min.js"></script>
|
||||
<script src="./assets/dat.gui.min.js"></script>
|
||||
<script src="../build/L7.js"></script>
|
||||
<script>
|
||||
const scene = new L7.Scene({
|
||||
id: 'map',
|
||||
mapStyle: 'dark', // 样式URL
|
||||
center: [ -155, 60 ],
|
||||
pitch: 0,
|
||||
zoom: 4.5
|
||||
});
|
||||
window.scene = scene;
|
||||
scene.on('loaded', () => {
|
||||
$.get('./data/earthquakes.geojson', data => {
|
||||
scene.HeatmapLayer({
|
||||
zIndex: 2
|
||||
})
|
||||
.source(data)
|
||||
.size('mag', [ 0, 1 ]) // weight映射通道
|
||||
.style({
|
||||
intensity: 1,
|
||||
radius: 10,
|
||||
rampColors: {
|
||||
colors: [ 'rgba(33,102,172,0)', 'rgb(103,169,207)', 'rgb(209,229,240)', 'rgb(253,219,199)', 'rgb(239,138,98)', 'rgb(178,24,43)' ],
|
||||
positions: [ 0.0, 0.2, 0.4, 0.6, 0.8, 1.0 ]
|
||||
}
|
||||
})
|
||||
.render();
|
||||
|
||||
/* scene.PointLayer({
|
||||
zIndex: 3
|
||||
})
|
||||
.source(data)
|
||||
.shape('2d:circle')
|
||||
.size(2)
|
||||
.color('#EE2C2C')
|
||||
.render();*/
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
varying vec2 v_uv;
|
||||
void main(){
|
||||
v_uv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import Layer from '../core/layer';
|
||||
import * as THREE from '../core/three';
|
||||
import { LineBuffer } from '../geom/buffer/index';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -37,7 +37,6 @@ export default class RasterLayer extends Layer {
|
|||
const rasterMesh = new THREE.Mesh(this.geometry, material);
|
||||
this.add(rasterMesh);
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
animateFunc() {
|
||||
|
|
Loading…
Reference in New Issue