threejs+vue 省份3D可视化地图

本文介绍了如何利用three.js和d3库在网页上动态生成基于地图区域的JSON数据可视化,包括获取地图数据、安装依赖、创建场景、渲染几何形状和交互功能的实现过程。
摘要由CSDN通过智能技术生成

先上效果图

在这里插入图片描述
在这里插入图片描述

需要查找地图的josn数据、需要引入的js、 和需要安装的依赖如下

1.先获取想要展示地图的区域json数据
阿里云获取地图区域的json
示例为陕西省
在这里插入图片描述

2.npm安装three.js和d3 npm i threenpm i d3
3.引入相关方法和json数据

import * as THREE from 'three';
import { OrbitControls } from "../../../node_modules/three/examples/jsm/controls/OrbitControls.js"
import { TextGeometry } from '../../../node_modules/three/examples/jsm/geometries/TextGeometry.js';
import { FontLoader } from '../../../node_modules/three/examples/jsm/loaders/FontLoader.js';
import * as d3 from 'd3';
import jsondata from './shanxi.json'

具体代码

<template>
    <div class="center-map-box" id="contant">
    </div>
</template>
 mounted() {
		// 第一步新建一个场景
        this.scene = new THREE.Scene()
        this.contant = document.getElementById('contant')
        // 辅助线
        // const axesHelper = new THREE.AxesHelper(10);
        // this.scene.add(axesHelper);
        // 光源
        this.spotLight = new THREE.PointLight('#fff', 4, 100)
        this.spotLight.position.set(0.2, -0.4, 1)
        this.scene.add(this.spotLight)
        //环境光
        const ambient = new THREE.AmbientLight('#fff', 4)
        this.scene.add(ambient)
        // 可视化点光源
        // const pointLightHelper = new THREE.PointLightHelper(this.spotLight, 0.1)
        // this.scene.add(pointLightHelper)
        this.setCamera()
        this.setRenderer()
        this.generateGeometry()
        this.setClickFn()
        this.setController()
        this.animate()
        window.onresize = () => {
            this.renderer.setSize(this.contant.clientWidth, this.contant.clientHeight);
            this.camera.aspect = this.contant.clientWidth / this.contant.clientHeight;
            this.camera.updateProjectionMatrix();
        };
 }
  methods: {
  	 // 新建透视相机
        setCamera() {
            this.camera = new THREE.PerspectiveCamera(60, this.contant.clientWidth / this.contant.clientHeight, 0.1, 500);
            this.camera.position.z = 10
        },
        // 设置渲染器
        setRenderer() {
            this.renderer = new THREE.WebGLRenderer()
            // 设置画布的大小
            this.renderer.setSize(this.contant.clientWidth, this.contant.clientHeight)
            //这里 其实就是canvas 画布  renderer.domElement
            this.contant.appendChild(this.renderer.domElement)
            this.renderer.setClearColor(0x000000, 0)
        },
        render() {
            this.renderer.render(this.scene, this.camera)
        },
        generateGeometry() {
            // 初始化一个地图对象
            this.map = new THREE.Object3D()

            // 墨卡托投影转换
            const projection = d3
                .geoMercator()
                .center([104.0, 37.5])
                .scale(80)
                .translate([0, 0])

            jsondata.features.forEach((elem) => {
                this.renderer.render(this.scene, this.camera);
                const coordinates = elem.geometry.coordinates
                // 循环坐标数组
                coordinates.forEach((multiPolygon) => {
                    multiPolygon.forEach((polygon, index) => {
                        const province = new THREE.Object3D()
                        const shape = new THREE.Shape()
                        const lineMaterial = new THREE.LineBasicMaterial({
                            color: 'white',
                        })
                        const lineGeometry = new THREE.BufferGeometry()
                        const pointsArray = new Array()
                        for (let i = 0; i < polygon.length; i++) {
                            const [x, y] = projection(polygon[i])
                            if (i === 0) {
                                shape.moveTo(x, -y)
                            }
                            shape.lineTo(x, -y)
                            pointsArray.push(new THREE.Vector3(x, -y, 0))
                        }
                        lineGeometry.setFromPoints(pointsArray)


                        const extrudeSettings = {
                            depth: 0.07,
                            bevelEnabled: false,
                        }

                        const geometry = new THREE.ExtrudeGeometry(
                            shape,
                            extrudeSettings
                        )
                        const material = new THREE.MeshPhongMaterial({
                            color: '#43A7FF',
                            transparent: true,
                            opacity: 0.8,
                        })
                        const material1 = new THREE.MeshBasicMaterial({
                            color: '#3480C4',
                            transparent: true,
                            opacity: 0.4,
                        })
                        const loader = new FontLoader();
                        //字体需放到根目录public下
                        loader.load('./fonts/FZCuHeiSongS-B-GB_Regular.json', (font) => {
                            const fontOption = {
                                font: font,
                                size: 0.07,
                                height: 0.01,
                                curveSegments: 1,
                                bevelThickness: 1,
                                bevelSize: 0,
                                bevelEnabled: false,
                                bevelSegments: 0
                            };
                            const txtMater = new THREE.MeshBasicMaterial({ color: 0xffffff });
                            const txtGeometry = new TextGeometry(name, fontOption);
                            const txtMesh = new THREE.Mesh(txtGeometry, txtMater);
                            const [x, y] = projection(elem.properties.center)
                            txtMesh.position.set(x - 8.3, -y + 4.4, 0.08)
                            if (name == 'xx县') {
                            //这里位置不对可以做微调
                                txtMesh.position.set(x - 8.33, -y + 4.55, 0.08)
                            }
                            this.scene.add(txtMesh);
                        });
                        var name = elem.properties.name;//区县名
                        const mesh = new THREE.Mesh(geometry, [material, material1])
                        const line = new THREE.Line(lineGeometry, lineMaterial)
                        this.materialArr.push(material)
                        province.properties = elem.properties
                        province.add(mesh)
                        province.add(line)
                        this.map.add(province)
                        this.render()
                    })
                })
            })
            this.map.position.set(-8.2, 4.4, 0);
            this.scene.add(this.map);
            this.spotLight.target = this.map;
            this.camera.position.set(0, -0.7, 2.5);
            this.renderer.render(this.scene, this.camera);

        },
        //加事件
        setClickFn() {
            this.raycaster = new THREE.Raycaster();
            this.mouse = new THREE.Vector2();
            const onMouseMove = (event) => {
                var marginLeft = this.contant.offsetLeft
                var marginTop = this.contant.offsetTop + 92
                // 如果该地图不是占满全屏需要减去margintop和marginleft
                // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
                // this.mouse.x = (event.clientX / this.contant.clientWidth) * 2 - 1;
                // this.mouse.y = -(event.clientY / this.contant.clientHeight) * 2 + 1;
                this.mouse.x = ((event.clientX - marginLeft) / this.contant.clientWidth) * 2 - 1;
                this.mouse.y = -((event.clientY - marginTop) / this.contant.clientHeight) * 2 + 1;
            };

            let clickPosition;
            window.addEventListener("mousemove", onMouseMove, false);
            const onclick = (event) => {
                var marginLeft = this.contant.offsetLeft
                var marginTop = this.contant.offsetTop
                // let x = (event.clientX / this.contant.clientWidth) * 2 - 1;
                // let y = -(event.clientY / this.contant.clientHeight) * 2 + 1;
                // 如果该地图不是占满全屏需要减去margintop和marginleft
                let x = ((event.clientX - marginLeft) / this.contant.clientWidth) * 2 - 1;
                let y = -((event.clientY - marginTop) / this.contant.clientHeight) * 2 + 1;
                clickPosition = { x: x, y: y };
                this.raycaster.setFromCamera(clickPosition, this.camera);
                // 算出射线 与当场景相交的对象有那些
                const intersects = this.raycaster.intersectObjects(this.scene.children, true);
                let clickObj = intersects.find(
                    (item) => item.object.material && item.object.material.length === 2
                );
                // 点击区县
                if (clickObj && clickObj.object) {
                	console.log(clickObj)
                    // this.$emit('clickAreaCheck',clickObj)
                }
            };
            window.addEventListener("mousedown", onclick, false);
        },
        // 设置最大旋转的角度
        setController() {
            const controls = new OrbitControls(this.camera, this.renderer.domElement);
            controls.maxPolarAngle = 2.5
            controls.minPolarAngle = 1
            controls.maxAzimuthAngle = 1
            controls.minAzimuthAngle = -1
            controls.addEventListener("change", () => {
                this.renderer.render(this.scene, this.camera);
            });
        },
         animate() {
            window.requestAnimationFrame(this.animate);
            this.raycaster.setFromCamera(this.mouse, this.camera);
            this.renderer.render(this.scene, this.camera);
        },
  }

该文章只做记录,具体在场景中使用中自己调整。

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值