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>
相关推荐
潼心1412o6 分钟前
C++初阶(长期更新)第9讲:string(上)
开发语言·c++
朝朝辞暮i18 分钟前
C++ 第 39 章: 阶段性总复习——类、对象、构造、继承、this、指针与智能指针
开发语言·c++·算法·ros2
掉进电商坑三年没爬出来的东叔1 小时前
青龙面板进阶:一个面板聚合多平台签到,所有积分自动领
java·开发语言
Martina_03211 小时前
AI生成的过山车轨道导入后频繁脱轨?用5步检查样条、轨距与碰撞
图像处理·人工智能·游戏·3d·材质·游戏策划·关卡设计
iCxhust1 小时前
51单片机电力载波应用开发(七)广播通讯
c语言·开发语言·单片机·嵌入式硬件·51单片机
Martina_03212 小时前
AI生成的模块场景一烘焙就有黑边?用6步检查Lightmap UV、纹素密度与Padding
人工智能·游戏·3d·aigc·uv·游戏策划·关卡设计
殷色玫瑰2 小时前
C++ STL:stack、queue、priority_queue 与容器适配器详解
java·开发语言·数据结构·c++·算法·visualstudio
500842 小时前
React Native for OpenHarmony 实战:三方库 react-native-url-polyfill 的鸿蒙化适配指南
javascript·react native·react.js·性能优化·electron·harmonyos
IT方大同2 小时前
java输入输出+方法
java·开发语言·python
滕州市燕猫虎计算机科技工作室个体工商户2 小时前
《Rust程序设计》学习笔记一
开发语言·rust·学习笔记·rust程序设计