UNPKG

@orca-fe/x-map

Version:
249 lines (248 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const EffectComposer_1 = require("three/examples/jsm/postprocessing/EffectComposer"); const RenderPass_1 = require("three/examples/jsm/postprocessing/RenderPass"); const ShaderPass_1 = require("three/examples/jsm/postprocessing/ShaderPass"); const three_1 = require("three"); const ThreeLayer_1 = tslib_1.__importDefault(require("./ThreeLayer")); const coord_1 = require("../utils/coord"); const defaultColorsDefine = [ 'rgba(0,0,0,0)', 'rgba(17,21,245,0.1)', 'rgba(17,158,245,0.6)', 'rgba(99,245,17,0.5)', 'rgba(252,229,9,0.8)', 'rgba(246,150,11,0.8)', 'rgba(255,68,68,0.8)', ]; const defaultColors = defaultColorsDefine.map((color, index) => [ index / (defaultColorsDefine.length - 1), color, ]); class ThreeHeatmapLayer extends ThreeLayer_1.default { constructor(options = {}) { var _a; const { colors = defaultColors, colorPrecision, data, min = 0, dataPrecision = 20, max, z = 1, opacity = 0.9, maxZoom, minZoom, radius = 30 } = options, otherOptions = tslib_1.__rest(options, ["colors", "colorPrecision", "data", "min", "dataPrecision", "max", "z", "opacity", "maxZoom", "minZoom", "radius"]); super(Object.assign(Object.assign({}, otherOptions), { simpleThreeOptions: Object.assign(Object.assign({}, otherOptions.simpleThreeOptions), { preventDefaultRender: true }) })); this.heatmapObj3D = new three_1.Group(); this.planeGeometry = new three_1.PlaneGeometry(1, 1); this.dom.className = `${(_a = this.dom.className) !== null && _a !== void 0 ? _a : ''} three-heatmap-layer`; this.dom.style.pointerEvents = 'none'; this.dataPrecision = dataPrecision; this.opacity = opacity; this.dataOrigin = data; this.min = min; this.max = max; this.radius = radius; this.minZoom = minZoom; this.maxZoom = maxZoom; this.z = z; // 构造用于热力图纹理的渐变圆 const canvas2d = document.createElement('canvas'); canvas2d.width = 100; canvas2d.height = 100; const ctx = canvas2d.getContext('2d'); if (ctx) { const grd = ctx.createRadialGradient(50, 50, 0, 50, 50, 50); grd.addColorStop(0, 'rgba(255,255,255,1'); grd.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = grd; ctx.fillRect(0, 0, 100, 100); } this.heatmapTexture = new three_1.CanvasTexture(canvas2d); const { renderer, scene, camera } = this.three; scene.add(this.heatmapObj3D); // 后期渲染 this.composer = new EffectComposer_1.EffectComposer(renderer); this.renderPass = new RenderPass_1.RenderPass(scene, camera); this.composer.addPass(this.renderPass); this.three.on('render', () => { this.composer.render(); }); this.setHeatmapColor(colors, colorPrecision); } /** * 设置热力图颜色 * @param colors 热力值对应的颜色 * @param precision 热力图颜色精度 */ setHeatmapColor(colors, precision = 100) { const canvasColor = document.createElement('canvas'); canvasColor.width = precision; canvasColor.height = 1; const ctxColor = canvasColor.getContext('2d'); if (ctxColor) { const grd = ctxColor.createLinearGradient(0, 0, precision, 0); colors.forEach(([percent, color], index) => { grd.addColorStop(percent, color); }); ctxColor.fillStyle = grd; ctxColor.fillRect(0, 0, precision, 10); } const HeatmapShader = { uniforms: { tDiffuse: { value: null }, opacity: { value: this.opacity }, colorTexture: { value: new three_1.CanvasTexture(canvasColor) }, }, vertexShader: /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`, fragmentShader: /* glsl */ ` uniform float opacity; uniform sampler2D tDiffuse; uniform sampler2D colorTexture; varying vec2 vUv; void main() { // 得到一个 rgba 颜色 vec4 texel = texture2D( tDiffuse, vUv ); // 以 alpha 值作为热力 float alpha = texel.a; // 从色阶表中取得该热力对应的颜色 vec4 color = texture2D( colorTexture, vec2( alpha, 0 )); // 过滤透明度特别低的区域(否则热力图边界会出现白边) gl_FragColor = opacity * step(0.04, alpha) * color; }`, }; if (this.shaderPass) { this.composer.removePass(this.shaderPass); if (this.shaderPass.fsQuad) { // @ts-expect-error fsQuad can dispose this.shaderPass.fsQuad.dispose(); } } this.shaderPass = new ShaderPass_1.ShaderPass(HeatmapShader); this.composer.addPass(this.shaderPass); this.composer.render(); } createObject() { var _a; if (this.map && this.dataOrigin) { const { threeCenter } = this.map; if (threeCenter) { // 计算最大值 let max = (_a = this.max) !== null && _a !== void 0 ? _a : 100; if (max == null) { max = this.dataOrigin.reduce((v, { value }) => Math.max(v, Number.isNaN(value) ? -Infinity : value), 0); } const precision = Math.max(1, this.dataPrecision); const pointsArray = new Array(precision).fill([]) .map(() => []); // 遍历数据,并归类 this.dataOrigin.forEach(({ lng, lat, value }) => { const [x, y] = (0, coord_1.lonLat2Mercator)([lng, lat]).map((value, index) => value - threeCenter[index]); const percent = Math.max(0, Math.min(1, (value - this.min) / max)); const index = Math.round(percent * precision); if (pointsArray[index]) { pointsArray[index].push({ x, y }); } }); this.heatmapObj3D.clear(); // 创建 不同热力值的 Mesh pointsArray.forEach((points, index) => { const opacity = (index + 1) / precision; const mesh = new three_1.InstancedMesh(this.planeGeometry, new three_1.MeshBasicMaterial({ opacity, blending: three_1.AdditiveBlending, depthTest: false, transparent: true, side: three_1.DoubleSide, map: this.heatmapTexture, }), points.length); const obj = new three_1.Object3D(); // 设置每个点的位置 points.forEach(({ x, y }, i) => { obj.position.set(x, y, this.z); obj.updateMatrix(); mesh.setMatrixAt(i, obj.matrix); }); this.heatmapObj3D.add(mesh); }); this.composer.render(); } } } setData(data, max, min = 0) { this.dataOrigin = data; this.max = max; this.min = min; this.createObject(); } setMin(min = 0) { this.min = min; this.createObject(); } setMax(max = 0) { this.max = max; this.createObject(); } setMinZoom(minZoom) { this.minZoom = minZoom; this.updatePositionDebounce(); } setMaxZoom(maxZoom) { this.maxZoom = maxZoom; this.updatePositionDebounce(); } setOpacity(opacity = 0.9) { var _a; this.opacity = opacity; if ((_a = this.shaderPass) === null || _a === void 0 ? void 0 : _a.material.uniforms.opacity) { this.shaderPass.material.uniforms.opacity.value = opacity; } this.updatePosition(); } setRadius(radius) { this.radius = radius; this.updatePosition(); } updatePosition() { if (this.map) { let { zoom } = this.map.getViewport(); if (this.minZoom) zoom = Math.max(this.minZoom, zoom); if (this.maxZoom) zoom = Math.min(this.maxZoom, zoom); const scale = (this.radius * coord_1.MercatorMax * 2) / 256 / Math.pow(2, zoom); if (!this.planeGeometry.boundingBox) { this.planeGeometry.computeBoundingBox(); } const { boundingBox } = this.planeGeometry; if (boundingBox) { const originScale = boundingBox.max.x - boundingBox.min.x; const scaleDiff = scale / originScale; if (Math.abs(scaleDiff - 1) > 0.03) { this.planeGeometry.scale(scaleDiff, scaleDiff, 1); this.planeGeometry.computeBoundingBox(); } } } super.updatePosition(); } setMap(map) { super.setMap(map); this.createObject(); } add() { throw new Error('ThreeHeatmapLayer can not add markers'); } destroy() { super.destroy(); this.three.dispose(); if (this.shaderPass) { this.composer.removePass(this.shaderPass); if (this.shaderPass.fsQuad) { // @ts-expect-error fsQuad can dispose this.shaderPass.fsQuad.dispose(); } } if (this.renderPass) { this.composer.removePass(this.renderPass); } } } exports.default = ThreeHeatmapLayer;