Threejs项目实战之三:根据JSON格式数据创建三维地图

最终效果

今天我们来实现在Threejs中根据JSON格式数据创建三维地图的效果,先看下最终项目完成后的效果展示
在这里插入图片描述

实现原理

通过读取JSON格式地图数据,获取地图边界及各区域边界经坐标,使用使用 D3.js 库中的 geoMercator() 方法进行墨卡托投影转换,将经纬度坐标转换为平面直角坐标系中的像素坐标,根据转换后的像素坐标,使用Threejs提供的ExtrudeGeometry方法对形状进行拉伸,形成三维立体效果;然后通过使用Threejs提供的Raycaster射线,获取鼠标与Mesh的焦点,并监听鼠标的mousemove移动事件和click点击事件,在mousemove移动事件处理Mesh变色,在click事件中对点击的边界进行拉伸,最终形成如图所示效果

准备JSON格式数据

JSON格式数据可以从阿里云提供的DataV工具中下载,具体下载地址为:https://datav.aliyun.com/portal/school/atlas/area_selector#&lat=30.332329214580188&lng=106.72278672066881&zoom=3.5,界面如下图所示:
在这里插入图片描述
打开页面后,选择你需要下载的省、市或者区县的边界地图,点击右面的下载即可,我这里选取的是杭州及其下属区县的地图JSON格式数据。

创建项目

  • 在D盘新建vue-map文件夹,鼠标右键点击新建的文件夹,使用vscode打开;
  • 在vscode中使用快捷键Ctrl+Shift+~打开终端,在终端中使用vite构建工具创建项目,输入pnpm create earth-vue-map --template vue创建项目
  • 创建成功后,在终端中输入cd earth-vue-map进入文件夹
  • 输入pnpm i 安装依赖包
  • 安装完成后,输入pnpm run div 启动项目,打开浏览器,可以看到系统默认的页面,说明项目环境搭建成功
  • 安装ThreeJS库,在终端中输入pnpm install three安装threejs插件
  • 安装D3库,在终端中输入 pnpm install d3安装D3库
  • 删除vite构建工具为我们创建的HelloWord.vue文件和style.css中的样式,删除App.vue中的样式
  • 在components文件夹下新建MapView.vue文件
  • 在App.vue的Template模板中调用 MapView.vue
    App.vue中代码如下
    <template>
     <MapView></MapView>
    </template>
    <script setup>
    import MapView from './components/MapView.vue';
    </script>
    <style scoped>
    </style>
    

style.css中的样式代码如下:
*{ margin: 0; padding: 0; list-style: none; }

  • 在src目录下新建js文件夹,并在该文件夹下新建json文件夹,将上面下载的json文件拷贝到该文件夹。

编写代码

  • 在MapView.vue的template模板中添加一个div,id设置为scene,作为承载Threejs的容器;
    template模板中代码如下:
<template>
  <div id="scene"></div>
</template>
  • 在script标签中引入threejs
    import * as THREE from 'three'
  • 在script标签中引入D3
    import * as d3 from 'd3'
  • 引入threejs的OrbitControls控制器
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
  • 引入threejs的CSS2DRenderer和CSS2DObject
    import { CSS2DRenderer,CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
  • 引入vue的onMounted方法
    import { onMounted } from 'vue';
  • 引入下载的json地理数据文件
    import hangzhou from '../js/json/hangzhou.json'
  • 定义变量.分别定义renderer,scene,camera,orbitControls,map,labelRenderer,mouse等变量
    let renderer, scene, camera, orbitControls, map, labelRenderer, mouse
  • 使用 D3.js 库中的 geoMercator() 方法来创建一个地理坐标系进行墨卡托投影转换
    let projectpos = d3
      .geoMercator() 
      .center([120.21,30.25]) 
      .scale(1600) 
      .translate([0,0])
    
  • 定义init初始化函数
function init() {
}
  • 在onMounted中调用init函数
onMounted(() => {
  init()
})
  • 分别定义initScene方法初始化场景;定义initCamera方法初始化相机参数,定义initGeoJson方法处理json数据,定义initLight方法添加灯光效果,定义setRaycaster方法处理鼠标与地图的交互,定义initControls方法初始化控制器参数,定义initRenderer方法渲染场景,定义animate方法实现渲染动画,定义onClick方法用于鼠标点击实现;定义stretchMesh实现地图拉伸效果;代码如下
    1、 initScene方法代码
    const initScene = () => {
      scene = new THREE.Scene()
    }
    
    2 、initCamera方法 代码
    const initCamera = () => {
      camera = new THREE.PerspectiveCamera(45,document.querySelector('#scene').clientWidth/document.querySelector('#scene').clientHeight)
      camera.position.set(50,50,50)
      camera.lookAt(scene.position)
    }
    
    3、initGeoJson方法代码
    const initGeoJson() => {
      map = new THREE.Group()
      initMap(hangzhou)
    }
    
    这里定义了一个initMap方法,该方法用于处理JSON数据,核心代码如下
    const initMap = (hangzhou) => {
      hangzhou.features.forEach(element => {
        // 定义一个province 的省份3D对象
        const province = new THREE.Object3D()
        // 结构hangzhou.json中features.geometry中的coordinates
        // 对应的是每个点的坐标数组
        let { coordinates } = element.geometry
        initText(element.properties)
        // 循环坐标数组
        coordinates.forEach(multiPolygon => {
          multiPolygon.forEach(polygon => {
            // 定义shape对象
            const shape = new THREE.Shape()
            const lineMaterial = new THREE.LineBasicMaterial({
              color: '#ffffff',      
            })
            const lineGeometry = new THREE.BufferGeometry()
            const pointsArray = new Array()
            for (let i = 0; i < polygon.length; i++) {
              let [x, y] = projectpos(polygon[i])
              if (!isNaN(x)) {
                shape.moveTo(x, -y)
              }
              shape.lineTo(x, -y)
              pointsArray.push(new THREE.Vector3(x, -y, meshHeight))
            }
            lineGeometry.setFromPoints(pointsArray)
            const extrudsSettings = {
              depth: meshHeight,
              bevelEnabled: false,// 对挤出的形状应用是否斜角
            }
            const geometry = new THREE.ExtrudeGeometry(shape, extrudsSettings)
            const material = new THREE.MeshPhongMaterial({
              color: '#4161ff',
              transparent: true,
              opacity: 0.7,
              side: THREE.FrontSide
            })
            const material1 = new THREE.MeshLambertMaterial({
              color: '#cfc5de',
              transparent: true,
              opacity: 0.7,
              side: THREE.FrontSide
            })
            const mesh = new THREE.Mesh(geometry, [material, material1])
            const line = new THREE.Line(lineGeometry, lineMaterial)
            province.properties = element.properties
            // 将城市信息方到模型中,后续做动画用
            if (element.properties.centroid) {
              const [x, y] = projectpos(element.properties.centroid)
              province.properties._centroid = [x, y]
            }
            province.add(mesh)
            province.add(line)
          })
        })
        map.add(province)
      })
      scene.add(map)
    }
    
    4、initLight方法代码
    const initLight = () => {
      const ambientLight = new THREE.AmbientLight(0x404040, 1.2)
      scene.add(ambientLight)
      // 平行光
      const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0)
      scene.add(directionalLight)
      // 点光源 - 照模型
      const test = new THREE.PointLight("#ffffff", 1.8, 20)
      test.position.set(1, -7, 7)
      scene.add(test)
      const testHelperMap = new THREE.PointLightHelper(test)
      scene.add(testHelperMap)
    }
    
    5、setRaycaster方法代码
    const setRaycaster = () => {
      rayCaster = new THREE.Raycaster()
      mouse = new THREE.Vector2()	
      const onMouseMove = (event) => {
        mouse.x = (event.clientX / document.querySelector('#scene').clientWidth) * 2 - 1
        mouse.y = - (event.clientY / document.querySelector('#scene').clientHeight) * 2 + 1
    
      } 
      // 点击地图事件
      const onClick = (event) => {  
        if(selectedMesh === lastPick?.object){
          return
        }
        // 恢复上一次清空的 
        if (lastPick && 'point' in lastPick) {
          // 单击在Mesh上
          const mesh = lastPick.object
          if(selectedMesh) { 
            selectedMesh === mesh ? (resetMeshHeight(selectedMesh),selectedMesh = null) : (resetMeshHeight(selectedMesh),stretchMesh(mesh),selectedMesh = mesh)	     
          } else {
            // 没有选定的 Mesh,将选定的 Mesh 高度增加  
            // 伸展 mesh
            stretchMesh(mesh)
            selectedMesh = mesh 
          } 
        } else {  
          // 单击在 Mesh 区域外
          if(selectedMesh) { 
            // 重置被选中的 mesh 的高度
            resetMeshHeight(selectedMesh)
            selectedMesh = null
          }
          
          // resetCameraTween()
        }
      }
      window.addEventListener('mousemove', onMouseMove, false);
      window.addEventListener('click', onClick, false);
    }
    
    6、initControls代码如下
    const initControls = () => {
      orbitControls = new OrbitControls(camera, renderer.domElement)
      orbitControls.minDistance = 2  //距离屏幕最近的距离
      orbitControls.maxDistance = 5.5 //距屏幕最远距离
      // 鼠标左右旋转幅度
      orbitControls.minAzimuthAngle = -Math.PI / 4
      orbitControls.maxAzimuthAngle = Math.PI / 4
      // 鼠标上下转动幅度
      // 鼠标上下转动幅度
      orbitControls.minPolarAngle = Math.PI / 2
      orbitControls.maxPolarAngle = Math.PI - 0.1
      // 阻尼(惯性)
      orbitControls.enableDamping = true
      orbitControls.dampingFactor = 0.04
    }
    
    7、initRenderer代码如下
    const initRenderer = () => {
      renderer = new THREE.WebGLRenderer({
        antialias: true,
      })
    
    8、animate代码如下
    const animate = () => {
      requestAnimationFrame(animate)
      animationMouseover()
      // city
      animationCityWave()
      animationCityEdgeLight()
      animationCityMarker()
      orbitControls.update()
      renderer.render(scene, camera)
      labelRenderer.render(scene, camera)
    }
    
    以上就是根据JSON格式数据创建三维地图的核心代码,所有代码完成后,刷新浏览器,可以看到效果如下:
    在这里插入图片描述
    ok,threejs项目实战的第三个小项目就实现了,小伙伴们有疑问的评论区留言,喜欢的小伙伴点赞关注+收藏哦!
  • 25
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
引用\[1\]:上面分享的三维地图大屏涉及到的技术点包括echart使用json解析生成地图、projection投影、svg解析生成三维地图模型、动态材质修改、贴图的offset和repeat算法、经纬度定位、双线性差值、三维坐标转平面坐标的投影算法等。\[1\]引用\[2\]:其中方式1能达到最好的效果,但是工作量较大,需要建立中国地图和各个省份的地图,所以最终放弃了建模的思路,而是通过json数据生成三维地图。\[2\]引用\[3\]:中国地图json数据实际上包括了每个省份的数据,通过使用d3库中的投影函数projection,可以将经纬度坐标转换为平面坐标。\[3\] 根据以上引用内容,使用Three.js可以实现三维地图的展示。可以通过解析json数据生成地图模型,并使用投影函数将经纬度坐标转换为平面坐标。同时,可以使用动态材质修改和贴图的算法来实现地图的样式和效果的调整。另外,还可以使用双线性差值算法来实现三维坐标转平面坐标的投影。 #### 引用[.reference_title] - *1* *2* *3* [threejs三维地图大屏项目分享](https://blog.csdn.net/netcy/article/details/127766732)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九仞山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值