实现3D热力图

打印 上一主题 下一主题

主题 1026|帖子 1026|积分 3078

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
实现思绪


  • 首先是须要用canvas绘制一个2D的热力图,如果你还不会,请看json绘制热力图。
  • 使用Threejs中的canvas贴图,将贴图贴在PlaneGeometry平面上。
  • 使用着色东西质,更具json中的数据让平面模型 拔地而起。
  • 使用Threejs内置的TWEEN,加上一个动画。
结果 

结果如下:

具体代码 

具体实现结果代码:
  1. import * as THREE from 'three';
  2. import * as TWEEN from 'three/examples/jsm/libs/tween.module.js';
  3. import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
  4. import * as d3geo from 'd3-geo'
  5. import trafficJSON from '../assets/json/traffic.json'
  6. export default (domId) => {
  7.     /* ------------------------------初始化三件套--------------------------------- */
  8.     const dom = document.getElementById(domId);
  9.     const { innerHeight, innerWidth } = window
  10.     const scene = new THREE.Scene();
  11.     const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 2000);
  12.     camera.position.set(-25, 288, 342);
  13.     camera.lookAt(scene.position);
  14.     const renderer = new THREE.WebGLRenderer({
  15.         antialias: true,// 抗锯齿
  16.         alpha: false,// 透明度
  17.         powerPreference: 'high-performance',// 性能
  18.         logarithmicDepthBuffer: true,// 深度缓冲
  19.     })
  20.     // renderer.setClearColor(0x000000, 0);// 设置背景色
  21.     // renderer.clear();// 清除渲染器
  22.     renderer.shadowMap.enabled = true;// 开启阴影
  23.     renderer.shadowMap.type = THREE.PCFSoftShadowMap;// 阴影类型
  24.     renderer.outputEncoding = THREE.sRGBEncoding;// 输出编码
  25.     renderer.toneMapping = THREE.ACESFilmicToneMapping;// 色调映射
  26.     renderer.toneMappingExposure = 1;// 色调映射曝光
  27.     renderer.physicallyCorrectLights = true;// 物理正确灯光
  28.     renderer.setPixelRatio(devicePixelRatio);// 设置像素比
  29.     renderer.setSize(innerWidth, innerHeight);// 设置渲染器大小
  30.     dom.appendChild(renderer.domElement);
  31.     // 重置大小
  32.     window.addEventListener('resize', () => {
  33.         const { innerHeight, innerWidth } = window
  34.         camera.aspect = innerWidth / innerHeight;
  35.         camera.updateProjectionMatrix();
  36.         renderer.setSize(innerWidth, innerHeight);
  37.     })
  38.     /* ------------------------------初始化工具--------------------------------- */
  39.     const controls = new OrbitControls(camera, renderer.domElement) // 相机轨道控制器
  40.     controls.enableDamping = true // 是否开启阻尼
  41.     controls.dampingFactor = 0.05// 阻尼系数
  42.     controls.panSpeed = -1// 平移速度
  43.     const axesHelper = new THREE.AxesHelper(10);
  44.     scene.add(axesHelper);
  45.     /* ------------------------------正题--------------------------------- */
  46.     let geoFun;// 地理投影函数
  47.     let info = {
  48.         max: Number.MIN_SAFE_INTEGER,
  49.         min: Number.MAX_SAFE_INTEGER,
  50.         maxlng: Number.MIN_SAFE_INTEGER,
  51.         minlng: Number.MAX_SAFE_INTEGER,
  52.         maxlat: Number.MIN_SAFE_INTEGER,
  53.         minlat: Number.MAX_SAFE_INTEGER,
  54.         data: []
  55.     };
  56.     // 初始化地理投影
  57.     const initGeo = (size) => {
  58.         geoFun = d3geo.geoMercator().scale(size || 100)
  59.     }
  60.     // 经纬度转像素坐标
  61.     const latlng2px = (pos) => {
  62.         if (pos[0] >= -180 && pos[0] <= 180 && pos[1] >= -90 && pos[1] <= 90) {
  63.             return geoFun(pos);
  64.         }
  65.         return pos;
  66.     };
  67.     // 创建颜色
  68.     const createColors = (option) => {
  69.         const canvas = document.createElement('canvas');
  70.         const ctx = canvas.getContext('2d');
  71.         canvas.width = 256;
  72.         canvas.height = 1;
  73.         const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
  74.         for (let k in option.colors) {
  75.             grad.addColorStop(k, option.colors[k]);
  76.         }
  77.         ctx.fillStyle = grad;
  78.         ctx.fillRect(0, 0, canvas.width, canvas.height);
  79.         return ctx.getImageData(0, 0, canvas.width, 1).data;
  80.     }
  81.     // 绘制圆
  82.     const drawCircle = (ctx, option, item) => {
  83.         let { lng, lat, value } = item;
  84.         let x = lng - option.minlng + option.radius;
  85.         let y = lat - option.minlat + option.radius;
  86.         const grad = ctx.createRadialGradient(x, y, 0, x, y, option.radius);
  87.         grad.addColorStop(0.0, 'rgba(0,0,0,1)');
  88.         grad.addColorStop(1.0, 'rgba(0,0,0,0)');
  89.         ctx.fillStyle = grad;
  90.         ctx.beginPath();
  91.         ctx.arc(x, y, option.radius, 0, 2 * Math.PI);
  92.         ctx.closePath();
  93.         ctx.globalAlpha = (value - option.min) / option.size;
  94.         ctx.fill();
  95.     }
  96.     // 创建热力图
  97.     const createHeatmap = (option) => {
  98.         const canvas = document.createElement('canvas');
  99.         canvas.width = option.width;
  100.         canvas.height = option.height;
  101.         const ctx = canvas.getContext('2d');
  102.         option.size = option.max - option.min;
  103.         option.data.forEach((item) => {
  104.             drawCircle(ctx, option, item);
  105.         });
  106.         const colorData = createColors(option);
  107.         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  108.         for (let i = 3; i < imageData.data.length; i = i + 4) {
  109.             let opacity = imageData.data[i];
  110.             let offset = opacity * 4;
  111.             //red
  112.             imageData.data[i - 3] = colorData[offset];
  113.             //green
  114.             imageData.data[i - 2] = colorData[offset + 1];
  115.             //blue
  116.             imageData.data[i - 1] = colorData[offset + 2];
  117.         }
  118.         ctx.putImageData(imageData, 0, 0);
  119.         return { canvas, option };
  120.     }
  121.     // 初始化
  122.     const init = () => {
  123.         initGeo(1000)
  124.         // 处理数据
  125.         trafficJSON.features.forEach((item) => {
  126.             let pos = latlng2px(item.geometry.coordinates);// 经纬度转像素坐标
  127.             let newitem = {
  128.                 lng: pos[0],
  129.                 lat: pos[1],
  130.                 value: item.properties.avg
  131.             }
  132.             info.max = Math.max(newitem.value, info.max);
  133.             info.maxlng = Math.max(newitem.lng, info.maxlng);
  134.             info.maxlat = Math.max(newitem.lat, info.maxlat);
  135.             info.min = Math.min(newitem.value, info.min);
  136.             info.minlng = Math.min(newitem.lng, info.minlng);
  137.             info.minlat = Math.min(newitem.lat, info.minlat);
  138.             info.data.push(newitem);
  139.         })
  140.         info.size = info.max - info.min;
  141.         info.sizelng = info.maxlng - info.minlng;
  142.         info.sizelat = info.maxlat - info.minlat;
  143.         const radius = 50;
  144.         const { canvas, option } = createHeatmap({
  145.             width: info.sizelng + radius * 2,
  146.             height: info.sizelng + radius * 2,
  147.             colors: {
  148.                 0.1: '#2A85B8',
  149.                 0.2: '#16B0A9',
  150.                 0.3: '#29CF6F',
  151.                 0.4: '#5CE182',
  152.                 0.5: '#7DF675',
  153.                 0.6: '#FFF100',
  154.                 0.7: '#FAA53F',
  155.                 1: '#D04343'
  156.             },
  157.             radius,
  158.             ...info
  159.         })
  160.         const map = new THREE.CanvasTexture(canvas);
  161.         map.wrapS = THREE.RepeatWrapping;
  162.         map.wrapT = THREE.RepeatWrapping;
  163.         // 创建平面
  164.         const geometry = new THREE.PlaneGeometry(
  165.             option.width * 0.5,
  166.             option.height * 0.5,
  167.             500,
  168.             500
  169.         );
  170.         const material = new THREE.ShaderMaterial({
  171.             transparent: true,
  172.             side: THREE.DoubleSide,
  173.             uniforms: {
  174.                 map: { value: map },
  175.                 uHeight: { value: 100 },
  176.                 uOpacity: { value: 2.0 }
  177.             },
  178.             vertexShader: `
  179.             uniform sampler2D map;
  180.             uniform float uHeight;
  181.             varying vec2 v_texcoord;
  182.             void main(void)
  183.             {
  184.                 v_texcoord = uv;
  185.                 float h=texture2D(map, v_texcoord).a*uHeight;
  186.                 gl_Position = projectionMatrix * modelViewMatrix * vec4( position.x,position.y,h, 1.0 );
  187.             }`,
  188.             fragmentShader: `
  189.             precision mediump float;
  190.             uniform sampler2D map;
  191.             uniform float uOpacity;
  192.             varying vec2 v_texcoord;
  193.             void main (void)
  194.             {
  195.                 vec4 color= texture2D(map, v_texcoord);
  196.                 float a=color.a*uOpacity;
  197.                 gl_FragColor.rgb =color.rgb;
  198.                 gl_FragColor.a=a>1.0?1.0:a;
  199.             }`
  200.         });
  201.         const plane = new THREE.Mesh(geometry, material);
  202.         plane.rotateX(-Math.PI * 0.5);
  203.         scene.add(plane);
  204.         const tween = new TWEEN
  205.             .Tween({ v: 0 })
  206.             .to({ v: 100 }, 2000)
  207.             .onUpdate(obj => {
  208.                 material.uniforms.uHeight.value = obj.v;
  209.             })
  210.             .easing(TWEEN.Easing.Quadratic.InOut)
  211.             .start();
  212.         TWEEN.add(tween);
  213.     }
  214.     init();
  215.     /* ------------------------------动画函数--------------------------------- */
  216.     const animation = () => {
  217.         TWEEN.update();
  218.         controls.update();// 如果不调用,就会很卡
  219.         renderer.render(scene, camera);
  220.         requestAnimationFrame(animation);
  221.     }
  222.     animation();
  223. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

伤心客

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表