基于 Three.js 的 3D 地图可视化:核心原理与实现步骤

项目概述

这是一个基于Three.js的3D交互式地图可视化系统,以广东省地图为展示对象,实现了丰富的3D视觉效果和交互功能。本文将对项目中的核心函数进行逐步骤、逐函数的详细分析,帮助读者深入理解系统的实现原理。

技术栈

  • 前端框架:Vue 3
  • 3D渲染引擎:Three.js
  • 构建工具:Vite
  • 动画库:Tween.js
  • 辅助库:Delaunator、geo-point-in-polygon等地理计算库

项目初始化流程

1. App.vue - 主组件入口

onMounted - 组件挂载函数

javascript 复制代码
onMounted(async () => {
  // 1. 加载地图数据
  let provinceData = await requestData("./data/map/广东省.json")
  provinceData = transfromGeoJSON(provinceData)

  // 2. 继承Map3d类创建当前地图实例
  class CurrentMap3d extends Map3d {
    // ... 自定义地图方法
  }

  // 3. 初始化地图实例
  baseMap = new CurrentMap3d({
    container: "#app-32-map",
    axesVisibel: true,
    controls: {
      enableDamping: true,
      maxPolarAngle: (Math.PI / 2) * 0.98,
    },
  })

  // 4. 运行地图
  baseMap.run()
  
  // 5. 添加窗口大小变化监听
  window.addEventListener("resize", resize)
})

作用:组件挂载时执行,完成地图的初始化、数据加载、渲染和事件监听设置。

执行步骤

  1. 加载并转换广东省地图数据
  2. 定义自定义地图类继承Map3d基类
  3. 创建地图实例并配置参数
  4. 运行地图渲染循环
  5. 添加窗口大小变化监听

数据处理模块

1. useFileLoader.js - 文件加载钩子

requestData - 异步数据请求函数

javascript 复制代码
const requestData = async (url) => {
  try {
    const response = await fetch(url)
    const data = await response.json()
    return data
  } catch (error) {
    console.error('数据加载失败:', error)
    return null
  }
}

作用:异步加载GeoJSON地图数据。

参数

  • url:地图数据文件路径

返回值:解析后的JSON数据对象

2. useConversionStandardData.js - 数据格式转换钩子

transfromGeoJSON - GeoJSON数据转换函数

javascript 复制代码
const transfromGeoJSON = (worldData) => {
  let features = worldData.features
  for (let i = 0; i < features.length; i++) {
    const element = features[i]
    // 将Polygon处理跟MultiPolygon一样的数据结构
    if (element.geometry.type === 'Polygon') {
      element.geometry.coordinates = [element.geometry.coordinates]
    }
  }
  return worldData
}

作用:统一GeoJSON数据格式,将Polygon类型数据转换为与MultiPolygon相同的二维数组结构。

参数

  • worldData:原始GeoJSON数据

返回值:标准化后的GeoJSON数据

实现原理:遍历features数组,检测geometry.type,如果是Polygon类型,则将coordinates转换为二维数组格式,确保后续处理的一致性。

3. useCoord.js - 坐标处理钩子

geoMercatorCoord - 经纬度转墨卡托坐标

javascript 复制代码
const geoMercatorCoord = (longitude, latitude) => {
  var E = longitude
  var N = latitude
  var x = (E * 20037508.34) / 180
  var y = Math.log(Math.tan(((90 + N) * Math.PI) / 360)) / (Math.PI / 180)
  y = (y * 20037508.34) / 180
  return {
    x: x, //墨卡托x坐标------对应经度
    y: y, //墨卡托y坐标------对应维度
  }
}

作用:将地理经纬度坐标转换为墨卡托投影坐标。

参数

  • longitude:经度值
  • latitude:纬度值

返回值:包含x、y属性的墨卡托坐标对象

实现原理:使用墨卡托投影公式进行坐标转换,将经度直接线性映射,纬度通过对数函数进行非线性映射,使地图在赤道附近保持比例正确。

geoSphereCoord - 经纬度转球面坐标

javascript 复制代码
const geoSphereCoord = (R, longitude, latitude) => {
  var lon = (longitude * Math.PI) / 180 //转弧度值
  var lat = (latitude * Math.PI) / 180 //转弧度值
  lon = -lon // three.js坐标系z坐标轴对应经度-90度,而不是90度

  // 经纬度坐标转球面坐标计算公式
  var x = R * Math.cos(lat) * Math.cos(lon)
  var y = R * Math.sin(lat)
  var z = R * Math.cos(lat) * Math.sin(lon)
  // 返回球面坐标
  return {
    x: x,
    y: y,
    z: z,
  }
}

作用:将地理经纬度坐标转换为三维球面上的坐标。

参数

  • R:球体半径
  • longitude:经度值
  • latitude:纬度值

返回值:包含x、y、z属性的球面坐标对象

实现原理:使用球面坐标转换公式,将经纬度转换为三维空间坐标,适用于创建地球等球面模型。

getBoundingBox - 计算模型包围盒

javascript 复制代码
const getBoundingBox = group => {
  // 包围盒计算模型对象的大小和位置
  var box3 = new THREE.Box3()
  box3.expandByObject(group) // 计算模型包围盒
  var size = new THREE.Vector3()
  box3.getSize(size) // 计算包围盒尺寸
  var center = new THREE.Vector3()
  box3.getCenter(center) // 计算一个层级模型对应包围盒的几何体中心坐标
  return {
    box3,
    center,
    size,
  }
}

作用:计算3D模型或模型组的包围盒、尺寸和中心坐标。

参数

  • group:Three.js模型或模型组对象

返回值:包含包围盒(box3)、中心坐标(center)和尺寸(size)的对象

实现原理:使用Three.js的Box3类计算模型的最小包围立方体,用于后续的相机定位和模型布局。

3D地图建模模块

1. Map3d.js - 地图基类

constructor - 构造函数

javascript 复制代码
constructor(options = {}) {
  let defaultOptions = {
    isFull: true,
    container: null,
    width: window.innerWidth,
    height: window.innerHeight,
    bgColor: 0x000000,
    materialColor: 0xff0000,
    controls: {
      visibel: true,
      enableDamping: true,
      autoRotate: false,
      maxPolarAngle: Math.PI,
    },
    statsVisibel: true,
    axesVisibel: true,
    axesHelperSize: 250,
  }
  this.options = deepMerge(defaultOptions, options)
  this.container = document.querySelector(this.options.container)
  this.options.width = this.container.offsetWidth
  this.options.height = this.container.offsetHeight
  this.scene = new THREE.Scene()
  this.camera = null
  this.renderer = null
  this.mesh = null
  this.animationStop = null
  this.controls = null
  this.stats = null

  this.init()
}

作用:初始化地图实例,设置默认参数,创建基本的Three.js场景、相机、渲染器等对象。

参数

  • options:地图配置参数对象

执行步骤

  1. 合并默认参数和用户参数
  2. 获取容器元素并设置尺寸
  3. 初始化Three.js核心对象
  4. 调用init方法进行进一步初始化

init - 初始化函数

javascript 复制代码
init() {
  this.initStats()
  this.initCamera()
  this.initModel()
  this.initRenderer()
  this.initLight()
  this.initAxes()
  this.initControls()
  let gl = this.renderer.domElement.getContext('webgl')
  gl && gl.getExtension('WEBGL_lose_context').loseContext()
}

作用:统一调用各个初始化方法,完成地图的全面初始化。

执行步骤

  1. 初始化性能统计
  2. 初始化相机
  3. 初始化模型(由子类实现)
  4. 初始化渲染器
  5. 初始化光源
  6. 初始化坐标轴辅助
  7. 初始化控制器
  8. 释放WebGL上下文(优化内存)

initCamera - 相机初始化

javascript 复制代码
initCamera() {
  let { width, height } = this.options
  let rate = width / height
  this.camera = new THREE.PerspectiveCamera(45, rate, 0.001, 90000000)
  this.camera.up.set(0, 0, 1)
  this.camera.position.set(102.97777217804006, 17.660260562607277, 8.029548316292933)
  this.camera.lookAt(...centerXY, 0)
}

作用:初始化透视相机,设置相机位置、朝向和视野参数。

执行步骤

  1. 计算宽高比
  2. 创建透视相机实例
  3. 设置相机上方向(Z轴向上)
  4. 设置相机位置坐标
  5. 设置相机看向地图中心点

initRenderer - 渲染器初始化

javascript 复制代码
initRenderer() {
  let { width, height, bgColor } = this.options
  let renderer = new THREE.WebGLRenderer({
    antialias: true,
  })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setSize(width, height)
  renderer.setClearColor(bgColor, 1)
  this.container.appendChild(renderer.domElement)
  this.renderer = renderer
}

作用:初始化WebGL渲染器,设置渲染参数并将渲染画布添加到容器中。

执行步骤

  1. 创建WebGL渲染器实例(启用抗锯齿)
  2. 设置像素比适应高DPI屏幕
  3. 设置渲染尺寸
  4. 设置背景颜色
  5. 将渲染画布添加到DOM容器

initLight - 光源初始化

javascript 复制代码
initLight() {
  // 平行光1
  let directionalLight1 = new THREE.DirectionalLight(0x7af4ff, 1)
  directionalLight1.position.set(...centerXY, 30)
  // 平行光2
  let directionalLight2 = new THREE.DirectionalLight(0x7af4ff, 1)
  directionalLight2.position.set(...centerXY, 30)
  // 环境光
  let ambientLight = new THREE.AmbientLight(0x7af4ff, 1)
  // 将光源添加到场景中
  this.addObject(directionalLight1)
  this.addObject(directionalLight2)
  this.addObject(ambientLight)
}

作用:初始化场景光源,包括平行光和环境光,增强3D效果。

执行步骤

  1. 创建两个平行光并设置位置
  2. 创建环境光
  3. 将所有光源添加到场景

initControls - 控制器初始化

javascript 复制代码
initControls() {
  try {
    let {
      controls: { enableDamping, autoRotate, visibel, maxPolarAngle },
    } = this.options
    if (!visibel) return false
    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.maxPolarAngle = maxPolarAngle
    this.controls.autoRotate = autoRotate
    this.controls.enableDamping = enableDamping
  } catch (error) {
    console.log(error)
  }
}

作用:初始化轨道控制器,实现地图的交互控制。

执行步骤

  1. 检查控制器是否启用
  2. 创建OrbitControls实例
  3. 设置控制器参数(最大极角、自动旋转、阻尼效果等)

loop - 渲染循环

javascript 复制代码
loop() {
  this.animationStop = window.requestAnimationFrame(() => {
    this.loop()
  })
  this.renderer.render(this.scene, this.camera)
  if (this.options.controls.visibel && this.controls) {
    this.controls.update()
  }
  if (this.options.statsVisibel) this.stats.update()
  if (this.rotatingApertureMesh) {
    this.rotatingApertureMesh.rotation.z += 0.0005
  }
  if (this.rotatingPointMesh) {
    this.rotatingPointMesh.rotation.z -= 0.0005
  }
  if (this.css2dRender) {
    this.css2dRender.render(this.scene, this.camera)
  }
  if (this.particleArr.length) {
    for (let i = 0; i < this.particleArr.length; i++) {
      this.particleArr[i].updateSequenceFrame()
      this.particleArr[i].position.z += 0.01
      if (this.particleArr[i].position.z >= 6) {
        this.particleArr[i].position.z = -6
      }
    }
  }
  TWEEN.update()
}

作用:实现地图的持续渲染和动画效果更新。

执行步骤

  1. 使用requestAnimationFrame创建渲染循环
  2. 渲染3D场景
  3. 更新控制器状态
  4. 更新性能统计
  5. 更新旋转光圈动画
  6. 更新旋转点动画
  7. 渲染2D标签
  8. 更新粒子动画
  9. 更新Tween.js动画

2. App.vue - 自定义地图模型初始化

initModel - 模型初始化(在CurrentMap3d类中重写)

javascript 复制代码
initModel() {
  try {
    // 创建组
    this.mapGroup = new THREE.Group()
    // 标签初始化
    this.css2dRender = initCSS2DRender(this.options, this.container)

    provinceData.features.forEach((elem, index) => {
      // 定一个省份对象
      const province = new THREE.Object3D()
      // 坐标
      const coordinates = elem.geometry.coordinates

      // 循环坐标
      coordinates.forEach((multiPolygon) => {
        multiPolygon.forEach((polygon) => {
          const shape = new THREE.Shape()
          // 绘制shape
          for (let i = 0; i < polygon.length; i++) {
            let [x, y] = polygon[i]
            if (i === 0) {
              shape.moveTo(x, y)
            }
            shape.lineTo(x, y)
          }
          // 拉伸设置
          const extrudeSettings = {
            depth: 0.2,
            bevelEnabled: true,
            bevelSegments: 1,
            bevelThickness: 0.1,
          }
          const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
          const mesh = new THREE.Mesh(geometry, [topFaceMaterial, sideMaterial])
          province.add(mesh)
        })
      })
      this.mapGroup.add(province)
      // 创建标点和标签
      initLightPoint(properties, this.mapGroup)
      initLabel(properties, this.scene)
    })
    // 创建上下边框
    initBorderLine(provinceData, this.mapGroup)

    let earthGroupBound = getBoundingBox(this.mapGroup)
    centerXY = [earthGroupBound.center.x, earthGroupBound.center.y]
    let { size } = earthGroupBound
    let width = size.x < size.y ? size.y + 1 : size.x + 1
    // 添加背景,修饰元素
    this.rotatingApertureMesh = initRotatingAperture(this.scene, width)
    this.rotatingPointMesh = initRotatingPoint(this.scene, width - 2)
    initCirclePoint(this.scene, width)
    initSceneBg(this.scene, width)

    // 将组添加到场景中
    this.scene.add(this.mapGroup)
    this.particleArr = initParticle(this.scene, earthGroupBound)
    initGui()
  } catch (error) {
    console.log(error)
  }
}

作用:初始化3D地图模型,包括省份几何体、材质、标签、装饰元素等。

执行步骤

  1. 创建地图模型组
  2. 初始化2D标签渲染器
  3. 遍历地图数据创建省份模型
  4. 为每个省份创建3D几何体和材质
  5. 添加光柱标记和标签
  6. 创建地图边框
  7. 计算地图包围盒和中心点
  8. 添加装饰元素(旋转光圈、背景等)
  9. 将地图组添加到场景
  10. 初始化粒子系统
  11. 初始化GUI控制器

视觉效果增强模块

1. useMapMarkedLightPillar.js - 光柱标记钩子

createLightPillar - 创建光柱标记

javascript 复制代码
const createLightPillar = (lon, lat, heightScaleFactor = 1) => {
  let group = new THREE.Group()
  // 柱体高度
  const height = heightScaleFactor
  // 柱体的geo,6.19=柱体图片高度/宽度的倍数
  const geometry = new THREE.PlaneBufferGeometry(height / 6.219, height)
  // 柱体旋转90度,垂直于Y轴
  geometry.rotateX(Math.PI / 2)
  // 柱体的z轴移动高度一半对齐中心点
  geometry.translate(0, 0, height / 2)
  // 柱子材质
  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(defaultOptions.lightPillarUrl),
    color: 0x00ffff,
    transparent: true,
    depthWrite: false,
    side: THREE.DoubleSide,
  })
  // 光柱01
  let light01 = new THREE.Mesh(geometry, material)
  light01.renderOrder = 99
  light01.name = "createLightPillar01"
  // 光柱02:复制光柱01
  let light02 = light01.clone()
  light02.name = "createLightPillar02"
  // 光柱02,旋转90°,跟光柱01交叉
  light02.rotateZ(Math.PI / 2)
  // 创建底部标点
  const bottomMesh = createPointMesh()
  // 创建光圈
  const lightHalo = createLightHalo()
  // 将光柱和标点添加到组里
  group.add(bottomMesh, lightHalo, light01, light02)
  // 设置组对象的姿态
  group.position.set(lon, lat, 0)
  return group
}

作用:创建包含底部标记、呼吸光圈和交叉光柱的完整标记效果。

参数

  • lon:经度坐标
  • lat:纬度坐标
  • heightScaleFactor:光柱高度缩放系数

返回值:包含完整光柱效果的Three.js Group对象

执行步骤

  1. 创建光柱组容器
  2. 计算柱体尺寸和几何体
  3. 创建柱体贴图材质
  4. 创建第一个光柱并设置渲染顺序
  5. 克隆并旋转创建第二个交叉光柱
  6. 创建底部标记点
  7. 创建呼吸光圈
  8. 将所有元素添加到组中
  9. 设置组的位置坐标
  10. 返回完整的光柱组

createPointMesh - 创建标记点

javascript 复制代码
const createPointMesh = () => {
  // 标记点:几何体,材质
  const geometry = new THREE.PlaneBufferGeometry(1, 1)
  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(defaultOptions.pointTextureUrl),
    color: 0x00ffff,
    side: THREE.DoubleSide,
    transparent: true,
    depthWrite: false, //禁止写入深度缓冲区数据
  })
  let mesh = new THREE.Mesh(geometry, material)
  mesh.renderOrder = 97
  mesh.name = "createPointMesh"
  // 缩放
  const scale = 0.15 * defaultOptions.scaleFactor
  mesh.scale.set(scale, scale, scale)
  return mesh
}

作用:创建光柱底部的标记点。

返回值:标记点Mesh对象

实现原理:使用PlaneGeometry创建平面,加载标记点纹理,设置透明和渲染顺序。

createLightHalo - 创建呼吸光圈

javascript 复制代码
const createLightHalo = () => {
  // 标记点:几何体,材质
  const geometry = new THREE.PlaneBufferGeometry(1, 1)
  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(defaultOptions.lightHaloTextureUrl),
    color: 0x00ffff,
    side: THREE.DoubleSide,
    opacity: 0,
    transparent: true,
    depthWrite: false, //禁止写入深度缓冲区数据
  })
  let mesh = new THREE.Mesh(geometry, material)
  mesh.renderOrder = 98
  mesh.name = "createLightHalo"
  // 缩放
  const scale = 0.3 * defaultOptions.scaleFactor
  mesh.scale.set(scale, scale, scale)
  // 动画延迟时间
  const delay = random(0, 2000)
  // 动画:透明度缩放动画
  mesh.tween1 = new TWEEN.Tween({ scale: scale, opacity: 0 })
    .to({ scale: scale * 1.5, opacity: 1 }, 1000)
    .delay(delay)
    .onUpdate((params) => {
      let { scale, opacity } = params
      mesh.scale.set(scale, scale, scale)
      mesh.material.opacity = opacity
    })
  mesh.tween2 = new TWEEN.Tween({ scale: scale * 1.5, opacity: 1 })
    .to({ scale: scale * 2, opacity: 0 }, 1000)
    .onUpdate((params) => {
      let { scale, opacity } = params
      mesh.scale.set(scale, scale, scale)
      mesh.material.opacity = opacity
    })
  mesh.tween1.chain(mesh.tween2)
  mesh.tween2.chain(mesh.tween1)
  mesh.tween1.start()
  return mesh
}

作用:创建带有呼吸动画效果的光圈。

返回值:光圈Mesh对象(带有tween动画)

实现原理:创建平面并加载光圈纹理,使用Tween.js实现透明度和缩放的循环动画,形成呼吸效果。

2. useSequenceFrameAnimate.js - 序列帧动画钩子

createSequenceFrame - 创建序列帧动画

javascript 复制代码
const createSequenceFrame = ({ image, width, height, frame, column, row, speed = 0.1 }) => {
  // 创建平面几何体
  const geometry = new THREE.PlaneGeometry(width, height)
  // 创建纹理
  const texture = new THREE.TextureLoader().load(image)
  // 设置纹理参数
  texture.wrapS = THREE.RepeatWrapping
  texture.wrapT = THREE.RepeatWrapping
  // 计算每个帧的大小
  const frameWidth = 1 / column
  const frameHeight = 1 / row
  // 设置纹理显示区域
  texture.repeat.set(frameWidth, frameHeight)
  // 创建材质
  const material = new THREE.MeshBasicMaterial({
    map: texture,
    transparent: true,
    side: THREE.DoubleSide,
  })
  // 创建网格
  const mesh = new THREE.Mesh(geometry, material)
  // 添加动画属性
  mesh.currentFrame = 0
  mesh.totalFrames = frame
  mesh.column = column
  mesh.frameWidth = frameWidth
  mesh.frameHeight = frameHeight
  mesh.speed = speed
  mesh.texture = texture
  
  // 添加更新方法
  mesh.updateSequenceFrame = function() {
    this.currentFrame += this.speed
    if (this.currentFrame >= this.totalFrames) {
      this.currentFrame = 0
    }
    const frameIndex = Math.floor(this.currentFrame)
    const x = (frameIndex % this.column) * this.frameWidth
    const y = 1 - Math.floor(frameIndex / this.column) * this.frameHeight - this.frameHeight
    this.texture.offset.set(x, y)
  }
  
  return mesh
}

作用:创建基于序列帧图片的动画效果。

参数

  • image:序列帧图片路径
  • width:动画宽度
  • height:动画高度
  • frame:总帧数
  • column:每行帧数
  • row:每列帧数
  • speed:动画播放速度

返回值:带有动画更新方法的Three.js Mesh对象

实现原理:通过控制纹理的offset属性,实现序列帧图片的逐帧播放,形成动画效果。

2D标签渲染模块

1. useCSS2DRender.js - CSS2D渲染钩子

initCSS2DRender - 初始化2D渲染器

javascript 复制代码
const initCSS2DRender = (options, container) => {
  const css2dRender = new THREE.CSS2DRenderer()
  css2dRender.setSize(options.width, options.height)
  css2dRender.domElement.style.position = 'absolute'
  css2dRender.domElement.style.top = '0px'
  css2dRender.domElement.style.pointerEvents = 'none'
  container.appendChild(css2dRender.domElement)
  return css2dRender
}

作用:初始化CSS2DRenderer,用于在3D场景中渲染2D HTML元素。

参数

  • options:渲染器配置参数
  • container:DOM容器元素

返回值:初始化完成的CSS2DRenderer实例

实现原理:使用Three.js的CSS2DRenderer创建一个与3D渲染器叠加的2D渲染层,用于显示HTML标签。

create2DTag - 创建2D标签

javascript 复制代码
const create2DTag = (className) => {
  const div = document.createElement('div')
  div.className = className
  div.style.color = '#fff'
  div.style.padding = '4px 8px'
  div.style.borderRadius = '4px'
  div.style.fontSize = '12px'
  div.style.whiteSpace = 'nowrap'
  div.style.opacity = '0'
  
  const label = new THREE.CSS2DObject(div)
  label.visible = false
  
  // 添加显示方法
  label.show = function(text, position) {
    this.element.innerHTML = text
    this.position.copy(position)
    this.visible = true
    this.element.style.opacity = '1'
  }
  
  // 添加隐藏方法
  label.hide = function() {
    this.visible = false
    this.element.style.opacity = '0'
  }
  
  return label
}

作用:创建可显示在3D场景中的2D HTML标签。

参数

  • className:标签的CSS类名

返回值:带有show和hide方法的CSS2DObject实例

实现原理:创建HTML元素并封装为CSS2DObject,添加显示和隐藏方法,便于在3D场景中控制标签的显示。

地图装饰元素模块

1. App.vue - 装饰元素创建函数

initRotatingAperture - 初始化旋转光圈

javascript 复制代码
const initRotatingAperture = (scene, width) => {
  let plane = new THREE.PlaneBufferGeometry(width, width)
  let material = new THREE.MeshBasicMaterial({
    map: rotatingApertureTexture,
    transparent: true,
    opacity: 1,
    depthTest: true,
  })
  let mesh = new THREE.Mesh(plane, material)
  mesh.position.set(...centerXY, 0)
  mesh.scale.set(1.1, 1.1, 1.1)
  scene.add(mesh)
  return mesh
}

作用:创建地图底部的旋转光圈效果。

参数

  • scene:Three.js场景对象
  • width:光圈宽度

返回值:光圈Mesh对象(在loop函数中更新旋转)

initParticle - 初始化粒子系统

javascript 复制代码
const initParticle = (scene, bound) => {
  // 获取中心点和中间地图大小
  let { center, size } = bound
  // 构建范围,中间地图的2倍
  let minX = center.x - size.x
  let maxX = center.x + size.x
  let minY = center.y - size.y
  let maxY = center.y + size.y
  let minZ = -6
  let maxZ = 6

  let particleArr = []
  for (let i = 0; i < 16; i++) {
    const particle = createSequenceFrame({
      image: "./data/map/上升粒子1.png",
      width: 180,
      height: 189,
      frame: 9,
      column: 9,
      row: 1,
      speed: 0.5,
    })
    let particleScale = random(5, 10) / 1000
    particle.scale.set(particleScale, particleScale, particleScale)
    particle.rotation.x = Math.PI / 2
    let x = random(minX, maxX)
    let y = random(minY, maxY)
    let z = random(minZ, maxZ)
    particle.position.set(x, y, z)
    particleArr.push(particle)
  }
  scene.add(...particleArr)
  return particleArr
}

作用:创建上升粒子效果,增强地图的动态感。

参数

  • scene:Three.js场景对象
  • bound:地图边界信息对象

返回值:粒子对象数组(在loop函数中更新位置和动画)

执行步骤

  1. 计算粒子生成范围
  2. 循环创建粒子对象
  3. 加载序列帧粒子图片
  4. 设置粒子大小和旋转角度
  5. 随机分布粒子位置
  6. 将粒子添加到场景
  7. 返回粒子数组

总结

本项目通过模块化设计和组件化开发,构建了一个功能丰富、性能优良的3D交互式地图可视化系统。核心函数按照数据处理、3D建模、视觉效果、交互控制等模块进行组织,形成了清晰的调用关系和执行流程。

系统的主要技术亮点包括:

  1. 高效的数据处理:实现了GeoJSON数据的标准化转换和坐标系统转换
  2. 精美的3D模型:使用ExtrudeGeometry创建具有立体感的地图模型
  3. 丰富的视觉效果:包括光柱标记、呼吸光圈、粒子动画等
  4. 流畅的交互体验:基于OrbitControls实现的相机控制
  5. 灵活的2D标签:使用CSS2DRenderer实现的3D场景中2D标签渲染

通过对这些核心函数的详细分析,我们可以深入理解3D地图可视化系统的实现原理和技术细节,为类似项目的开发提供参考和借鉴。

相关推荐
叫我詹躲躲8 小时前
Vue 3 动态组件详解
前端·vue.js
TimelessHaze8 小时前
算法复杂度分析与优化:从理论到实战
前端·javascript·算法
旧梦星轨9 小时前
掌握 Vite 环境配置:从 .env 文件到运行模式的完整实践
前端·前端框架·node.js·vue·react
PieroPC9 小时前
NiceGui 3.4.0 的 ui.pagination 分页实现 例子
前端·后端
晚霞的不甘9 小时前
实战前瞻:构建高可用、强实时的 Flutter + OpenHarmony 智慧医疗健康平台
前端·javascript·flutter
精神病不行计算机不上班9 小时前
[Java Web]Java Servlet基础
java·前端·servlet·html·mvc·web·session
玉木成琳9 小时前
Taro + React + @nutui/nutui-react-taro 时间选择器重写
前端·react.js·taro
lxh01139 小时前
2025/12/17总结
前端·webpack
芳草萋萋鹦鹉洲哦9 小时前
【elementUI】form表单rules没生效
前端·javascript·elementui