实现3D热力图
实现思路
- 首先是需要用canvas绘制一个2D的热力图,如果你还不会,请看json绘制热力图。
- 使用Threejs中的canvas贴图,将贴图贴在PlaneGeometry平面上。
- 使用着色器材质,更具json中的数据让平面模型 拔地而起。
- 使用Threejs内置的TWEEN,加上一个动画。
效果
效果如下:

具体代码
具体实现效果代码:
import * as THREE from 'three';
import * as TWEEN from 'three/examples/jsm/libs/tween.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import * as d3geo from 'd3-geo'
import trafficJSON from '../assets/json/traffic.json'export default (domId) => {/* ------------------------------初始化三件套--------------------------------- */const dom = document.getElementById(domId);const { innerHeight, innerWidth } = windowconst scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 2000);camera.position.set(-25, 288, 342);camera.lookAt(scene.position);const renderer = new THREE.WebGLRenderer({antialias: true,// 抗锯齿alpha: false,// 透明度powerPreference: 'high-performance',// 性能logarithmicDepthBuffer: true,// 深度缓冲})// renderer.setClearColor(0x000000, 0);// 设置背景色// renderer.clear();// 清除渲染器renderer.shadowMap.enabled = true;// 开启阴影renderer.shadowMap.type = THREE.PCFSoftShadowMap;// 阴影类型renderer.outputEncoding = THREE.sRGBEncoding;// 输出编码renderer.toneMapping = THREE.ACESFilmicToneMapping;// 色调映射renderer.toneMappingExposure = 1;// 色调映射曝光renderer.physicallyCorrectLights = true;// 物理正确灯光renderer.setPixelRatio(devicePixelRatio);// 设置像素比renderer.setSize(innerWidth, innerHeight);// 设置渲染器大小dom.appendChild(renderer.domElement);// 重置大小window.addEventListener('resize', () => {const { innerHeight, innerWidth } = windowcamera.aspect = innerWidth / innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth, innerHeight);})/* ------------------------------初始化工具--------------------------------- */const controls = new OrbitControls(camera, renderer.domElement) // 相机轨道控制器controls.enableDamping = true // 是否开启阻尼controls.dampingFactor = 0.05// 阻尼系数controls.panSpeed = -1// 平移速度const axesHelper = new THREE.AxesHelper(10);scene.add(axesHelper);/* ------------------------------正题--------------------------------- */let geoFun;// 地理投影函数let info = {max: Number.MIN_SAFE_INTEGER,min: Number.MAX_SAFE_INTEGER,maxlng: Number.MIN_SAFE_INTEGER,minlng: Number.MAX_SAFE_INTEGER,maxlat: Number.MIN_SAFE_INTEGER,minlat: Number.MAX_SAFE_INTEGER,data: []};// 初始化地理投影const initGeo = (size) => {geoFun = d3geo.geoMercator().scale(size || 100)}// 经纬度转像素坐标const latlng2px = (pos) => {if (pos[0] >= -180 && pos[0] <= 180 && pos[1] >= -90 && pos[1] <= 90) {return geoFun(pos);}return pos;};// 创建颜色const createColors = (option) => {const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');canvas.width = 256;canvas.height = 1;const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);for (let k in option.colors) {grad.addColorStop(k, option.colors[k]);}ctx.fillStyle = grad;ctx.fillRect(0, 0, canvas.width, canvas.height);return ctx.getImageData(0, 0, canvas.width, 1).data;}// 绘制圆const drawCircle = (ctx, option, item) => {let { lng, lat, value } = item;let x = lng - option.minlng + option.radius;let y = lat - option.minlat + option.radius;const grad = ctx.createRadialGradient(x, y, 0, x, y, option.radius);grad.addColorStop(0.0, 'rgba(0,0,0,1)');grad.addColorStop(1.0, 'rgba(0,0,0,0)');ctx.fillStyle = grad;ctx.beginPath();ctx.arc(x, y, option.radius, 0, 2 * Math.PI);ctx.closePath();ctx.globalAlpha = (value - option.min) / option.size;ctx.fill();}// 创建热力图const createHeatmap = (option) => {const canvas = document.createElement('canvas');canvas.width = option.width;canvas.height = option.height;const ctx = canvas.getContext('2d');option.size = option.max - option.min;option.data.forEach((item) => {drawCircle(ctx, option, item);});const colorData = createColors(option);const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);for (let i = 3; i < imageData.data.length; i = i + 4) {let opacity = imageData.data[i];let offset = opacity * 4;//redimageData.data[i - 3] = colorData[offset];//greenimageData.data[i - 2] = colorData[offset + 1];//blueimageData.data[i - 1] = colorData[offset + 2];}ctx.putImageData(imageData, 0, 0);return { canvas, option };}// 初始化const init = () => {initGeo(1000)// 处理数据trafficJSON.features.forEach((item) => {let pos = latlng2px(item.geometry.coordinates);// 经纬度转像素坐标let newitem = {lng: pos[0],lat: pos[1],value: item.properties.avg}info.max = Math.max(newitem.value, info.max);info.maxlng = Math.max(newitem.lng, info.maxlng);info.maxlat = Math.max(newitem.lat, info.maxlat);info.min = Math.min(newitem.value, info.min);info.minlng = Math.min(newitem.lng, info.minlng);info.minlat = Math.min(newitem.lat, info.minlat);info.data.push(newitem);})info.size = info.max - info.min;info.sizelng = info.maxlng - info.minlng;info.sizelat = info.maxlat - info.minlat;const radius = 50;const { canvas, option } = createHeatmap({width: info.sizelng + radius * 2,height: info.sizelng + radius * 2,colors: {0.1: '#2A85B8',0.2: '#16B0A9',0.3: '#29CF6F',0.4: '#5CE182',0.5: '#7DF675',0.6: '#FFF100',0.7: '#FAA53F',1: '#D04343'},radius,...info})const map = new THREE.CanvasTexture(canvas);map.wrapS = THREE.RepeatWrapping;map.wrapT = THREE.RepeatWrapping;// 创建平面const geometry = new THREE.PlaneGeometry(option.width * 0.5,option.height * 0.5,500,500);const material = new THREE.ShaderMaterial({transparent: true,side: THREE.DoubleSide,uniforms: {map: { value: map },uHeight: { value: 100 },uOpacity: { value: 2.0 }},vertexShader: `uniform sampler2D map;uniform float uHeight;varying vec2 v_texcoord;void main(void){v_texcoord = uv;float h=texture2D(map, v_texcoord).a*uHeight;gl_Position = projectionMatrix * modelViewMatrix * vec4( position.x,position.y,h, 1.0 );}`,fragmentShader: `precision mediump float;uniform sampler2D map;uniform float uOpacity;varying vec2 v_texcoord;void main (void){vec4 color= texture2D(map, v_texcoord);float a=color.a*uOpacity;gl_FragColor.rgb =color.rgb;gl_FragColor.a=a>1.0?1.0:a;}`});const plane = new THREE.Mesh(geometry, material);plane.rotateX(-Math.PI * 0.5);scene.add(plane);const tween = new TWEEN.Tween({ v: 0 }).to({ v: 100 }, 2000).onUpdate(obj => {material.uniforms.uHeight.value = obj.v;}).easing(TWEEN.Easing.Quadratic.InOut).start();TWEEN.add(tween);}init();/* ------------------------------动画函数--------------------------------- */const animation = () => {TWEEN.update();controls.update();// 如果不调用,就会很卡renderer.render(scene, camera);requestAnimationFrame(animation);}animation();
}
相关文章:
实现3D热力图
实现思路 首先是需要用canvas绘制一个2D的热力图,如果你还不会,请看json绘制热力图。使用Threejs中的canvas贴图,将贴图贴在PlaneGeometry平面上。使用着色器材质,更具json中的数据让平面模型 拔地而起。使用Threejs内置的TWEEN&…...
GEE ui界面实现:用户自画多边形, 按面积比例在多边形中自动生成样点,导出多边形和样点shp,以及删除上一组多边形和样点(有视频效果展示)
零、背景 这几天在选样点,发现GEE有强大的ui功能,于是应用在我的工作上。 下述代码实现了几个功能: ①用户可以自己勾勒多边形,随后程序会按面积比例在多边形中自动生成样点,同时根据改多边形的区域生成区域平均月N…...
React diff算法和Vue diff算法的主要区别
React和Vue都是流行的前端框架,它们各自实现了diff算法来优化虚拟DOM的更新过程。以下是React diff算法和Vue diff算法的主要区别: 1. diff策略 React diff算法: React的diff算法主要采用了同层级比较的策略,即它不会跨层级比较节…...
WSL 2 中 FastReport 与 FastCube 的设置方法与优化策略
软件开发人员长期以来一直在思考这个问题:“我们如何才能直接在 Windows 中运行 Linux 应用程序,而无需使用单独的虚拟机?” WSL 技术为这个问题提供了一个可能的答案。WSL 的历史始于 2016 年。当时,其实现涉及使用 Windows 内核…...
《线性代数》学习笔记
列向量无关 上个星期继续学线性代数,一个矩阵,如何判断它是的列向量有几个是线性无关呢?其实有好几个方法。第一个就是一个一个判断。 先选定一个,然后看下这两个,怎么看呢?如果两个列向量线性相关&#…...
Redis三种集群模式:主从模式、哨兵模式和Cluster模式
目录标题 1、背景及介绍2、 Redis 主从复制2.1、主从复制特点2.2、Redis主从复制原理2.3 PSYNC 工作原理2.3.1、启动或重连判断:2.3.2、第一次同步处理:2.3.3、断线重连处理:2.3.4、主节点响应2.3.5、全量同步触发条件:2.3.6、复制…...
CDH大数据平台部署
二、CDH简介 全称Cloudera’s Distribution Including Apache Hadoop。 hadoop的版本 (Apache、CDH、Hotonworks版本) 在公司中一般使用cdh多一些(收费的)、也有公司使用阿里云大数据平台、微软的大数据平台。 国内也有一些平台:星环大数…...
7.4、实验四:RIPv2 认证和触发式更新
源文件 一、引言:为什么要认证和采用触发式更新? 1. RIP v2 认证 RIP(Routing Information Protocol)版本 2 添加了认证功能,以提高网络的安全性。认证的作用主要包括以下几点: 防止路由欺骗 RIP v1 是不…...
【一步步开发AI运动小程序】二十一、如果将AI运动项目配置持久化到后端?
**说明:**本文所涉及的AI运动识别、计时、计数能力,都是基于云智「Ai运动识别引擎」实现。云智「Ai运动识别」插件识别引擎,可以为您的小程序或Uni APP赋于原生、本地、广覆盖、高性能的人体识别、姿态识别、10余种常见的运动计时、计数识别及…...
LED和QLED的区别
文章目录 1. 基础背光技术2. 量子点技术的引入3. 色彩表现4. 亮度和对比度5. 能效6. 寿命7. 价格总结 LED和 QLED都是基于液晶显示(LCD)技术的电视类型,但它们在显示技术、色彩表现和亮度方面有一些关键区别。以下是两者的详细区别ÿ…...
2024 年Postman 如何安装汉化中文版?
2024 年 Postman 的汉化中文版安装教程...
转化古老的Eclipse项目为使用gradle构建
很多古老的Java项目,是使用Eclipse作为IDE开发的。 那么,使用其它IDE的开发者,如何快速地进入这种古老项目的开发呢?或者说,一个Eclipse构建的古老项目,能不能转化成一个IDE无关的项目,进而所有…...
openGauss常见问题与故障处理(二)
2.网络故障定位手段 2.1 网络故障定位手段--常见网络故障引发的异常 在数据库正常工作的情况下,网络层对上层用户是透明的,但数据库在长期运行时,可能会由于各种原因导致出现网络异常或错误。 常见的因网络故障引发的异常有: 1>…...
Mysql 8迁移到达梦DM8遇到的报错
在实战迁移时,遇到两个报错。 一、列[tag]长度超出定义 在mysql中,tag字段的长度是varchar(20),在迁移到DM8后,这个长度不够用了。怎么解决? 在迁移过程中,“指定对象”时,选择转换。 在“列映…...
Android HandlerThread 基础
HandlerThread **一、HandlerThread的基本概念和用途**1. **目的**2. **与普通线程的区别** **二、HandlerThread的使用步骤**1. **创建HandlerThread对象并启动线程**2. **创建Handler并关联到HandlerThread的消息队列**3. **发送消息到HandlerThread的消息队列** **三、Handl…...
【智能算法应用】人工水母搜索算法求解二维路径规划问题
摘要 本文基于人工水母搜索算法(Jellyfish Search Algorithm, JSA),对二维路径规划问题进行了研究。JSA作为一种新兴的群体智能优化算法,模仿了水母在海洋中觅食和迁移的行为,以求解非线性、复杂的优化问题。实验结果…...
【Altium】原理图如何利用参数管理器批量修改元器件属性
【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决在使用AD设计原理图的时候,使用参数管理器批量修改元器件的属性。 2、 问题场景 客户在使用ad时,想大批量修改元器件的属性,类似于Cadence中,批量修改Manufactur…...
基于Spring Boot与Redis的令牌主动失效机制实现
目录 前言1. 项目结构和依赖配置1.1 项目依赖配置1.2 Redis连接配置 2. 令牌主动失效机制的实现流程2.1 登录成功后将令牌存储到Redis中2.2 使用拦截器验证令牌2.3 用户修改密码后删除旧令牌 3. Redis的配置与测试4. 可能的扩展与优化结语 前言 在现代Web系统中,用…...
深度学习之循环神经网络(RNN)
1 为什么需要RNN? 时间序列数据是指在不同时间点上收集到的数据,这类数据反映了某一事物、现象等随时间的变化状态或程度。一般的神经网络,在训练数据足够、算法模型优越的情况下,给定特定的x,就能得到期望y。其一…...
Autosar CP Network Management模块规范导读
Network Management模块的主要功能 网络管理适配:作为通信管理器和总线特定网络管理模块之间的适配层,实现不同总线网络管理功能的统一接口,确保系统中各种网络的协同工作。协调功能 网络协调关闭:使用协调算法协调多个网络的关闭,确保它们在合适的时间同步进入睡眠模式,…...
【深度解析】Hermes Agent:持久记忆、自学习闭环与桌面化 Autonomous AI 工作流实践
摘要 Hermes Agent 的核心价值不只是“带工具的聊天机器人”,而是面向长期运行的自主智能体系统。本文从持久记忆、自学习技能、工具编排和桌面化管理角度,解析其架构思想,并给出一个可落地的 Python 实战示例。背景介绍:从 Chatb…...
ThreeFingerDragOnWindows:在Windows上实现macOS三指拖动的终极指南
ThreeFingerDragOnWindows:在Windows上实现macOS三指拖动的终极指南 【免费下载链接】ThreeFingersDragOnWindows Enables macOS-style three-finger dragging functionality on Windows Precision touchpads. 项目地址: https://gitcode.com/gh_mirrors/th/Three…...
从Prompt Gateway到Content SLA引擎:2026奇点大会上最受瞩目的5个开源组件,已集成至CNCF沙箱(限前500名开发者获取部署手册)
更多请点击: https://intelliparadigm.com 第一章:AI原生内容生成平台:2026奇点智能技术大会AIGC系统搭建 在2026奇点智能技术大会上,主办方构建了一套面向多模态协同创作的AI原生内容生成平台(AIGC-OS)&a…...
3分钟掌握Navicat重置脚本:让Mac版数据库工具无限试用
3分钟掌握Navicat重置脚本:让Mac版数据库工具无限试用 【免费下载链接】navicat_reset_mac navicat mac版无限重置试用期脚本 Navicat Mac Version Unlimited Trial Reset Script 项目地址: https://gitcode.com/gh_mirrors/na/navicat_reset_mac 还在为Navi…...
Ruby 变量
Ruby 变量 引言 在编程语言中,变量是存储数据的基本单元。Ruby 作为一种动态、面向对象的语言,同样依赖变量来存储和处理数据。本文将详细介绍 Ruby 中的变量类型、作用域、生命周期以及相关操作,帮助读者全面了解 Ruby 变量的使用。 变量类型 Ruby 中的变量类型主要分为…...
在Node.js后端服务中集成Taotoken调用多模型API
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 在Node.js后端服务中集成Taotoken调用多模型API 将大模型能力集成到后端服务是现代应用开发的常见需求。通过Taotoken平台…...
企业内如何通过Taotoken实现API密钥的统一管理与审计
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 企业内如何通过Taotoken实现API密钥的统一管理与审计 在将大模型能力引入企业内部的实践中,如何安全、有序地管理API密…...
不想花百元订阅 Microsoft 365?三种免费使用方法来了!
ZDNET 核心要点Microsoft 365 需要订阅才能解锁全部功能,不过,仍可免费使用 Word 和 Excel 等应用程序,免费使用方式包括网页版和移动应用程序。无论处于学习或职业生涯的哪个阶段,可能时不时仍需使用 Microsoft 365(前…...
如何在Vue项目中快速实现Office文档预览:vue-office完整指南
如何在Vue项目中快速实现Office文档预览:vue-office完整指南 【免费下载链接】vue-office 支持word(.docx)、excel(.xlsx,.xls)、pdf、pptx等各类型office文件预览的vue组件集合,提供一站式office文件预览方案,支持vue2和3,也支持…...
PyTorch 笔记(05)— Tensor 元素级运算实战:从基础函数到运算符重载
1. Tensor元素级运算的核心概念 第一次接触PyTorch的Tensor运算时,我完全被各种函数搞晕了。后来才发现,元素级运算(Element-wise Operations)其实就是对Tensor中每个元素单独做计算,就像Excel里对每个单元格做加减乘…...
