行业知识
three.js实现3d地图,包含散点,地图背景图片(vue)
2026-11-07 05:47:26
来源:互联网转载
效果图
图片资源:
1、下载three.js及d3依赖,我这里使用的是three r153版本
npm i threenpm i d3
2、使用d3将地理坐标转换成坐标轴xy值,在地图区域及边界线加载到场景后,通过包围盒计算完整地图的max和min坐标,再根据max和min的值重新计算每个地图区域的uv坐标,如果不重新计算uv坐标会导致贴图显示异常
this.projection = d3 .geoMercator() .center(this.centerCoordinate) .translate([0, 0]);// 加载地图背景const backgroundTexture = new THREE.TextureLoader().load( require("@/assets/images/map.png"));// 加载地图let fileLoader = new THREE.FileLoader();fileLoader.load("/anhui.json", (data) => { // 添加地图及边界线 this.addMapGeometry(data); // 重新计算地图uv坐标 let arr = []; let box = new THREE.Box3(); for (let v of this.map.children) { for (let v2 of v.children) { // 判断是否为ExtrudeGeometry if (v2.geometry instanceof THREE.ExtrudeGeometry) { arr.push(v2); let itemBox = new THREE.Box3().setFromObject(v); box.union(itemBox); } } } var bboxMin = box.min; var bboxMax = box.max; // 计算UV的缩放比例 var uvScale = new THREE.Vector2( 1 / (bboxMax.x - bboxMin.x), 1 / (bboxMax.y - bboxMin.y) ); for (let v of arr) { let uvAttribute = v.geometry.getAttribute("uv"); for (let i = 0; i { // 定一个省份3D对象 const province = new THREE.Object3D(); // 每个的 坐标 数组 const coordinates = elem.geometry.coordinates; if (elem.geometry.type === "MultiPolygon") { // 循环坐标数组 coordinates.forEach((multiPolygon) => { multiPolygon.forEach((polygon) => { this.drawItem(elem, polygon, province); }); }); this.map.add(province); } else if (elem.geometry.type === "Polygon") { // 循环坐标数组 coordinates.forEach((polygon) => { this.drawItem(elem, polygon, province); }); this.map.add(province); }});this.scene.add(this.map);},drawItem(elem, polygon, province) {const shape = new THREE.Shape();const pointsArray = new Array();for (let i = 0; i 3、设置后期处理
//设置光晕this.composer = new EffectComposer(this.renderer); //效果组合器//创建通道let renderScene = new RenderPass(this.scene, this.camera);this.composer.addPass(renderScene);let outlinePass = new OutlinePass( new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera, this.boundaryLineArr // 边界线数组);outlinePass.renderToScreen = true;outlinePass.edgeGlow = 2; // 光晕效果outlinePass.usePatternTexture = false;outlinePass.edgeThickness = 10; // 边框宽度outlinePass.edgeStrength = 1.5; // 光晕效果outlinePass.pulsePeriod = 0; // 光晕闪烁的速度outlinePass.visibleEdgeColor.set("#1acdec");outlinePass.hiddenEdgeColor.set("#1acdec");this.composer.addPass(outlinePass); 完整代码
注:我这边使用的是安徽的地图,需要替换自己的地图json以及地图中心地理坐标
{{ selectedPointData.name }} import * as THREE from "three";import * as d3 from "d3";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";export default { data() { return { scene: null, camera: null, renderer: null, controls: null, centerCoordinate: [117.13, 31.89], // 地图中心地理坐标 projection: null, // Mercator 投影 mapConfig: { deep: 0.2, // 挤出的深度 }, boundaryLineArr: [], // 边界线 composer: "", // 后期处理 pointData: [ { coordinates: [117.33, 31.79], type: 1, name: "合肥", value: 100, }, { coordinates: [118.502, 31.684], type: 1, name: "马鞍山", value: 100, }, ], pointInstanceArr: [], // 坐标点实例 show: false, // 是否显示tooltip selectedPointData: {}, // 选中的坐标点数据 }; }, mounted() { this.init(); window.addEventListener("resize", () => { this.renderer.setSize(window.innerWidth, window.innerHeight); this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); }); }, methods: { init() { this.renderer = new THREE.WebGLRenderer(); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace; document.querySelector("#page").appendChild(this.renderer.domElement); this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.set(5, 5, 26); this.camera.lookAt(0, 0, 0); // let axesHelp = new THREE.AxesHelper(5); // this.scene.add(axesHelp); this.controls = new OrbitControls(this.camera, this.renderer.domElement); // 墨卡托投影转换 this.projection = d3 .geoMercator() .center(this.centerCoordinate) .translate([0, 0]); // 根据地球贴图做轻微调整 // 添加地图 this.addMap(); // 给地图边界线添加outline效果 this.setLineOutline(); // 添加灯光 let ambientLight = new THREE.AmbientLight(0xffffff, 1); this.scene.add(ambientLight); // 添加散点 this.setPoint(); // 设置光线投射 this.setRaycaster(); this.render(); }, render() { this.renderer.render(this.scene, this.camera); this.controls.update(); if (this.composer) this.composer.render(); requestAnimationFrame(this.render); }, // 添加地图 addMap() { // 加载地图背景 const backgroundTexture = new THREE.TextureLoader().load( require("@/assets/images/map.png") ); // 加载地图 let fileLoader = new THREE.FileLoader(); fileLoader.load("/anhui.json", (data) => { // 添加地图及边界线 this.addMapGeometry(data); // 重新计算地图uv坐标 let arr = []; let box = new THREE.Box3(); for (let v of this.map.children) { for (let v2 of v.children) { // 判断是否为ExtrudeGeometry,只计算所有地图区域总和的包围盒大小 if (v2.geometry instanceof THREE.ExtrudeGeometry) { arr.push(v2); let itemBox = new THREE.Box3().setFromObject(v2); box.union(itemBox); } } } var bboxMin = box.min; var bboxMax = box.max; // 计算UV的缩放比例 var uvScale = new THREE.Vector2( 1 / (bboxMax.x - bboxMin.x), 1 / (bboxMax.y - bboxMin.y) ); for (let v of arr) { let uvAttribute = v.geometry.getAttribute("uv"); for (let i = 0; i { // 定一个省份3D对象 const province = new THREE.Object3D(); // 每个的 坐标 数组 const coordinates = elem.geometry.coordinates; if (elem.geometry.type === "MultiPolygon") { // 循环坐标数组 coordinates.forEach((multiPolygon) => { multiPolygon.forEach((polygon) => { this.drawItem(elem, polygon, province); }); }); this.map.add(province); } else if (elem.geometry.type === "Polygon") { // 循环坐标数组 coordinates.forEach((polygon) => { this.drawItem(elem, polygon, province); }); this.map.add(province); } }); this.scene.add(this.map); }, drawItem(elem, polygon, province) { const shape = new THREE.Shape(); const pointsArray = new Array(); for (let i = 0; i { pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, this.camera); const intersects = raycaster.intersectObjects(this.pointInstanceArr); if (intersects && intersects.length > 0) { let tooltip = this.$refs.tooltip; tooltip.style.left = event.pageX + "px"; tooltip.style.top = event.pageY + "px"; this.selectedPointData = intersects[0].object.properties; this.show = true; } else { this.selectedPointData = {}; this.show = false; } }); }, },};.page { height: 100vh; .tooltip { position: absolute; background-color: #fff; padding: 10px; border-radius: 8px; }}
上一篇:完美世界怎么选服务器号?