Threejs 3D智慧城市 (mapbox+threejs)

发布于:2022-07-17 ⋅ 阅读:(13110) ⋅ 点赞:(12)

2022年7月17日

经过一到两个礼拜的对前端可视化的了解与学习,开始着手于3D智慧城市的项目


话不多说先看效果图: 

 

 

导入底层地图

//创建底图  
var map = new mapboxgl.Map({
            style:'mapbox://styles/mapbox/navigation-guidance-night-v2',//风格样式        
            center: [XXXX.XXXX,XXXX,XXXX],//地图中心
            zoom: 15.5,//起始高度
            pitch: 45,//视觉角度
            container: 'map',//加载地图
        });

添加旋转

//获取时间戳来实现旋转
function rotateCamera(timestamp) {
    map.rotateTo((timestamp / 100) % 360, {duration: 0});
    requestAnimationFrame(rotateCamera);//requestAnimationFrame接口实现渲染
}

将threejs对象的位置转换成地图位置

  • 设置坐标基点
  • Y轴转换
  • 经纬度坐标转换

新建一个用户层,在该层添加建筑,精灵图,飞线等。

老规矩,threejs三大件,场景,相机,渲染器。

    this.camera = new THREE.Camera();//添加相机
    this.scene = new THREE.Scene();//添加场景

    var directionalLight = new THREE.DirectionalLight(0xffffff,6,0);//设置灯光1
    directionalLight.position.set(3.8, 1, 1).normalize();//设置灯光位置
    this.scene.add(directionalLight);//添加灯光1

    var directionalLight2 = new THREE.DirectionalLight(0xffffff,6,0);//设置灯光2
    directionalLight2.position.set(-3, -1, -1).normalize();//设置灯光位置
    this.scene.add(directionalLight2);//添加灯光2
   
    //添加渲染器
    this.renderer = new THREE.WebGLRenderer({
                    canvas: map.getCanvas(),//threejs的画布即为mapbox的地图
                    context: gl,
                    antialias: true//抗锯齿打开
                });

通过threejs的加载器导入地标建筑

      loader.load('XXXXXX.gltf', (function (gltf) {
              this.scene.add(gltf.scene);
              }).bind(this));

构造三维三次贝塞尔曲线(CubicBezierCurve3)实现飞线

 const curve = new THREE.CubicBezierCurve3(
      new THREE.Vector3(0, 0, 0),//起始位置
      new THREE.Vector3(20, 15, 0),//飞线形状
      new THREE.Vector3(0, 150, 0),//飞线形状
      new THREE.Vector3(-70, 10, 0)//终点位置
    );

    const pointsPosition = curve.getPoints(5000); // 点数组,5000个采样点
    const lineLength = 3000 // 飞线长度,800个点
    let currentIndex = 0 // 当前索引
    const circle = 3000 // 周期3秒
    const startTime = Date.now() // 开始时间

    // 创建缓冲几何体
    const pointsGeometry = new THREE.BufferGeometry();

    setInterval(() => {
      const now = Date.now() // 当前时间
      currentIndex = (now - startTime) % circle / circle * pointsPosition.length // 当前索引
      currentIndex = Math.floor(currentIndex) // 索引修正
      if (currentIndex > pointsPosition.length) currentIndex = 0 // 索引修正

      // 飞线点位数组
      const lineArr = pointsPosition.filter((point, index) => {
        if (index >= currentIndex && index <= currentIndex + lineLength) return true
      })
      const arr = lineArr.reduce((arr, point) => {
        const { x, y, z } = point
        arr.push(x, y, z)
        return arr
      }, [])

      // 几何体中添加位置属性
      pointsGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(arr), 3)); // 一个顶点由3个坐标构成

      // 几何体添加图元尺寸的缩放属性
      const aScaleArr = lineArr.map((point, index) => { return index / lineLength })
      pointsGeometry.setAttribute('aScale', new THREE.BufferAttribute(new Float32Array(aScaleArr), 1)); // 一个缩放量由1个浮点数表示

    }, 50)

    // 纹理和材质
    const pointTexture = new THREE.TextureLoader().load("XXXXXXXXX.png");
    const pointsMaterial = new THREE.PointsMaterial({
      color: '#FFFF00',
      map: pointTexture, // 材质含有透明度
      transparent: true, // 开启透明度
      depthWrite: false, // 禁用深度写入
      size: 70,// 点大小
      sizeAttenuation: true, // 大小是否随相机深度衰减
    });

   // 修正图元大小
    pointsMaterial.onBeforeCompile = (shader) => {
      const vertex = `
        attribute float aScale;
        void main() {          
      `
      const vertex1 = `gl_PointSize = size * aScale;` 
      shader.vertexShader = shader.vertexShader.replace("void main() {", vertex);
      shader.vertexShader = shader.vertexShader.replace("gl_PointSize = size;", vertex1);
    }

    // 创建点系统,加入场景
    const points = new THREE.Points(pointsGeometry, pointsMaterial);
    this.scene.add(points);

    // 图元重新排序
    setInterval(() => {
      // 提取点
      const position = pointsGeometry.attributes.position
      const scale = pointsGeometry.attributes.aScale // 缩放属性
      const { count, array, itemSize } = position
      const pointArr = []
      for (let i = 0; i < count; i++) {
        const point = {
          position: [array[i * itemSize + 0], array[i * itemSize + 1], array[i * itemSize + 2],],
          scale: scale.array[i]
        }
        pointArr.push(point)
      }

      // 重排序
      pointArr.sort((point2, point1) => {
        const v1 = new THREE.Vector3(...point1.position)
        const v2 = new THREE.Vector3(...point2.position)
        if (v1.applyMatrix4(points.modelViewMatrix).z > v2.applyMatrix4(points.modelViewMatrix).z) return -1 // 交互顺序
        return 1 // 不交换顺序
      })

      // 更新几何体的position位置
      const arr = pointArr.reduce((arr, point) => {
        arr.push(...point.position)
        return arr
      }, [])
      pointsGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(arr), 3)); // 一个顶点由3个坐标构成


      // 更新几何体的aScale数据
      const arr1 = pointArr.reduce((arr, point) => {
        arr.push(point.scale)
        return arr
      }, [])
      pointsGeometry.setAttribute('aScale', new THREE.BufferAttribute(new Float32Array(arr1), 1)); // 一个缩放量由1个浮点数表示

    }, 50)

 用mapbox自带图层实现精灵图

map.loadImage(
            'XXXXXXXX.PNG',//导入精灵图
         function(error, image) {
        if (error) throw error;//错误类型抛出
        map.addImage('spirit1', image);//重命名
        map.addLayer({
            "id": "points",//id
            "type": "symbol",//类型
             
            //精灵图相关属性设置
            "source": {
                "type": "geojson",
                "data": {
                    "type": "FeatureCollection",
                    "features": [{
                        "type": "Feature", 
                        "properties": {
                                "MyBuildName": ` 区域:地方本部 
                                状态:正常`
                            },
                        "geometry": {          
                            "type": "Point",
                            "coordinates": [121.155361299823,30.051971308151167],
       
                        }
                    }]
                }
            },

            //字体样式设置
            "layout": {
                "text-field": "{MyBuildName}",//加载文本
                "text-offset": [0, -2.2],//设置图标与图标注相对之间的距离
                "text-anchor": "top",//标记文本相对于定位点的位置
                "text-size": 15,//字号   
                "icon-image": "spirit1",//选择图片
                "icon-size": 0.3,//精灵图大小
            },

            //字体颜色设置
            paint: {            
             "text-color": "#FFFFFF"
                   }
                    });
                });

以此类推创建4个飞线,已经与之对应的4个精灵图


最后导入城市建筑的geojson文件,通过自定义图层加载城市建筑


//导入geojson数据
 var url = "city.geojson"
            var request = new XMLHttpRequest();
            request.open("get", url);/*设置请求方法与路径*/
            request.send(null);/*不发送数据到服务器*/
            request.onload = function () {/*XHR对象获取到返回信息后执行*/
                if (request.status == 200) {/*返回状态为200,即为数据获取成功*/
                    var json = JSON.parse(request.responseText);
                    for (var i = 0; i < json.length; i++) {
                        console.log(json[i].name);
                    }
                    console.log(json);
                    addBuildingLayer(json)
                    // 添加自定义的gltb模型图层
                    map.addLayer(customLayer);
                }
            }

//设置建筑高度
     feature.features.forEach((item) => {
                let he //默认建筑物高度
                if (item.properties.height === '') {
                    he = '20'
                } else {
                    he = item.properties.height
                }
                //模拟数据
                //item.properties.pkid = parseInt(item.properties.xh)
                item.properties.height = parseInt(he)
                item.properties.base_height = parseInt(0)
            })
//自定义图层
     map.addLayer({
                id: 'entity_layer',
                source: 'states',
                type: 'fill-extrusion',
                layout: {},
                minzoom: 5,
                paint: {
                    'fill-extrusion-color': '#FFF111',
                    'fill-extrusion-height': [
                        'interpolate',
                        ['linear'],
                        ['zoom'],
                        14,
                        0,
                        14.05,
                        ['get', 'height'],
                    ],
                    'fill-extrusion-base': [
                        'interpolate',
                        ['linear'],
                        ['zoom'],
                        14,
                        0,
                        14.05,
                        ['get', 'base_height'],
                    ],
                   
                    'fill-extrusion-opacity': 0.5,
                },
            });

以为后期团队调整路线,所以mapbox+threejs的实现方案只做到了目前效果。

接下一章便是开启maptalks+threejs的3D城市方案路线。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到