Skip to content

可视域分析


ts
import { Viewer, VectorTileImageryProvider, ViewShed3D } from "joDVF";
import { Pane } from "tweakpane";
import {
  Cartesian3,
  Color,
  Cartographic,
  SceneMode,
  RectangleGraphics,
  SampledPositionProperty,
  Math as CesiumMath,
  Cesium3DTileset,
  HeadingPitchRange,
  JulianDate,
  ClockRange,
  Transforms,
  HeadingPitchRoll,
  ShadowMode,
  TimeIntervalCollection,
  TimeInterval,
  VelocityOrientationProperty,
  PolylineGlowMaterialProperty
} from "joCesium";
import { vi } from "element-plus/es/locales.mjs";

const { mapContainer } = createContainer();

const viewer = new Viewer(mapContainer, {
  sceneOptions: SceneMode.SCENE3D
});
const scene = viewer.scene;
const camera = viewer.camera;

const dataBaseURL =
  "https://www.southsmart.com/w3d-data/modeldata/shaoguanshuju/shaoguantestbaimo/tileset.json";

const tileset = await Cesium3DTileset.fromUrl(dataBaseURL);
scene.primitives.add(tileset);
// Set the camera to view the newly added tileset
const boundingSphere = tileset.boundingSphere;
const radius = boundingSphere.radius;
viewer.zoomTo(tileset, new HeadingPitchRange(-1.2, -0.6, radius * 4.0));

scene.globe.depthTestAgainstTerrain = true;
let shed3d;

let gltfModel, pathEntity;

const start = JulianDate.fromDate(new Date(2024, 2, 25, 16));
const stop = JulianDate.addSeconds(start, 16, new JulianDate());

const clock = viewer.clock;
// Make sure viewer is at the desired time.
clock.startTime = start.clone();
clock.stopTime = stop.clone();
clock.currentTime = start.clone();
clock.clockRange = ClockRange.LOOP_STOP; //Loop at the end
clock.multiplier = 0;

const flyPositions = [
  [113.583671, 24.775492],
  [113.584071, 24.776239],
  [113.584772, 24.777343],
  [113.584772, 24.777343],
  [113.584846, 24.778412],
  [113.58532, 24.779481],
  [113.58599, 24.780662],
  [113.58626, 24.781563],
  [113.586955, 24.782855],
  [113.587077, 24.783909],
  [113.589087, 24.784334],
  [113.58868, 24.78513],
  [113.587913, 24.786173],
  [113.587209, 24.786362],
  [113.586346, 24.786333],
  [113.585409, 24.784744]
];
addModel();
addPath();

let radar;

function addModel() {
  // 模型加载 by entity
  gltfModel = viewer.entities.add({
    orientation: Transforms.headingPitchRollQuaternion(
      new Cartesian3.fromDegrees(117.081376, 31.655497, 150.8386),
      new HeadingPitchRoll(
        CesiumMath.toRadians(50), // 顺时针旋转的角度值
        CesiumMath.toRadians(0),
        CesiumMath.toRadians(0)
      )
    ),

    position: Cartesian3.fromDegrees(113.583671, 24.775492, 287.8386),
    model: {
      uri: "./assets/model/glb/15-0.glb", //模型的地址
      scale: 0.05, //模型缩放比例
      // minimumPixelSize: 128, // 最小像素大小
      // maximumScale: 1, // 模型的最大比例尺大小。 minimumPixelSize 的上限
      incrementallyLoadTextures: true, // 加载模型后纹理是否可以继续流入
      runAnimations: true, // 是否应启动模型中指定的 glTF 动画
      clampAnimations: true, // 指定 glTF 动画是否应在没有关键帧的持续时间内保持最后一个姿势
      eyeOffset: new Cartesian3(0, 0, -10000), // 设置模型的可见度
      disableDepthTestDistance: Number.POSITIVE_INFINITY,
      // 指定模型是否投射或接收来自光源的阴影 type:ShadowMode
      // DISABLED 对象不投射或接收阴影; ENABLED 对象投射并接收阴影; CAST_ONLY 对象仅投射阴影; RECEIVE_ONLY 对象仅接收阴影
      shadows: ShadowMode.DISABLED,
      show: true
      // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND // 设置模型贴地,
    }
  });
}

function addPath() {
  const position = computeClularFlight();
  pathEntity = viewer.entities.add({
    availability: new TimeIntervalCollection([
      new TimeInterval({
        start: start,
        stop: stop
      })
    ]),
    // Use our computed positions
    position,
    // Automatically compute orientation based on position movement.
    orientation: new VelocityOrientationProperty(position),
    // Show the path as a pink line sampled in 1 second increments.
    path: {
      resolution: 1,
      material: new PolylineGlowMaterialProperty({
        glowPower: 0.1,
        color: Color.YELLOW
      }),
      width: 10
    }
  });

  // pathEntity.position.setInterpolationOptions({
  //   interpolationDegree: 2,
  //   interpolationAlgorithm: Cesium.HermitePolynomialApproximation,
  // });
}

// Generate a random circular pattern with varying heights.
function computeClularFlight() {
  const property = new SampledPositionProperty();
  for (let i = 0; i < flyPositions.length; i++) {
    const radians = CesiumMath.toRadians(i);
    const time = JulianDate.addSeconds(start, i, new JulianDate());

    const position = Cartesian3.fromDegrees(
      flyPositions[i][0],
      flyPositions[i][1],
      287.8386
    );
    property.addSample(time, position);

    // Also create a point for each sample we generate.
    viewer.entities.add({
      position: position,
      point: {
        pixelSize: 8,
        color: Color.TRANSPARENT,
        outlineColor: Color.YELLOW,
        outlineWidth: 3
      }
    });
  }
  return property;
}

addUI();

function addUI() {
  const pane = new Pane({
    title: "操作"
  });

  const startBtn = pane.addButton({ title: "开始" });
  startBtn.on("click", () => {
    if (shed3d) shed3d = shed3d.destroy();
    if (!shed3d) shed3d = new ViewShed3D(viewer);
    // viewer.scene.primitives.add(shed3d)
    scene.primitives.add(shed3d);
    shed3d.start();
  });

  const clearBtn = pane.addButton({ title: "清除" });
  clearBtn.on("click", () => {
    if (shed3d) shed3d = shed3d.destroy();
  });

  const flyBtn = pane.addButton({ title: "开始飞行" });
  flyBtn.on("click", () => {
    const position = computeClularFlight();
    clock.multiplier = 0.3;
    gltfModel.position = position;
    gltfModel.orientation = new VelocityOrientationProperty(position);

    pathEntity.position = position;
    pathEntity.orientation = new VelocityOrientationProperty(position);

    clock.onTick.addEventListener((clock) => {
      const cartesian = position.getValue(clock.currentTime);
      if (cartesian) {
        const cartographic = Cartographic.fromCartesian(cartesian);
        // if (shed3d) shed3d = shed3d.destroy();
        if (shed3d) {
          // shed3d = shed3d.destroy();
          const currentPosition = [
            CesiumMath.toDegrees(cartographic.longitude),
            CesiumMath.toDegrees(cartographic.latitude),
            0
          ];
          shed3d.viewer?.scene.postProcessStages.remove(shed3d.postProcess);
          shed3d.primitiveCollection.removeAll();

          // viewer.scene.primitives.add(shed3d);
          shed3d.cameraPosition = cartesian;
          shed3d.viewPosition = Cartesian3.fromDegrees(...currentPosition);
          shed3d._addToScene();
          // shed3d.addRadar(shed3d.cameraPosition, shed3d.frustumQuaternion);
          if (radar) {
            // viewer.entities.remove(radar);
            radar.position = shed3d.cameraPosition;
            // @ts-ignore
            radar.orientation = shed3d.frustumQuaternion;
          } else {
            const rectangularSensor = new RectangleGraphics({
              radius: shed3d.distance,
              xHalfAngle: CesiumMath.toRadians(shed3d.horizontalAngle / 2),
              yHalfAngle: CesiumMath.toRadians(shed3d.verticalAngle / 2),
              material: new Color(0, 1, 1, 0.4),
              lineColor: new Color(1, 1, 1, 1),
              slice: 8,
              showScanPlane: false,
              scanPlaneColor: new Color(0, 1, 1, 1),
              scanPlaneMode: "vertical",
              scanPlaneRate: 3,
              showThroughEllipsoid: false,
              showLateralSurfaces: false,
              showDomeSurfaces: false
            });

            radar = viewer.entities.add({
              position: shed3d.cameraPosition,
              // @ts-ignore
              orientation: shed3d.frustumQuaternion,
              show: true,
              // @ts-ignore
              rectangularSensor
            });
          }
        }
        if (!shed3d) {
          shed3d = new ViewShed3D(viewer, {
            isOutputTexture: true
          });
          scene.primitives.add(shed3d);
        }

        // 进行录制视频
        if (shed3d._reflectionImageData) {
          const canvas = document.getElementById(
            "canvas-cap"
          ) as HTMLCanvasElement;
          const ctx = canvas.getContext("2d");
          canvas.width = shed3d._reflectionImageData.width;
          canvas.height = shed3d._reflectionImageData.height;
          ctx?.putImageData(shed3d._reflectionImageData, 0, 0);
        }
        // const currentPosition = [
        //   Cesium.Math.toDegrees(cartographic.longitude),
        //   Cesium.Math.toDegrees(cartographic.latitude),
        //   0,
        // ];
        // // viewer.scene.primitives.add(shed3d);
        // shed3d.cameraPosition = cartesian;
        // shed3d.viewPosition = Cesium.Cartesian3.fromDegrees(
        //   ...currentPosition,
        // );
        // shed3d._addToScene();
        // shed3d.addRadar(shed3d.cameraPosition, shed3d.frustumQuaternion);
      }
    });
  });
}

function createContainer() {
  const container = document.createElement("div");
  container.style.width = "100%";
  container.style.height = "100%";

  const uiContainer = document.createElement("div");
  uiContainer.style.position = "fixed";
  uiContainer.style.top = "5px";
  uiContainer.style.left = "5px";

  const video = document.createElement("canvas");
  video.id = "canvas-cap";
  video.style.position = "absolute";
  video.style.bottom = "10px";
  video.style.left = "10px";
  video.style.height = "360px";
  video.style.width = "640px";
  video.style.zIndex = "20000";
  video.style.background = "#fff";

  document.body.appendChild(container);
  document.body.appendChild(uiContainer);
  document.body.appendChild(video);

  return {
    mapContainer: container,
    uiContainer
  };
}