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

news/2024/9/24 14:32:38

三维地图效果如下,gif压缩导致画质变差了,哈哈

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/1hbMPq5BSE6A43RLzDP4HsA
提取码:fxj6

 

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

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

相关文章

SAP HR-OM模块-组织架构管理

名词介绍名词描述信息类型区分组织管理中的不同信息,例如组织关系、党组织信息、股权信息等等。对象SAP对HR日常业务中涉及到的人、部门、岗位等等的抽象,每个对象会对应一个编号,用于唯一标识。例如,员工编号。组织单位对应企业和企业所管理的部门的抽象。标识符为O职位/岗…

nssctf (2)

misc *1.转为十六进制编码 先是放入winhex,没有发现什么。然后属性也没有。 就放入binwalk分离 得到一个文档 S1ADBBQAAQAAADkwl0xs4x98WgAAAE4AAAAEAAAAY29kZePegfAPrkdnhMG2gb86/AHHpS0GMqCrR9s21bP43SqmesL+oQGo50ljz4zIctqxIsTHV25+1mTE7vFc9gl5IUif7f1/rHIpHql7nqKPb+2M6n…

Crypto(18)——CTFHub

栅栏解救sign inlittle RSA脚本:点击查看代码 import mod c=32949 n=64741 e=42667 p = None for i in range(2,n):if n % i == 0:p = ibreakq = n // pem = mod.Mod(e, (p-1) * (q-1))d = int(1//em)cm = mod.Mod(c,n)ans = int(cm ** d) print(ans)`18429`回转十三位 EzkuM0…

掌握 Nuxt 3 中的状态管理:实践指南

摘要:该文指南详述了Nuxt 3的概况与安装,聚焦于在Nuxt 3框架下运用Vuex进行高效的状态管理,涵盖基础配置、模块化实践至高阶策略,助力开发者构建高性能前后端分离应用。title: 掌握 Nuxt 3 中的状态管理:实践指南 date: 2024/6/22 updated: 2024/6/22 author: cmdragon …

[AI资讯0622] Claude3.5超越GPT-4o,360推出AI搜索,OpenAI收购Rockset,华为发布大模型

360推出AI搜索、浏览器及甄选平台,通过流量曝光优秀AI工具,OpenAI收购Rockset实时数据分析能力,华为发布盘古大模型5.0,Anthropic推出Claude3.5超越OpenAI的GPT-4o,华为发布自研编程语言仓颉,提供AI辅助工具,国内科技公司竞争核心在于持续吸引顶尖AI人才……AI资讯「网红…

树的序列化笔记

\(dfs\)序 以\(DFS\)(先根遍历)⾸次访问顺序将节点重新排列。 特征:每个顶点在序列中出现恰好⼀次(废话) ⽗节点排在⼦节点前⾯(废话) 每棵⼦树都占据序列的⼀个区间欧拉序 记录\(DFS\)递归/回溯时依次经过的所有点。 特征:每个点出现次数=度数(根多1次) 相邻点深度差…

Transformers--4-37-中文文档-七-

Transformers 4.37 中文文档(七)原文:huggingface.co/docs/transformers骨干原文链接:huggingface.co/docs/transformers/v4.37.2/en/main_classes/backbones骨干是用于计算机视觉任务的特征提取模型。可以通过两种方式之一将模型用作骨干:使用预训练模型初始化AutoBackb…

GDB配置

gdb --help # 可查看配置文件路径全局配置/etc/gdbinit;用户配置文件~/.gdbinit 美观打印STL 当你尝试使用 GDB 的 "print"(打印)命令来显示向量、堆栈或任何其他 GDB 抽象数据结构的内容时,你将得到无用的结果。 GDB7.0之后,将支持用Python编写pretty-printers…