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>
相关推荐
小陈同学呦3 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
开发者每周简报3 小时前
网海三部曲·无名宗师传
javascript·人工智能
isyangli_blog3 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008113 小时前
FastAPI APIRouter
开发语言·python
Benszen3 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆3 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木3 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充4 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~4 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言
basketball6164 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang