高德地图+three.js实战:5步搞定景区3D大屏(附完整代码)
高德地图与three.js融合实战打造沉浸式景区3D可视化大屏在数字化景区建设的浪潮中3D可视化大屏已成为展示景区风貌、提升游客体验的重要窗口。本文将手把手教你如何将高德地图与three.js这两个强大的工具无缝结合从零开始构建一个功能完善、视觉效果震撼的景区3D大屏系统。1. 项目准备与环境搭建任何成功的项目都始于充分的准备工作。在开始编码之前我们需要确保开发环境配置正确并理解核心工具的基本原理。首先创建一个新的项目目录初始化npm环境mkdir scenic-spot-3d-screen cd scenic-spot-3d-screen npm init -y npm install three.js接下来我们需要准备基本的HTML结构。这里有一个经过优化的模板考虑了现代浏览器的兼容性和响应式设计!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title景区3D可视化大屏/title style body { margin: 0; overflow: hidden; font-family: Microsoft YaHei, sans-serif; } #container { position: relative; width: 100vw; height: 100vh; } #mapContainer, #threeContainer { position: absolute; width: 100%; height: 100%; top: 0; left: 0; } /style /head body div idcontainer div idmapContainer/div div idthreeContainer/div /div !-- 使用CDN引入必要的库 -- script srchttps://cdn.jsdelivr.net/npm/three0.132.2/build/three.min.js/script script srchttps://webapi.amap.com/maps?v2.0key您的高德API密钥/script script srcjs/main.js typemodule/script /body /html提示在实际项目中建议将高德API密钥存储在环境变量中而不是直接写在HTML文件里以提高安全性。2. 高德地图基础集成高德地图作为国内领先的地图服务提供商其API的稳定性和功能丰富度都非常出色。让我们先完成地图的基本集成工作。在main.js文件中我们首先初始化高德地图// 初始化高德地图 const map new AMap.Map(mapContainer, { viewMode: 3D, // 启用3D视图 zoom: 15, // 初始缩放级别 pitch: 60, // 俯仰角度 rotation: 0, // 旋转角度 center: [116.397428, 39.90923], // 默认中心点(天安门) mapStyle: amap://styles/darkblue // 使用深色主题 }); // 添加缩放控件 map.addControl(new AMap.ControlBar({ showZoomBar: true, showControlButton: true, position: { right: 10px, top: 10px } }));为了增强地图的视觉效果我们可以添加一些常用的图层// 添加卫星图层 const satelliteLayer new AMap.TileLayer.Satellite(); map.add(satelliteLayer); // 添加路网图层 const roadNetLayer new AMap.TileLayer.RoadNet(); map.add(roadNetLayer); // 添加实时交通图层 const trafficLayer new AMap.TileLayer.Traffic({ zIndex: 10 }); map.add(trafficLayer);3. three.js场景构建three.js是一个强大的3D图形库我们将使用它来创建景区内的3D模型和特效。首先初始化基本的three.js场景// 初始化three.js场景 const scene new THREE.Scene(); scene.background null; // 透明背景以便显示地图 // 设置相机 const camera new THREE.PerspectiveCamera( 60, // 视野角度 window.innerWidth / window.innerHeight, // 宽高比 0.1, // 近裁剪面 10000 // 远裁剪面 ); camera.position.set(0, 500, 1000); // 创建渲染器 const renderer new THREE.WebGLRenderer({ antialias: true, alpha: true // 开启透明通道 }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById(threeContainer).appendChild(renderer.domElement); // 添加环境光和方向光 const ambientLight new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(1, 1, 1).normalize(); scene.add(directionalLight);为了展示景区特色我们可以创建一个简单的3D建筑模型// 创建景区建筑模型 function createBuilding(width, height, depth, color) { const geometry new THREE.BoxGeometry(width, height, depth); const material new THREE.MeshPhongMaterial({ color: color, transparent: true, opacity: 0.9 }); const building new THREE.Mesh(geometry, material); return building; } // 添加几个示例建筑 const mainBuilding createBuilding(100, 200, 80, 0x8a2be2); mainBuilding.position.set(0, 100, 0); scene.add(mainBuilding); const tower createBuilding(40, 300, 40, 0xff6347); tower.position.set(150, 150, -100); scene.add(tower);4. 地图与3D场景的深度融合将高德地图与three.js无缝结合是本项目的核心挑战。我们需要解决坐标转换、图层叠加和交互同步等关键问题。4.1 坐标系统转换高德地图使用经纬度坐标(WGS84)而three.js使用笛卡尔坐标系需要进行转换// 经纬度转three.js坐标 function lngLatToVector3(lng, lat, elevation 0) { // 简单线性映射实际项目中需要更精确的转换 const x (lng - 116.397428) * 100000; const z (lat - 39.90923) * -100000; return new THREE.Vector3(x, elevation, z); } // 示例在地图点击位置放置3D模型 map.on(click, (e) { const { lng, lat } e.lnglat; const position lngLatToVector3(lng, lat, 50); const marker createBuilding(30, 60, 30, 0x1e90ff); marker.position.copy(position); scene.add(marker); });4.2 图层叠加与Z轴管理确保3D场景正确叠加在地图之上需要仔细管理z-index// 确保three.js画布在地图画布之上 const mapCanvas document.querySelector(#mapContainer .amap-canvas); const threeCanvas renderer.domElement; mapCanvas.style.zIndex 0; threeCanvas.style.zIndex 1; threeCanvas.style.pointerEvents none; // 允许点击穿透到地图 // 对于需要交互的3D对象单独设置pointer-events threeCanvas.style.pointerEvents none; const interactiveObjects [mainBuilding, tower]; interactiveObjects.forEach(obj { obj.userData.interactive true; obj.layers.enable(1); // 使用额外的图层 });4.3 相机同步与视角控制为了让3D场景与地图视角保持一致我们需要同步两者的相机// 同步地图和three.js相机 function syncCameras() { const mapCenter map.getCenter(); const mapZoom map.getZoom(); const mapPitch map.getPitch(); const mapRotation map.getRotation(); // 根据地图参数更新three.js相机 camera.position.copy(lngLatToVector3(mapCenter.lng, mapCenter.lat, 500)); camera.lookAt(lngLatToVector3(mapCenter.lng, mapCenter.lat, 0)); camera.rotation.z -mapRotation; // 根据缩放级别调整视野 camera.fov 60 - (mapZoom - 10) * 2; camera.updateProjectionMatrix(); } // 监听地图变化事件 map.on(movestart, syncCameras); map.on(moveend, syncCameras); map.on(zoomchange, syncCameras); map.on(rotate, syncCameras);5. 高级功能实现基础框架搭建完成后我们可以添加一些增强用户体验的高级功能。5.1 景区热点标注在地图上标注景区重要景点并关联3D模型// 添加景区热点 const scenicSpots [ { name: 主入口, lng: 116.397428, lat: 39.90923, color: 0xff0000 }, { name: 观景台, lng: 116.399, lat: 39.908, color: 0x00ff00 }, { name: 服务中心, lng: 116.395, lat: 39.910, color: 0x0000ff } ]; scenicSpots.forEach(spot { // 在地图上添加标记 const marker new AMap.Marker({ position: [spot.lng, spot.lat], title: spot.name, map: map }); // 在3D场景中添加对应模型 const position lngLatToVector3(spot.lng, spot.lat, 30); const model createBuilding(40, 60, 40, spot.color); model.position.copy(position); scene.add(model); // 添加点击交互 marker.on(click, () { map.setCenter([spot.lng, spot.lat]); map.setZoom(18); }); });5.2 游客路径可视化使用three.js的Line对象可视化游客常用路径// 创建路径可视化 function createPath(points, color 0xffff00) { const vertices points.map(p lngLatToVector3(p.lng, p.lat, 5)); const geometry new THREE.BufferGeometry().setFromPoints(vertices); const material new THREE.LineBasicMaterial({ color }); return new THREE.Line(geometry, material); } // 示例路径 const tourRoute [ { lng: 116.397, lat: 39.909 }, { lng: 116.398, lat: 39.9085 }, { lng: 116.399, lat: 39.908 }, { lng: 116.3995, lat: 39.9085 }, { lng: 116.399, lat: 39.909 } ]; const route createPath(tourRoute, 0x00ffff); scene.add(route);5.3 实时数据展示结合WebSocket实现实时游客数据可视化// 模拟实时数据更新 function setupDataVisualization() { // 创建数据展示面板 const dataPanel new THREE.Group(); const panelGeometry new THREE.PlaneGeometry(300, 200); const panelMaterial new THREE.MeshBasicMaterial({ color: 0x333333, transparent: true, opacity: 0.7 }); const panel new THREE.Mesh(panelGeometry, panelMaterial); panel.position.set(0, 200, 0); dataPanel.add(panel); // 创建文字 const loader new THREE.FontLoader(); loader.load(https://cdn.jsdelivr.net/npm/three0.132.2/examples/fonts/helvetiker_regular.typeface.json, font { const textGeometry new THREE.TextGeometry(游客数: 0, { font: font, size: 20, height: 5 }); const textMaterial new THREE.MeshBasicMaterial({ color: 0xffffff }); const text new THREE.Mesh(textGeometry, textMaterial); text.position.set(-120, 180, 1); dataPanel.add(text); // 模拟数据更新 setInterval(() { const visitorCount Math.floor(Math.random() * 1000); text.geometry.dispose(); text.geometry new THREE.TextGeometry(游客数: ${visitorCount}, { font: font, size: 20, height: 5 }); }, 3000); }); scene.add(dataPanel); } setupDataVisualization();6. 性能优化与大屏适配景区大屏通常需要长时间运行并展示复杂场景性能优化至关重要。6.1 渲染优化策略// 实现按需渲染 let needsRender true; map.on(movestart, () { needsRender true }); map.on(moveend, () { needsRender true }); map.on(zoomchange, () { needsRender true }); function render() { requestAnimationFrame(render); if (needsRender) { renderer.render(scene, camera); needsRender false; } } render();6.2 大屏适配技巧// 响应式布局调整 window.addEventListener(resize, () { // 更新three.js渲染器尺寸 camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); // 更新地图尺寸 map.setSize(new AMap.Size(window.innerWidth, window.innerHeight)); needsRender true; });6.3 内存管理// 定期清理不再需要的资源 function cleanupScene() { scene.children.forEach(object { if (object.userData.disposable) { scene.remove(object); if (object.geometry) object.geometry.dispose(); if (object.material) { if (Array.isArray(object.material)) { object.material.forEach(m m.dispose()); } else { object.material.dispose(); } } } }); } // 每10分钟清理一次 setInterval(cleanupScene, 600000);