diff --git a/demos/heatmap.html b/demos/heatmap.html index ed25b8dbed..0a5d3ff327 100644 --- a/demos/heatmap.html +++ b/demos/heatmap.html @@ -13,14 +13,14 @@ </head> <body> <div id="map"></div> -<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script> +<script src="https://webapi.amap.com/maps?v=1.4.8&key=f28fca5384129d180ad82915156a9baf&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 + mapStyle: 'amap://styles/c9f1d10cae34f8ab05e425462c5a58d7', // 样式URL center: [ -155, 60 ], pitch: 0, zoom: 4.5 @@ -35,11 +35,12 @@ scene.on('loaded', () => { .source(data) .size('mag', [ 0, 1 ]) // weight映射通道 .style({ - intensity: 100, - radius: 30, + intensity: 10, + radius: 10, + opacity:1, 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 ] + colors: [ '#ffda45ff', '#fde725ff', '#ffc934ff', '#ffb824ff', '#ffb824ff', '#fa8100ff' ], + positions: [ 0, 0.2, 0.4, 0.6, 0.9, 1.0 ] } }) .render(); diff --git a/package.json b/package.json index 5a8d96aa9a..7dc407177f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@antv/l7", - "version": "1.1.5", + "version": "1.1.7", "description": "Large-scale WebGL-powered Geospatial Data Visualization", "main": "build/l7.js", "browser": "build/l7.js", @@ -111,6 +111,7 @@ "polyline-normals": "^2.0.2", "rbush": "^2.0.2", "simple-statistics": "^7.0.1", + "supercluster": "^6.0.1", "three": "^0.101.1", "venn.js": "^0.2.20", "viewport-mercator-project": "^5.2.0", diff --git a/src/core/engine/composer.js b/src/core/engine/composer.js old mode 100644 new mode 100755 index 5a1e2672ce..0575cfe3f2 --- a/src/core/engine/composer.js +++ b/src/core/engine/composer.js @@ -1,10 +1,10 @@ // jscs:disable /* eslint-disable */ -import THREE from '../three'; -import CopyShader from './CopyShader'; -import ShaderPass from './ShaderPass'; -import MaskPass, {ClearMaskPass} from './MaskPass'; +import * as THREE from '../three'; +import CopyShader from './copy-shader'; +import ShaderPass from './shader-pass'; +import MaskPass, {ClearMaskPass} from './mask-pass'; /** * @author alteredq / http://alteredqualia.com/ @@ -50,7 +50,8 @@ EffectComposer.prototype = { this.writeBuffer = tmp; }, - + visible:true, + type:'composer', addPass: function ( pass ) { this.passes.push( pass ); @@ -71,7 +72,6 @@ EffectComposer.prototype = { var maskActive = false; var pass, i, il = this.passes.length; - for ( i = 0; i < il; i ++ ) { pass = this.passes[ i ]; @@ -147,4 +147,3 @@ EffectComposer.prototype = { }; export default EffectComposer; -THREE.EffectComposer = EffectComposer; diff --git a/src/core/engine/copy-shader.js b/src/core/engine/copy-shader.js new file mode 100755 index 0000000000..afed95ae63 --- /dev/null +++ b/src/core/engine/copy-shader.js @@ -0,0 +1,53 @@ +// jscs:disable +/* eslint-disable */ + +import * as THREE from '../three'; + +/** + * @author alteredq / http://alteredqualia.com/ + * + * Full-screen textured quad shader + */ + +var CopyShader = { + + uniforms: { + + "tDiffuse": { type: "t", value: null }, + "opacity": { type: "f", value: 1.0 } + + }, + + vertexShader: [ + + "varying vec2 vUv;", + + "void main() {", + + "vUv = uv;", + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "uniform float opacity;", + + "uniform sampler2D tDiffuse;", + + "varying vec2 vUv;", + + "void main() {", + + "vec4 texel = texture2D( tDiffuse, vUv );", + "gl_FragColor = opacity * texel;", + + "}" + + ].join( "\n" ) + +}; + +export default CopyShader; diff --git a/src/core/engine/effect-composer.js b/src/core/engine/effect-composer.js new file mode 100755 index 0000000000..23a8d690ed --- /dev/null +++ b/src/core/engine/effect-composer.js @@ -0,0 +1,21 @@ +import EffectComposer from './composer'; + +export default function(renderer, container) { + const composer = new EffectComposer(renderer); + + const updateSize = function() { + // TODO: Re-enable this when perf issues can be solved + // + // Rendering double the resolution of the screen can be really slow + // var pixelRatio = window.devicePixelRatio; + const pixelRatio = 1; + + composer.setSize(container.clientWidth * pixelRatio, container.clientHeight * pixelRatio); + }; + + window.addEventListener('resize', updateSize, false); + updateSize(); + + return composer; +} + diff --git a/src/core/engine/index.js b/src/core/engine/index.js index a12f2e06f6..a08bc77d76 100644 --- a/src/core/engine/index.js +++ b/src/core/engine/index.js @@ -16,14 +16,16 @@ export default class Engine extends EventEmitter { this._scene.add(this.world); this._picking = Picking(this._world, this._renderer, this._camera, this._scene); this.clock = new THREE.Clock(); + this.composerLayers = []; } _initPostProcessing() { - + this.composerLayers.forEach(layer => { + layer.visible && layer.render(); + }); } update() { - this._renderer.render(this._scene, this._camera); - + this._initPostProcessing(); } destroy() { diff --git a/src/core/engine/mask-pass.js b/src/core/engine/mask-pass.js new file mode 100755 index 0000000000..b4f965bbb7 --- /dev/null +++ b/src/core/engine/mask-pass.js @@ -0,0 +1,94 @@ +// jscs:disable +/* eslint-disable */ + +import * as THREE from '../three'; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +var MaskPass = function ( scene, camera ) { + + this.scene = scene; + this.camera = camera; + + this.enabled = true; + this.clear = true; + this.needsSwap = false; + + this.inverse = false; + +}; + +MaskPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + var context = renderer.context; + + // don't update color or depth + + context.colorMask( false, false, false, false ); + context.depthMask( false ); + + // set up stencil + + var writeValue, clearValue; + + if ( this.inverse ) { + + writeValue = 0; + clearValue = 1; + + } else { + + writeValue = 1; + clearValue = 0; + + } + + context.enable( context.STENCIL_TEST ); + context.stencilOp( context.REPLACE, context.REPLACE, context.REPLACE ); + context.stencilFunc( context.ALWAYS, writeValue, 0xffffffff ); + context.clearStencil( clearValue ); + + // draw into the stencil buffer + + renderer.render( this.scene, this.camera, readBuffer, this.clear ); + renderer.render( this.scene, this.camera, writeBuffer, this.clear ); + + // re-enable update of color and depth + + context.colorMask( true, true, true, true ); + context.depthMask( true ); + + // only render where stencil is set to 1 + + context.stencilFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 + context.stencilOp( context.KEEP, context.KEEP, context.KEEP ); + + } + +}; + + +var ClearMaskPass = function () { + + this.enabled = true; + +}; + +ClearMaskPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + var context = renderer.context; + + context.disable( context.STENCIL_TEST ); + + } + +}; + +export default MaskPass; +export {ClearMaskPass as ClearMaskPass}; diff --git a/src/core/engine/render-pass.js b/src/core/engine/render-pass.js new file mode 100644 index 0000000000..5a0dfc384c --- /dev/null +++ b/src/core/engine/render-pass.js @@ -0,0 +1,58 @@ +// jscs:disable +/* eslint-disable */ + +import * as THREE from '../three'; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +var RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { + + this.scene = scene; + this.camera = camera; + + this.overrideMaterial = overrideMaterial; + + this.clearColor = clearColor; + this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 1; + + this.oldClearColor = new THREE.Color(); + this.oldClearAlpha = 1; + + this.enabled = true; + this.clear = false; + this.needsSwap = false; + +}; + +RenderPass.prototype = { + + render: function ( renderer, writeBuffer, readBuffer, delta ) { + + this.scene.overrideMaterial = this.overrideMaterial; + if ( this.clearColor ) { + + this.oldClearColor.copy( renderer.getClearColor() ); + this.oldClearAlpha = renderer.getClearAlpha(); + + renderer.setClearColor( this.clearColor, this.clearAlpha ); + + } + + renderer.render( this.scene, this.camera, readBuffer, this.clear ); + + + if ( this.clearColor ) { + + renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); + + } + + this.scene.overrideMaterial = null; + + } + +}; + +export default RenderPass; diff --git a/src/core/engine/renderpass.js b/src/core/engine/renderpass.js deleted file mode 100644 index f6d3f0c0df..0000000000 --- a/src/core/engine/renderpass.js +++ /dev/null @@ -1,45 +0,0 @@ -import * as THREE from '../three'; - -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(); - const defaultRenderCfg = { - minFilter: THREE.NearestFilter, - magFilter: THREE.NearestFilter, - format: THREE.RGBAFormat, - stencilBuffer: false, - depthBuffer: false - }; - 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; - } - - add(mesh) { - this.scene.add(mesh); - } - - remove(mesh) { - this.scene.remove(mesh); - } - - render() { - - this.renderer.setClearColor(this.clearColor, this.clearAlpha); - this.renderer.render(this.scene, this.camera, this.pass, true); - this.renderer.setRenderTarget(null); - this.renderer.setClearColor(this.originClearColor, this.originClearAlpha); - } -} diff --git a/src/core/engine/shader-pass.js b/src/core/engine/shader-pass.js new file mode 100644 index 0000000000..6e91880e81 --- /dev/null +++ b/src/core/engine/shader-pass.js @@ -0,0 +1,74 @@ +// jscs:disable +/* eslint-disable */ + +import * as THREE from '../three'; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +var ShaderPass = function( shader, textureID ) { + + this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; + + if ( shader instanceof THREE.ShaderMaterial ) { + + this.uniforms = shader.uniforms; + + this.material = shader; + + } + else if ( shader ) { + + this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + this.material = new THREE.ShaderMaterial( { + + defines: shader.defines || {}, + uniforms: this.uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader + + } ); + + } + + this.renderToScreen = false; + + this.enabled = true; + this.needsSwap = true; + this.clear = true; + + + this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); + this.scene = new THREE.Scene(); + + this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); + this.scene.add( this.quad ); + +}; + +ShaderPass.prototype = { + + render: function( renderer, writeBuffer, readBuffer, delta ) { + if ( this.uniforms[ this.textureID ] ) { + this.uniforms[ this.textureID ].value = readBuffer.texture; + + } + renderer.autoClear = false; + this.quad.material = this.material; + + if ( this.renderToScreen ) { + renderer.render( this.scene, this.camera ); + + } else { + + renderer.render( this.scene, this.camera, writeBuffer, this.clear ); + + } + renderer.autoClear = true; + } + +}; + +export default ShaderPass; diff --git a/src/core/layer.js b/src/core/layer.js index a8b101b97d..4852e0afb7 100644 --- a/src/core/layer.js +++ b/src/core/layer.js @@ -82,8 +82,14 @@ export default class Layer extends Base { * @param {*} type mesh类型是区别是填充还是边线 */ add(object, type = 'fill') { - type === 'fill' ? this.layerMesh = object : this.layerLineMesh = object; + // composer合图层绘制 + if (object.type === 'composer') { + this._object3D = object; + this.scene._engine.composerLayers.push(object); + return; + } + type === 'fill' ? this.layerMesh = object : this.layerLineMesh = object; this._visibleWithZoom(); this._zoomchangeHander = this._visibleWithZoom.bind(this); this.scene.on('zoomchange', this._zoomchangeHander); @@ -108,6 +114,12 @@ export default class Layer extends Base { } } remove(object) { + if (object.type === 'composer') { + this.scene._engine.composerLayers = this.scene._engine.composerLayers.filter(layer => { + return (layer !== object); + }); + return; + } this._object3D.remove(object); } _getUniqueId() { @@ -650,6 +662,11 @@ export default class Layer extends Base { */ destroy() { this.removeAllListeners(); + if (this._object3D.type === 'composer') { + this.remove(this._object3D); + + return; + } if (this._object3D && this._object3D.children) { let child; for (let i = 0; i < this._object3D.children.length; i++) { diff --git a/src/core/source.js b/src/core/source.js index 1a3847dfa5..305e535178 100644 --- a/src/core/source.js +++ b/src/core/source.js @@ -47,6 +47,10 @@ export default class Source extends Base { }); this._transforms = trans; } + transform(option) { + const data = getTransform(option.type)(this.data, option); + Object.assign(this.data, data); + } _projectCoords() { this.data.dataArray.forEach(data => { // data.coordinates = this._coordProject(data.coordinates); diff --git a/src/core/three.js b/src/core/three.js index a2e468cafd..f832dfbf66 100644 --- a/src/core/three.js +++ b/src/core/three.js @@ -18,6 +18,7 @@ export { InstancedBufferGeometry } from 'three/src/core/InstancedBufferGeometry' export { PlaneBufferGeometry } from 'three/src/geometries/PlaneGeometry.js'; export { BoxBufferGeometry } from 'three/src/geometries/BoxGeometry.js'; export { Raycaster } from 'three/src/core/Raycaster.js'; +export { UniformsUtils } from 'three/src/renderers/shaders/UniformsUtils.js'; export { Matrix4 } from 'three/src/math/Matrix4.js'; export { Matrix3 } from 'three/src/math/Matrix3.js'; export { Line } from 'three/src/objects/Line.js'; diff --git a/src/geom/material/heatmapMateial.js b/src/geom/material/heatmapMateial.js index 5c0aa893a5..315d19c7b9 100644 --- a/src/geom/material/heatmapMateial.js +++ b/src/geom/material/heatmapMateial.js @@ -13,6 +13,7 @@ export function HeatmapIntensityMaterial(opt) { vertexShader: vs, fragmentShader: fs, transparent: true, + depthTest: false, blending: THREE.AdditiveBlending }); return material; @@ -23,7 +24,8 @@ export function HeatmapColorizeMaterial(opt) { const material = new Material({ uniforms: { u_texture: { value: opt.texture }, - u_colorRamp: { value: opt.colorRamp } + u_colorRamp: { value: opt.colorRamp }, + u_opacity: { value: opt.opacity } }, vertexShader: vs, fragmentShader: fs, diff --git a/src/geom/shader/heatmap_colorize_frag.glsl b/src/geom/shader/heatmap_colorize_frag.glsl index cf531398db..4cd75f67ed 100644 --- a/src/geom/shader/heatmap_colorize_frag.glsl +++ b/src/geom/shader/heatmap_colorize_frag.glsl @@ -1,11 +1,12 @@ uniform sampler2D u_texture; uniform sampler2D u_colorRamp; + uniform float u_opacity; 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; - gl_FragColor.a = color.a * smoothstep(0.,0.05,intensity); + gl_FragColor.a = color.a * smoothstep(0.,0.05,intensity) * u_opacity; } \ No newline at end of file diff --git a/src/layer/heatmapLayer.js b/src/layer/heatmapLayer.js index 0a6931bfb2..ae8e34b1df 100644 --- a/src/layer/heatmapLayer.js +++ b/src/layer/heatmapLayer.js @@ -2,7 +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, updateIntensityPass } from './render/heatmap/heatmap'; +import { drawHeatmap } from './render/heatmap/heatmap'; import hexagonBuffer from '../geom/buffer/heatmap/hexagon'; export default class HeatMapLayer extends Layer { @@ -56,10 +56,10 @@ export default class HeatMapLayer extends Layer { this.add(girdMesh); } - afterRender() { - if (this.shapeType !== 'grid' && this.shapeType !== 'hexagon') { - updateIntensityPass(this); - } - } + // afterRender() { + // if (this.shapeType !== 'grid' && this.shapeType !== 'hexagon') { + // updateIntensityPass(this); + // } + // } } diff --git a/src/layer/render/heatmap/heatmap.js b/src/layer/render/heatmap/heatmap.js index 87a5322b7e..b035f38942 100644 --- a/src/layer/render/heatmap/heatmap.js +++ b/src/layer/render/heatmap/heatmap.js @@ -1,21 +1,32 @@ 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 Renderpass from '../../../core/engine/renderpass.bak'; +import RenderPass from '../../../core/engine/render-pass'; +import ShaderPass from '../../../core/engine/shader-pass'; +import EffectComposer from '../../../core/engine/effect-composer'; import * as THREE from '../../../core/three'; export function drawHeatmap(layer) { - const bbox = calBoundingBox(layer.layerData); - layer.dataBbox = bbox; + const colors = layer.get('styleOptions').rampColors; layer.colorRamp = createColorRamp(colors); - createIntensityPass(layer, bbox); - createColorizePass(layer, bbox); + const heatmap = new heatmapPass(layer); + const copy = new copyPass(layer); + copy.renderToScreen = true; + const composer = new EffectComposer(layer.scene._engine._renderer, layer.scene._container); + composer.addPass(heatmap); + composer.addPass(copy); + layer.add(composer); + } -function createIntensityPass(layer, bbox) { + +function heatmapPass(layer) { + const scene = new THREE.Scene(); const style = layer.get('styleOptions'); const data = layer.layerData; + const camera = layer.scene._engine._camera; // get attributes data const buffer = new HeatmapBuffer({ data @@ -27,107 +38,26 @@ function createIntensityPass(layer, bbox) { 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; - layer.intensityMesh = mesh; - updateIntensityPass(layer); -} - -export function updateIntensityPass(layer) { - const mesh = layer.intensityMesh; - const zoom = layer.scene.getZoom(); - const bbox = layer.dataBbox; - mesh.material.uniforms.u_zoom.value = zoom; - const passWidth = Math.min(8000, Math.pow(zoom, 2.0) * 250); - const passHeight = passWidth * (bbox.height / bbox.width); - layer.intensityPass.pass.setSize(passWidth, passHeight); - layer.intensityPass.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]; - } - } - - minX -= ((maxX - minX) * 0.5); - maxX += ((maxX - minX) * 0.5); - minY -= ((maxY - minY) * 0.5); - maxY += ((maxY - minY) * 0.5); - - const width = maxX - minX; - const height = maxY - minY; - - - return { - minX, - maxX, - minY, - maxY, - width, - height + scene.add(mesh); + scene.onBeforeRender = () => { // 每次渲染前改变状态 + const zoom = layer.scene.getZoom(); + mesh.material.setUniformsValue('u_zoom', zoom); }; + const pass = new RenderPass(scene, camera); + return pass; +} +function copyPass(layer) { + const style = layer.get('styleOptions'); + const material = new HeatmapColorizeMaterial({ + colorRamp: layer.colorRamp, + opacity: style.opacity + }); + const copyPass = new ShaderPass(material, 'u_texture'); + return copyPass; } diff --git a/src/map/AMap.js b/src/map/AMap.js index 0d4daff491..29374ad778 100644 --- a/src/map/AMap.js +++ b/src/map/AMap.js @@ -55,8 +55,6 @@ export default class GaodeMap extends Base { asyncCamera(engine) { this._engine = engine; const camera = engine._camera; - const scene = engine._scene; - const pickScene = engine._picking._pickingScene; this.map.on('camerachange', e => { const mapCamera = e.camera; let { fov, near, far, height, pitch, rotation, aspect } = mapCamera; @@ -70,15 +68,16 @@ export default class GaodeMap extends Base { camera.position.z = height * Math.cos(pitch); camera.position.x = height * Math.sin(pitch) * Math.sin(rotation); camera.position.y = -height * Math.sin(pitch) * Math.cos(rotation); - camera.up.x = -Math.cos(pitch) * Math.sin(rotation); camera.up.y = Math.cos(pitch) * Math.cos(rotation); camera.up.z = Math.sin(pitch); camera.lookAt(0, 0, 0); - scene.position.x = -e.camera.position.x; - scene.position.y = e.camera.position.y; - pickScene.position.x = -e.camera.position.x; - pickScene.position.y = e.camera.position.y; + camera.position.x += e.camera.position.x; + camera.position.y += -e.camera.position.y; + // scene.position.x = -e.camera.position.x; + // scene.position.y = e.camera.position.y; + // pickScene.position.x = -e.camera.position.x; + // pickScene.position.y = e.camera.position.y; }); } @@ -129,6 +128,10 @@ export default class GaodeMap extends Base { scene.setZoom = zoom => { return map.setZoom(zoom); }; + scene.setZoomAndCenter = (zoom, center) => { + const lnglat = new AMap.LngLat(center[0], center[1]); + return map.setZoomAndCenter(zoom, lnglat); + }; scene.setBounds = extent => { return map.setBounds(new AMap.Bounds([ extent[0], extent[1] ], [ extent[2], extent[3] ])); }; diff --git a/src/source/transform/cluster.js b/src/source/transform/cluster.js new file mode 100644 index 0000000000..645c7abcc0 --- /dev/null +++ b/src/source/transform/cluster.js @@ -0,0 +1,52 @@ +import Supercluster from 'supercluster'; +export function cluster(data, option) { + const { radius = 40, maxZoom = 16, minZoom = 0, field, zoom = 2 } = option; + if (data.pointIndex) { + const clusterPoint = data.pointIndex.getClusters(data.extent, zoom); + data.dataArray = formatData(clusterPoint); + return data; + } + const pointIndex = new Supercluster({ + radius, + minZoom, + maxZoom, + map: props => ({ sum: props[field] }), + reduce: (accumulated, props) => { accumulated.sum += props.sum; } + }); + const geojson = { + type: 'FeatureCollection' + }; + geojson.features = data.dataArray.map(item => { + return { + type: 'Feature', + properties: { + [field]: item[field] + }, + geometry: { + type: 'Point', + coordinates: item.coordinates + } + }; + }); + pointIndex.load(geojson.features); + const clusterPoint = pointIndex.getClusters(data.extent, zoom); + const resultData = clusterPoint.map((point, index) => { + return { + coordinates: point.geometry.coordinates, + _id: index + 1, + ...point.properties + }; + }); + data.dataArray = resultData; + data.pointIndex = pointIndex; + return data; +} +function formatData(clusterPoint) { + return clusterPoint.map((point, index) => { + return { + coordinates: point.geometry.coordinates, + _id: index + 1, + ...point.properties + }; + }); +} diff --git a/test/unit/source/transfrom/cluster-spec.js b/test/unit/source/transfrom/cluster-spec.js new file mode 100644 index 0000000000..f46ed7c23a --- /dev/null +++ b/test/unit/source/transfrom/cluster-spec.js @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { pointData } from '../../../asset/data/point'; +import { cluster } from '../../../../src/source/transform/cluster'; +describe('hexagon Test', function() { + + it('pointToCuster', function() { + const dataArray = pointData.map(item => { + const lng = 1e-6 * (250 * item.grid_x + 125), + lat = 1e-6 * (250 * item.grid_y + 125); + return { + v: item.count * 1, + coordinates: [ lng, lat ] + }; + }); + + const data = { + dataArray, + extent: [ -180, -85, 180, 85 ] + }; + const grid = cluster(data, { radius: 40, field: 'v', zoom: 13 }); + expect(grid.dataArray.length).eql(26); + }); +});