每日一练-threejs实现三维动态热力图

news2024/11/12 13:10:19

前言:学习自用Three.js搞个炫酷热力山丘图,作者讲解的十分详细,在这里不再过多赘述,直接上代码!

<template>
  <div class="map" ref="map"></div>
</template>

<script setup>
import * as THREE from "three"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
import { Tween, Easing } from 'three/examples/jsm/libs/tween.module.js';
import { ref, onMounted } from "vue"
import axios from 'axios'
import { latlng2px } from "@/lib/index.js"
import { createHeatmap } from "./heatmap.js"
let map = ref(null);

onMounted(() => {
  // 创建三维场景
  let scene = new THREE.Scene();
  let width = map.value.offsetWidth;
  let height = map.value.offsetHeight;
  
  let camera = new THREE.PerspectiveCamera(45, width/height, 1, 10000);
  camera.position.set(100, 100, 100);
  camera.lookAt(0, 0, 0);
  let renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setSize(width, height);
  map.value.appendChild(renderer.domElement)

  let orbitControls = new OrbitControls(camera, renderer.domElement);
  orbitControls.position0.set(100, 100, 100);
  orbitControls.target.set(0, 0, 0);

  // 生成二维热力图
  function init2DMap(geojson) { 
    let option = {
      max: Number.MIN_SAFE_INTEGER,
      min: Number.MAX_SAFE_INTEGER,
      maxlon: Number.MIN_SAFE_INTEGER,
      minlon: Number.MAX_SAFE_INTEGER,
      maxlat: Number.MIN_SAFE_INTEGER,
      minlat: Number.MAX_SAFE_INTEGER,
      data: []
    }

    geojson.features.forEach(item => {
      let position = latlng2px(item.geometry.coordinates);  // 经纬度转像素
      let newItem = {
        lon: position[0],
        lat: position[1],
        value: item.properties.avg  // 决定热力图高度的属性值
      };
      option.max = Math.max(newItem.value, option.max);
      option.maxlon = Math.max(newItem.lon, option.maxlon);
      option.maxlat = Math.max(newItem.lat, option.maxlat);

      option.min = Math.min(newItem.value, option.min);
      option.minlon = Math.min(newItem.lon, option.minlon);
      option.minlat = Math.min(newItem.lat, option.minlat);
      option.data.push(newItem);
    });
    option.radius = 8;
    option.width = option.maxlon - option.minlon + option.radius * 2;
    option.height = option.maxlat - option.minlat + option.radius * 2;
    option.colors = {
      0.1: '#2A85B8',
      0.2: '#16B0A9',
      0.3: '#29CF6F',
      0.4: '#5CE182',
      0.5: '#7DF675',
      0.6: '#FFF100',
      0.7: '#FAA53F',
      1: '#D04343'
    }    
    let canvas = createHeatmap(option)
    return { canvas, option }
  }
  // 生成三维热力图
  function initHeatmap(data) {
    let { canvas: heatmapCanvas, option } = init2DMap(data);
    const map = new THREE.CanvasTexture(heatmapCanvas);
    map.wrapS = THREE.RepeatWrapping;
    map.wrapT = THREE.RepeatWrapping;
    let geometry = new THREE.PlaneGeometry(option.width, option.height, 500, 500);
    let material = new THREE.ShaderMaterial({
      transparent: true,
      side: THREE.DoubleSide,
      uniforms: {
        map: { value: map },
        uHeight: { value: 20 },
        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;
        }`
    })

    let mesh = new THREE.Mesh(geometry, material);
    mesh.rotateX( -Math.PI / 2);
    scene.add(mesh);

    tween.to({ v: 20 }, 4000)
        .onUpdate((obj) => { material.uniforms.uHeight.value = obj.v;})
        .easing(Easing.Quadratic.Out);
    tween.start();
  }

  // 动画
  let tween = new Tween({ v: 0 })
  // 基于高德地图交通数据生成的热力图
  axios.get("https://a.amap.com/Loca/static/loca-v2/demos/mock_data/traffic.json")
        .then(res => {
          if (res.status == 200 && res.data) {
            initHeatmap(res.data);
          }
        })
        
  // 渲染
  function render() {
    tween.update();
    renderer.render(scene, camera);
    requestAnimationFrame(render);
  }
  render()
})

</script>

<style scoped>
.map {
  width: 100%;
  height: 100%;
}
</style>
// heatmap.js

function createColors(option) {
  const canvas = document.createElement('canvas');
  // document.body.appendChild(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;
}
function drawCircle(ctx, option, item) {
  let { lon, lat, value } = item;
  let x = lon - option.minlon + 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();
}
export function createHeatmap(option) {
  const canvas = document.createElement('canvas');
  // document.body.appendChild(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;
    imageData.data[i - 3] = colorData[offset];
    imageData.data[i - 2] = colorData[offset + 1];
    imageData.data[i - 1] = colorData[offset + 2];
  }

  ctx.putImageData(imageData, 0, 0);
  return canvas;
}
// lib/index.js

import { geoMercator } from 'd3-geo';

export const latlng2px = (pos) => {
  if (pos[0] >= -180 && pos[0] <= 180 && pos[1] >= -90 && pos[1] <= 90) {
    return geoMercator()(pos);
  }
  return pos;
};

效果图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2076901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

亿发工单管理系统助力五金行业智造升级:高效生产新篇章

在五金制造行业&#xff0c;效率和质量决定了企业的竞争力。五金厂通过引入先进的工单管理系统&#xff0c;成功实现了从传统制造向智能制造的转型。今天&#xff0c;我们将带您深入了解这场变革背后的力量&#xff0c;揭示工单管理系统如何在五金工厂的各个环节中发挥重要作用…

Redis的哨兵(高可用)

实验环境&#xff1a;用一主两从来实现Redis的高可用架构。 一、Redis哨兵 Sentinel 进程是用于监控redis集群中Master主服务器工作的状态&#xff0c;在Master主服务器发生故障的时候&#xff0c; 可以实现Master和Slave服务器的切换&#xff0c;保证系统的高可用&#xff0c…

java一键生成数据库说明文档html格式

要验收项目了&#xff0c;要写数据库文档&#xff0c;一大堆表太费劲了&#xff0c;直接生成一个吧&#xff0c;本来想用个别人的轮子&#xff0c;网上看了几个&#xff0c;感觉效果不怎么好&#xff0c;自己动手写一个吧。抽空再把字典表补充进去就OK了 先看效果&#xff1a; …

找不到msvcp140.dll无法继续执行代码的基本解决策略,快速修复msvcp140.dll错误文件

在我们日常使用电脑的过程中&#xff0c;偶尔会遇到一些令人头疼的技术问题。其中&#xff0c;"找不到 msvcp140.dll 无法继续执行代码" 的错误提示便是许多 Windows 用户常见的难题之一。这条错误消息通常在尝试启动某些程序时出现&#xff0c;指出系统缺少一个关键…

SAP HR 逻辑数据库PNP简单说明

逻辑数据库是专门在逻辑数据库生成器中编辑的开发对象&#xff0c;其核心价值在于为其他ABAP程序提供来自分层树结构节点的数据访问能力。尽管在SAP的最新帮助文档中&#xff0c;逻辑数据库被标记为已过时的&#xff0c;但是在HR模块中仍然被广泛使用&#xff0c;提供读取和处理…

基于Echarts的大屏可视化

效果图 基于vue3echartsTailwind CSS技术实现 实现思路 新建一个新项目 找任一目录进行 pnpm create vitelatest选择vue和js开始项目 导入tailwindcss https://tailwindcss.com/ 选择vue按照里面的步骤一步一步完成即可 将事先准备好的资料导入到assets包中即可 写入项…

Android setText不生效问题(文字不显示)

1.直接说解决方案&#xff1a; 在代码没问题的情况下&#xff0c;将你的TextView的Id改一下&#xff0c;然后再重启编译器即可(注意&#xff0c;不修改TextView的ID&#xff0c;单独重启是没有作用的&#xff01;) 2.出现问题的过程&#xff1a; 新增的一个页面与之前做好的界…

“workon不是内部命令“/virtualenvwrapper-win安装配置

如果出现“workon不是内部命令”&#xff0c;先卸载 pip uninstall virtualenvwrapper-win -y 一&#xff0c;指定所有虚拟环境存放路径&#xff1a; # “E:/Python_Envs_List”换成自己想要的路径 setx WORKON_HOME "E:/Python_Envs_List"二&#xff0c;增加pytho…

【轻松掌握】Jenkins入门指南:构建高效的CI/CD Pipeline

文章目录 前言安装部署安装JDK安装Jenkins下载Jenkins运行Jenkins访问页面填写管理员密码安装推荐的插件 Maven安装下载上传到Linux解压配置镜像运行查看Maven使用的JDK版本 Jenkins安装Maven插件 创建Demo项目创建Jenkins任务填写代码仓库地址Linux安装GIT解决报错配置Maven版…

一文吃透SpringMVC

一、SpringMVC简介 1、什么是MVC MVC是一种软件架构模式&#xff08;是一种软件架构设计思想&#xff0c;不止Java开发中用到&#xff0c;其它语言也需要用到&#xff09;&#xff0c;它将应用分为三块&#xff1a; M&#xff1a;Model&#xff08;模型&#xff09;&#xf…

Phone X│“齐刘海”里到底搭载了哪些传感器?

揭开 “齐刘海”面纱 随着iPhone X的发布&#xff0c;3D人脸识别功能一度成为人们口中津津乐道的新科技&#xff0c;下面就让SITRI的专业团队带领大家揭开人脸识别的神秘面纱。 先从苹果iPhone X说起&#xff0c;让我们看一看这个可爱的“齐刘海”里到底搭载了哪些传感器&am…

杀软对抗 ----> 简简单单免杀某60

在开始之前还是宇宙免责 本Blog讨论的技术问题仅限于学习用&#xff0c;严禁用于非授权情况下执行攻击活动&#xff0c;如未授权攻击所造成一切后果由攻击者独自承担&#xff0c;所展示的代码仅用于杀软对抗学习&#xff0c;测试环境并非真实环境&#xff0c;并无对任何杀软有任…

我的sql我做主!Mysql 的集群架构详解之组从复制、半同步模式、MGR、Mysql路由和MHA管理集群组

目录 Mysql 集群技术一、Mysql 在服务器中的部署方法1.1 在Linux下部署mysql1.1.1 安装依赖性&#xff1a;1.1.2 下载并解压源码包1.1.3 源码编译安装mysql1.1.4 部署mysql 二、Mysql的组从复制2.1 配置mastesr2.2 配置salve2.3 当有数据时添加slave22.4 延迟复制2.5 慢查询日志…

医疗器械法规标准相关资料

文章目录 前言如何查找法规文件与标准1. 法规清单2. 医疗器械法规文件汇编常用链接常见网站微信公众号前言 在前文 医疗器械软件相关法律法规与标准 中介绍了在软件设计过程常见的法规与标准,并给出部分标准如何查找和下载的方法,但是上文中列举的部分不全面,真实在产品设计…

springboot公众号模板消息推送

文章目录 参考1、微信公众平台测试号管理1.1 访问微信公众平台测试账号页面1.2 获取appID和appsecret1.3 扫码二维码添加测试号1.4 添加模版消息 2、集成微信SDK2.1 引入微信工具包2.2 添加配置文件 3、API调用3.1 发送消息模版的实现3.2 测试类调用3.3 效果展示 4、回调配置回…

通过IMB看高效裁员

高效裁员是企业在面临经营压力或战略调整时不得不采取的措施之一。为了确保裁员过程既高效又尽量减少负面影响,可以遵循以下步骤和策略: 一、明确裁员目标和计划 分析需求:首先,企业需要明确裁员的原因,比如经营困难、业务重组、技术升级等,并基于这些原因确定裁员的范…

Centos系统二进制安装mysql5.7.44、添加环境变量、复制启动脚本、初始化数据库、设置用户密码

MySQL :: Download MySQL Community Server (Archived Versions) https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz 删除默认存在的mariadb-libs rpm -e mariadb-libs --nodeps 安装mysql cd /usr/local/src/ && …

Redis中的 大/热 key问题 ,如何解决(面试版)

big key 什么是 big key? big key&#xff1a;就是指一个内存空间占用比较大的键(Key) 造成的问题&#xff1a; 内存分布不均。在集群模式下&#xff0c;不同 slot分配到不同实例中&#xff0c;如果大 key 都映射到一个实例&#xff0c;则分布不均&#xff0c;查询效率也…

拿下英语翻译!这四款在线翻译功不可没!

作为一个刚踏入职场的小白&#xff0c;我最近在为工作中的英语翻译问题头疼不已。不过&#xff0c;经过一番尝试和比较&#xff0c;我找到了几款翻译工具&#xff0c;它们在帮我解决翻译难题上表现得相当不错。今天&#xff0c;就让我以一个职场新手的身份&#xff0c;来跟大家…

OpenCV几何图像变换(6)计算反转仿射变换函数invertAffineTransform()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 反转一个仿射变换。 该函数计算由 23 矩阵 M 表示的逆仿射变换&#xff1a; [ a 11 a 12 b 1 a 21 a 22 b 2 ] \begin{bmatrix} a_{11} & a…