three.js实现3d地图,包含散点,地图背景图片(vue)

效果图

图片资源:

1、下载three.js及d3依赖,我这里使用的是three r153版本

npm i three
npm i d3

2、使用d3将地理坐标转换成坐标轴xy值,在地图区域及边界线加载到场景后,通过包围盒计算完整地图的max和min坐标,再根据max和min的值重新计算每个地图区域的uv坐标,如果不重新计算uv坐标会导致贴图显示异常

this.projection = d3
        .geoMercator()
        .center(this.centerCoordinate)
        .translate([0, 0]);


// 加载地图背景
const backgroundTexture = new THREE.TextureLoader().load(
  require("@/assets/images/map.png")
);
// 加载地图
let fileLoader = new THREE.FileLoader();
fileLoader.load("/anhui.json", (data) => {
  // 添加地图及边界线
  this.addMapGeometry(data);
  // 重新计算地图uv坐标
  let arr = [];
  let box = new THREE.Box3();
  for (let v of this.map.children) {
    for (let v2 of v.children) {
      // 判断是否为ExtrudeGeometry
      if (v2.geometry instanceof THREE.ExtrudeGeometry) {
        arr.push(v2);
        let itemBox = new THREE.Box3().setFromObject(v);
        box.union(itemBox);
      }
    }
  }
  var bboxMin = box.min;
  var bboxMax = box.max;
  // 计算UV的缩放比例
  var uvScale = new THREE.Vector2(
    1 / (bboxMax.x - bboxMin.x),
    1 / (bboxMax.y - bboxMin.y)
  );
  for (let v of arr) {
    let uvAttribute = v.geometry.getAttribute("uv");
    for (let i = 0; i < uvAttribute.count; i++) {
      let u = uvAttribute.getX(i);
      let v = uvAttribute.getY(i);
      // 将UV坐标进行归一化
      let normalizedU = (u - bboxMin.x) * uvScale.x;
      let normalizedV = (v - bboxMin.y) * uvScale.y;
      // 更新UV坐标
      uvAttribute.setXY(i, normalizedU, normalizedV);
    }
    // 更新几何体的UV属性
    v.geometry.setAttribute("uv", uvAttribute);
    v.material.map = backgroundTexture;
    v.material.needsUpdate = true;
  }
});

addMapGeometry(jsondata) {
// 初始化一个地图对象
this.map = new THREE.Object3D();
jsondata = JSON.parse(jsondata);
jsondata.features.forEach((elem) => {
  // 定一个省份3D对象
  const province = new THREE.Object3D();
  // 每个的 坐标 数组
  const coordinates = elem.geometry.coordinates;

  if (elem.geometry.type === "MultiPolygon") {
    // 循环坐标数组
    coordinates.forEach((multiPolygon) => {
      multiPolygon.forEach((polygon) => {
        this.drawItem(elem, polygon, province);
      });
    });
    this.map.add(province);
  } else if (elem.geometry.type === "Polygon") {
    // 循环坐标数组
    coordinates.forEach((polygon) => {
      this.drawItem(elem, polygon, province);
    });
    this.map.add(province);
  }
});
this.scene.add(this.map);
},
drawItem(elem, polygon, province) {
const shape = new THREE.Shape();
const pointsArray = new Array();
for (let i = 0; i < polygon.length; i++) {
  const [x, y] = this.projection(polygon[i]);
  if (i === 0) {
    shape.moveTo(x, -y);
  }
  shape.lineTo(x, -y);
  pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
}
let curve = new THREE.CatmullRomCurve3(pointsArray);
// 这里使用TubeGeometry没有使用line,主要考虑到line的宽度无法设置,也可以使用其他第三方依赖
var tubeGeometry = new THREE.TubeGeometry(
  curve,
  Math.floor(pointsArray.length),
  0.02,
  10
);

const extrudeSettings = {
  depth: this.mapConfig.deep,
  bevelEnabled: false, // 对挤出的形状应用是否斜角
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
geometry.computeBoundingBox();
// 创建地图区域材质
let meshMaterial = new THREE.MeshStandardMaterial({
  color: "#ffffff",
  transparent: true,
  opacity: 1,
});
// 创建地图边界线材质
let lineMaterial = new THREE.MeshBasicMaterial({
  color: "#ceebf7",
});

const mesh = new THREE.Mesh(geometry, meshMaterial);
const line = new THREE.Mesh(tubeGeometry, lineMaterial);
// 将省份的属性 加进来
province.properties = elem.properties;
province.add(mesh);
this.boundaryLineArr.push(line);
province.add(line);
},

3、设置后期处理

//设置光晕
this.composer = new EffectComposer(this.renderer); //效果组合器
//创建通道
let renderScene = new RenderPass(this.scene, this.camera);
this.composer.addPass(renderScene);

let outlinePass = new OutlinePass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  this.scene,
  this.camera,
  this.boundaryLineArr // 边界线数组
);
outlinePass.renderToScreen = true;
outlinePass.edgeGlow = 2; // 光晕效果
outlinePass.usePatternTexture = false;
outlinePass.edgeThickness = 10; // 边框宽度
outlinePass.edgeStrength = 1.5; // 光晕效果
outlinePass.pulsePeriod = 0; // 光晕闪烁的速度
outlinePass.visibleEdgeColor.set("#1acdec");
outlinePass.hiddenEdgeColor.set("#1acdec");
this.composer.addPass(outlinePass);

完整代码

注:我这边使用的是安徽的地图,需要替换自己的地图json以及地图中心地理坐标

<template>
  <div class="page" id="page" ref="page">
    <div class="tooltip" ref="tooltip" v-show="show">
      {{ selectedPointData.name }}
    </div>
  </div>
</template>

<script>
import * as THREE from "three";
import * as d3 from "d3";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
export default {
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      centerCoordinate: [117.13, 31.89], // 地图中心地理坐标
      projection: null, // Mercator 投影
      mapConfig: {
        deep: 0.2, // 挤出的深度
      },
      boundaryLineArr: [], // 边界线
      composer: "", // 后期处理
      pointData: [
        {
          coordinates: [117.33, 31.79],
          type: 1,
          name: "合肥",
          value: 100,
        },
        {
          coordinates: [118.502, 31.684],
          type: 1,
          name: "马鞍山",
          value: 100,
        },
      ],
      pointInstanceArr: [], // 坐标点实例
      show: false, // 是否显示tooltip
      selectedPointData: {}, // 选中的坐标点数据
    };
  },

  mounted() {
    this.init();
    window.addEventListener("resize", () => {
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
    });
  },

  methods: {
    init() {
      this.renderer = new THREE.WebGLRenderer();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
      document.querySelector("#page").appendChild(this.renderer.domElement);

      this.scene = new THREE.Scene();
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      this.camera.position.set(5, 5, 26);
      this.camera.lookAt(0, 0, 0);

      // let axesHelp = new THREE.AxesHelper(5);
      // this.scene.add(axesHelp);

      this.controls = new OrbitControls(this.camera, this.renderer.domElement);

      // 墨卡托投影转换
      this.projection = d3
        .geoMercator()
        .center(this.centerCoordinate)
        .translate([0, 0]); // 根据地球贴图做轻微调整
      // 添加地图
      this.addMap();
      // 给地图边界线添加outline效果
      this.setLineOutline();

      // 添加灯光
      let ambientLight = new THREE.AmbientLight(0xffffff, 1);
      this.scene.add(ambientLight);

      // 添加散点
      this.setPoint();
      // 设置光线投射
      this.setRaycaster();

      this.render();
    },
    render() {
      this.renderer.render(this.scene, this.camera);
      this.controls.update();
      if (this.composer) this.composer.render();
      requestAnimationFrame(this.render);
    },
    // 添加地图
    addMap() {
      // 加载地图背景
      const backgroundTexture = new THREE.TextureLoader().load(
        require("@/assets/images/map.png")
      );
      // 加载地图
      let fileLoader = new THREE.FileLoader();
      fileLoader.load("/anhui.json", (data) => {
        // 添加地图及边界线
        this.addMapGeometry(data);
        // 重新计算地图uv坐标
        let arr = [];
        let box = new THREE.Box3();
        for (let v of this.map.children) {
          for (let v2 of v.children) {
            // 判断是否为ExtrudeGeometry,只计算所有地图区域总和的包围盒大小
            if (v2.geometry instanceof THREE.ExtrudeGeometry) {
              arr.push(v2);
              let itemBox = new THREE.Box3().setFromObject(v2);
              box.union(itemBox);
            }
          }
        }
        var bboxMin = box.min;
        var bboxMax = box.max;
        // 计算UV的缩放比例
        var uvScale = new THREE.Vector2(
          1 / (bboxMax.x - bboxMin.x),
          1 / (bboxMax.y - bboxMin.y)
        );
        for (let v of arr) {
          let uvAttribute = v.geometry.getAttribute("uv");
          for (let i = 0; i < uvAttribute.count; i++) {
            let u = uvAttribute.getX(i);
            let v = uvAttribute.getY(i);
            // 将UV坐标进行归一化
            let normalizedU = (u - bboxMin.x) * uvScale.x;
            let normalizedV = (v - bboxMin.y) * uvScale.y;
            // 更新UV坐标
            uvAttribute.setXY(i, normalizedU, normalizedV);
          }
          // 更新几何体的UV属性
          v.geometry.setAttribute("uv", uvAttribute);
          v.material.map = backgroundTexture;
          v.material.needsUpdate = true;
        }
      });
    },
    addMapGeometry(jsondata) {
      // 初始化一个地图对象
      this.map = new THREE.Object3D();
      jsondata = JSON.parse(jsondata);
      jsondata.features.forEach((elem) => {
        // 定一个省份3D对象
        const province = new THREE.Object3D();
        // 每个的 坐标 数组
        const coordinates = elem.geometry.coordinates;

        if (elem.geometry.type === "MultiPolygon") {
          // 循环坐标数组
          coordinates.forEach((multiPolygon) => {
            multiPolygon.forEach((polygon) => {
              this.drawItem(elem, polygon, province);
            });
          });
          this.map.add(province);
        } else if (elem.geometry.type === "Polygon") {
          // 循环坐标数组
          coordinates.forEach((polygon) => {
            this.drawItem(elem, polygon, province);
          });
          this.map.add(province);
        }
      });
      this.scene.add(this.map);
    },
    drawItem(elem, polygon, province) {
      const shape = new THREE.Shape();
      const pointsArray = new Array();
      for (let i = 0; i < polygon.length; i++) {
        const [x, y] = this.projection(polygon[i]);
        if (i === 0) {
          shape.moveTo(x, -y);
        }
        shape.lineTo(x, -y);
        pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
      }
      let curve = new THREE.CatmullRomCurve3(pointsArray);
      // 这里使用TubeGeometry没有使用line,主要考虑到line的宽度无法设置,也可以使用其他第三方依赖去做
      var tubeGeometry = new THREE.TubeGeometry(
        curve,
        Math.floor(pointsArray.length),
        0.02,
        10
      );

      const extrudeSettings = {
        depth: this.mapConfig.deep,
        bevelEnabled: false, // 对挤出的形状应用是否斜角
      };
      const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
      geometry.computeBoundingBox();
      // 创建地图区域材质
      let meshMaterial = new THREE.MeshStandardMaterial({
        color: "#ffffff",
        transparent: true,
        opacity: 1,
      });
      // 创建地图边界线材质
      let lineMaterial = new THREE.MeshBasicMaterial({
        color: "#ceebf7",
      });

      const mesh = new THREE.Mesh(geometry, meshMaterial);
      const line = new THREE.Mesh(tubeGeometry, lineMaterial);
      // 将省份的属性 加进来
      province.properties = elem.properties;
      province.add(mesh);
      this.boundaryLineArr.push(line);
      province.add(line);
    },
    // 给地图边界线添加outline效果
    setLineOutline() {
      //设置光晕
      this.composer = new EffectComposer(this.renderer); //效果组合器
      //创建通道
      let renderScene = new RenderPass(this.scene, this.camera);
      this.composer.addPass(renderScene);

      let outlinePass = new OutlinePass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        this.scene,
        this.camera,
        this.boundaryLineArr
      );
      outlinePass.renderToScreen = true;
      outlinePass.edgeGlow = 2; // 光晕效果
      outlinePass.usePatternTexture = false;
      outlinePass.edgeThickness = 10; // 边框宽度
      outlinePass.edgeStrength = 1.5; // 光晕效果
      outlinePass.pulsePeriod = 0; // 光晕闪烁的速度
      outlinePass.visibleEdgeColor.set("#1acdec");
      outlinePass.hiddenEdgeColor.set("#1acdec");
      this.composer.addPass(outlinePass);
    },
    // 添加散点
    setPoint() {
      let pointTexture = new THREE.TextureLoader().load(
        require("@/assets/images/point.png")
      );
      for (let v of this.pointData) {
        let [x, y] = this.projection(v.coordinates);
        const sprite = new THREE.Sprite(
          new THREE.SpriteMaterial({
            map: pointTexture,
          })
        );
        sprite.scale.set(0.7, 0.7, 1);
        sprite.position.set(x, -y, this.mapConfig.deep + 0.5);
        sprite.properties = v;
        this.pointInstanceArr.push(sprite);
        this.scene.add(sprite);
      }
    },
    // 光线投射
    setRaycaster() {
      const raycaster = new THREE.Raycaster();
      const pointer = new THREE.Vector2();
      this.$refs.page.addEventListener("click", (event) => {
        pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
        pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;

        raycaster.setFromCamera(pointer, this.camera);
        const intersects = raycaster.intersectObjects(this.pointInstanceArr);
        if (intersects && intersects.length > 0) {
          let tooltip = this.$refs.tooltip;
          tooltip.style.left = event.pageX + "px";
          tooltip.style.top = event.pageY + "px";
          this.selectedPointData = intersects[0].object.properties;
          this.show = true;
        } else {
          this.selectedPointData = {};
          this.show = false;
        }
      });
    },
  },
};
</script>
<style scoped lang="scss">
.page {
  height: 100vh;
  .tooltip {
    position: absolute;
    background-color: #fff;
    padding: 10px;
    border-radius: 8px;
  }
}
</style>

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* *author:XudongChen *Date:2010-03-09 *QQ:233828249 81023617(不才) *Email:xznd@163.com */ 2009-8-13 1.加载分块地图 2.添加全景标记窗体 2009-8-14 1.增加控制条 2.增加全景标记、公交车标记显示层 3.解决地图定位问题 4.增加经纬度层功能 5.未修正图标层的定位 2009-8-15 1.已修正8.14地图定位错误,还存在放大缩小时定位不准 2.存在ie内存泄漏问题 2009-8-16 1.已修正8.15 ie内存泄漏问题,chrome下可能还存在内存泄漏 2.增加图标定位功能 3.增加鼠标滚轮事件(http://yongzhi.blog.hexun.com/5057947_d.html) 4.通过jquery加载json数据文件(图标显示层数据) 2009-8-17 1.增加建筑物高亮显示(还需完成鼠标mouseover和mouseout事件) 2009-8-18 1.完成建筑物高亮显示,有点小bug 2009-8-19 1.增加记录原始缩放比例的全局变量 2.解决建筑物高亮显示bug 2009-8-20 1.增加三维全景展示功能 2.浏览建筑详细功能 2009-8-21 1.引入jqueryAlert插件,美化弹出窗体 2009-8-23 1.增加搜索功能 2.清理建筑信息显示页和公交信息页多余数据 2009-10-22 1.增加小沙盘拖动类(鹰眼视图) BirdEye.js 完成小沙盘到地图的同步,同步方法:检测mouseup事件触发->修改url->request->计算坐标->同步行为 2.增加小沙盘样式表BirdEye.css 地图图片路径birdeyemap 3.完成window.parent地图->小沙盘的同步 问题:小沙盘->window.parent地图存在bug,可以尝试开启 2009-10-23 1.在小沙盘中增加浮动绿色框 2009-11-6 1.实现“鹰眼地图”不需移动,一幅可以看到见全景, 当主场景移动时,“鹰眼地图”只有小框在移动。 同时“小框” 主场景也在移动。 2009-12-17 1.测距功能事件配置 2009-12-21 1.完成测距功能 2009-12-22 1.配置搜索功能,后台改用s2sh框架 2.完成hessian+spring+hibernate整合,提供建筑信息和公司信息hessian查找服务 2009-12-23 1.配置hessian服务端缓存 2.配置hessian日志记录,输出到文件/log/wzucxd/html 3.完成建筑信息显示页,配置2级缓存 4.配置oscache 5.配置新闻信息模块 2010-1-13 1.完成chrome和ff下的搜索功能 2.搜索功能支持ie6.0+ //设置Theodolite$setPoint var cpointtmp = new CPoint(this.holder.offsetLeft + evt.clientX - this.mvl.offsetLeft, this.mvl.offsetTop + evt.clientY - this.mvl.offsetTop); Theodolite$setPoint(cpointtmp);
### 回答1: 是的,Vue.jsThree.js 可以很好地结合,用于开发3D可视化项目。其中,Vue.js 用于构建整个应用程序的前端界面,而 Three.js 用于创建和呈现3D场景。以下是一些简单的步骤: 1. 安装 Vue.jsThree.js 首先,你需要安装 Vue.jsThree.js。你可以使用 npm 或 yarn 安装这两个库。 2. 创建 Vue.js 应用程序。 使用 Vue CLI 创建一个新的 Vue.js 应用程序。在项目根目录下,可以使用以下命令: ``` vue create my-app ``` 3. 引入 Three.jsVue.js 应用程序中,可以通过以下方式引入 Three.js: ``` import * as THREE from 'three'; ``` 你可以在 Vue.js 组件的任何地方使用 THREE 对象。 4. 创建 Three.js 场景 在 Vue.js 应用程序中,你可以在 Vue.js 组件中创建 Three.js 场景。在组件的 mounted 钩子中创建场景。例如: ``` mounted() { // 创建 Three.js 场景 this.scene = new THREE.Scene(); } ``` 5. 创建 Three.js 渲染器 在 Vue.js 组件中,你可以创建 Three.js 渲染器并将其附加到 HTML 元素上。例如: ``` mounted() { // 创建 Three.js 渲染器 this.renderer = new THREE.WebGLRenderer(); // 添加到 HTML 元素 this.$refs.renderer.appendChild(this.renderer.domElement); } ``` 6. 创建 Three.js 相机 在 Vue.js 组件中,你可以创建 Three.js 相机并将其添加到场景中。例如: ``` mounted() { // 创建 Three.js 相机 this.camera = new THREE.PerspectiveCamera( 75, // 视角 this.width / this.height, // 宽高比 0.1, // 近处平面 1000 // 远处平面 ); // 将相机添加到场景中 this.scene.add(this.camera); } ``` 7. 渲染 Three.js 场景 在 Vue.js 应用程序中,你可以使用 requestAnimationFrame() 方法渲染 Three.js 场景。例如: ``` mounted() { // 渲染 Three.js 场景 const render = () => { this.renderer.render(this.scene, this.camera); requestAnimationFrame(render); } requestAnimationFrame(render); } ``` 这样,你就可以在 Vue.js 应用程序中创建 Three.js 场景了。你可以使用 Three.js 的其他功能,在场景中添加物体,创建动画等等。 ### 回答2: VueThree.js是两个独立的库,分别用于Web开发和3D可视化开发。Vue是一个用于构建用户界面的JavaScript框架,能够帮助开发者构建响应式的单页应用程序。而Three.js是一个用于创建和渲染三维图形的JavaScript库,它提供了很多功能强大的3D绘制工具和特效。 结合VueThree.js开发可视化项目,可以充分发挥两者的优势。首先,可以使用Vue来构建项目的界面,包括导航栏、按钮、输入框等交互元素。Vue的响应式特性可以允许用户输入和状态变化实时更新页面,给用户以良好的交互体验。 然后,利用Three.js创建和渲染项目中的三维图形。通过Three.js的API,可以创建各种形状的几何体、灯光、材质等,并在页面中展示出来。可以将数据与Three.js的图形进行绑定,实现实时的数据可视化。例如,可以将数据表示为柱状图、折线图或散点图,然后使用Three.js在页面中绘制出相应的3D图形。 同时,VueThree.js的相互配合也可以提供更多的功能。例如,可以使用Vue的组件化开发方式将Three.js的场景、模型等封装为可复用的组件,方便在项目中的多个地方使用。Vue的动画特性也可以与Three.js的动画效果进行结合,实现更加丰富的动态效果。 总之,通过结合VueThree.js开发可视化项目,可以充分发挥两者的优势,使项目具有良好的交互性和丰富的3D效果。这种结合方式可以满足项目对于界面和数据可视化的需求,并为用户提供更好的用户体验。 ### 回答3: Vue结合three.js开发可视化项目的概念是将Vue.js框架与Three.js库相结合,通过Vue.js的组件化开发思想和Three.js的三维可视化技术,构建出一个灵活、交互且具有动态效果的可视化项目。 首先,Vue.js是一种轻量级的JavaScript框架,通过它可以更方便地创建用户界面和构建交互式的单页面应用。利用Vue.js的组件化开发思想,可以将项目拆解成小的组件,提高代码的重用性和维护性。 然后,Three.js是基于WebGL的JavaScript库,用于创建和渲染各类三维场景和动画效果。它提供了丰富的功能和API,可以在网页上创建3D模型、添加光照效果、实现模型动画等。结合Vue.js,可以通过Vue组件的方式创建Three.js场景,并在场景中进行交互操作。 在Vue结合Three.js开发可视化项目时,可以使用Vue.js的生命周期钩子来控制Three.js场景的初始化、渲染和销毁。可以将Three.js的代码封装为Vue组件,通过props和events实现组件之间的数据传递和交互。同时,可以结合Vue的数据绑定特性,动态更新Three.js场景中的元素。 此外,还可以借助Vue的动画特性来实现一些动态效果,如模型旋转、相机缩放等。同时,Vue框架的响应式特性也能够方便地监听数据变化,实现数据驱动的可视化效果。 综上所述,通过Vue结合Three.js开发可视化项目可以更加高效地创建、管理和操控三维场景,开发出富有交互性和动态效果的可视化应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值