博客 / 詳情

返回

基於 Three.js 的 3D 地圖可視化:核心原理與實現步驟

項目概述

這是一個基於Three.js的3D交互式地圖可視化系統,以廣東省地圖為展示對象,實現了豐富的3D視覺效果和交互功能。本文將對項目中的核心函數進行逐步驟、逐函數的詳細分析,幫助讀者深入理解系統的實現原理。

技術棧

  • 前端框架:Vue 3
  • 3D渲染引擎:Three.js
  • 構建工具:Vite
  • 動畫庫:Tween.js
  • 輔助庫:Delaunator、geo-point-in-polygon等地理計算庫

項目初始化流程

1. App.vue - 主組件入口

onMounted - 組件掛載函數

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 - 異步數據請求函數

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數據轉換函數

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 - 經緯度轉墨卡託座標

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 - 經緯度轉球面座標

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 - 計算模型包圍盒

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 - 構造函數

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 - 初始化函數

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 - 相機初始化

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 - 渲染器初始化

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 - 光源初始化

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 - 控制器初始化

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 - 渲染循環

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類中重寫)

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 - 創建光柱標記

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 - 創建標記點

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 - 創建呼吸光圈

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 - 創建序列幀動畫

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渲染器

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標籤

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 - 初始化旋轉光圈

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 - 初始化粒子系統

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地圖可視化系統的實現原理和技術細節,為類似項目的開發提供參考和借鑑。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.