threejs教程:绘制3D地图(广东省区划图)

一、效果展示:

二、开发准备

Three.js中文文档:Three.js中文网

Three.js文本渲染插件:Troika 3D Text - Troika JS

行政区划边界数据查询(阿里云数据可视化平台):DataV.GeoAtlas地理小工具系列

1. 在项目中添加 `threejs` 依赖

npm install three

2. 添加相关声明依赖,为Typescript项目提供类型提示和类型检查的支持

npm install @types/three -D

3. 添加 `troika-three-text` 依赖,本案例中用于绘制所有市名文字,如无需绘制文字可不用

npm install troika-three-text

4.  使用阿里云数据可视化平台的范围选择器工具查询你所需要的区划边界数据,点击右下角其他类型的蓝色下载图标下载JSON文件,并导入项目中。本案例以广东省为例, `@/src/assets/广东省.json`。

三、代码实现

1.  添加canvas组件和基础样式


<template>
  <div>
    <canvas id="three" />
  </div>
</template>

<style scoped>
#three {
  width: 100vw;
  height: 100vh;
  position: absolute;
  top: 0;
  left: 0;
}
</style>

2. 导入three,创建三维场景并设置背景颜色,案例中的蓝底即为此处设置的颜色。

import * as THREE from 'three';
import { onMounted } from 'vue';

onMounted(() => {
  // 创建3D场景对象Scene
  const scene = new THREE.Scene();
  // 设置背景颜色
  scene.background = new THREE.Color("#a4cdff");
});

3. Threejs要把三维场景Scene渲染到web网页上需创建个虚拟相机

// 实例化一个透视投影相机对象
const camera = new THREE.PerspectiveCamera(
  30,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
// 设置相机在Three.js三维坐标系中的位置,此处修改z坐标为调整正面远近,值越小越近
camera.position.z = 200;

THREE.PerspectiveCamera构造函数的参数说明

4.  有了场景和相机后,关键还需要一个渲染器去生成影像

// 创建渲染器对象
const threeDemo = document.getElementById("three");
const renderer = new THREE.WebGLRenderer({ canvas: threeDemo, antialias: true });

5. 处理每一帧动画和分辨率问题

function resizeDevicePixel(renderer: THREE.WebGLRenderer) {
  const canvas = renderer.domElement
  const width = window.innerWidth
  const height = window.innerHeight
  const devicePixelWidth = canvas.width / window.devicePixelRatio
  const devicePixelHeight = canvas.height / window.devicePixelRatio

  const needResize = devicePixelWidth !== width || devicePixelHeight !== height
  if (needResize) {
    renderer.setSize(width, height, false)
  }
  return needResize
}

// Three.js 需要一个动画循环函数,Three.js 的每一帧都会执行这个函数
function annimate() {
  renderer.render(scene, camera);
  requestAnimationFrame(annimate);
  // 矫正设备的物理像素分辨率与CSS像素分辨率的比值,解决模糊问题
  if(resizeDevicePixel(renderer)) {
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }
}
annimate();

6. 要让模型动起来还需添加轨道控制器

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();

7.以上便完成了所有初始配置,开始进入正题,渲染3D广东省区划图

导入之前下载的广东省区划边界数据:

import GuangDong from './assets/广东省.json';

遍历所有城市边界数据生成对应板块模型 

let xy = [0, 0, 0, 0];
GuangDong.features.forEach((city, index) => {
  let boundingBox, x = 0, y = 0;
  // 遍历城市的所有块边界数据(城市可能包含陆和岛多个块组成)
  city.geometry.coordinates.forEach((coordinate, cindex) => {
    const data = coordinate[0];
    // 一组二维向量表示一个多边形轮廓坐标
    const pointsArr = data.map(e => new THREE.Vector2(e[0] * 10, e[1] * 10));
    // Shape表示一个平面多边形轮廓,参数是二维向量构成的数组pointsArr
    const shape = new THREE.Shape(pointsArr);
    // 多边形shape轮廓作为ShapeGeometry参数,生成一个多边形几何体
    const geometry = new THREE.ExtrudeGeometry(shape, {
      depth: 2, // 这里设置形状的厚度
      bevelEnabled: false, // 是否开启倒角,默认关闭
    });

    // 计算几何体的中心点并居中
    geometry.computeBoundingBox();
    // 计算平面几何体的中心点
    const { min, max } = geometry.boundingBox;
    xy[2] = (min.x + max.x) / 2;
    xy[3] = (min.y + max.y) / 2;
    if (index === 0) { // 记录第一个几何体的位置,作为其他几何体的位置参考点
      xy[0] = xy[2];
      xy[1] = xy[3];
    }
    // 移动几何体以便中心点位于原点
    const center = new THREE.Vector3();
    geometry.boundingBox!.getCenter(center);
    geometry.translate(-center.x + (xy[2] - xy[0]), -center.y + (xy[3] - xy[1]), -1);
    // 记录每个城市核心板块的位置信息
    if (cindex === 0) {
      boundingBox = geometry.boundingBox;
      x = xy[2];
      y = xy[3];
    }
    let material = new THREE.MeshBasicMaterial({ color: "#fcf9f2" });
    let mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    // 从原始几何体中获取边缘几何体
    const edgesGeometry = new THREE.EdgesGeometry(geometry);
    // 创建一个具有宽度的线条材质(这里我们使用LineBasicMaterial并设置linewidth)
    const edgeMaterial = new THREE.LineBasicMaterial({
        color: 0xcccccc, // 白色边缘
        linewidth: 1, // 边缘宽度
    });
    // 创建线段对象来渲染边缘线条
    const edgeLines = new THREE.LineSegments(edgesGeometry, edgeMaterial);
    scene.add(edgeLines);
  });

  // 渲染市名
  const text = new Text();
  text.text = city.properties.name;
  text.fontSize = 1;
  text.color = 0x333333;
  text.geometry.computeBoundingBox();
  const textCenter = new THREE.Vector3();
  text.geometry.boundingBox.getCenter(textCenter);
  // 计算市中心位置
  const point = city.properties.center
  const { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } } = boundingBox;
  const c1 = [(minX + maxX) / 2, (minY + maxY) / 2];
  const c2 = [(point[0] * 10 - x) + c1[0], (point[1] * 10 - y) + c1[1]];
  // 把市名文字绘制到市中心点
  text.position.set(c2[0] + 0.3, c2[1] + 0.3, 1.2);
  scene.add(text);

  // 在场景中创建圆环
  const ringGeometry = new THREE.RingGeometry(0.4, 0.3, 32);
  // 创建圆环的材质
  const ringMaterial = new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide});
  const ring = new THREE.Mesh(ringGeometry, ringMaterial);
  // 把圆环绘制到市中心点
  ring.position.set(c2[0]-0.2, c2[1]-0.2, 1.1);
  scene.add(ring);

  // 在场景中创建圆形
  const circleGeometry = new THREE.CircleGeometry(0.2, 32);
  // 创建圆形的材质
  const circleMaterial = new THREE.MeshBasicMaterial({color: 0xff0000});
  const circle = new THREE.Mesh(circleGeometry, circleMaterial);
  // 把圆形直接加到圆环的中心点
  ring.add(circle);
});

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/722136.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32学习 时钟树

在单片机中&#xff0c;时钟的概念非常重要&#xff0c;这次记录一下时钟树相关的知识。 STM32的时钟树是由多个时钟源和时钟分频组成的&#xff0c;为STM32芯片提供各种时钟信号。也就是说&#xff0c;在使用STM32的时候&#xff0c;所有的频率和时钟都是通过时钟树产生的。 …

Maven添加reactor依赖失败

目录 情况说明 解决过程 情况说明 起初是自己在学spring boot3&#xff0c;结果到了reactor这一部分的时候&#xff0c;在项目的pom.xml文件中添加下列依赖报错&#xff1a; <dependencyManagement><dependencies><dependency><groupId>io.projectr…

github配置可拉取项目到本地

首先配置用户名和邮箱&#xff1a; git config --global user.name 自己的名字git config --global user.email 自己的邮箱配置完之后检查一下&#xff1a; git config --global user.namegit config --global user.email如果提示的是自己配置好的名字和邮箱就Ok 然后拉取githu…

NLP入门——基于梯度下降法分类的应用

问题分析 我们前面研究的都是基于统计的方法&#xff0c;通过不同的统计方法得到不同的准确率&#xff0c;通过改善统计的方式来提高准确率。现在我们要研究基于数学的方式来预测准确率。 假设我们有一个分词 s_{class,word}&#xff0c;class是该对象的类别&#xff0c;word…

数据库大作业——音乐平台数据库管理系统

W...Y的主页&#x1f60a; 代码仓库分享&#x1f495; 《数据库系统》课程设计 &#xff1a;流行音乐管理平台数据库系统&#xff08;本数据库大作业使用软件sql server、dreamweaver、power designer&#xff09; 目录 系统需求设计 数据库概念结构设计 实体分析 属性分…

Redis小对象压缩

小对象压缩存储 如果Redis内部管理的集合数据结构很小&#xff0c;他会使用紧凑存储形式压缩存储。 Redis的ziplist是一个紧凑的字节数组结构&#xff0c;如下图所示&#xff0c;每个元素之间都是紧挨着的。 如果他存储的是hash结构&#xff0c;那么key和value会作为两个ent…

Arcgis导入excel出现的问题

我手动添加了object-id字段也没有用&#xff0c;然后再excel里面又添加了一行&#xff0c;关闭后打开还是不行&#xff0c;额案后在网上看到了一种方法&#xff0c;很有效&#xff0c;予以记录。 1、我的文件是csv格式&#xff0c; 先在excel里面另存为xlsx格式 2、转换工具里…

【机器学习】对大规模的文本数据进行多标签的分类处理

1. 引言 1.1. NLP研究的背景 随着人工智能技术的飞速发展&#xff0c;智能助手、聊天机器人和虚拟客服的需求正呈现出爆炸性增长。这些技术不仅为人们提供了极大的生活便利&#xff0c;如日程管理、信息查询和情感陪伴&#xff0c;还在工作场景中显著提高了效率。聊天机器人凭…

阿里又出AI神器,颠覆传统图像编辑,免费开源!

文章首发于公众号&#xff1a;X小鹿AI副业 大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 最近阿里开源了 Mi…

【云手机】数据安全如何保障?

安全办公&#xff0c;信息安全&#xff0c;这是企业使用云手机的初衷和目的&#xff0c;云手机在数据保密&#xff0c;远程办公等功能上有巨大的优势&#xff0c;也为企业提供了支持 首先就是云手机能够实现数据的集中管理和加密存储。所有办公相关的数据都存储在云端的安全服务…

ES 8.14 Java 代码调用,增加knnSearch 和 混合检索 mixSearch

1、pom依赖 <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-client</artifactId><version>8.14.0</version></dependency><dependency><groupId>co.elastic.clients<…

Vue62-$nextTick和$event

一、$nextTick 1-1、需求 点击编辑按钮&#xff0c;文本框自动获取焦点。 没有生效&#xff01;因为vue是将function中的代码执行完&#xff0c;再重新解析模版&#xff0c;所以存在时间上的问题。 解决方式一&#xff1a;使用定时器 解决方式二&#xff1a;$nextTick $nextT…

TikTok账号养号的流程分享

对于很多刚开始运营TikTok的新手小白来说&#xff0c;都会有一个同样的疑问&#xff0c;那就是&#xff1a;TikTok到底需不需要养号&#xff1f;这里明确告诉大家是需要养号的&#xff0c;今天就把我自己实操过的养号经验和策略总结出来&#xff0c;分享给大家。 一、什么是Ti…

配置OSPF认证(华为)

#交换设备 配置OSPF认证-基于华为路由器 OSPF&#xff08;开放最短路径优先&#xff09;是一种内部网关协议&#xff08;IGP&#xff09;&#xff0c;用于在单一自治系统&#xff08;AS&#xff09;内决策路由。OSPF认证功能是路由器中的一项安全措施&#xff0c;它的主要用途…

uniapp顶部导航栏实现自定义功能按钮+搜索框并监听响应事件

目录 第一步&#xff1a;先下载按钮需要展示的图标&#xff08;若不使用图标&#xff0c;直接使用文字可跳过这步&#xff09; 1、点击需要的图标&#xff0c;添加入库 2、点击旁边的购物车&#xff0c;在弹出的窗口中选择下载代码 3、解压下载的压缩包&#xff0c;将这几个…

SpringMVC的使用

SpringMVC详情 RequestMapping("/hello") 负责用户的请求路径与后台服务器之间的映射关系 如果请求路径不匹配,则用户报错404 ResponseBody 作用: 将服务器的返回值转化为JSON. 如果服务器返回的是String类型,则按照自身返回. 新增: post请求类型 PostMapping("…

【anaconda】本地永久设置镜像源

【anaconda】本地永久设置镜像源 可以通过命令行设置全局的 pip 配置&#xff1a; pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

支付宝直付通 VS 微信收付通分账产品异同

随着电子商务的蓬勃发展&#xff0c;支付分账产品成为了各大平台不可或缺的一部分。在众多支付分账产品中&#xff0c;支付宝和微信无疑是两大巨头。通过我们公司项目的实际使用经验&#xff0c;我们将从多个维度对这两款产品进行全方位的比较&#xff0c;帮助大家更直观地了解…

【AI基础】租用云GPU之autoDL部署大模型ollama+llama3

在这个显卡昂贵的年代&#xff0c;很多想要尝试一下AI的人可能都止步于第一步。这个时候我们可以租用在线的GPU资源来使用AI。autoDL就是这样的一个云平台。 一、创建服务器 1.1 注册账号 官网&#xff1a;https://www.autodl.com/ | 租GPU就上AutoDL 帮助文档&#xff1a;…

获取天气预报

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 using Newtonsoft.Json; using System.Net; using System.Text;namespace 获取天气预报 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void button1_Click(object sender, Eve…