vue3+three.js中国3D地图

代码如下

china.json文件放在public文件夹里面
china.json文件在https://datav.aliyun.com/portal/school/atlas/area_selector这里面下载GeoJSON
javascript 复制代码
<template>
  <div>
    <div id="provinceInfo" ref="provinceInfo"></div>
  </div>
</template>

<script setup lang="ts">
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { onMounted, ref, onUnmounted } from 'vue'
import * as d3 from 'd3-geo'

// 浮层 DOM 引用
const provinceInfo = ref<HTMLDivElement | null>(null)

// 全局变量
let renderer: THREE.WebGLRenderer
let scene: THREE.Scene
let camera: THREE.PerspectiveCamera
let controller: OrbitControls
let raycaster: THREE.Raycaster
let mouse: THREE.Vector2
let map: THREE.Object3D
let activeIntersect: any[] = []
let eventOffset = { x: 0, y: 0 }

onMounted(() => {
  init()
})

onUnmounted(() => {
  window.removeEventListener('resize', onWindowResize)
})

function init() {
  // 渲染器
  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement)

  // 场景
  scene = new THREE.Scene()
  // 背景颜色
  scene.background = new THREE.Color(0x072761)

  // 相机
  camera = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  )
  camera.position.set(0, -70, 150)
  camera.lookAt(0, 0, 0)

  setController()
  setLight()
  setRaycaster()
  loadMapData()
  animate()
  window.addEventListener('resize', onWindowResize)
}

// 窗口缩放
function onWindowResize() {
  renderer.setSize(window.innerWidth, window.innerHeight)
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
}

// 加载地图数据
function loadMapData() {
  const loader = new THREE.FileLoader()
  loader.load('/china.json', (data: string) => {
    const jsonData = JSON.parse(data)
    initMap(jsonData)
  })
}

// 初始化地图
function initMap(chinaJson: any) {
  map = new THREE.Object3D()

  const projection = d3.geoMercator()
    .center([104.0, 37.5])
    .scale(80)
    .translate([0, 0])

  chinaJson.features.forEach((elem: any) => {
    const province = new THREE.Object3D()
    const coordinates = elem.geometry.coordinates

    coordinates.forEach((multiPolygon: any) => {
      multiPolygon.forEach((polygon: any) => {
        const shape = new THREE.Shape()
        const points: THREE.Vector3[] = []

        for (let i = 0; i < polygon.length; i++) {
          const [x, y] = projection(polygon[i])
          const px = x
          const py = -y

          if (i === 0) shape.moveTo(px, py)
          shape.lineTo(px, py)
          points.push(new THREE.Vector3(px, py, 4.01))
        }

        const extrudeSettings = { depth: 4, bevelEnabled: false }
        const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)

        const material = new THREE.MeshBasicMaterial({
          color: '#1478d2',
          transparent: true,
          opacity: 0.8
        })
        const material1 = new THREE.MeshBasicMaterial({
          color: '#41c7fd',
          transparent: true,
          opacity: 0.8
        })

        const mesh = new THREE.Mesh(geometry, [material, material1])

        // 边框(修复新版 three.js 废弃 Geometry)
        const lineGeometry = new THREE.BufferGeometry().setFromPoints(points)
        const lineMaterial = new THREE.LineBasicMaterial({ color: '#41c7fd' })
        const line = new THREE.Line(lineGeometry, lineMaterial)

        province.add(mesh)
        province.add(line)
      })
    })

    // 省份信息
    province.userData = elem.properties
    if (elem.properties.contorid) {
      const [x, y] = projection(elem.properties.contorid)
      province.userData._centroid = [x, y]
    }

    map.add(province)
  })

  scene.add(map)
}

// 射线拾取(鼠标检测)
function setRaycaster() {
  raycaster = new THREE.Raycaster()
  mouse = new THREE.Vector2()

  window.addEventListener('mousemove', (event) => {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    eventOffset.x = event.clientX
    eventOffset.y = event.clientY

    if (provinceInfo.value) {
      provinceInfo.value.style.left = eventOffset.x + 2 + 'px'
      provinceInfo.value.style.top = eventOffset.y + 2 + 'px'
    }
  })
}

// 灯光
function setLight() {
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
  scene.add(ambientLight)
}

// 控制器
function setController() {
  controller = new OrbitControls(camera, renderer.domElement)
}

// 动画循环
function animate() {
  requestAnimationFrame(animate)
  controller.update()

  raycaster.setFromCamera(mouse, camera)
  const intersects = raycaster.intersectObjects(scene.children, true)

  // 恢复上一次颜色
  if (activeIntersect.length > 0) {
    activeIntersect.forEach(item => {
      item.object.material[0].color.set('#1478d2')
      item.object.material[1].color.set('#41c7fd')
    })
  }

  activeIntersect = []

  // 选中变色
  for (let i = 0; i < intersects.length; i++) {
    const obj = intersects[i].object
    if (obj.material && obj.material.length === 2) {
      activeIntersect.push(intersects[i])
      obj.material[0].color.set(0x41c7fd)
      obj.material[1].color.set(0x41c7fd)
      break
    }
  }

  createProvinceInfo()
  renderer.render(scene, camera)
}

// 显示省份浮层
function createProvinceInfo() {
  if (!provinceInfo.value) return

  if (activeIntersect.length !== 0 && activeIntersect[0].object.parent.userData?.name) {
    const info = activeIntersect[0].object.parent.userData
    provinceInfo.value.textContent = info.name
    provinceInfo.value.style.visibility = 'visible'
  } else {
    provinceInfo.value.style.visibility = 'hidden'
  }
}
</script>

<style>
/* 必须全局样式,不能 scoped */
html,
body {
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

#provinceInfo {
  position: absolute;
  z-index: 2;
  background: white;
  padding: 10px;
  visibility: hidden;
  pointer-events: none;
}
</style>
相关推荐
Xiaoᴗo.2 小时前
C语言2.0---------
c语言·开发语言·数据结构
ghie90902 小时前
MATLAB 解线性方程组的迭代法
开发语言·算法·matlab
m0_743106462 小时前
【浙大&南洋理工最新综述】Feed-Forward 3D Scene Modeling(二)
人工智能·算法·计算机视觉·3d·几何学
人道领域2 小时前
【LeetCode刷题日记】:151翻转字符串的单词(两种解法)
java·开发语言·算法·leetcode·面试
XS0301062 小时前
Java 基础(五)值传递
java·开发语言
会编程的土豆2 小时前
【日常做题】栈 中缀前缀后缀
开发语言·数据结构·算法
阿扬ABCD2 小时前
python项目:外星人入侵小游戏
开发语言·python·pygame
不吃香菜学java10 小时前
Redis的java客户端
java·开发语言·spring boot·redis·缓存
贵沫末11 小时前
python——打包自己的库并安装
开发语言·windows·python