From 8edd88c0025bae954943fabc75c5852d0215e964 Mon Sep 17 00:00:00 2001 From: wangxk Date: Mon, 30 Jun 2025 14:11:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=AB=E6=98=9F=E5=9B=BE=E5=B1=82=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E9=A1=B5=E9=9D=A2=E5=A4=B1=E6=95=88=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E5=92=8C=E4=BD=8D=E7=BD=AE=E5=8F=98=E5=8A=A8=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E8=A7=A3=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/components/ChartComponentG2Plot.vue | 266 ++++++++++++++---- 1 file changed, 213 insertions(+), 53 deletions(-) diff --git a/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue b/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue index 1a953c9..72d69d5 100644 --- a/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue +++ b/core/core-frontend/src/views/chart/components/views/components/ChartComponentG2Plot.vue @@ -30,6 +30,10 @@ import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7' import { useI18n } from '@/hooks/web/useI18n' import { ExportImage, Scale, Fullscreen, Control, Scene, TileLayer } from '@antv/l7' import { GaodeMap } from '@antv/l7-maps'; +//三维导入 +import * as THREE from 'three'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; +import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 控制器 const { t } = useI18n() const dvMainStore = dvMainStoreWithOut() const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack, inMobile } = @@ -118,7 +122,11 @@ const state = reactive({ let chartData = shallowRef>({ fields: [] }) - +// 添加普通变量存储Three.js对象 +let threeScene: THREE.Scene | null = null; +let threeRenderer: THREE.WebGLRenderer | null = null; +let threeCamera: THREE.PerspectiveCamera | null = null; +let model: THREE.Object3D | null = null; const containerId = 'container-' + showPosition.value + '-' + view.value.id + '-' + suffixId.value const viewTrack = ref(null) @@ -332,8 +340,51 @@ const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView { + // console.log('12345677',isSatelliteVisible); + isSatelliteVisible = !isSatelliteVisible; + if (isSatelliteVisible) { + // 使用 scene.addLayer 方法添加卫星图层 + btn.style.backgroundColor = '#409eff'; + scene.map.add(satelliteLayer) + } else { + // 使用 scene.removeLayer 方法移除卫星图层 + btn.style.backgroundColor = '#000'; + scene.map.remove(satelliteLayer) + + } + }; + // // 确保地图加载完成 + // scene.on('loaded', () => { + // console.log('地图加载完成'); + + // }); + + return btn; + } +} +let mapL7Timer: any let scaleControl: Scale | null = null // 存储比例尺实例 let fullscreenControl let satelliteControlInstance = null; // 用于存储卫星控件实例 @@ -355,10 +406,6 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView, callb }); myChart.getScene()?.addControl(scaleControl); } - - // 创建并添加新比例尺 - - // 添加全屏控件 if (fullscreenControl) { @@ -368,60 +415,14 @@ const renderL7 = async (chart: ChartObj, chartView: L7ChartView, callb }); myChart.getScene()?.addControl(fullscreenControl, 'bottomright'); } - - // ====== 使用高德地图原生API实现卫星图层切换 ====== - let satelliteLayer: any = null; - let isSatelliteVisible = false; - - class SatelliteControl extends Control { - protected onAdd() { - const btn = document.createElement('button'); - btn.className = 'l7-control-button l7-satellite-control'; - btn.innerHTML = '卫星'; - // btn.title = '切换到卫星视图'; - btn.style.backgroundColor = '#000'; - btn.style.color = '#fff'; - btn.style.padding = '2px'; - btn.style.borderRadius = '4px'; - btn.style.cursor = 'pointer' - btn.style.fontSize = '11px'; - const scene = myChart.getScene() - // 确保地图加载完成 - scene.on('loaded', () => { - // 创建高德卫星图层 - satelliteLayer = new window.AMap.TileLayer.Satellite(); - btn.onclick = () => { - isSatelliteVisible = !isSatelliteVisible; - - if (isSatelliteVisible) { - // 使用 scene.addLayer 方法添加卫星图层 - btn.style.backgroundColor = '#409eff'; - scene.map.add(satelliteLayer) - } else { - // 使用 scene.removeLayer 方法移除卫星图层 - btn.style.backgroundColor = '#000'; - scene.map.remove(satelliteLayer) - - } - }; - }); - - return btn; - } - } - // 添加控件到地图 // 移除之前的卫星控件(如果存在) - if (satelliteControlInstance) { - - } else { + if (!satelliteControlInstance) { // 添加新的卫星控件到地图 satelliteControlInstance = new SatelliteControl({ position: 'bottomright' }); myChart.getScene()?.addControl(satelliteControlInstance); } - - // ====== 修复完成 ====== myChart?.render(); @@ -734,6 +735,138 @@ defineExpose({ trackMenu, clearLinkage }) + +//三维 three.js 实现方法 +// 新增方法 - 初始化 Three.js +const initThree = (container: HTMLElement) => { + // 创建场景 + threeScene = new THREE.Scene(); + + // 创建相机 + threeCamera = new THREE.PerspectiveCamera( + 75, + container.clientWidth / container.clientHeight, + 0.1, + 1000 + ); + threeCamera.position.z = 5; + + // 创建渲染器 + threeRenderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); + threeRenderer.setSize(container.clientWidth, container.clientHeight); + // 设置关键样式 + threeRenderer.domElement.style.position = 'absolute'; + threeRenderer.domElement.style.top = '0'; + threeRenderer.domElement.style.left = '0'; + threeRenderer.domElement.style.zIndex = '1'; // 确保在地图之上 + container.appendChild(threeRenderer.domElement); + + + // 添加光源 + const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); + threeScene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); + directionalLight.position.set(10, 10, 10); + threeScene.add(directionalLight); +}; +// 新增方法 - 加载模型 +const loadModel = (filePath: string) => { + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader() + dracoLoader.setDecoderPath('./static/js/') + dracoLoader.setDecoderConfig({ type: 'js' }) + loader.setDRACOLoader(dracoLoader) + + // 确保地图场景存在 + if (!myChart?.getScene()) { + console.warn('地图场景尚未初始化,延迟加载模型'); + // 延迟重试 + setTimeout(() => loadModel(filePath), 500); + return; + } + + const scene = myChart.getScene(); + + // 检查场景是否已加载 + if (scene.loaded) { + // 场景已加载,直接加载模型 + loadModelInternal(loader, filePath); + } else { + // 添加一次性事件监听器 + const loadedHandler = () => { + scene.off('loaded', loadedHandler); // 移除监听器 + loadModelInternal(loader, filePath); + }; + scene.on('loaded', loadedHandler); + } +}; + +// 内部加载方法 +const loadModelInternal = (loader, filePath) => { + loader.load( + filePath, + (gltf) => { + console.log('Model loaded successfully:', gltf); + model = gltf.scene; + threeScene?.add(model); + // animate(); + placeModelByLngLat(116.3974, 39.9093); // 直接调用放置方法 + }, + (progress) => { + console.log('Loading progress:', progress); + }, + (error) => { + console.error('模型加载失败:', error); + } + ); +}; +// 新增方法 - 根据经纬度放置模型 +const placeModelByLngLat = (lng: number, lat: number) => { + debugger + if (!model || !myChart?.getScene()) { + console.warn('模型或地图场景未初始化'); + return; + } + + const scene = myChart.getScene(); + console.log('地图场景状态:', scene); + + try { + const containerPoint = scene.lngLatToContainer([lng, lat]); + console.log('容器坐标:', containerPoint); + + const container = document.getElementById(containerId); + if (!container) { + console.error('容器元素未找到'); + return; + } + + // 转换为Three.js的标准化设备坐标 + const x = (containerPoint.x / container.clientWidth) * 2 - 1; + const y = -(containerPoint.y / container.clientHeight) * 2 + 1; + + console.log('Three.js坐标:', { x, y }); + + // 更新模型位置 + model.position.set(x * 5, y * 5, 0); + + // 确保相机看向模型 + if (threeCamera) { + threeCamera.lookAt(model.position); + console.log('相机已调整朝向模型'); + } + } catch (e) { + console.error('坐标转换错误:', e); + } +}; +// 动画循环 +const animate = () => { + requestAnimationFrame(animate); + if (threeRenderer && threeScene && threeCamera) { + threeRenderer.render(threeScene, threeCamera); + } +}; let resizeObserver const TOLERANCE = 0.01 const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map', 'heat-map'] @@ -760,6 +893,15 @@ onMounted(() => { resizeObserver.observe(containerDom) useEmitt({ name: 'l7-prepare-picture', callback: preparePicture }) useEmitt({ name: 'l7-unprepare-picture', callback: unPreparePicture }) + // 初始化 Three.js + // const container = document.getElementById(containerId); + // if (container) { + // initThree(container); + // // 加载模型(替换为实际路径) + // // loadModel(`./static/3DModel/Camera1.glb`); + // // loadModel('static/3DModel/scene.glb'); + + // } }) onBeforeUnmount(() => { try { @@ -768,6 +910,9 @@ onBeforeUnmount(() => { } catch (e) { console.warn(e) } + if (threeRenderer) { + threeRenderer.dispose(); + } }) @@ -809,6 +954,21 @@ onBeforeUnmount(() => { fill: #fff !important; color: #fff !important; } +:deep(.l7-control-zoom){ + position:fixed; + right: 0px; + bottom: 65px; +} +:deep(.l7-control-button){ + position:fixed; + right: 0px; + bottom: 35px; +} +// :deep(.l7-button-control){ +// position:fixed; +// right: 0px; +// bottom: 0px; +// } // :deep(.l7-control-container .l7-top) { // top: auto !important;