merge heatmap

This commit is contained in:
thinkinggis 2019-03-01 15:03:47 +08:00
commit 5913a3f5e4
21 changed files with 383 additions and 31 deletions

View File

@ -29,7 +29,7 @@ const scene = new L7.Scene({
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
var layer = scene.HeatMapLayer({
var layer = scene.HeatmapLayer({
zIndex: 2
})
.source(data, {
@ -59,7 +59,7 @@ scene.on('loaded', () => {
}
]
})
.shape('gird')
.shape('grid')
.style({
coverage: 0.8
})

50
demos/heatmap.html Normal file
View File

@ -0,0 +1,50 @@
<!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('https://gw.alipayobjects.com/os/basement_prod/08c6ea00-dc5f-4bb0-b0b5-52bde5edf0a3.json', data => {
scene.HeatmapLayer({
zIndex: 2
})
.source(data)
.size('mag', [ 0, 1 ]) // weight映射通道
.style({
intensity: 3,
radius: 20,
rampColors: {
colors: [ 'rgba(33,102,172,0.0)', 'rgb(103,169,207)', 'rgb(209,229,240)', 'rgb(253,219,199)', 'rgb(239,138,98)', 'rgb(178,24,43,1.0)' ],
positions: [ 0, 0.2, 0.4, 0.6, 0.8, 1.0 ]
}
})
.render();
});
});
</script>
</body>
</html>

View File

@ -29,7 +29,7 @@ const scene = new L7.Scene({
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
var layer = scene.HeatMapLayer({
var layer = scene.HeatmapLayer({
zIndex: 2
})
.source(data, {

View File

@ -89,7 +89,8 @@
},
"pre-commit": {
"run": [
"lint"
"lint",
"test"
],
"silent": false
},

View File

@ -1,25 +1,27 @@
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._init(cfg);
}
_init() {
this.scene = new THREE.Scene();
const parameters = { minFilter: THREE.NearestFilter,
this.size = cfg.size ? cfg.size : cfg.renderer.getSize();
const defaultRenderCfg = {
minFilter: THREE.NearestFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBAFormat,
stencilBuffer: false,
depthBuffer: false
};
const size = this.renderer.getSize();
this.pass = new THREE.WebGLRenderTarget(size.width, size.height, parameters);
this.renderCfg = cfg.renderCfg ? cfg.renderCfg : defaultRenderCfg;
this._init(cfg);
}
_init() {
this.scene = new THREE.Scene();
this.pass = new THREE.WebGLRenderTarget(this.size.width, this.size.height, this.renderCfg);
this.originClearColor = this.renderer.getClearColor();
this.originClearAlpha = this.renderer.getClearAlpha();
this.texture = this.pass.texture;
@ -35,7 +37,7 @@ 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);
}
}

View File

@ -12,7 +12,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';
@ -22,6 +24,7 @@ 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 { DataTexture } from 'three/src/textures/DataTexture.js';
export { Color } from 'three/src/math/Color.js';
export {
Float64BufferAttribute,
Float32BufferAttribute,

View File

@ -0,0 +1,101 @@
import BufferBase from '../bufferBase';
import { colorScales } from '../../../attr/colorscales';
import * as THREE from '../../../core/three';
export default class HeatmapBuffer extends BufferBase {
geometryBuffer() {
const data = this.get('data');
const positions = [];
const dirs = [];
const weights = [];
// const indices = [];
// 组织顶点数据
data.forEach(d => {
// const totalIndex = index * 4;
const coord = d.coordinates;
const weight = d.size;
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(...coord, ...coord, ...coord, ...coord, ...coord, ...coord);
dirs.push(...dir, ...dir2, ...dir3, ...dir1, ...dir, ...dir3);
weights.push(weight, weight, weight, weight, weight, weight);
// 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 = 1;
canvas.height = 256;
const gradient = ctx.createLinearGradient(0, 0, 0, 256);
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, 1, 256);
data = new Uint8ClampedArray(ctx.getImageData(0, 0, 1, 256).data);
}
if (Object.prototype.toString.call(colorscale) === '[object Uint8Array]') {
data = ctx.createImageData(1, 256);
}
return new ImageData(data, 1, 256);
}
function getTexture(image) {
const texture = new THREE.Texture(image);
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestFilter;
texture.format = THREE.RGBAFormat;
texture.type = THREE.UnsignedByteType;
texture.needsUpdate = true;
return texture;
}

View File

@ -0,0 +1,34 @@
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,
transparent: true
});
return material;
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
varying vec2 v_uv;
void main(){
v_uv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

View File

@ -0,0 +1,12 @@
precision highp float;
uniform float u_intensity;
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 * u_intensity * GAUSS_COEF * exp(d);
gl_FragColor = vec4(val, val, val, val);
}

View File

@ -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-u_zoom);
vec4 pos = vec4( position.xy+ extrude, 0.0, 1.0);
gl_Position = projectionMatrix * modelViewMatrix * pos;
}

View File

@ -4,13 +4,8 @@ import Scene from './core/scene';
import Global from './global';
const version = Global.version;
const track = function(enable) {
Global.trackable = enable;
};
export {
version,
Scene,
track
Scene
};

View File

@ -2,6 +2,7 @@ import Layer from '../core/layer';
import gridBuffer from '../geom/buffer/heatmap/grid';
import DrawGrid from './render/heatmap/gird';
import DrawHexagon from './render/heatmap/hexagon';
import drawHeatmap from './render/heatmap/heatmap';
import hexagonBuffer from '../geom/buffer/heatmap/hexagon';
export default class HeatMapLayer extends Layer {
@ -15,6 +16,7 @@ export default class HeatMapLayer extends Layer {
}
_prepareRender() {
this.init();
this.type = 'heatmap';
switch (this.shapeType) {
case 'grid' :
this._drawGrid();
@ -23,7 +25,7 @@ export default class HeatMapLayer extends Layer {
this._drawHexagon();
break;
default:
this._drawGrid();
drawHeatmap(this);
}
}
_drawHexagon() {
@ -38,7 +40,6 @@ export default class HeatMapLayer extends Layer {
this.add(Mesh);
}
_drawGrid() {
this.type = 'heatmap';
const style = this.get('styleOptions');
const { xOffset, yOffset } = this.layerSource.data;
this._buffer = new gridBuffer(this.layerData);

View File

@ -4,14 +4,14 @@ import PointLayer from './pointLayer';
import LineLayer from './lineLayer';
import ImageLayer from './imageLayer';
import RasterLayer from './rasterLayer';
import HeatMapLayer from './heatmap';
import HeatmapLayer from './heatmapLayer';
registerLayer('PolygonLayer', PolygonLayer);
registerLayer('PointLayer', PointLayer);
registerLayer('LineLayer', LineLayer);
registerLayer('ImageLayer', ImageLayer);
registerLayer('RasterLayer', RasterLayer);
registerLayer('HeatMapLayer', HeatMapLayer);
registerLayer('HeatmapLayer', HeatmapLayer);
export { LAYER_MAP } from './factory';

View File

@ -1,4 +1,3 @@
import Layer from '../core/layer';
import * as THREE from '../core/three';
import { LineBuffer } from '../geom/buffer/index';

View File

@ -12,7 +12,6 @@ const { pointShape } = Global;
* shape 3d cubecolumn, sphere
* shape Model ,自定义
* image
*
*/
export default class PointLayer extends Layer {

View File

@ -31,7 +31,6 @@ export default class RasterLayer extends Layer {
const rasterMesh = new THREE.Mesh(this.geometry, material);
this.add(rasterMesh);
return this;
}
animateFunc() {

View File

@ -0,0 +1,123 @@
import HeatmapBuffer from '../../../geom/buffer/heatmap/heatmap';
import { createColorRamp } from '../../../geom/buffer/heatmap/heatmap';
import { HeatmapIntensityMaterial, HeatmapColorizeMaterial } from '../../../geom/material/heatmapMateial';
import Renderpass from '../../../core/engine/renderpass';
import * as THREE from '../../../core/three';
export default function drawHeatmap(layer) {
const bbox = calBoundingBox(layer.layerData);
const colors = layer.get('styleOptions').rampColors;
layer.colorRamp = createColorRamp(colors);
createIntensityPass(layer, bbox);
createColorizePass(layer, bbox);
}
function createIntensityPass(layer, bbox) {
const style = layer.get('styleOptions');
const data = layer.layerData;
// get attributes data
const buffer = new HeatmapBuffer({
data
});
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: layer.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 renderer = layer.scene._engine._renderer;
// get extension for bilinear texture interpolation:https://threejs.org/docs/#api/en/textures/DataTexture
const gl = renderer.domElement.getContext('webgl') ||
renderer.domElement.getContext('experimental-webgl');
gl.getExtension('OES_texture_float_linear');
const renderpass = new Renderpass({
renderer,
camera: passOrth,
size: {
width: 2000,
height: 2000 * (bbox.height / bbox.width)
},
clear: {
clearColor: 0x000000,
clearAlpha: 0.0
},
renderCfg: {
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping,
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: false,
depthBuffer: false
}
});
renderpass.add(mesh);
renderpass.render();
layer.intensityPass = renderpass;
const scene = layer.scene;
render();
function render() {
requestAnimationFrame(render);
const zoom = scene.getZoom();
mesh.material.uniforms.u_zoom.value = zoom;
const passWidth = Math.min(10000, Math.pow(zoom, 2.0) * 300);
const passHeight = passWidth * (bbox.height / bbox.width);
renderpass.pass.setSize(passWidth, passHeight);
renderpass.render();
}
}
function createColorizePass(layer, bbox) {
// create plane geometry
const geometery = new THREE.PlaneBufferGeometry(bbox.width, bbox.height);
const material = new HeatmapColorizeMaterial({
texture: layer.intensityPass.texture,
colorRamp: layer.colorRamp
});
const mesh = new THREE.Mesh(geometery, material);
mesh.position.set(bbox.minX + bbox.width / 2, bbox.minY + bbox.height / 2, 0.0);
layer.add(mesh);
}
function calBoundingBox(data) {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (let i = 0; i < data.length; i++) {
const p = data[i].coordinates;
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
};
}

View File

@ -21,7 +21,6 @@ export default function DrawAnimate(attributes, style) {
ANIMATE: true
});
const fillPolygonMesh = new THREE.Mesh(geometry, material);
this.fillPolygonMesh = fillPolygonMesh;
return fillPolygonMesh;
}

View File

@ -1,2 +0,0 @@