threejs 绘制3d地图效果添加label标签

threejs 3d地图 yekong

vue 数据可视化大屏 项目开发中,会添加一些echarts地图效果,但是一些大屏项目中对地图的效果要求比较高,一般的echarts无法满足客户需要了,所以我们需要3d效果的地图,这里我们使用threejs来实现3d地图效果。今天我们要实现的是3d地图加标签效果。效果类似echarts的label标签。

threejs 绘制3d地图效果添加label标签

效果演示地址

threejs 绘制3d地图效果添加label标签

threejs版本

因为threejs不同版本会有差异,为了避免出现代码无法其他版本下正常运行,我们事先指定好版本,这样可以避免因为版本不同导致的问题。

"three": "0.126.0",

vue内代码

threejs是要在vue实现效果,所以框架也是vue脚手架,vue内html

<template>
  <div class="canvasGLTFBody" ref="canvasGLTFBody">
    <div ref='canvasGLTF' class="canvasGLTF" id="canvasGLTF">
    </div>
    <div id="LoadingInfo" class="LoadingInfo"></div>
  </div>
</template>

<script>

监听窗口变化

因为数据大屏项目一般时要求适应屏幕宽高的,所以我们需要监听窗口变化,当窗口有所变化是,我们需要重新调整three窗口的大小进行适配。

监听窗口大小

mounted() {
    var that = this;
    const viewElem = document.body;
    setTimeout(() => {
      this.getData()
    }, 300)
    // 监听窗口变化
    const resizeObserver = new ResizeObserver(() => {
      setTimeout(() => {
        that.getResize();
      }, 300)
    });
    resizeObserver.observe(viewElem);
  },

调整threejs窗口

这里为了让threejs项目能够快速应用到vue项目中,threejs项目的宽高动态获取指定div宽度,而这个指定div又是随着父div的大小变化而变化。

getResize() {
  var that = this;
  this.width = this.$refs.canvasGLTF.offsetWidth
  this.height = this.$refs.canvasGLTF.offsetHeight
  that.renderer.setSize(that.width, that.height);
  that.labelRenderer.setSize(this.width, this.height)
},

加载地图数据

首先我们需要引入地图数据,然后遍历地图边界数据,绘制地图上的边线以及区域。

import data from './data/china.json'
function getJson() {
    return new Promise(resolve => {
      data.features.forEach(function (area) {
        if (area.geometry.type === "Polygon") {
          area.geometry.coordinates = [area.geometry.coordinates];
        }
        var listItem = {
          title: area.properties.name,
          center: area.properties.center,
        }
        tagList.push(listItem)
        lineGroup.add(borderLine(area.geometry.coordinates));
        var height = TensileHeight;//TensileHeight
        var mesh = ExtrudeMesh(area.geometry.coordinates, height)
        mesh.name = area.properties.name
        meshGroup.add(mesh);
      });
      var lineGroup2 = lineGroup.clone();
      mapGroup.add(lineGroup2);
      lineGroup2.position.z = -TensileHeight * 0.1;
      resolve('data')
    })
  }

地图边线方法

我们在获取边界数据后,通过THREE.LineBasicMaterial来绘制地图上的边线,这里我们可以指定边界线的颜色。

import * as THREE from 'three';

function borderLine(pointsArrs) {
  var group = new THREE.Group();
  pointsArrs.forEach(polygon => {
    var pointArr = [];
    polygon[0].forEach(elem => {
      pointArr.push(elem[0], elem[1], 0);
    });
    group.add(outline(pointArr));
  });
  return group;
}
function outline(pointArr) {
  /**
   * 通过BufferGeometry构建一个几何体,传入顶点数据
   * 通过Line模型渲染几何体,连点成线
   * LineLoop和Line功能一样,区别在于首尾顶点相连,轮廓闭合
   */
  var geometry = new THREE.BufferGeometry();
  var vertices = new Float32Array(pointArr);
  // 创建属性缓冲区对象
  var attribue = new THREE.BufferAttribute(vertices, 3);
  geometry.attributes.position = attribue;
  var material = new THREE.LineBasicMaterial({
    color: 0xa9dbff
  });
  var line = new THREE.LineLoop(geometry, material);
  return line;
}
export { borderLine };

地图区域绘制方法

我们通过threejs的MeshLambertMaterial来绘制地图区域,这里我们可以指定地图的颜色,以及高度。

import * as THREE from 'three';

function ExtrudeMesh(pointsArrs, height) {
    var shapeArr = [];
    pointsArrs.forEach(pointsArr => {
        var vector2Arr = [];
        pointsArr[0].forEach(elem => {
            vector2Arr.push(new THREE.Vector2(elem[0], elem[1]))
        });
        var shape = new THREE.Shape(vector2Arr);
        shapeArr.push(shape);
    });
    var material = new THREE.MeshLambertMaterial({
        color: 0x6793e0,
    }); //材质对象
    var sidebarMaterial = new THREE.MeshLambertMaterial({
        color: 0x174ca3,
    }); //材质对象
    var geometry = new THREE.ExtrudeBufferGeometry( //拉伸造型
        shapeArr,
        {
            depth: height,
            bevelEnabled: false
        }
    );
    var mesh = new THREE.Mesh(geometry, [material, sidebarMaterial]); //网格模型对象
    return mesh;
}


export {ExtrudeMesh};

绘制光源

要能看到地图,我们还需要光源。

// 光源
var directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(400, 200, 300);
scene.add(directionalLight);
var directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight2.position.set(-400, -200, -300);
scene.add(directionalLight2);
var ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
// 光源

添加css2d标签

要添加标签,还需要添加css2d渲染器

  that.labelRenderer = new CSS2DRenderer();
  that.labelRenderer.setSize(that.width, that.height);
  that.labelRenderer.domElement.style.position = 'absolute';
// // 避免renderer.domElement影响HTMl标签定位,设置top为0px
  that.labelRenderer.domElement.style.top = '0px';
  that.labelRenderer.domElement.style.left = '0px';
// //设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
  that.labelRenderer.domElement.style.pointerEvents = 'none';

将渲染器插入到div

threejs的两个渲染器需要都插入到同一个div内。

that.$refs.canvasGLTF.appendChild(that.labelRenderer.domElement)
that.$refs.canvasGLTF.appendChild(that.renderer.domElement)

地图居中处理

因为地图的多样性,比如我们除了渲染中国地图,还可能渲染某个省的,某个市的地图,这时候我们就需要染地图无论渲染什么样的地图,都是处在中间的。我们可以通过获取地图的中心点,地图的大小,来实现地图居中的效果。

import * as THREE from 'three';

function centerCamera(mapGroup, camera, k) {
    // 地图mapGroup的包围盒计算
    var box3 = new THREE.Box3(); //创建一个包围盒
    box3.expandByObject(mapGroup); // .expandByObject()方法:计算层级模型group包围盒
    var center = new THREE.Vector3(); //scaleV3表示包围盒的几何体中心
    box3.getCenter(center); // .getCenter()计算一个层级模型对应包围盒的几何体中心
    // console.log('查看几何中心', center);
    // 重新设置模型的位置
    mapGroup.position.x = mapGroup.position.x - center.x;
    mapGroup.position.y = mapGroup.position.y - center.y;
    mapGroup.position.z = mapGroup.position.z - center.z;


    /*可以根据中国地图mapGroup的包围盒尺寸设置相机参数s */
    var scaleV3 = new THREE.Vector3(); //scaleV3表示包围盒长宽高尺寸
    box3.getSize(scaleV3) // .getSize()计算包围盒长宽高尺寸
    console.log('查看包围盒尺寸', scaleV3)
    // frame.js文件中var s = 150; 150更改为scaleV3.x/2
    var maxL = maxLFun(scaleV3);
    //重新设置s值 乘以0.5适当缩小显示范围,地图占canvas画布比例更大,自然渲染范围更大
    var s = maxL / 2 * 0.5;
    camera.left = -s * k;
    camera.right = s * k;
    camera.top = s;
    camera.bottom = -s;
    //更新相机视图矩阵
    camera.updateProjectionMatrix();
    return maxL
}

// 获取地图的中心点
function getBoxCenter(mapGroup) {
    // 地图mapGroup的包围盒计算
    var box3 = new THREE.Box3(); //创建一个包围盒
    box3.expandByObject(mapGroup); // .expandByObject()方法:计算层级模型group包围盒
    var center = new THREE.Vector3(); //scaleV3表示包围盒的几何体中心
    box3.getCenter(center); // .getCenter()计算一个层级模型对应包围盒的几何体中心
    return center
}

// 获取地图的大小
function getBoxSize(mapGroup) {
    // 地图mapGroup的包围盒计算
    var box3 = new THREE.Box3(); //创建一个包围盒
    box3.expandByObject(mapGroup); // .expandByObject()方法:计算层级模型group包围盒
    var scaleV3 = new THREE.Vector3(); //scaleV3表示包围盒长宽高尺寸
    box3.getSize(scaleV3) // .getSize()计算包围盒长宽高尺寸
    return scaleV3
}

// 计算包围盒的最大边长
function maxLFun(v3) {
    var max;
    if (v3.x > v3.y) {
        max = v3.x
    } else {
        max = v3.y
    }
    if (max > v3.z) {
    } else {
        max = v3.z
    }
    return max;
}

export {centerCamera, getBoxCenter, getBoxSize};

完整实例代码

getData() {
      var that = this
      this.width = this.$refs.canvasGLTF.offsetWidth
      this.height = this.$refs.canvasGLTF.offsetHeight
      var color1 = new THREE.Color(0xedc551);
      var tagList = []
      var scene = new THREE.Scene();
      var mapGroup = new THREE.Group();
      scene.add(mapGroup);
      var lineGroup = new THREE.Group();
      mapGroup.add(lineGroup);
      var meshGroup = new THREE.Group();
      var center = mapGroup.add(meshGroup);
      var TensileHeight = 1;// 拉伸高度
      lineGroup.position.z = TensileHeight + TensileHeight * 0.1;
      // 光源
      var directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
      directionalLight.position.set(400, 200, 300);
      scene.add(directionalLight);
      var directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
      directionalLight2.position.set(-400, -200, -300);
      scene.add(directionalLight2);
      var ambient = new THREE.AmbientLight(0xffffff, 0.6);
      scene.add(ambient);
      // 光源
      var k = that.width / that.height;
      var s = 0;
      that.labelRenderer = new CSS2DRenderer();
      that.labelRenderer.setSize(that.width, that.height);
      that.labelRenderer.domElement.style.position = 'absolute';
// // 避免renderer.domElement影响HTMl标签定位,设置top为0px
      that.labelRenderer.domElement.style.top = '0px';
      that.labelRenderer.domElement.style.left = '0px';
// //设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
      that.labelRenderer.domElement.style.pointerEvents = 'none';
      getJson();
      // 添加tag标签
      tagList.forEach((type) => {
        scene.add(tagName(type.title, type.center[0], type.center[1], TensileHeight + 0.01));
      });
      s = (getBoxSize(mapGroup).x + getBoxSize(mapGroup).y) / 3
      var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
      camera.position.set(104, -105, 200);
      camera.lookAt(scene.position);
      that.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
      });
      // 设置背景颜色
      that.renderer.setClearColor(0x000000, 0)
      that.renderer.setSize(that.width, that.height);
      function render() {
        that.labelRenderer.render(scene, camera);
        that.renderer.render(scene, camera);
        requestAnimationFrame(render);
      }

      render();
      var controls = new OrbitControls(camera, that.renderer.domElement);
      // 获取中心点
      var getBoxCenterData = getBoxCenter(mapGroup)
      // 设置中心
      controls.target.set(getBoxCenterData.x, getBoxCenterData.y, 0);
      controls.update();

      // 获取Json数据
      function getJson() {
        return new Promise(resolve => {
          data.features.forEach(function (area) {
            if (area.geometry.type === "Polygon") {
              area.geometry.coordinates = [area.geometry.coordinates];
            }
            var listItem = {
              title: area.properties.name,
              center: area.properties.center,
            }
            tagList.push(listItem)
            lineGroup.add(borderLine(area.geometry.coordinates));
            var height = TensileHeight;//TensileHeight
            var mesh = ExtrudeMesh(area.geometry.coordinates, height)
            mesh.name = area.properties.name
            meshGroup.add(mesh);
          });
          var lineGroup2 = lineGroup.clone();
          mapGroup.add(lineGroup2);
          lineGroup2.position.z = -TensileHeight * 0.1;
          resolve('data')
        })
      }

      that.$refs.canvasGLTF.appendChild(that.labelRenderer.domElement)
      that.$refs.canvasGLTF.appendChild(that.renderer.domElement)
    }

源码实例下载

当前完整演示实例代码下载
项目基于Vue3 vite js实现

相关文件下载地址
此资源需支付 ¥1 后下载
支付宝购买扫右侧红包码购买更优惠,如无法下载请联系微信:17331886870
喜欢
threejs 绘制3d地图效果添加label标签