当前位置: 代码网 > it编程>编程语言>Javascript > three.js+vue实现酷炫三维地图web3d大屏可视化GIS地图

three.js+vue实现酷炫三维地图web3d大屏可视化GIS地图

2024年08月03日 Javascript 我要评论
three.js+vue实现酷炫三维地图web3d大屏可视化GIS地图

三维地图效果如下:

3d地图可视化three.js三维地图vue3下钻地图gis地图大屏源码

three.js+vue代码如下:

<template>
  <div id="chinamap">
    <div id="threejs"></div>
    <!-- 右侧按钮 -->
    <div class="rightbutton">
      <div v-for="(item, index) in rightbutitem" :key="index" :value="item.value" :class="item.selected ? 'selected common' : 'common'" @click="rightbutclick">
        {{ item.name }}
      </div>
    </div>
    <!-- 地图名称元素 -->
    <div id="provincename" style="display: none"></div>
    <!-- 光柱上方数值元素 -->
    <div id="cylindervalue" style="display: none"></div>
    <!-- 地图标牌元素 -->
    <div id="maptag" style="display: none">
      <div class="content">
        <div>旅客:</div>
        <div id="maptag_value">1024万</div>
      </div>
      <div class="arrow"></div>
    </div>
    <!-- 弹框元素 -->
    <div id="popup" style="display: none">
      <div class="popup_line"></div>
      <div class="popup_main">
        <div class="popupmain_top"></div>
        <div class="popup_content">
          <div class="popup_head">
            <div class="popup_title">
              <div class="title_icon"></div>
              <div id="popup_name">湖北省</div>
            </div>
            <div class="close" @click="popupclose"></div>
          </div>
          <div class="popup_item">
            <div>当前流入:</div>
            <div class="item_value">388万人次</div>
          </div>
          <div class="popup_item">
            <div>景区容量:</div>
            <div class="item_value">2688万人次</div>
          </div>
          <div class="popup_item">
            <div>交通资源利用率:</div>
            <div class="item_value">88.7%</div>
          </div>
          <div class="popup_item">
            <div>省市热搜指数:</div>
            <div class="item_value">88.7%</div>
          </div>
        </div>
        <div class="popupmain_footer"></div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onmounted, reactive, ref, watch } from 'vue';
import * as three from 'three';
// 引入tweenjs
import tween from '@tweenjs/tween.js';
import { css2dobject } from 'three/addons/renderers/css2drenderer.js';
// threejs基础配置,场景相机渲染器等
import { scene, camera, controls, renderer, css3drenderer, css2drenderer, outlinepass, composer, finalcomposer, mirror } from './baseconfig/index.js';
// 加载地图
import { initmap, citydata, mapuf, waterobj, projection } from './initchinamap/index.js';
// 地图底部网格背景
import { gridhelper, meshpoint } from './backgroundmesh/index.js';
// 初始化鼠标移入地图浮动效果
import { initmapfloat } from './mapfloat/index.js';
// 地图圆圈背景
import { circleuf, outercircle, innercircle, diffusecircle, gradientplane, planeuf } from './backgroundcircle/index.js';
// 飞线组对象,更新飞线函数,飞线动画
import { flygroup, updateflyline, flylineanimation } from './flyline/index.js';
// 光柱组对象,创建光柱函数
import { cylindergroup, createcylindern, cylinderglowarr, cylinderobj, apertureanimation } from './cylinder/index.js';
// import { createprovincename } from "./provincename/index.js";
import { createmaptag, taggroup } from './maptag/index.js';
import { particlesupdate, createparticles, particles } from './particles/index.js';
import { disposeobject } from './disposeobject/index.js';

// 右侧按钮选项
const rightbutitem = reactive([
  { value: 'tourism', name: '刷色图', selected: false },
  { value: 'cylinder', name: '光柱', selected: false },
  { value: 'flyline', name: '飞线', selected: false },
  { value: 'tag', name: '标牌', selected: true },
  { value: 'particles', name: '粒子', selected: false },
  { value: 'mirror', name: '倒影', selected: false },
  { value: 'ripple', name: '波纹', selected: false },
]);
// 描边模型
let outlinemodel = null;
// 时钟对象,用于获取两帧渲染之间的时间值
const clock = new three.clock();
// 射线拾取中模型对象
let raymodel = null;
// 弹框元素
let divtag = null;
// css2d弹框对象
let css2dpopup = null;
// 需要辉光的模型数组
let glowarr = [];
let mapmodel;

onmounted(async () => {
  document.getelementbyid('threejs').appendchild(renderer.domelement);
  document.getelementbyid('threejs').appendchild(css3drenderer.domelement);
  document.getelementbyid('threejs').appendchild(css2drenderer.domelement);
  // 创建省份名称对象
  // createprovincename();
  // 创建光柱
  createcylindern();
  // 创建粒子
  createparticles();

  // 加载中国地图
  mapmodel = await initmap();
  // 初始化鼠标移入地图浮动效果
  initmapfloat(camera, mapmodel);
  // 初始化地图点击发光效果
  initmapclickglow();

  // 创建地图标牌
  createmaptag(citydata, waterobj);
  scene.add(mapmodel, gridhelper, meshpoint, outercircle, innercircle, diffusecircle, gradientplane, taggroup);
  // 设置需要辉光物体数组
  glowarr = [...cylinderglowarr, flygroup.children];
  // 开始循环渲染
  render();
  // 首次进入动画
  eventanimation();
});

// 循环渲染
function render() {
  requestanimationframe(render);
  camera.updateprojectionmatrix();
  controls.update();
  // 两帧渲染间隔
  let deltatime = clock.getdelta();
  // 地图模型侧边渐变效果
  mapuf.utime.value += deltatime;
  if (mapuf.utime.value >= 5) {
    mapuf.utime.value = 0.0;
  }

  if (rightbutitem[1].selected) apertureanimation(); // 光圈缩放动画

  // 背景外圈内圈旋转
  outercircle.rotation.z -= 0.003;
  innercircle.rotation.z += 0.003;
  // 飞线动画
  if (rightbutitem[2].selected) {
    flylineanimation();
  }
  // 波纹扩散动画
  if (rightbutitem[6].selected) {
    circleuf.utime.value += deltatime;
    if (circleuf.utime.value >= 6) {
      circleuf.utime.value = 0.0;
    }
  }
  // 粒子动画
  if (rightbutitem[4].selected) {
    particlesupdate();
  }

  // composer.render(scene, camera);
  // css3drenderer.render(scene, camera);
  // css2drenderer.render(scene, camera);
  // tween更新
  tween.update();
  // 将场景内的物体材质设置为黑色
  scene.traverse(darkenmaterial);
  // 渲染辉光
  composer.render();
  // 还原材质
  scene.traverse(restorematerial);
  // 最终渲染
  finalcomposer.render();
  css3drenderer.render(scene, camera);
  css2drenderer.render(scene, camera);
}
// 右侧按钮点击事件
function rightbutclick(e) {
  const value = e.target.getattribute('value');
  const clickitem = rightbutitem.filter((obj) => obj.value === value)[0];
  clickitem.selected = !clickitem.selected;
  // 点击刷色图按钮
  if (clickitem.value === 'tourism') {
    mapmodel.traverse((item) => {
      if (item.color) {
        if (clickitem.selected) {
          item.material[0].color = item.color;
          item.material[0].metalness = 0.65;
          item.material[0].map = undefined;
          item.material[0].needsupdate = true;
        } else {
          item.material[0].color = new three.color('#00ffff');
          item.material[0].metalness = 0.0;
          item.material[0].map = item.texture;
          item.material[0].needsupdate = true;
        }
      }
    });
  }
  // 点击飞线按钮
  else if (clickitem.value === 'flyline') {
    if (clickitem.selected) {
      scene.add(flygroup);
      updateflyline('湖北', citydata);
    } else {
      scene.remove(flygroup);
    }
  }
  // 点击光柱按钮
  else if (clickitem.value === 'cylinder') {
    if (clickitem.selected) {
      console.log(cylindergroup, 'cylindergroup');
      scene.add(cylindergroup);
      for (let item in cylinderobj) {
        cylinderobj[item].visible = true;
        cylinderobj[item].children[0].visible = true;
      }
    } else {
      for (let item in cylinderobj) {
        cylinderobj[item].visible = false;
        cylinderobj[item].children[0].visible = false;
      }
      for (const iterator of cylindergroup.children) {
        if (iterator.children) {
          css2drenderer.domelement.removechild(iterator.children[0].element); // 重点
        }
      }
      scene.remove(cylindergroup);
    }
  }
  // 点击波纹按钮
  else if (clickitem.value === 'ripple') {
    if (clickitem.selected) {
      diffusecircle.visible = true;
    } else {
      diffusecircle.visible = false;
    }
    circleuf.utime.value = 0.0;
  }
  // 点击倒影按钮
  else if (clickitem.value === 'mirror') {
    if (clickitem.selected) {
      scene.add(mirror);
      planeuf.opacitys.radius = 0.05;
      planeuf.opacitys.value = 0.4;
    } else {
      scene.remove(mirror);
      planeuf.opacitys.radius = 0.35;
      planeuf.opacitys.value = 0.7;
    }
  }
  // 点击标牌按钮
  else if (clickitem.value === 'tag') {
    if (clickitem.selected) {
      scene.add(taggroup);
    } else {
      for (const iterator of taggroup.children) {
        css2drenderer.domelement.removechild(iterator.element); // 重点
      }
      scene.remove(taggroup);
    }
  }
  // 点击粒子按钮
  else if (clickitem.value === 'particles') {
    if (clickitem.selected) {
      scene.add(particles);
    } else {
      scene.remove(particles);
    }
  }
}
// 初始化地图点击发光效果
function initmapclickglow() {
  divtag = document.getelementbyid('popup');
  const widthscale = window.innerwidth / 1920;
  const heightscale = window.innerheight / 941;
  divtag.style.top += (37 * heightscale).tofixed(2) + 'px';
  divtag.style.left += (390 * widthscale).tofixed(2) + 'px';
  // 转换为css2d对象
  css2dpopup = new css2dobject(divtag);
  // 设置一个较高的渲染顺序,防止弹框被标牌等物体遮挡住
  css2dpopup.renderorder = 99;
  // 弹框名称元素
  const namediv = document.getelementbyid('popup_name');

  let temp = true;
  // 添加鼠标点击事件
  addeventlistener('click', (e) => {
    const px = e.offsetx;
    const py = e.offsety;
    // 屏幕坐标转为标准设备坐标
    const x = (px / window.innerwidth) * 2 - 1;
    const y = -(py / window.innerheight) * 2 + 1;
    // 创建射线
    const raycaster = new three.raycaster();
    // 设置射线参数
    raycaster.setfromcamera(new three.vector2(x, y), camera);
    // 射线交叉计算拾取模型
    let intersects = raycaster.intersectobjects(mapmodel.children);
    // 检测结果过滤掉光圈
    intersects = intersects.filter(function (intersect) {
      return intersect.object.name !== '光圈' && intersect.object.name !== '光柱' && intersect.object.parent.name !== '省份边界线';
    });
    // 点击选中模型时
    if (intersects.length > 0) {
      // 清除上一次选中模型
      if (outlinemodel) {
        disposeobject(outlinemodel);
        outlinemodel.parent.remove(outlinemodel);
        outlinemodel = null;
      }
      // 射线拾取中的模型
      const raymodel = intersects[0].object.parent;
      // 地图边线数据
      const maplinedata = raymodel.userdata.mapdata;
      // 创建shape对象
      const shape = new three.shape();
      // 当数据为多个多边形时
      if (maplinedata.type === 'multipolygon') {
        // 遍历数据,绘制shape对象数据
        maplinedata.coordinates.foreach((coordinate, index) => {
          if (index === 0) {
            coordinate.foreach((rows) => {
              rows.foreach((row) => {
                const [x, y] = projection(row);
                if (index === 0) {
                  shape.moveto(x, y);
                }
                shape.lineto(x, y);
              });
            });
          }
        });
      }
      // 当数据为单个多边形时
      if (maplinedata.type === 'polygon') {
        maplinedata.coordinates.foreach((coordinate) => {
          // 遍历数据,绘制shape对象数据
          maplinedata.coordinates.foreach((rows, index) => {
            if (index === 0) {
              rows.foreach((row) => {
                const [x, y] = projection(row);
                if (index === 0) {
                  shape.moveto(x, y);
                }
                shape.lineto(x, y);
              });
            }
          });
        });
      }
      // 创建形状几何体,shape对象作为参数
      const geometry = new three.shapegeometry(shape);
      const material = new three.meshbasicmaterial({
        color: raymodel.children[1].material[0].color,
        map: raymodel.children[1].material[0].map,
        side: three.doubleside,
      });
      let mesh = new three.mesh(geometry, material);
      mesh.rotatex(-math.pi);
      mesh.name = '描边模型';

      outlinemodel = mesh;
      raymodel.add(outlinemodel);

      // 设置描边效果
      outlinepass.selectedobjects = [outlinemodel];
      // 获取中心位置
      const center = raymodel.userdata.center;
      // 设置弹框位置
      css2dpopup.position.set(center[0], center[1], 0);
      outlinemodel.add(css2dpopup);
      // 设置弹框名称
      namediv.innerhtml = raymodel.parent.name;

      // 弹框逐渐显示
      new tween.tween({ opacity: 0 })
        .to({ opacity: 1.0 }, 500)
        .onupdate(function (obj) {
          // 动态更新div元素透明度
          divtag.style.opacity = obj.opacity;
        })
        .start();
    }
  });
}
// 弹框关闭事件
function popupclose() {
  if (outlinemodel) {
    // 描边效果清除
    outlinepass.selectedobjects = [];
    // 弹框逐渐隐藏
    new tween.tween({ opacity: 1 })
      .to({ opacity: 0 }, 500)
      .onupdate(function (obj) {
        //动态更新div元素透明度
        divtag.style.opacity = obj.opacity;
      })
      .oncomplete(function () {
        // 清除选中模型
        disposeobject(outlinemodel);
        outlinemodel.parent.remove(outlinemodel);
        outlinemodel = null;
      })
      .start();
  }
}
// 将材质设置成黑色
function darkenmaterial(obj) {
  // 场景颜色单独保存
  if (obj instanceof three.scene) {
    obj.bg = obj.background;
    obj.background = null;
  }
  const material = obj.material;
  if (material && !glowarr.includes(obj) && !material.isshadermaterial) {
    obj.originalmaterial = obj.material;
    const proto = object.getprototypeof(material).constructor;
    obj.material = new proto({ color: new three.color('#000') });
  }
}
// 还原材质
function restorematerial(obj) {
  if (obj instanceof three.scene) {
    // obj.background = obj.bg;
  }
  if (!obj.originalmaterial) return;
  obj.material = obj.originalmaterial;
  delete obj.originalmaterial;
}
// 首次进入动画
function eventanimation() {
  new tween.tween(camera.clone().position)
    .to(new three.vector3(-5, 250, 150), 1500)
    .easing(tween.easing.sinusoidal.inout)
    .onupdate((e) => {
      camera.position.copy(e);
      controls.target.set(-5, 0, 10);
      controls.update();
    })
    .start();
}
</script>
<style lang="less">
/* 当视口宽度小于 600 像素时,设置最小字体大小 */
@media (max-width: 1400px) {
  #maptag {
    font-size: 12px !important;
    width: 80px !important;
    height: 30px !important;
  }
}
#chinamap {
  width: 100%;
  height: 100%;
  position: absolute;
  overflow: hidden;
}
#threejs {
  width: 100%;
  height: 100%;
}
.rightbutton {
  position: absolute;
  right: 1vw;
  bottom: 40vh;
  width: 4vw;

  .common {
    width: 100%;
    height: 3vh;
    border: 1px solid #00ffff;
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 1.2vh 0;
    color: #fafafa;
    opacity: 0.5;
    font-size: 0.8vw;
    cursor: pointer;
    transition: 1s;
  }

  .selected {
    opacity: 1 !important;
    transition: 1s;
  }
}
#provincename {
  pointer-events: none;
  position: absolute;
  left: 0;
  top: 0;
  color: #8ee5ee;
  padding: 10px;
  width: 200px;
  height: 20px;
  line-height: 20px;
  text-align: center;
  font-size: 13px;
}
#popup {
  z-index: 999;
  position: absolute;
  left: 0px;
  top: 0px;
  width: 41.66vw;
  height: 26.59vh;
  display: flex;

  .popup_line {
    margin-top: 4%;
    width: 24%;
    height: 26%;
    background: url('../../public/popup_line.png') no-repeat;
    background-size: 100% 100%;
  }
  .popup_main {
    width: 35%;
    height: 80%;

    .popupmain_top {
      width: 100%;
      height: 10%;
      background: url('../../public/popupmain_head.png') no-repeat;
      background-size: 100% 100%;
    }
    .popupmain_footer {
      width: 100%;
      height: 10%;
      background: url('../../public/popupmain_footer.png') no-repeat;
      background-size: 100% 100%;
    }
    .popup_content {
      color: #fafafa;
      // background: rgba(47, 53, 121, 0.9);
      background-image: linear-gradient(to bottom, rgba(15, 36, 77, 1), rgba(8, 124, 190, 1));
      border-radius: 10px;
      width: 100%;
      height: 70%;
      padding: 5% 0%;
      .popup_head {
        width: 100%;
        height: 12%;
        margin-bottom: 2%;
        display: flex;
        align-items: center;
        .popup_title {
          color: #8ee5ee;
          font-size: 1vw;
          letter-spacing: 5px;
          width: 88%;
          height: 100%;
          display: flex;
          align-items: center;

          .title_icon {
            width: 0.33vw;
            height: 100%;
            background: #2586ff;
            margin-right: 10%;
          }
        }
        .close {
          cursor: pointer;
          pointer-events: auto;
          width: 1.5vw;
          height: 1.5vw;
          background: url('../../public/close.png') no-repeat;
          background-size: 100% 100%;
        }
      }
      .popup_item {
        display: flex;
        align-items: center;
        width: 85%;
        padding-left: 5%;
        height: 18%;
        // background: rgb(160, 196, 221);
        border-radius: 10px;
        margin: 2.5% 0%;
        margin-left: 10%;

        div {
          line-height: 100%;
          margin-right: 10%;
        }
        .item_value {
          font-size: 0.9vw;
          color: #00ffff;
          font-weight: 600;
          letter-spacing: 2px;
        }
      }
    }
  }
}
#cylindervalue {
  position: absolute;
  top: 0;
  left: 0;
  color: #bbffff;
}
#maptag {
  z-index: 997;
  position: absolute;
  top: 0;
  left: 0;
  font-size: 0.6vw;
  width: 4.2vw;
  height: 4.7vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  .content {
    width: 100%;
    height: calc(100% - 1vw);
    // background: #0e1937;
    background: #0e2346;
    border: 1px solid #6298a9;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fafafa;

    #maptag_value {
      color: #ffd700;
    }
  }
  .content::before {
    content: '';
    width: 100%;
    // height: calc(100% - 1vw);
    position: absolute;
    background: linear-gradient(to top, #26aad1, #26aad1) left top no-repeat,
      //上左
      linear-gradient(to right, #26aad1, #26aad1) left top no-repeat,
      linear-gradient(to top, #26aad1, #26aad1) right bottom no-repeat,
      //下右
      linear-gradient(to left, #26aad1, #26aad1) right bottom no-repeat; //右下
    background-size: 2px 10px, 16px 2px, 2px 10px, 16px 2px;
    pointer-events: none;
  }

  .arrow {
    background: url('../../public/arrow.png') no-repeat;
    background-size: 100% 100%;
    width: 1vw;
    height: 1vw;
  }
}
</style>

上面是主页面代码,需要完整案例全部代码,访问下面百度网盘:

链接:https://pan.baidu.com/s/1sxbraba-mrlgvavua1w3xw 
提取码:59b7

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com