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

dengjiexs8 2024-07-31 09:03:02 阅读 88

三维地图效果如下:

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

three.js+vue代码如下:

<code><template>

<div id="chinaMap">code>

<div id="threejs"></div>code>

<!-- 右侧按钮 -->

<div class="rightButton">code>

<div v-for="(item, index) in rightButItem" :key="index" :value="item.value" :class="item.selected ? 'selected common' : 'common'" @click="rightButClick">code>

{ { item.name }}

</div>

</div>

<!-- 地图名称元素 -->

<div id="provinceName" style="display: none"></div>code>

<!-- 光柱上方数值元素 -->

<div id="cylinderValue" style="display: none"></div>code>

<!-- 地图标牌元素 -->

<div id="mapTag" style="display: none">code>

<div class="content">code>

<div>旅客:</div>

<div id="mapTag_value">1024万</div>code>

</div>

<div class="arrow"></div>code>

</div>

<!-- 弹框元素 -->

<div id="popup" style="display: none">code>

<div class="popup_line"></div>code>

<div class="popup_Main">code>

<div class="popupMain_top"></div>code>

<div class="popup_content">code>

<div class="popup_head">code>

<div class="popup_title">code>

<div class="title_icon"></div>code>

<div id="popup_Name">湖北省</div>code>

</div>

<div class="close" @click="popupClose"></div>code>

</div>

<div class="popup_item">code>

<div>当前流入:</div>

<div class="item_value">388万人次</div>code>

</div>

<div class="popup_item">code>

<div>景区容量:</div>

<div class="item_value">2688万人次</div>code>

</div>

<div class="popup_item">code>

<div>交通资源利用率:</div>

<div class="item_value">88.7%</div>code>

</div>

<div class="popup_item">code>

<div>省市热搜指数:</div>

<div class="item_value">88.7%</div>code>

</div>

</div>

<div class="popupMain_footer"></div>code>

</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">code>

/* 当视口宽度小于 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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。