Threejs 实现3D 地图(02)创建3d 地图

"d3": "^7.9.0",
"three": "^0.169.0",
"vue": "^3.5.10"

地图数据来源: DataV.GeoAtlas地理小工具系列

<script setup>
import {onMounted, ref} from 'vue'
import * as THREE from 'three'
import * as d3 from "d3";  //莫开托坐标 矫正地图坐标
import map from './constant/map.json'
// 引入轨道控制器扩展库OrbitControls.js
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
// 拿到页面的宽高
const width = window.innerWidth, height = window.innerHeight;

// d3投影转换函数 (109, 34.5  中心点位置)
const handleProject = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0])
// 存储地图容器
const mapContainer = new THREE.Object3D()

// 创建场景
const scene = new THREE.Scene();
// 将背景颜色设置为白色
scene.background = new THREE.Color("#000000");

// 创建相机
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000);
// 设置相机位置
camera.position.z = 1000;

// // 辅助线 AxesHelper
const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(width, height);

// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
//阻尼 更真实
controls.enableDamping = true

// 地图数据处理
const initGeom = () => {
  // 将地图数据显示在屏幕上
  // 如果是服务器返回的数据,需要先将数据转为geojson格式 这里默认用的本地数据
  handleData(map)
}

const handleData = (jsonData) => {
  const featureList = jsonData.features;
  featureList.forEach((feature) => {
    // 创建省的容器
    const province = new THREE.Object3D;
    // 省份名称
    province.properties = feature.properties.name
    province.name = feature.properties.name // 省份名称
    mapContainer.name = feature.properties.name // 省份名称
    // 省份坐标信息
    const coordinates = feature.geometry.coordinates
    if (feature.geometry.type === 'MultiPolygon') {
      coordinates.forEach((cord) => {
        // coordinate 就是边界素组坐标系
        cord.forEach((coordinate) => {
          // 三维多边形
          const extrudeMesh = creatDepthPolygon(coordinate)
          // 给extrudeMesh对象加一个自定义的属性(properties)用来存放省份名称
          extrudeMesh.properties = feature.properties.name
          // 绘制省份边缘线条
          const line = createLine(coordinate);
          // 将面加入3d 中
          province.add(extrudeMesh)
          // 将线加入3d 中
          province.add(line)
        })
      })
    }
    if (feature.geometry.type === 'Polygon') {
      coordinates.forEach((coordinate) => {
        // 三维多边形
        const extrudeMesh = creatDepthPolygon(coordinate)
        extrudeMesh.properties = feature.properties.name
        // 线条
        const line = createLine(coordinate);
        province.add(extrudeMesh)
        province.add(line)
      })
    }
    // 将每个省份的容器加入到地图容器中
    mapContainer.add(province)
  })
  // 将地图容器加入到场景中
  scene.add(mapContainer)
}

// 创建三维多边形(也就是每个省份的地图)
const creatDepthPolygon = (coordinate) => {
  const shape = new THREE.Shape();
  // 每一个item都是省份边界坐标 需要把 json 的坐标系 转化为d3的坐标系  例如:[117.429915,40.576141]
  coordinate.forEach((item, index) => {
    const [x_XYZ, y_XYZ] = handleProject(item)
    if (index === 0) {
      // moveTo 的主要作用是定义形状的起点位置
      shape.moveTo(x_XYZ, -y_XYZ)
    } else {
      // 依次按照坐标 形成一个多边形
      shape.lineTo(x_XYZ, -y_XYZ)
    }
  })
  const extrudeSettings = {
    steps: 2,
    depth: 16,
    bevelEnabled: true,
    bevelThickness: 1,
    bevelSize: 1,
    bevelOffset: 0,
    bevelSegments: 1
  };
  // ExtrudeGeometry  创建一个多边形
  const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)  //挤压缓冲几何体
  // 设置每个身份的背景颜色
  const material = new THREE.MeshBasicMaterial({
    // color: new THREE.Color(Math.random() * 0xffffff), // 每个省随机赋色
    color: '#d13a34',
    transparent: true,
    opacity: 0.6
  })
  return new THREE.Mesh(geometry, material)
}

// 创建省份边界线条
const createLine = (coordinate) => {
  const material = new THREE.LineBasicMaterial({
    color: '#ffffff'
  });
  const points = []
  coordinate.forEach((item, index) => {
    // 每一个item都是省份边界坐标 需要把 json 的坐标系 转化为d3的坐标系  例如:[117.429915,40.576141]
    const [x_XYZ, y_XYZ] = handleProject(item)
    points.push(new THREE.Vector3(x_XYZ, -y_XYZ, 25))
  })

  const geometry = new THREE.BufferGeometry().setFromPoints(points);

  return new THREE.Line(geometry, material);
}


// 渲染页面
const render = () => {
  // 将场景(scene)和摄像机(camera 加入进来)
  renderer.render(scene, camera)
  // 渲染下一帧的时候会调用render函数
  requestAnimationFrame(render)
  controls.update()
}

const initLight = () => {
  // 基本光源
  const ambLight = new THREE.AmbientLight('#ffffff', 0.3)
  /**
   * 设置聚光灯相关的的属性
   */
  const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯
  spotLight.position.set(40, 200, 10);
  spotLight.castShadow = true; // 只有该属性为true时,该点光源允许产生阴影,并且下列属性可用
  scene.add(ambLight, spotLight); // 向场景中添加光源
}

onMounted(() => {
  // 添加物体到场景
  initGeom()
  // 渲染
  render()
  // 设置环境光
  initLight()
  // 将渲染加入到页面上
  document.body.appendChild(renderer.domElement);
})
</script>

<template>
  <div id="info"></div>
</template>

<style scoped>

</style>
相关推荐
活宝小娜1 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点1 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow1 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
刚刚好ā2 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
会发光的猪。5 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客5 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
周全全5 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
ZwaterZ6 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
码农六六6 小时前
vue3封装Element Plus table表格组件
javascript·vue.js·elementui
徐同保6 小时前
el-table 多选改成单选
javascript·vue.js·elementui