Advertisement

使用 Three.js 和 D3 可视化全球新冠疫情

阅读量:

话不多说,整体效果是这样的,本文主要讲解的地球实现

在这里插入图片描述

核心需求

  • 地球半透明,可以看到背面
  • 点阵式的全球地图
  • 根据数据的经纬度生成对应的柱体
  • 数值越大,柱体的颜色和高度就越深越长

引入Threejs和D3

复制代码
    <script src="https://cdn.staticfile.org/three.js/r125/three.min.js"></script>
    <script src="https://d3js.org/d3.v6.js"></script>
    <script src="https://unpkg.com/three@0.125.0/examples/js/controls/OrbitControls.js"></script>
    <script src="https://unpkg.com/three@0.125.0/examples/js/utils/BufferGeometryUtils.js"></script>
    
    
      
      
      
      
    
    AI写代码

基本HTML结构

复制代码
    <div id="box" style="width: 100%; height: 100%">
      <canvas id="canvas" style="width: 100%; height: 100%" />
    </div>
    
    
      
      
      
    
    AI写代码

定义必要的变量

复制代码
    const box = document.getElementById("box");
    const canvas = document.getElementById("canvas");
    
    let glRender; // webgl渲染器
    let camera; // 摄像机
    let earthMesh; // 地球的Mesh
    let scene; // 场景,一个大容器,可以理解为html中的body
    let meshGroup; // 所有Mesh的容器,后面所有Mesh都会放在这里面,方便我们管理,可理解为一个div
    let controls; // 轨道控制器,实现整体场景的控制
    
    
      
      
      
      
      
      
      
      
      
    
    AI写代码

初始化相关变量

复制代码
    // 创建webgl渲染器
    glRender = new THREE.WebGLRenderer({ canvas, alpha: true });
    glRender.setSize(canvas.clientWidth, canvas.clientHeight, false);
    // 创建场景
    scene = new THREE.Scene();
    
    // 创建相机
    const fov = 45;
    const aspect = canvas.clientWidth / canvas.clientHeight;
    const near = 1;
    const far = 4000;
    camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    // 设置一个适当的位置
    camera.position.z = 400;
    
    // 轨道控制器
    controls = new THREE.OrbitControls(camera, canvas);
    controls.target.set(0, 0, 0);
    
    // 创建容器
    meshGroup = new THREE.Group();
    // 把容器添加到场景中
    scene.add(meshGroup);
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

创建地球

复制代码
    // 首先创建一个球体
    const globeRadius = 100; // 球体半径
    const globeSegments = 64; // 球体面数,数量越大越光滑,性能消耗越大
    const geometry = new THREE.SphereGeometry(
      globeRadius,
      globeSegments,
      globeSegments
    );
    // 创建球体材质,让球体有质感
    const material = new THREE.MeshBasicMaterial({
      transparent: true, // 设置是否透明
      opacity: 0.5, // 透明度
      color: 0x000000, // 颜色
    });
    // 生成地球的网格对象
    earthMesh = new THREE.Mesh(geometry, material);
    // 把地球添加到mesh容器里
    meshGroup.add(earthMesh);
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

基本的元素创建好了,但界面没有任何显示,我们还需要渲染场景

复制代码
    function screenRender(){
      glRender.render(scene, camera);
      controls.update();
      requestAnimationFrame(screenRender);
    }
    
    screenRender()
    
    
      
      
      
      
      
      
      
    
    AI写代码

这个时候你应该看到一个圆形的物体,接下来我们开始制作点阵式地图

在这里插入图片描述

使用绘图处理工具绘制点阵墨卡托投影的贴图

在这里插入图片描述

我们把地图所有点阵的坐标记录下,最终结果保存在mapPoints.js

复制代码
    export default {
      "points": [
    {
      "x": 1.5,
      "y": 2031.5
    },
    {
      "x": 1.5,
      "y": 2016.5
    },
    ...
      ]
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

创建点阵式地图

复制代码
    // 引入点阵数据
    import mapPoints from "./mapPoints.js";
    /** * 生成点状世界地图方法
     */
    function createMapPoints() {
      // 点的基本材质.
      const material = new THREE.MeshBasicMaterial({
    color: "#AAA",
      });
    
      const sphere = [];
      // 循环遍历所有点将2维坐标映射到3维坐标
      for (let point of mapPoints.points) {
    const pos = convertFlatCoordsToSphereCoords(point.x, point.y);
    if (pos.x && pos.y && pos.z) {
      // 生成点阵
      const pingGeometry = new THREE.SphereGeometry(0.4, 5, 5);
      pingGeometry.translate(pos.x, pos.y, pos.z);
      sphere.push(pingGeometry);
    }
      }
      // 合并所有点阵生成一个mesh对象
      const earthMapPoints = new THREE.Mesh(
    THREE.BufferGeometryUtils.mergeBufferGeometries(sphere),
    material
      );
      // 加入到mesh容器中
      meshGroup.add(earthMapPoints);
    }
    
    /** * 我们需要获取2维点的数组,循环遍历并将每个点转换为其3维位置。这是执行转换的功能。根据您创建的模板投影的大小,您可能需要调整前几个变量
     */
    const globeWidth = 4098 / 2;
    const globeHeight = 1968 / 2;
    
    function convertFlatCoordsToSphereCoords(x, y) {
      let latitude = ((x - globeWidth) / globeWidth) * -180;
      let longitude = ((y - globeHeight) / globeHeight) * -90;
      latitude = (latitude * Math.PI) / 180;
      longitude = (longitude * Math.PI) / 180; 
      const radius = Math.cos(longitude) * globeRadius;
      const x = Math.cos(latitude) * radius;
      const y = Math.sin(longitude) * globeRadius;
      const z = Math.sin(latitude) * radius;
      return {
    x,
    y,
    z,
      };
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

在meshGroup.add(earthMesh)后面调用createMapPoints方法

复制代码
    ...
    // 把地球添加到mesh容器里
    meshGroup.add(earthMesh);
    // 创建点阵图
    createMapPoints();
    
    // 渲染场景
    screenRender();
    ...
    
    
      
      
      
      
      
      
      
      
      
    
    AI写代码

漂亮!

在这里插入图片描述

接下来我们生成柱体,数据采集于https://disease.sh,转换成方便我们使用的结构,保存在data.js

复制代码
    // 转换好的数据
    import data from "./data.js"
    // 定义数据的颜色映射关系,可以根据实际数据动态计算
    const colors = ["#ffdfe0","#ffc0c0","#FF0000","#ee7070","#c80200","#900000","#510000","#290000"];
    const domain = [1000,3000,10000,50000,100000,500000,1000000,1000000];
    
    
      
      
      
      
      
    
    AI写代码

创建生成柱体方法

复制代码
    function createBar() {
      if (!data || data.length === 0) return;
    
      let color;
      // d3比例尺
      const scale = d3.scaleLinear().domain(domain).range(colors);
    
      // 循环遍历数据
      data.forEach(({ lat, lng, value: size }) => {
    // 通过比例尺获取数据对应的颜色
    color = scale(size);
    const pos = convertLatLngToSphereCoords(lat, lng, globeRadius);
    if (pos.x && pos.y && pos.z) {
      // 我们使用立方体来生成柱状图
      const geometry = new THREE.BoxGeometry(2, 2, 1);
      // 移动立方体Z使其立在地球表面
      geometry.applyMatrix4(
        new THREE.Matrix4().makeTranslation(0, 0, -0.5)
      );
      // 生成立方体mesh
      const barMesh = new THREE.Mesh(
        geometry,
        new THREE.MeshBasicMaterial({
          color,
        })
      );
      // 设置位置
      barMesh.position.set(pos.x, pos.y, pos.z);
      // 设置朝向
      barMesh.lookAt(earthMesh.position);
    
      // 根据数据设置柱的长度, 除20000主了为了防止柱体过长,可以根据实际情况调整,或做成参数
      barMesh.scale.z = Math.max(size/20000, 0.1);
      barMesh.updateMatrix();
      // 加入到mesh容器
      meshGroup.add(barMesh);
    }
      });
    }
    
    // 经纬度转成球体坐标
      function convertLatLngToSphereCoords(latitude, longitude, radius) {
    const phi = (latitude * Math.PI) / 180;
    const theta = ((longitude - 180) * Math.PI) / 180;
    const x = -(radius + -1) * Math.cos(phi) * Math.cos(theta);
    const y = (radius + -1) * Math.sin(phi);
    const z = (radius + -1) * Math.cos(phi) * Math.sin(theta);
    return {
      x,
      y,
      z,
    };
      }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

在createMapPoints()后调用createBar()

复制代码
    ...
    // 在这个方法后面调用createMapPoints方法
    meshGroup.add(earthMesh);
    // 创建点阵图
    createMapPoints();
    // 创建柱状图
    createBar()
    
    // 渲染场景
    screenRender();
    ...
    
    
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

最终效果

在这里插入图片描述

教程完整代码:https://github.com/drinkjs/EarthDemo

整体效果代码:https://github.com/drinkjs/mojito

全部评论 (0)

还没有任何评论哟~