three3D地图

基于three.js的3D地图

1.引言

基于Vue实现Three.js+GeoJSON三维可视化炫酷地图(省市标签、地图流光、动态文字、高光定位、星空背景)

three官方文档:Three.js – JavaScript 3D Library

2.实际效果

在这里插入图片描述

您的浏览器不支持播放该视频!

3.GeoJSON

可以通过阿里云DataV获取GeoJSON数据,也可以在其他地理信息平台获取数据并转换为GeoJson数据:DataV.GeoAtlas地理小工具系列 (aliyun.com)

4.基础代码

4.1 加载解析GeoJSON数据

import beijing from './json/beijing.json'//包含子区域
import beijingout from './json/beijingout.json'//不包含子区域
/**
* @description 解析GeoJson数据并创建地图
*/
initGeoJson() {
    // 引入json数据
    // 方法一 使用Three提供的FileLoader加载数据并对JSON数据进行解析:
    const loader = new THREE.FileLoader();
    loader.load("https://geo.datav.aliyun.com/areas_v3/bound/110000.json", (data) => {
        console.log(data);
        const jsonData = JSON.parse(data);
        this.initMap(jsonData) //初始化加载地图
        this.initMapLine(beijing)//加载地图边缘线
        console.log('jsonData :>> ', jsonData);
    });
    // 方法二 通过import引入JSON文件再调用
    // this.initMap(beijingout)//初始化加载地图
    // this.initMapLine(beijing)//加载地图边缘线
},

4.2 加载地图

initMap(beijingOutJson) {
    // 创建环境贴图
    let textureMap = textureLoader.load(require("./mapimg/gz-map.jpeg"));
    let texturefxMap = textureLoader.load(
        require("./mapimg/gz-map-fx.jpeg")
    );
    textureMap.wrapS = THREE.RepeatWrapping; //纹理水平方向的平铺方式
    textureMap.wrapT = THREE.RepeatWrapping; //纹理垂直方向的平铺方式
    textureMap.flipY = texturefxMap.flipY = false;// 如果设置为true,纹理在上传到GPU的时候会进行纵向的翻转。默认值为true。
    textureMap.rotation = texturefxMap.rotation = THREE.MathUtils.degToRad(45);//rotation纹理将围绕中心点旋转多少度
    const scale = 0.01;
    textureMap.repeat.set(scale, scale);//repeat决定纹理在表面的重复次数
    texturefxMap.repeat.set(scale, scale);
    textureMap.offset.set(0.5, 0.5);//offset贴图单次重复中的起始偏移量
    texturefxMap.offset.set(0.5, 0.5);
    // MeshPhongMaterial(一种用于具有镜面高光的光泽表面的材质)
    const material = new THREE.MeshPhongMaterial({
        map: textureMap,//颜色贴图
        normalMap: texturefxMap,//用于创建法线贴图的纹理
        // normalScale: new THREE.Vector2(12.2, 2.2),//法线贴图对材质的影响程度
        color: "#7bc6c2",
        combine: THREE.MultiplyOperation,//如何将表面颜色的结果与环境贴图
        transparent: true,
        opacity: 1,
    });
    // MeshLambertMaterial(一种非光泽表面的材质,没有镜面高光)
    const material1 = new THREE.MeshLambertMaterial({
        color: 0x123024,
        transparent: true,
        opacity: 0.9,
    });
    // d3-geo墨卡托坐标转化
    const projection = d3
    .geoMercator()//地图投影方式(用于绘制球形墨卡托投影)
    .center(centerPos)//地图中心点经纬度坐标
    .scale(2500) //缩放
    .translate([0, 0]);//移动地图位置
    console.log('beijingOutJson.features :>> ', beijingOutJson.features);
    // 遍历省份构建模型
    beijingOutJson.features.forEach((elem) => {
        // 新建一个省份容器:用来存放省份对应的模型和轮廓线
        const meshArrs = new THREE.Object3D();
        const coordinates = elem.geometry.coordinates;//坐标合集
        const properties = elem.properties;
        coordinates.forEach((multiPolygon) => {
            multiPolygon.forEach((polygon) => {
                //Shape使用路径以及可选的孔洞来定义一个二维形状平面
                // 创建一条空路径,.currentPoint将被设置为原点。
                const shape = new THREE.Shape();
                var v3ps = [];
                for (let i = 0; i < polygon.length; i++) {
                    const [x, y] = projection(polygon[i]);
                    if (i === 0) {
                        shape.moveTo(x, -y);//将.currentPoint移动到x, y
                    }
                    shape.lineTo(x, -y);//在当前路径上,从.currentPoint连接一条直线到x,y
                    v3ps.push(new THREE.Vector3(x, -y, 4.02));
                }
                const extrudeSettings = {
                    depth: 2, //该属性指定图形可以拉伸多高,默认值是100
                    bevelEnabled: false, //是否给这个形状加斜面,默认加斜面。
                };
                //拉升成地图(从一个形状路径中,挤压出一个BufferGeometry)
                // ExtrudeGeometry --- 当使用这个几何体创建Mesh的时候,如果你希望分别对它的表面和它挤出的侧面使用单独的材质,你可以使用一个材质数组。 第一个材质将用于其表面;第二个材质则将用于其挤压出的侧面。
                const geometry = new THREE.ExtrudeGeometry(
                    shape,
                    extrudeSettings
                );
                const mesh = new THREE.Mesh(geometry, [
                    material,//表面材质
                    material1,//侧面材质
                ]);
                mesh.rotateX(-Math.PI / 2);//x轴旋转
                mesh.position.set(0, 1.5, -3);//设置放置位置
                meshArrs.add(mesh);
            });
        });
        map.add(meshArrs);
    });
    this.scene.add(map);
},

4.3 加载地图边缘线

initMapLine(beijingJson) {
    var matLine = new LineMaterial({
        color: 0xffffff,
        linewidth: 0.0013,
        vertexColors: true,
        dashed: false,
        alphaToCoverage: true,
    });

    var matLine2 = new LineMaterial({
        color: "#01bdc2",
        linewidth: 0.0025,
        vertexColors: true,
        dashed: false,
        alphaToCoverage: true,
    });
    // d3-geo转化坐标
    const projection = d3
    .geoMercator()
    .center(centerPos)
    .scale(2500)
    .translate([0, 0]);
    // 遍历省份构建模型
    beijingJson.features.forEach((elem) => {
        const province = new THREE.Object3D();
        const coordinates = elem.geometry.coordinates;
        const properties = elem.properties;
        //这里创建光柱、文字坐标
        this.initLightPoint(properties, projection);
        // 创建地图上面和下面的边缘线
        coordinates.forEach((multiPolygon) => {
            multiPolygon.forEach((polygon) => {
                const positions = [];
                var colors = [];
                const color = new THREE.Color();
                var linGeometry = new LineGeometry();
                for (let i = 0; i < polygon.length; i += 1) {
                    const [x, y] = projection(polygon[i]);
                    positions.push(x, -y, 4.01);
                    color.setHSL(1, 1, 1);
                    colors.push(color.r, color.g, color.b);
                }

                // Line2方式绘制线条
                linGeometry.setPositions(positions);
                linGeometry.setColors(colors);
                const line = new Line2(linGeometry, matLine);
                const line2 = new Line2(linGeometry, matLine2);
                line.computeLineDistances();
                line.rotateX(-Math.PI / 2);
                line2.rotateX(-Math.PI / 2);
                line.position.set(0, 0.1, -3);
                line2.position.set(0, -3.5, -3);
                line2.computeLineDistances();
                line.scale.set(1, 1, 1);
                province.add(line);
                province.add(line2);
            });
        });
        map.add(province);
    });
    this.scene.add(map);
},

4.4 创建光柱、文字坐标

/**
* @description 创建光柱、文字坐标
* @param {*} properties 属性、详情
* @param {Function} projection  d3-geo转化坐标
*/
initLightPoint(properties, projection) {
    // 创建光柱
    let heightScaleFactor = 8 + this.random(1, 5) / 5;
    let lightCenter = properties.centroid || properties.center;
    let areaName = properties.name;
    // projection用来把经纬度转换成坐标
    const [x, y] = projection(lightCenter);
    let light = this.createLightPillar(x, y, heightScaleFactor);
    light.position.z -= 3;
    map.add(light);
    //这里创建文字坐标
    this.createTextPoint(x, y, areaName);
}
/**
* @description 创建文字坐标
* @param {*} x d3 - 经纬度转换后的x轴坐标
* @param {*} z d3 - 经纬度转换后的z轴坐标
* @param {*} areaName 地名
*/
createTextPoint(x, z, areaName) {
    let tag = document.createElement("div");
    tag.innerHTML = name;
    // tag.className = className
    tag.style.color = "#fff";
    // tag.style.pointerEvents = "none";
    tag.style.pointerEvents = "auto";
    // tag.style.visibility = 'hidden'
    tag.style.position = "absolute";
    // tag.setAttribute('data-name', 'label');
    // tag.addEventListener('click', this.clickLabel, false);
    tag.addEventListener('mousedown', this.clickLabel, false);// 有时候PC端click事件不生效,不知道什么原因,就使用mousedown事件
    tag.addEventListener('touchstart', this.clickLabel, false);
    let label = new CSS2DObject(tag);
    label.element.innerHTML = areaName;
    label.element.style.visibility = "visible";
    label.position.set(x, 5, z);
    label.position.z -= 3;
    this.scene.add(label)
}
/**
* @description 文字坐标点击
* @param {*} e
*/
clickLabel(e) {
    console.log('e :>> ', e);
}
/**
* @description // 创建光柱
* @param {*} x d3 - 经纬度转换后的x轴坐标
* @param {*} z d3 - 经纬度转换后的z轴坐标
* @param {*} heightScaleFactor
*/
createLightPillar(x, z, heightScaleFactor = 1) {
    let group = new THREE.Group();
    // 柱体高度
    const height = heightScaleFactor;
    // 柱体的geo,6.19=柱体图片高度/宽度的倍数
    const geometry = new THREE.PlaneGeometry(
        height / 6.219,
        height
    );
    // 柱体旋转90度,垂直于Y轴
    // geometry.rotateX(Math.PI / 2)
    // 柱体的z轴移动高度一半对齐中心点
    geometry.translate(0, height / 2, 0);
    // 柱子材质
    const material = new THREE.MeshBasicMaterial({
        map: textureLoader.load(require("./mapimg/光柱.png")),
        color: 0x00ffff,
        transparent: true,
        depthWrite: false,
        // depthTest:false,
        side: THREE.DoubleSide,
    });
    // 光柱01
    let light01 = new THREE.Mesh(geometry, material);
    light01.renderOrder = 2;
    light01.name = "createLightPillar01";
    // 光柱02:复制光柱01
    let light02 = light01.clone();
    light02.renderOrder = 2;
    light02.name = "createLightPillar02";
    // 光柱02,旋转90°,跟 光柱01交叉
    light02.rotateY(Math.PI / 2);

    // 创建底部标点
    const bottomMesh = this.createPointMesh(1.5);

    // 创建光圈
    const lightHalo = this.createLightHalo(1.5);
    WaveMeshArr.push(lightHalo);
    // 将光柱和标点添加到组里
    group.add(bottomMesh, lightHalo, light01, light02);
    // 设置组对象的姿态
    // group = setMeshQuaternion(group, R, lon, lat)
    group.position.set(x, 4.01, z);
    return group;
},

4.5 创建底部标点

/**
		 * @description 创建底部标点
		 * @param {number} size 缩放大小
		 */
createPointMesh(size) {
    // 标记点:几何体,材质,
    const geometry = new THREE.PlaneGeometry(1, 1);
    const material = new THREE.MeshBasicMaterial({
        map: textureLoader.load(require("./mapimg/标注.png")),
        color: 0x00ffff,
        side: THREE.DoubleSide,
        transparent: true,
        depthWrite: false, //禁止写入深度缓冲区数据
    });
    let mesh = new THREE.Mesh(geometry, material);
    mesh.renderOrder = 2;
    mesh.rotation.x = Math.PI / 2;
    mesh.name = "createPointMesh";
    // 缩放
    const scale = 1 * size;
    mesh.scale.set(scale, scale, scale);
    return mesh;
}
/**
		 * @description 创建底部标点的光圈
		 * @param {number} size 缩放大小
		 */
createLightHalo(size) {
    // 标记点:几何体,材质,
    const geometry = new THREE.PlaneGeometry(1, 1);
    const material = new THREE.MeshBasicMaterial({
        map: textureLoader.load(require("./mapimg/标注光圈.png")),
        color: 0x00ffff,
        side: THREE.DoubleSide,
        opacity: 0,
        transparent: true,
        depthWrite: false, //禁止写入深度缓冲区数据
    });
    let mesh = new THREE.Mesh(geometry, material);
    mesh.renderOrder = 2;
    mesh.name = "createLightHalo";
    mesh.rotation.x = Math.PI / 2;
    // 缩放
    const scale = 1.5 * size;
    mesh.size = scale; //自顶一个属性,表示mesh静态大小
    mesh.scale.set(scale, scale, scale);
    return mesh;
},

4.6 添加底部旋转背景

initFloor() {
    const geometry = new THREE.PlaneGeometry(400, 400);
    let texture = textureLoader.load(require("./mapimg/地板背景.png"));
    const material = new THREE.MeshPhongMaterial({
        color: 0xffffff,
        map: texture,
        // emissive:0xffffff,
        // emissiveMap:Texture,
        transparent: true,
        opacity: 1,
        depthTest: true,
        // roughness:1,
        // metalness:0,
        depthWrite: false,
        // side: THREE.DoubleSide
    });
    let plane = new THREE.Mesh(geometry, material);
    plane.rotateX(-Math.PI / 2);
    this.scene.add(plane);

    let rotatingApertureTexture = textureLoader.load(
        require("./mapimg/rotatingAperture.png")
    );
    let rotatingApertureerial = new THREE.MeshBasicMaterial({
        map: rotatingApertureTexture,
        transparent: true,
        opacity: 1,
        depthTest: true,
        depthWrite: false,
    });
    let rotatingApertureGeometry = new THREE.PlaneGeometry(
        100,
        100
    );
    rotatingApertureMesh = new THREE.Mesh(
        rotatingApertureGeometry,
        rotatingApertureerial
    );
    rotatingApertureMesh.rotateX(-Math.PI / 2);
    rotatingApertureMesh.position.y = 0.02;
    rotatingApertureMesh.scale.set(1.2, 1.2, 1.2);
    this.scene.add(rotatingApertureMesh);

    let rotatingPointTexture = textureLoader.load(
        require("./mapimg/rotating-point2.png")
    );
    let material2 = new THREE.MeshBasicMaterial({
        map: rotatingPointTexture,
        transparent: true,
        opacity: 1,
        depthTest: true,
        depthWrite: false,
    });

    rotatingPointMesh = new THREE.Mesh(
        rotatingApertureGeometry,
        material2
    );
    rotatingPointMesh.rotateX(-Math.PI / 2);
    rotatingPointMesh.position.y = 0.04;
    rotatingPointMesh.scale.set(1, 1, 1);
    this.scene.add(rotatingPointMesh);

    let circlePoint = textureLoader.load(require("./mapimg/circle-point.png"));
    let material3 = new THREE.MeshPhongMaterial({
        color: 0x00ffff,
        map: circlePoint,
        transparent: true,
        opacity: 1,
        depthWrite: false,
        // depthTest: false,
    });
    let plane3 = new THREE.PlaneGeometry(120, 120);
    let mesh3 = new THREE.Mesh(plane3, material3);
    mesh3.rotateX(-Math.PI / 2);
    mesh3.position.y = 0.06;
    this.scene.add(mesh3);
},

5.源码

gitee3Dthree地图源码
借鉴:https://space.bilibili.com/323405428

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
3D地图是一种以三维立体效果展示地理信息的地图类型。不同于传统的二维平面地图3D地图通过运用先进的三维技术,能够以更加真实和形象的方式呈现地理空间的特征和地貌。它能够提供更具交互性和可视化的视觉体验,为用户带来更加精确和生动的地理信息。 首先,3D地图能够准确呈现地理空间的立体特征和地形地貌。它能够通过使用高程数据和颜色渐变等技术手段,直观展示海拔高低、山川起伏、河流纵横等地理要素,使用户能够更好地理解和把握地理环境。 其次,3D地图具有更强的交互性。用户可以通过电子设备上的缩放、旋转、倾斜等操作,自由观察不同角度的地理信息。这种交互性能帮助用户更深入地探索地图中的细节,同时也能够根据自己的需求和兴趣进行个性化的查询和浏览。 最后,3D地图也可以应用于虚拟现实和增强现实领域。借助虚拟现实技术,用户可以身临其境地感受地理空间的真实性,获得更加沉浸式的探索体验。通过增强现实技术,用户可以将实际场景与虚拟地图相结合,获取更丰富的地理信息和导航指引。 总而言之,三维地图以其立体、交互和沉浸等特点,为用户提供了一种更加直观和真实的地理信息展示方式,具有更广泛的应用前景。无论是在导航、旅游、城市规划等领域,3D地图都能够发挥重要的作用,带来更好的用户体验和决策支持。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值