三维地图,智慧城市,商业智能BI,数据可视化大屏(Cesiumjs/UE)

绘图工具

三维地图:Cesiumjs

建模方式:激光点云建模、航拍倾斜摄影建模、GIS建模、BIM建模、手工建模

建模工具:C4D Blender GeoBuilding ArcGIS

Cesiumjs

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../Build/Cesium/Cesium.js"></script>
    <link href="../Build/Cesium/Widgets/widgets.css" rel="stylesheet"/>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
<div id="Cesium"></div>
<script>
    // 在 Cesium官网上 注册用户获取 token
    Cesium.Ion.defaultAccessToken = '***';
    // 基础图层,在线方式,从 Cesium 官方下载 瓦片
    let baseLayer = Cesium.ImageryLayer.fromProviderAsync(
          Cesium.IonImageryProvider.fromAssetId(3813)   // 需要在 Cesium官网 Asset Depot 添加 对应图层的 权限
        );
    // 离线方式,自行维护一个可访问的瓦片目录
    let baseLayer = Cesium.ImageryLayer.fromProviderAsync(
        Cesium.TileMapServiceImageryProvider.fromUrl(
            // 用模块的方式引入 Cesium 时,会有 /cesium/Assets/Textures 这个目录
            Cesium.buildModuleUrl('/cesium/Assets/Textures/3813')
            // 获取瓦片:用Chrome扩展程序 Save All Resources 保存用在线方式访问到的瓦片
        )
    );
            
    baseLayer.gamma = 0;  // 伽玛校正(对比度、亮度)
    baseLayer.hue = Cesium.Math.toRadians(0); // 色调【色相】,取值范围在 0-PI,参考色调环
    baseLayer.saturation = 1; // 饱和度,饱和度数值越低越(亮度高时)泛白(亮度低时)范黑
    baseLayer.alpha = 1;  // 透明度
    baseLayer.brightness = 1; // 亮度

    // 3D地图查看器
    const viewer =  new Cesium.Viewer('Cesium', {
        baseLayerPicker: false, // 底图[卫星、地形、矢量]切换按钮
        animation: false, // 左下角 时间播放控件
        timeline: false, // 下方 时间轴
        homeButton: false, // 右上角 主页按钮
        navigationHelpButton: false, // 右上角 问号按钮
        geocoder: false, // 右边上角 搜索框
        fullscreenButton: false, // 右下角 全屏按钮
        infoBox: false, // 点击实体时右侧出现的信息框
        selectionIndicator: false,  // 点击地球时鼠标处出现的指示框
        contextOptions: {
          webgl: {
            alpha: true,   // 允许透明背景
          }
        },
        baseLayer,
    });
    viewer.scene.globe.show = true;  // 显示地球
    viewer.scene.skyBox.show = false;  // 不显示星空
    viewer.scene.sun.show = false;     // 不显示太阳
    viewer.scene.moon.show = false;    // 不显示月球
    viewer.scene.skyAtmosphere.show = false;  // 不显示大气
    viewer.scene.backgroundColor = Cesium.Color.TRANSPARENT; // 透明背景,需设置viewer.contextOptions.webgl.alpha

    Cesium.GeoJsonDataSource.load("./world.json", {   // 载入 GeoJson 矢量数据
      fill: Cesium.Color.TRANSPARENT,   // 透明填充
    })

    let headingPitchRange = new Cesium.HeadingPitchRange(Cesium.Math.toRadians(50), Cesium.Math.toRadians(-90), 2000);
    // viewer.camera.lookAt(Cesium.Cartesian3.fromDegrees(116.39, 39.91), headingPitchRange);  // 设置相机观察目标,同时设定了相机控制器的环绕点
    viewer.scene.camera.setView({    // 切换相机视口
        destination: Cesium.Cartesian3.fromDegrees(116.39, 39.91, 500000),   // 相机经纬度和高度
        orientation: {                                                     // 相机姿态
            heading: Cesium.Math.toRadians(0), // 偏航角,在(相机与地心连线的法面)上的旋转,0为正北
            pitch: Cesium.Math.toRadians(-100), // 俯仰角,在(相机与地心连线所在的经线平面)上选择,-90朝向地心
            roll: 0 // 翻滚角
        }
    });
    let position = Cesium.Cartesian3.fromDegrees(116.39, 39.91, 400);
    viewer.entities.add({      // 添加实体
        polyline: {    // 线条实体
            show: true,
            positions: Cesium.Cartesian3.fromDegreesArray([116.39, 39.91, 116.40, 39.91]),
            width: 5,
            material: new Cesium.Color(0,0,1,1)
        }
    });
    viewer.entities.add({      // 添加实体
        id: 'point',
        position,  // 实体位置
        point: {                              // 圆点实体
            pixelSize: 100,                   // 圆点尺寸,为屏幕的像素尺寸,不随地图缩放和旋转
            color: new Cesium.Color(0,1,0,1)  // 圆点颜色
        },
        description: '<div>html</div>'        // 被点击时右侧弹窗的内容
    });
    viewer.entities.add({      // 添加实体
        position: Cesium.Cartesian3.fromDegrees(116.39, 39.91, 50),  // 实体位置
        plane: {                              // 矩形平面实体
            plane: new Cesium.Plane(Cesium.Cartesian3.UNIT_Z, 0),   // 朝向
            dimensions: new Cesium.Cartesian2(400, 300),
            material: Cesium.Color.RED.withAlpha(0.5),   // 可以为图片
            outline: true,
            outlineColor: Cesium.Color.BLACK
        }
    });
    let polygon = viewer.entities.add({      // 添加实体
        id: 'polygon',
        polygon: {                              // 多边形实体
            hierarchy: Cesium.Cartesian3.fromDegreesArray([116.39, 39.91, 116.40, 39.91, 116.40, 39.90]),
            material: Cesium.Color.YELLOW,   // 可以为图片
            extrudedHeight: 200   // 拉伸为三维物体
        }
    });
    viewer.entities.getById("polygon");  // 获取实体
    viewer.entities.remove(polygon);   // 删除
    viewer.entities.add({      // 添加实体
        position: Cesium.Cartesian3.fromDegrees(116.39, 39.91, 150),  // 实体位置
        label: {                              // 标签实体
            text: '标签',
            font: '50px Helvetica',
            fillColor: Cesium.Color.SKYBLUE
        }
    });
    viewer.entities.add({    // 添加实体
        position,  // 实体位置
        orientation: Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(-90, 0, 0)),  // 实体姿态
        model: {                // 3D模型实体
            uri: './***.glb',   // 载入模型
            minimumPixelSize: 128,          // 模型缩放时最小像素尺寸
            maximumScale: 1000,             // 模型缩放最大比率
            show: true,                     // 是否显示
        }
    });
    viewer.camera.viewBoundingSphere(new Cesium.BoundingSphere(position,20),new Cesium.HeadingPitchRange(0,0,0));  // 设置相机控制器360度环绕点
    // viewer.trackedEntity = entity;   // 相机控制器的环绕点

    /* Cesium 坐标系 */
    // WGS84弧度坐标系 new Cesium.Cartographic(经弧度, 维弧度, 高度);Cesium.Cartographic.fromDegrees(经度,维度,高度)
    // 笛卡尔空间直角坐标系,原点为地心 new Cesium.Cartesian3(x,y,z);Cesium.Cartesian3.fromDegrees(经度,维度,高度)
    // 屏幕坐标系 new Cesium.Cartesian2(x,y)
    /* 坐标转换 */
    // 弧度与角度互转:Cesium.Math.toRadians(),Cesium.Math.toDegrees()
    // WGS84坐标系与笛卡尔坐标系互转: Cesium.Ellipsoid.WGS84.cartographicToCartesian(wgs84);Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian3);Cesium.Cartographic.fromCartesian(cartesian3)
    // 笛卡尔坐标系与屏幕坐标系互转:viewer.scene.pickPosition(cartesian2);viewer.scene.globe.pick(viewer.camera.getPickRay(cartesian2),viewer.scene);viewer.scene.camera.pickEllipsoid(cartesian2)
    //                            Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3);scene.cartesianToCanvasCoordinates(cartesian3)

    // 鼠标拾取
    let handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(function (action) {
        let pick = viewer.scene.pick(action.position);
        if(Cesium.defined(pick)){
            console.log(pick.id.id)
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
</script>
</body>
</html>

三维建筑物

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="../Build/Cesium/Cesium.js"></script>
    <link href="../Build/Cesium/Widgets/widgets.css" rel="stylesheet"/>
    <style>
        html,body{
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
<div id="Cesium"></div>
<script>
    // 在 Cesium官网上 注册用户获取 token
    Cesium.Ion.defaultAccessToken = '****';
    // 加载ArcGIS卫星地图栅格数据,比 Cesium 自带地图更加精细
    const viewer =  new Cesium.Viewer('Cesium', {
        baseLayerPicker: false,
        imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
            url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
        }),
        // 地形,需要在 Cesium官网 Asset Depot 添加 Cesium World Terrain 权限;Ctrl+鼠标滑动改变相机视角可以进入地形
        terrainProvider: new Cesium.CesiumTerrainProvider({
            url: Cesium.IonResource.fromAssetId(1),
            requestVertexNormals: true,
            requestWaterMask: true,      // 水面效果
        }),
    });
    // 添加建筑物模型,需要在 Cesium官网 Asset Depot 添加 Cesium OSM Buildings 权限
    const tileset = viewer.scene.primitives.add(
        new Cesium.Cesium3DTileset({
            url: Cesium.IonResource.fromAssetId(96188),
        })
    );
    // 建筑物模型样式
    tileset.style = new Cesium.Cesium3DTileStyle({
        color: "color('blue', 0.5)",
        show: true
    });

    /* 加载夜晚地图,需要在 Cesium官网 Asset Depot 添加 Earth at Night 权限 */
    // const viewer =  new Cesium.Viewer('Cesium', {
    //     baseLayerPicker: false
    // });
    // 从 My Assets 里拷贝
    // const layer = viewer.imageryLayers.addImageryProvider(
    //     new Cesium.IonImageryProvider({ assetId: 3812 })
    // );
</script>
</body>
</html>

自转

javascript 复制代码
      rotate(116.39);  // 北京经度
      function rotate(longitude) {
        viewer.scene.camera.flyTo({
          destination: Cesium.Cartesian3.fromDegrees(longitude, 20, 30000000), // 相机经纬度和高度
          duration: 20, // 飞行时间
          flyOverLongitude: longitude > 0 ? 180 : 0,   // 转动时要经过的经度,从而确定转动的方向
          easingFunction: Cesium.EasingFunction.LINEAR_NONE,  // 均匀转动
          complete() {
            rotate(longitude > 0 ? longitude - 180 : 180 + longitude);  // 转到背面
          },
        });
      }

点位呼吸效果

javascript 复制代码
      viewer.entities.add({
          id: "NewYork",
          position: Cesium.Cartesian3.fromDegrees(-74.00, 40.43, 50),
          billboard: {
              image: './position.png',
          }
      });
      viewer.entities.add({
        id: 'NewYorkLabel',
        position: Cesium.Cartesian3.fromDegrees(-74.00, 40.43, 50),
        label: {                         // 标签
          text: '纽约分公司',
          font: '200 12px sans-serif',  // font-weight font-size font-family
          fillColor: Cesium.Color.fromCssColorString('#3D3D3D'),  // 字体颜色
          showBackground: true,
          backgroundColor: Cesium.Color.fromCssColorString('#F0DBAF'), // 背景色,没法渐变
          backgroundPadding: new Cesium.Cartesian2(10, 6),
          pixelOffset: new Cesium.Cartesian2(0, -30) // 在position基础上的屏幕偏移
        }
      });

      let NewYork = viewer.entities.getById("NewYork");
      NewYork.billboard.scale = 1;
      let progress = 0;  // 呼吸渐变进度
      let up = true; // 呼吸渐变方向
      breath();

      function breath() {
        requestAnimationFrame(function () {
          NewYork.billboard.scale = 1 + 0.1 * progress;
          if(up){
            if(progress >= 5){
              up = false;
              progress = progress - 1;
            }
            else {
              progress =  progress + 1;
            }
          }
          else {
            if(progress <= 0){
              up = true;
              progress = progress + 1;
            }
            else {
              progress =  progress - 1;
            }
          }
          setTimeout(breath, 150);
        });
      }

地点连线(OD线 Origin-Destination Line

html 复制代码
<img id="gif" src="" style="position: absolute" />
javascript 复制代码
        var gif = {
            name: "curve.gif", // 箭头从左侧中间点到右侧中间点
            width: 1920,
            height: 392,
          };
// 终点位置
        let toDegree = [108.947, 34.259];
        let toCartesian3 = Cesium.Cartesian3.fromDegrees(toDegree[0], toDegree[1]);
        let toCartesian2 = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
          viewer.scene,
          toCartesian3
        );
// 起点位置
        let fromDegree = [121.506377, 31.245105];
        let fromCartesian3 = Cesium.Cartesian3.fromDegrees(fromDegree[0], fromDegree[1]);
        if (!isVisible(fromCartesian3)) {
            fromCartesian3 = findVisibleEdge(fromDegree);
        }
        let fromCartesian2 =
          Cesium.SceneTransforms.wgs84ToWindowCoordinates(
            viewer.scene,
            fromCartesian3
          );
// 屏幕距离
        let distance = Cesium.Cartesian2.distance(
          toCartesian2,
          fromCartesian2
        );
/* 计算连线角度,试用 Cesium.Cartesian2.angleBetween 计算角度发现不对 */
        let angle = 0;
        let deltaY = toCartesian2.y - fromCartesian2.y;
        if (toCartesian2.x > fromCartesian2.x) {
          if (deltaY > 0) {
            angle = Math.asin(deltaY / distance);
          } else {
            angle = -Math.asin(Math.abs(deltaY) / distance);
          }
        } else {
          if (deltaY > 0) {
              angle = Math.PI - Math.asin(deltaY / distance);
          } else {
              angle = Math.asin(Math.abs(deltaY) / distance) - Math.PI;
          }
        }

        let width = distance; // 图片显示宽度
        let height = (width / gif.width) * gif.height; // 图片显示高度
        $("#gif")
          .attr("src", gif.name)
          .css("width", width + "px")
          .css(
            "left",
            (fromCartesian2.x + toCartesian2.x) / 2 - width / 2 + "px"
          )
          .css(
            "top",
            (fromCartesian2.y + toCartesian2.y) / 2- height / 2 + "px"
          )
          .css("transform", `rotate(${angle}rad)`);

        // 判断一个点是否可见,即是否在地球背面
        function isVisible(cartesian3) {
            return new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84, viewer.camera.position).isPointVisible(cartesian3);
        }


        // 给一个不可见的点找一个同维度的、可见的、离原点最近的点,此点在可见范围边缘上
        function findVisibleEdge(fromDegree) {
            let cartesian3From;
            let fromLongitude = fromDegree[0];
            let toLongitude = toDegree[0];
            let fromEast = fromLongitude - toLongitude > 0;
            let moveEast;
            if (fromEast) {
                moveEast = fromLongitude - toLongitude > 180;
            } else {
                moveEast = toLongitude - fromLongitude < 180;
            }
            do {
                if (moveEast) {
                    fromLongitude = fromLongitude + 0.1;
                    fromLongitude = fromLongitude < 180 ? fromLongitude : fromLongitude - 360;
                } else {
                    fromLongitude = fromLongitude - 0.1;
                    fromLongitude = fromLongitude > -180 ? fromLongitude : 360 + fromLongitude;
                }
                cartesian3From = Cesium.Cartesian3.fromDegrees(fromLongitude, fromDegree[1]);
            } while (!isVisible(cartesian3From));
            return cartesian3From;
        },

判断一个点是否在GeoJSON内

javascript 复制代码
import chinaJson from './100000.json';  // 中国区域

handler.setInputAction((action) => {            
    let inChina = false;
    // 屏幕坐标,如果用了 autofit.js,要进行处理
    position = this.autoFitPosition(action.endPosition);
    // 笛卡尔坐标
    let cartesian3 = viewer.scene.camera.pickEllipsoid(position);
    if (cartesian3) {
        // 经纬弧度坐标
        let cartographic = Ellipsoid.WGS84.cartesianToCartographic(cartesian3);
        chinaJson.features.forEach((feature) => {
            feature.geometry.coordinates[0].forEach((polygon) => {
                // 是否在区域内
                if (this.isInPolygon([CesiumMath.toDegrees(cartographic.longitude), CesiumMath.toDegrees(cartographic.latitude)], polygon)) {
                    inChina = true;
                }
            });
        });
    }
}, ScreenSpaceEventType.MOUSE_MOVE);


isInPolygon(checkPoint, polygonPoints) {
            let counter = 0;
            let pointCount = polygonPoints.length;
            let p1 = polygonPoints[0];
            let i, xinters, p2;

            for (i = 1; i <= pointCount; i++) {
                p2 = polygonPoints[i % pointCount];
                if (checkPoint[0] > Math.min(p1[0], p2[0]) && checkPoint[0] <= Math.max(p1[0], p2[0])) {
                    if (checkPoint[1] <= Math.max(p1[1], p2[1])) {
                        if (p1[0] !== p2[0]) {
                            xinters = ((checkPoint[0] - p1[0]) * (p2[1] - p1[1])) / (p2[0] - p1[0]) + p1[1];
                            if (p1[1] === p2[1] || checkPoint[1] <= xinters) {
                                counter++;
                            }
                        }
                    }
                }
                p1 = p2;
            }
            return counter % 2 > 0;
},

autoFitPosition(position) {
            let scale = 1;
            let transform = document.querySelector('body').style.transform;
            if (transform) {
                scale = transform.split('(')[1].split(')')[0];
                scale = parseFloat(scale);
            }
            return new Cartesian2(position.x / scale, position.y / scale);
},

UE 像素流

PixelStreamingInfrastructure

package.json

javascript 复制代码
  "dependencies": {
    "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^1.0.6",   // 版本与UE保持一致
    "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3": "^1.0.5",
  },

vue

javascript 复制代码
<template>
  <div class="ue-container" ref="ueContainer"></div>
</template>

<script>
import { Config, PixelStreaming, Flags } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3'
import {
  Application,
  PixelStreamingApplicationStyle,
  UIElementCreationMode
} from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3'
import io from 'socket.io-client'

let ueApplication, stream

export default {
  mounted() {
    this.createUE()
  },
  methods: {
    createUE() {
      const PixelStreamingApplicationStyles = new PixelStreamingApplicationStyle()
      PixelStreamingApplicationStyles.applyStyleSheet()

      const config = new Config({ useUrlParams: true })
      config.getTextSettings().at(0).value = 'ws://信令服务器地址'  // 设置 SignallingServerUrl
      config.setFlagEnabled(Flags.AutoConnect, true)  // 自动连接
      config.setFlagEnabled(Flags.AutoPlayVideo, true)  // 自动播放
      config.setFlagEnabled(Flags.StartVideoMuted, true) // 播放时静音,上面的自动播放才能生效
      // config.setFlagEnabled(Flags.MouseInput, false)  // 禁止鼠标操作
      // config.setFlagEnabled(Flags.KeyboardInput, false) // 禁止键盘操作
      config.setFlagEnabled(Flags.HoveringMouseMode, true)  // 进入操作状态时依然显示光标

      stream = new PixelStreaming(config)
      ueApplication = new Application({
        stream,
        onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode),
        fullScreenControlsConfig: {
          isEnabled: false  // 隐藏全屏按钮
        },
        settingsPanelConfig: {
          visibilityButtonConfig: { creationMode: UIElementCreationMode.Disable } // 隐藏设置按钮
        },
        statsPanelConfig: {
          visibilityButtonConfig: { creationMode: UIElementCreationMode.Disable } // 隐藏信息按钮
        },
        videoQpIndicatorConfig: {
          disableIndicator: { disableIndicator: true }  // 隐藏信号强度图标
        }
      })

      this.$refs.ueContainer.appendChild(ueApplication.rootElement)
      stream.addResponseEventListener('handle_responses', this.handleUeResponse) // 监听UE推送的消息
      stream.addEventListener('videoInitialized', this.initScene) // UE初始化完成事件
    },
    sendMessage(data) {
      stream.emitUIInteraction(data)
    },
    handleUeResponse(msg) {
      console.log(msg)
    },
    async initScene() {
      this.sendMessage('')
    },
    forbidMouse() {
      // 某种情况下禁止鼠标操作,如果用 ueApplication.stream.setFlagEnabled(Flags.MouseInput, false) 会导致不能解禁
      document.getElementById('videoElementParent').style.pointerEvents = 'none'
      document.getElementById('streamingVideo').style.pointerEvents = 'none'
    },
  }
}
</script>

<style lang="less">
body {
  width: 100vw;
  height: 100vh;
  min-height: -webkit-fill-available;
  margin: 0;

  #playerUI {
    position: absolute;
    z-index: 0;

    video {
      object-fit: fill;
    }
  }
}

.ue-container {
  position: absolute;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
</style>

peer-stream

vue

html 复制代码
<script>
import '@/utils/peer-stream.js'

let ueVideo
export default {
  mounted() {
      ueVideo = document.createElement('video', { is: 'peer-stream' })
      ueVideo.id = 'ws://信令地址'
      this.$refs.container.appendChild(ueVideo)
      ueVideo.addEventListener('playing', this.initScene) // UE场景开始渲染
      ueVideo.addEventListener('message', this.handleUeResponse) // 监听UE推送的消息
  },
  methods: {
      sendUeMessage(data) {
        // 发送消息
        ueVideo.emitMessage(data)
    },
  }
}
</script>
<style lang="less">
body {
  height: 100vh;
  min-height: -webkit-fill-available;
  margin: 0;
  min-width: 1920px;
  position: relative;

  .video-container {
    video {
      position: absolute;
      z-index: 0;
      width: 100%;
      height: 100%;
      background-color: #0A1B2F;
    }
  }
}
</style>
相关推荐
孤单网愈云23 分钟前
12.10深度学习_经典神经网络_GoogleNet自我理解
人工智能·深度学习·神经网络
中国云报37 分钟前
AI来了,云原生更稳了
人工智能·云原生
远洋录44 分钟前
前端性能优化实战:从加载到渲染的全链路提升
前端·人工智能·react
Koi慢热1 小时前
ChatGPT突然全球宕机,OpenAI致歉:并查明原因,正积极修复
人工智能·chatgpt
说私域3 小时前
开源 AI 智能名片 S2B2C 商城小程序对私域流量运营的全方位助力
人工智能·小程序·流量运营
Leweslyh4 小时前
智能时代的基石:神经网络
人工智能·深度学习·神经网络
微雨盈萍cbb5 小时前
BERT--自然语言处理的革命性进展
人工智能·自然语言处理·bert
dundunmm5 小时前
论文阅读:CONTRASTIVE DEEP NONNEGATIVE MATRIX FACTORIZATION FOR COMMUNITY DETECTION
论文阅读·人工智能·机器学习·数据挖掘·聚类·矩阵分解·社区检测
九年义务漏网鲨鱼9 小时前
【推荐算法】推荐系统中的特征工程
人工智能·算法·机器学习·推荐算法·特征工程
过去式的马马马9 小时前
文多多aippt(AI自动生成PPT文档项目)开放API接口免费测试
人工智能·powerpoint