feat(hexagon): hexagon heatmap

This commit is contained in:
thinkinggis 2019-02-27 15:33:38 +08:00
parent 374554037c
commit 00d58a03c5
19 changed files with 260 additions and 14 deletions

View File

@ -1,5 +1,4 @@
const path = require('path');
module.exports = {
babelrc: {
presets: [
@ -10,7 +9,7 @@ module.exports = {
include: [
'src/**/*.js',
'test/**/*.js',
'node_modules/three/**/*.js',
'node_modules/_three@0.96.0@three/**/*.js',
'node_modules/simple-statistics/src/*.js'
],
exclude: [

76
demos/hexgon.html Normal file
View File

@ -0,0 +1,76 @@
<!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>point_circle</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: [120.132624,30.281774],
pitch: 0,
zoom: 10
});
scene.on('loaded', () => {
$.get('https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', data => {
var layer = scene.HeatMapLayer({
zIndex: 2
})
.source(data, {
parser: {
type: 'csv',
x: 'lng',
y: 'lat'
},
transforms:[
{
type: 'map',
callback:function(item){
const [x, y] = item.coordinates;
item.lat = item.lat*1;
item.lng = item.lng*1;
item.v = item.v *1;
item.coordinates = [x*1,y*1];
return item;
}
},
{
type: 'hexagon',
size: 6000,
field:'v',
method:'sum'
}
]
})
.active(true)
.shape('hexagon')
.style({
coverage: 0.9,
angle: 0,
})
.color('count', ["#002466","#105CB3","#2894E0","#CFF6FF","#FFF5B8","#FFAB5C","#F27049","#730D1C"])
.render();
console.log(layer);
});
});
</script>
</body>
</html>

View File

@ -78,11 +78,11 @@
"prepublishOnly": "npm run build-lib && npm run dist",
"screenshot": "node ./bin/screenshot.js",
"start": "npm run dev",
"test": "torch --compile --renderer --recursive test/unit",
"test": "torch --compile-opts ./.torch.compile.opts.js --compile --renderer --recursive test/unit",
"test-all": "npm run test && npm run test-bugs",
"test-bugs": "torch --compile --renderer --recursive test/bugs",
"test-bugs-live": "torch --compile --interactive --watch --recursive test/bugs",
"test-live": "torch --compile --interactive --watch --recursive test/unit",
"test-live": "torch --compile --interactive --watch --recursive test/unit",
"watch": "webpack --config webpack-dev.config.js",
"win-dev": "node ./bin/win-dev.js"
},

View File

@ -35,4 +35,4 @@ export {
BufferAttribute
} from 'three/src/core/BufferAttribute.js';
// export * from '../../build/Three.js';
// export * from '../../build/three.js';

View File

@ -0,0 +1,32 @@
import { polygonPath } from '../../shape/path';
import { fill } from '../../shape/polygon';
export default function hexagonBuffer(layerData) {
const attribute = {
vertices: [],
miter: [],
colors: [],
pickingIds: []
};
const a = Math.cos(Math.PI / 6);
const points = [
[ 0, -1, 0 ],
[ -a, -0.5, 0 ],
[ -a, 0.5, 0 ],
[ 0, 1, 0 ],
[ a, 0.5, 0 ],
[ a, -0.5, 0 ],
[ 0, -1, 0 ]
];
// const hexgonPoints = polygonPath(6);
const hexgonFill = fill([ points ]);
const { positionsIndex, positions } = hexgonFill;
layerData.forEach(element => {
positionsIndex.forEach(pointIndex => {
attribute.vertices.push(...element.coordinates);
attribute.miter.push(positions[pointIndex][0], positions[pointIndex][1]);
attribute.pickingIds.push(element.id);
attribute.colors.push(...element.color);
});
});
return attribute;
}

View File

@ -0,0 +1,31 @@
import grid_frag from '../shader/hexagon_frag.glsl';
import grid_vert from '../shader/hexagon_vert.glsl';
import Material from './material';
export default class hexagonMaterial extends Material {
getDefaultParameters() {
return {
uniforms: {
u_opacity: { value: 1.0 },
u_time: { value: 0 },
u_radius: { value: 0.01 },
u_angle: { value: 0.01 },
u_coverage: { value: 0.8 }
},
defines: {
}
};
}
constructor(_uniforms, _defines, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
this.type = 'hexagonMaterial';
this.defines = Object.assign(defines, _defines);
this.vertexShader = grid_vert;
this.fragmentShader = grid_frag;
this.transparent = true;
}
}

View File

@ -0,0 +1,8 @@
precision highp float;
uniform float u_opacity;
varying vec4 v_color;
void main() {
vec4 color = v_color;
gl_FragColor = color;
gl_FragColor.a =color.a*u_opacity;
}

View File

@ -0,0 +1,17 @@
precision highp float;
attribute vec2 miter;
attribute vec4 a_color;
uniform float u_radius;
uniform float u_coverage;
uniform float u_angle;
varying vec4 v_color;
void main() {
mat4 matModelViewProjection = projectionMatrix * modelViewMatrix;
mat2 rotationMatrix = mat2(cos(u_angle), sin(u_angle), -sin(u_angle), cos(u_angle));
v_color = a_color;
vec2 offset =vec2(rotationMatrix * miter * u_radius * u_coverage );
float x = position.x + offset.x;
float y = position.y + offset.y;
gl_Position = matModelViewProjection * vec4(x, y, position.z, 1.0);
}

View File

@ -2,7 +2,6 @@ precision highp float;
attribute vec4 a_color;
attribute float a_size;
attribute float a_shape;
attribute vec4 a_idColor;
uniform vec4 u_stroke;
uniform float u_strokeWidth;
uniform float u_opacity;

View File

@ -3,7 +3,6 @@ precision highp float;
#define diffuseRatio 0.4
#define specularRatio 0.1
attribute vec4 a_color;
attribute vec4 a_idColor;
attribute vec2 faceUv;
attribute vec3 a_shape;
attribute vec3 a_size;

View File

@ -1,6 +1,8 @@
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 hexagonBuffer from '../geom/buffer/heatmap/hexagon';
export default class HeatMapLayer extends Layer {
shape(type) {
@ -13,6 +15,30 @@ export default class HeatMapLayer extends Layer {
}
_prepareRender() {
this.init();
switch (this.shapeType) {
case 'grid' :
this._drawGrid();
break;
case 'hexagon' :
this._drawHexagon();
break;
default:
this._drawGrid();
}
}
_drawHexagon() {
const style = this.get('styleOptions');
const { radius } = this.layerSource.data;
this._buffer = new hexagonBuffer(this.layerData);
const config = {
...style,
radius
};
const Mesh = new DrawHexagon(this._buffer, config);
this.add(Mesh);
}
_drawGrid() {
this.type = 'heatmap';
const style = this.get('styleOptions');
const { xOffset, yOffset } = this.layerSource.data;
@ -25,4 +51,5 @@ export default class HeatMapLayer extends Layer {
const girdMesh = new DrawGrid(this._buffer, config);
this.add(girdMesh);
}
}

View File

@ -0,0 +1,21 @@
import * as THREE from '../../../core/three';
import GridMaterial from '../../../geom/material/hexagon';
export default function DrawHexagon(attributes, style) {
const { opacity, radius, angle, coverage } = style;
const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3));
geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miter, 2));
geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4));
geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1));
const material = new GridMaterial({
u_opacity: opacity,
u_radius: radius,
u_angle: angle / 180 * Math.PI,
u_coverage: coverage
}, {
SHAPE: false
});
const hexgonMesh = new THREE.Mesh(geometry, material);
return hexgonMesh;
}

View File

@ -8,6 +8,7 @@ import raster from './parser/raster';
import { registerTransform, registerParser } from './factory';
import { aggregatorToGrid } from './transform/grid';
import { pointToHexbin } from './transform/hexagon';
import { map } from './transform/map';
registerParser('geojson', geojson);
@ -18,6 +19,7 @@ registerParser('raster', raster);
// 注册transform
registerTransform('grid', aggregatorToGrid);
registerTransform('hexagon', pointToHexbin);
registerTransform('map', map);
export { getTransform, registerTransform, getParser, registerParser } from './factory';

View File

@ -17,8 +17,8 @@ export function aggregatorToGrid(data, option) {
const { gridHash, gridOffset } = _pointsGridHash(dataArray, size);
const layerData = _getGridLayerDataFromGridHash(gridHash, gridOffset, option);
return {
xOffset: gridOffset.xOffset / 360 * (256 << 20) / 2,
yOffset: gridOffset.xOffset / 360 * (256 << 20) / 2,
xOffset: gridOffset.xOffset / 360 * (256 << 20) / 2,
dataArray: layerData
};
}

View File

@ -20,7 +20,7 @@ export function pointToHexbin(data, option) {
.y(d => d.coordinates[1]);
const hexbinBins = newHexbin(screenPoints);
const result = {
size: pixlSize
radius: pixlSize
};
result.dataArray = hexbinBins.map((hex, index) => {
if (option.field && option.method) {
@ -31,8 +31,9 @@ export function pointToHexbin(data, option) {
item[option.method] = hex[option.method];
return {
...item,
count: hex.length,
coordinates: unProjectFlat([ hex.x, hex.y ]),
id: index + 1
_id: index + 1
};
});
return result;

View File

@ -0,0 +1,35 @@
import { expect } from 'chai';
import hexagonBuffer from '../../../../../src/geom/buffer/heatmap/hexagon';
describe('hexagon heatMap buffer', () => {
const layerData = [
{ color: [ 1, 1, 0, 1 ], coordinates: [ 120.12063099925889, 30.263947783103486, 0 ], id: 1 },
{ color: [ 1, 0.5, 0, 1 ], coordinates: [ 120.12218696039365, 30.263947783103486, 0 ], id: 2 },
{
color: [ 1, 0.1, 0, 1 ],
coordinates: [ 120.12374292152843, 30.263947783103486, 0 ],
id: 3
},
{ color: [ 1, 0, 0.2, 1 ], coordinates: [ 120.1252988826632, 30.263947783103486, 0 ], id: 4 },
{
color: [ 0, 1, 0, 1 ],
coordinates: [ 120.12607686323062, 30.263947783103486, 0 ],
id: 5
},
{
color: [ 1, 1, 0, 1 ],
coordinates: [ 120.12685484379799, 30.263947783103486, 0 ],
id: 6
},
{ color: [ 1, 1, 1, 1 ], coordinates: [ 120.12841080493274, 30.263947783103486, 0 ], id: 7 },
{ color: [ 0, 1, 1, 1 ], coordinates: [ 120.13230070776972, 30.263947783103486, 0 ], id: 8 },
{ color: [ 0, 1, 0, 1 ], coordinates: [ 120.12763282436538, 30.263947783103486, 0 ], id: 9 },
{ color: [ 1, 1, 0, 1 ], coordinates: [ 120.12996676606754, 30.263947783103486, 0 ], id: 10 }
];
it('hexagon buffer', () => {
const attribute = hexagonBuffer(layerData);
expect(attribute.colors.length).eql(480);
expect(attribute.miter.length).eql(240);
expect(attribute.pickingIds.length).eql(120);
expect(attribute.vertices.length).eql(360);
});
});

View File

@ -24,9 +24,8 @@ describe('test shader module', function() {
it('should import a module correctly.', function() {
const { vs, fs } = getModule('module1');
expect(vs).eq('#define PI 3.14');
expect(fs).eq('');
expect(fs.replace(/(\s+)|(\n)+|(\r\n)+/g, '')).eqls('#ifdefGL_FRAGMENT_PRECISION_HIGHprecisionhighpfloat;#elseprecisionmediumpfloat;#endif');
});
});

View File

@ -18,6 +18,5 @@ describe('hexagon Test', function() {
};
const hexgonGrid = pointToHexbin(data, { size: 100, field: 'v', method: 'sum' });
expect(hexgonGrid.dataArray.length).eql(567);
console.log(hexgonGrid);
});
});

View File

@ -4,7 +4,8 @@ const pkg = require('./package.json');
module.exports = {
devtool: 'cheap-source-map',
entry: {
l7: './src/index.js'
l7: './src/index.js',
three: './src/core/three.js'
},
output: {
filename: '[name].js',