Browse Source

添加飞行功能

lkk 1 year ago
parent
commit
bbab581834

+ 110 - 1
src/components/sceneAtttribute/camera/camera.scss

@@ -21,4 +21,113 @@
 
 
 .flyBtn {
 .flyBtn {
     font-size: 18px;
     font-size: 18px;
-}
+}
+.ivu-tabs{
+    color: #fff;
+}
+
+
+// 横向布局:class = "row-item"
+.row-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 0.11rem;
+    // font-size: 14px; // number-input的罪魁祸首
+    font-size: 14px;
+  
+    span,
+    div {
+      font-size: 14px;
+    }
+  
+    .check-color-pick {
+      width: 1.96rem;
+      height: 0.33rem;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+  
+      .color-pick-box {
+        margin-left: 0.1rem;
+      }
+    }
+  
+    .check-box {
+      width: 1.96rem;
+      height: 0.32rem;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+    }
+  
+    .slider-box {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      width: 12.76rem;
+      height: 0.32rem;
+      padding: 0 .1rem;
+      border-radius: 0.04rem;
+      background: rgba(255, 255, 255, 0.04);
+      // box-sizing: border-box;
+      border: 0.01rem solid rgba(255, 255, 255, 0.15);
+  
+      .slider-unit {
+        margin-left: 0.1rem;
+      }
+    }
+  
+    .radio-group {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      width: 1.96rem;
+      height: 0.32rem;
+    }
+  
+    .row-slider-num {
+      width: .3rem;
+      display: flex;
+      justify-content: space-between;
+    }
+  
+    .row-content {
+      width: 192px;
+      height: 32px;
+      // background-color: rgba(255, 255, 255, 0.12);
+
+      // .el-input__inner {
+      //   height: 32px;
+      //   line-height: 32px;
+      // }
+      .el-checkbox{
+        margin-right: 5px !important; 
+      }
+    }
+  }
+
+
+
+// 面板上图标的排列样式等
+.icon-list {
+    display: flex;
+    width: 1.96rem;
+    height: 2.32rem;
+    line-height: 2.32rem;
+    text-align: center;
+    background: rgba(255, 255, 255, 0.04);
+    border: 0.01rem solid rgba(255, 255, 255, 0.15);
+    border-radius: 0.04rem;
+  
+    .icon-span {
+      width: 33%;
+      display: inline-block;
+      text-align: center;
+      cursor: pointer;
+    }
+  
+    .selected-icon {
+      color: #3499e5;
+    }
+  }

+ 532 - 98
src/components/sceneAtttribute/camera/camera.vue

@@ -1,109 +1,237 @@
 <template>
 <template>
   <div class="sm-function-module-content" v-show="cameraShow">
   <div class="sm-function-module-content" v-show="cameraShow">
-    <label class="label-container">{{ Resource.flyRoute }}</label>
-    <input
-      class="sm-input"
-      type="file"
-      accept=".fpf"
-      id="flyFile"
-      style="width: 100%"
-    />
-    <div class="flybox">
-      <i
-        class="el-icon-video-play flyBtn"
-        @click="flyStart"
-        :title="Resource.startFly"
-      ></i>
-      <i
-        class="el-icon-video-pause flyBtn"
-        @click="flyPause"
-        :title="Resource.pauseFly"
-      ></i>
-      <i
-        class="el-icon-document-delete flyBtn"
-        @click="flyStop"
-        :title="Resource.stopFly"
-      ></i>
-    </div>
-    <div class="sm-function-module-sub-section">
-      <label class="label-container">{{ Resource.stopChoose }}</label>
-      <select class="sm-select" id="stopList" v-model="stopSelected"></select>
-    </div>
-    <div class="sm-function-module-sub-section">
-      <label class="label-container">{{ Resource.observe }}</label>
-      <div class="flexbox">
-        <el-button type="primary" size="mini" @click="onSpinClk">{{
-          Resource.rotatePoint
-        }}</el-button>
-        <el-button type="primary" size="mini" @click="onCancelSpinClk">
-          {{ Resource.cancelRotatePoint }}
-        </el-button>
-        <label>{{ Resource.pauseFly }}</label>
-        <input type="checkbox" v-model="stopFlyCircle" />
-        <label>{{ Resource.rotateCirculation }}</label>
-        <input type="checkbox" v-model="circulation" />
-        <!-- <div class="rotateBtn"> -->
-        <!-- </div> -->
-      </div>
-      <label class="label-container">{{ Resource.rotateSpeed }}</label>
-      <div class="sm-solider-input-box">
+    <Tabs value="">
+      <TabPane :label="Resource.flyRoute" name="fxxl">
+        <label class="label-container">{{ Resource.flyRoute }}</label>
         <input
         <input
-          class="min-solider"
-          min="0"
-          max="50"
-          step="0.1"
-          style="width: 63%"
-          type="range"
-          v-model="speed"
+          class="sm-input"
+          type="file"
+          accept=".fpf"
+          id="flyFile"
+          style="width: 100%"
         />
         />
-        <input
-          class="min-solider"
-          min="0"
-          max="50"
-          step="0.1"
-          style="width: 34%"
-          type="number"
-          v-model="speed"
-        />
-      </div>
-    </div>
-    <div class="sm-function-module-sub-section">
-      <label class="label-container">{{ Resource.underground }}</label>
-      <br />
-      <label class="sm-viewshed-label-right">{{
-        Resource.openUnderground
-      }}</label>
-      <input type="checkbox" v-model="underground" />
-      <div class="sm-function-module-sub-section">
-        <label class="label-container">{{
-          Resource.cameraMinimumZoomDistance
-        }}</label>
-        <input
-          class="sm-input-long"
-          min="0"
-          type="number"
-          v-model="cameraMinimumZoomDistance"
-        />
-      </div>
-      <div class="sm-function-module-sub-section">
-        <label class="label-container">{{
-          Resource.SurfaceTransparency
-        }}</label>
-        <input
-          class="sm-input-long"
-          min="0"
-          max="1.0"
-          step="0.01"
-          type="number"
-          v-model="SurfaceTransparency"
-        />
-      </div>
-    </div>
+        <div class="flybox">
+          <i
+            class="el-icon-video-play flyBtn"
+            @click="flyStart"
+            :title="Resource.startFly"
+          ></i>
+          <i
+            class="el-icon-video-pause flyBtn"
+            @click="flyPause"
+            :title="Resource.pauseFly"
+          ></i>
+          <i
+            class="el-icon-document-delete flyBtn"
+            @click="flyStop"
+            :title="Resource.stopFly"
+          ></i>
+        </div>
+        <div class="sm-function-module-sub-section">
+          <label class="label-container">{{ Resource.stopChoose }}</label>
+          <select
+            class="sm-select"
+            id="stopList"
+            v-model="stopSelected"
+          ></select>
+        </div>
+        <div class="sm-function-module-sub-section">
+          <label class="label-container">{{ Resource.observe }}</label>
+          <div class="flexbox">
+            <el-button type="primary" size="mini" @click="onSpinClk">{{
+              Resource.rotatePoint
+            }}</el-button>
+            <el-button type="primary" size="mini" @click="onCancelSpinClk">
+              {{ Resource.cancelRotatePoint }}
+            </el-button>
+            <label>{{ Resource.pauseFly }}</label>
+            <input type="checkbox" v-model="stopFlyCircle" />
+            <label>{{ Resource.rotateCirculation }}</label>
+            <input type="checkbox" v-model="circulation" />
+          </div>
+          <label class="label-container">{{ Resource.rotateSpeed }}</label>
+          <div class="sm-solider-input-box">
+            <input
+              class="min-solider"
+              min="0"
+              max="50"
+              step="0.1"
+              style="width: 63%"
+              type="range"
+              v-model="speed"
+            />
+            <input
+              class="min-solider"
+              min="0"
+              max="50"
+              step="0.1"
+              style="width: 34%"
+              type="number"
+              v-model="speed"
+            />
+          </div>
+        </div>
+        <div class="sm-function-module-sub-section">
+          <label class="label-container">{{ Resource.underground }}</label>
+          <br />
+          <label class="sm-viewshed-label-right">{{
+            Resource.openUnderground
+          }}</label>
+          <input type="checkbox" v-model="underground" />
+          <div class="sm-function-module-sub-section">
+            <label class="label-container">{{
+              Resource.cameraMinimumZoomDistance
+            }}</label>
+            <input
+              class="sm-input-long"
+              min="0"
+              type="number"
+              v-model="cameraMinimumZoomDistance"
+            />
+          </div>
+          <div class="sm-function-module-sub-section">
+            <label class="label-container">{{
+              Resource.SurfaceTransparency
+            }}</label>
+            <input
+              class="sm-input-long"
+              min="0"
+              max="1.0"
+              step="0.01"
+              type="number"
+              v-model="SurfaceTransparency"
+            />
+          </div>
+        </div>
+      </TabPane>
+      <TabPane :label="Resource.createFlyRoute" name="cjfxxl">
+        <div class="icon-list" style="width: 12.96rem">
+          <span
+            v-for="(item, index) in state.itemOptions"
+            :key="index"
+            class="icon-span"
+            :title="item.lable"
+            :class="item.isSelect ? 'selected-icon' : ''"
+            @click="changleIconItem(item)"
+          >
+            <i
+              class="iconfont iconSize"
+              :class="item.iconName"
+              style="margin-top: 0px"
+            ></i>
+          </span>
+        </div>
+        <div v-show="state.customRouteNames.length > 0">
+          <div class="row-item">
+            <span>已经添加站点</span>
+            <div class="row-content">
+              <!-- <el-select
+                v-model="state.selectedAddedStopIndex"
+                :options="state.routeStops"
+                label-field="stopName"
+                value-field="index"
+              /> -->
+              <el-select v-model="state.selectedAddedStopIndex" >
+                <el-option
+                  v-for="item in state.routeStops"
+                  :key="item.value"
+                  :label="item.stopName"
+                  :value="item.index"
+                >
+                </el-option>
+              </el-select>
+            </div>
+          </div>
+
+          <div class="row-item" style="margin-bottom: 0px">
+            <span></span>
+            <div class="row-content" style="display: flex">
+              <el-checkbox v-model="showRoute" /><span class="checkbox-lable"
+                >显示路线</span
+              >
+              <el-checkbox v-model="showStop" /><span class="checkbox-lable"
+                >显示站点</span
+              >
+            </div>
+          </div>
+
+          <div>
+            <label class="label-container">飞行速度</label>
+            <div class="sm-solider-input-box">
+              <input
+                class="min-solider"
+                min="0"
+                max="500"
+                step="1"
+                style="width: 63%"
+                type="range"
+                v-model="state.routeSpeed"
+              />
+              <span>{{state.routeSpeed }}</span>
+              <!-- <input
+                class="min-solider"
+                min="0"
+                max="50"
+                step="0.1"
+                style="width: 34%"
+                type="number"
+                v-model="state.routeSpeed"
+              /> -->
+            </div>
+          </div>
+
+          <div class="icon-list" style="width: 12.96rem">
+            <span
+              v-for="(item, index) in state.actionOptions"
+              :key="index"
+              class="icon-span"
+              :title="item.lable"
+              :class="item.isSelect ? 'selected-icon' : ''"
+              @click="changleIconItemAction(item)"
+            >
+              <i
+                class="iconfont iconSize"
+                :class="item.iconName"
+                style="margin-top: 0px"
+              ></i>
+            </span>
+          </div>
+
+          <div class="btn-row-item" style="margin-left: 0.94rem">
+            <el-button
+              type="info"
+              color="#3499E5"
+              text-color="#fff"
+              @click="downLoad"
+              style="margin-right: 0.1rem; margin-left: 0.03rem"
+              >下载</el-button
+            >
+            <el-button
+              class="btn-secondary"
+              @click="clearRoute"
+              color="rgba(255, 255, 255, 0.65)"
+              ghost
+              >清除</el-button
+            >
+          </div>
+        </div>
+        <rotate></rotate>
+      </TabPane>
+    </Tabs>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
-let flyManager, camera, flyCircleDrawHandler;
+let flyManager,
+  camera,
+  flyCircleDrawHandler,
+  createXml,
+  currentStops,
+  routeCollection;
+let flyLineXmls = [];
+import tool from "./tool";
+import createFlyLine_xml from "./fly-line-xml.js";
+import rotate from './rotate.vue';
 // ,
 // ,
 // let pointLightSourceDrawHandler,
 // let pointLightSourceDrawHandler,
 //   spotOrDirectionalLightSourceDrawHandler,
 //   spotOrDirectionalLightSourceDrawHandler,
@@ -113,8 +241,12 @@ let flyManager, camera, flyCircleDrawHandler;
 //   entityPointLightPairs = new Map(), // Entity和点光源对象的键值对
 //   entityPointLightPairs = new Map(), // Entity和点光源对象的键值对
 //   entitySpotLightPairs = new Map(), // Entity和聚光灯对象的键值对
 //   entitySpotLightPairs = new Map(), // Entity和聚光灯对象的键值对
 //   entityDirectionalLightPairs = new Map(); // Entity和平行光对象的键值对
 //   entityDirectionalLightPairs = new Map(); // Entity和平行光对象的键值对
+
 export default {
 export default {
   name: "sceneCamera",
   name: "sceneCamera",
+  components:{
+    rotate
+},
   data() {
   data() {
     return {
     return {
       sharedState: store.state,
       sharedState: store.state,
@@ -127,6 +259,84 @@ export default {
       SurfaceTransparency: 1,
       SurfaceTransparency: 1,
       cameraMinimumZoomDistance: -1000,
       cameraMinimumZoomDistance: -1000,
       isDestroyFlag: true,
       isDestroyFlag: true,
+
+      // 设置默认值数据
+      state: {
+        routeType: "customRoute", //自定义还得指定路线类型
+        fileSrc: "", //文件地址,不能同时使用fpfUrl
+        fpfUrl: null, //指定fpf路径
+        selectedStopIndex: 0, //选中当前站点
+        addCurrentStopIndex: 0, // 记录当前添加站点的索引并赋值给站点index,以便n-select
+        showRoute: false, //显示路线
+        showStop: false, //显示站点
+        currentStopNames: [], //当前路线的站点名称集合
+        currentStopNamesIndex: 0, // 记录当前路线的索引并赋值,以便n-select
+        //自定义
+        customRouteNames: [], //保存自定义路线名称
+        addCurrentRouteIndex: 0, // 当前路线索引,以便n-select
+        customRouteSelectedIndex: null, //自定义选中路线索引
+        routeStops: [], //自定义当前路线的站点集合
+        selectedAddedStopIndex: undefined, //自定义已加站点选中索引
+        //站点
+        setStopName: "Stop-1", //设置当前站点名称
+        setStopSpeed: 0, // 设置当前站点速度
+        stopPlayMode: "StopPause", //设置站点模式:默认停留
+        waitTime: 0, //停留时间
+        surroundDuration: 1, //环绕模式时间
+        //飞行路线设置
+        isAlongline: false, //获取或者设置该飞行路线是否是沿线飞行。
+        routeSpeed: 200, //飞行路线速度
+        isSaveAutoFlag: false,
+        //   allRoutes:[]
+        itemOptions: [
+          {
+            index: 1,
+            lable: "添加站点",
+            iconName: "el-icon-circle-plus-outline",
+            isSelect: false,
+          },
+          {
+            index: 2,
+            lable: "删除站点",
+            iconName: "el-icon-delete",
+            isSelect: false,
+          },
+          {
+            index: 3,
+            lable: "恢复",
+            iconName: "el-icon-refresh-right",
+            isSelect: false,
+          },
+          {
+            index: 4,
+            lable: "保存",
+            iconName: "el-icon-document",
+            isSelect: false,
+          },
+        ],
+        actionOptions: [
+          {
+            index: 1,
+            lable: "播放",
+            iconName: "el-icon-video-play",
+            isSelect: false,
+          },
+          {
+            index: 2,
+            lable: "暂停",
+            iconName: "el-icon-video-pause",
+            isSelect: false,
+          },
+          {
+            index: 3,
+            lable: "停止",
+            iconName: "el-icon-help",
+            isSelect: false,
+          },
+        ],
+      },
+      showRoute: false, //显示路线
+      showStop: false, //显示站点
     };
     };
   },
   },
 
 
@@ -155,6 +365,8 @@ export default {
     if (this.SceneAtttributeShow && this.basicOptions) {
     if (this.SceneAtttributeShow && this.basicOptions) {
       this.init();
       this.init();
     }
     }
+    //初始化站点文件保存
+    this.initFlyManager();
   },
   },
   methods: {
   methods: {
     //子组件部分
     //子组件部分
@@ -258,6 +470,220 @@ export default {
       viewer.entities.removeById("fly-circle-point");
       viewer.entities.removeById("fly-circle-point");
       this.flyCirclePoint = null;
       this.flyCirclePoint = null;
     },
     },
+
+    // 功能切换
+    changleIconItem(item) {
+      this.state.itemOptions.map((itemObj) => {
+        if (itemObj.index == item.index) {
+          itemObj.isSelect = true;
+        } else {
+          itemObj.isSelect = false;
+        }
+      });
+
+      switch (item.index) {
+        case 1: {
+          this.addStop();
+          break;
+        }
+        case 2: {
+          this.deleteStop();
+          break;
+        }
+        case 3: {
+          this.restStops();
+          break;
+        }
+        case 4: {
+          this.saveStop();
+          break;
+        }
+        default:
+          break;
+      }
+    },
+
+    // 添加站点
+    addStop() {
+      if (flyManager) {
+        flyManager.stop();
+      }
+      let point = viewer.camera.position;
+      let position = tool.CartesiantoDegrees(point);
+      let stop = {
+        stopName: this.state.setStopName,
+        index: this.state.addCurrentStopIndex,
+        point: position,
+        heading: viewer.camera.heading,
+        tilt: viewer.camera.pitch,
+        speed: this.state.setStopSpeed,
+        stopPlayMode: this.state.stopPlayMode,
+        surroundDuration: this.state.surroundDuration,
+        waitTime: this.state.waitTime,
+      };
+      this.state.routeStops.push(stop);
+      if (this.state.isSaveAutoFlag) {
+        this.saveStop(); // 一旦添加站点,立即保存
+      }
+      let routeLen = this.state.routeStops.length;
+      if (routeLen > 0)
+        this.state.addCurrentStopIndex =
+          this.state.routeStops[routeLen - 1].index + 1; // 保证新增的站点index始终比前一位大1
+      this.$message.success(`${"站点添加成功"}: ${this.state.setStopName}`);
+
+      if (this.state.routeStops.length > 0) {
+        let len = this.state.routeStops.length;
+        let lastStopName = this.state.routeStops[len - 1].stopName;
+        let index = lastStopName.split("-")[1] || 1;
+        let name = "Stop-" + (Number(index) + 1);
+        this.state.setStopName = name;
+      }
+      this.state.selectedAddedStopIndex =
+        this.state.routeStops[this.state.routeStops.length - 1].index;
+    },
+
+    // 清除选中站点
+    deleteStop() {
+      let delIndex = this.state.routeStops.findIndex(
+        (e) => e.index == this.state.selectedAddedStopIndex
+      );
+      this.state.routeStops.splice(delIndex, 1);
+      if (this.state.routeStops.length > 1 && this.state.isSaveAutoFlag) {
+        this.saveStop(); // 一旦删除站点,实时保存
+      }
+      if (this.state.routeStops.length > 0) {
+        this.state.selectedAddedStopIndex =
+          this.state.routeStops[this.state.routeStops.length - 1].index;
+        return;
+      }
+      this.state.selectedAddedStopIndex = undefined;
+      this.state.setStopName = "Stop-1";
+    },
+
+    // 重置当前路线
+    restStops() {
+      let route = flyManager.currentRoute;
+      if (route) {
+        route.isLineVisible = false;
+        route.isStopVisible = false;
+      }
+      this.state.setStopName = "Stop-1";
+      this.state.routeStops = [];
+      this.state.addCurrentStopIndex = 0;
+      this.state.selectedAddedStopIndex = 0;
+      // this.state.routeStops.length = 0;
+      // this.state.setStopSpeed = 0;
+      // this.state.stopPlayMode = "StopPause";
+      // this.state.waitTime = 0;
+      // this.state.surroundDuration = 1;
+    },
+
+    // 保存站点
+    saveStop() {
+      console.log(5, this.state.routeStops);
+      if (this.state.routeStops.length < 2) {
+        if (this.state.customRouteNames.length == 0) {
+          this.$message.waring("至少两个节点才能保存");
+          console.log(1111);
+        }
+        return;
+      }
+
+      // 飞行路线配置
+      let route = {
+        routeName: "飞行路线-1",
+        index: this.state.addCurrentRouteIndex,
+        speed: this.state.routeSpeed,
+        isAlongLine: "False",
+        routeStops: this.state.routeStops,
+      };
+      let xml = createXml.createXMLflyLine(route);
+      flyLineXmls[0] = xml;
+      this.state.isSaveAutoFlag = true; //一旦点击保存,开启实时自动保存
+      // 保证只有一条飞行路线
+      if (this.state.customRouteNames.length === 0) {
+        this.state.customRouteNames.push({
+          label: route.routeName,
+          value: route.index,
+        });
+      }
+      this.updateRouteCollection();
+      this.state.addCurrentRouteIndex++;
+      if (this.state.customRouteSelectedIndex === null)
+        this.state.customRouteSelectedIndex = 0;
+    },
+
+    // 更新飞行路径
+    updateRouteCollection() {
+      flyManager && flyManager.stop();
+      let route = flyManager.currentRoute;
+      if (route) route.clear(); //清除之前的
+      routeCollection = new Cesium.RouteCollection(viewer.entities); //飞行路线底层默认第一条路线,所以重新new
+      routeCollection.fromXML(flyLineXmls[0]); // 默认飞行路径只有一条
+      this.readyPromise();
+    },
+    // 异步飞行管理准备就绪函数
+    readyPromise() {
+      routeCollection.readyPromise.then(() => {
+        flyManager.routes = routeCollection;
+        let route = flyManager.currentRoute;
+        route.isLineVisible = this.state.showRoute;
+        route.isStopVisible = this.state.showStop;
+        this.updateCurrentStops();
+      });
+    },
+    // 更新当前路线站点
+    updateCurrentStops() {
+      this.state.currentStopNames.length = 0;
+      currentStops = flyManager.getAllRouteStops();
+      this.state.currentStopNamesIndex = 0;
+      for (let i = 0, j = currentStops.length; i < j; i++) {
+        let stopName = currentStops[i].stopName || "Stop" + (i + 1);
+        this.state.currentStopNames.push({
+          label: stopName,
+          value: this.state.currentStopNamesIndex,
+        });
+        this.state.currentStopNamesIndex++;
+      }
+    },
+
+    //初始化飞行管理
+    initFlyManager() {
+      routeCollection = new Cesium.RouteCollection(viewer.entities);
+      flyManager = new Cesium.FlyManager({
+        scene: viewer.scene,
+        routes: routeCollection,
+      });
+      createXml = new createFlyLine_xml();
+    },
+
+    // 操作切换
+ changleIconItemAction(item) {
+  this.state.actionOptions.map((itemObj) => {
+    if (itemObj.index == item.index) {
+      itemObj.isSelect = true;
+    } else {
+      itemObj.isSelect = false;
+    }
+  });
+
+  switch (item.index) {
+    case 1: {
+      this.flyStart();//开始
+      break;
+    }
+    case 2: {
+      this.flyPause();//暂停
+      break;
+    }
+    case 3: {
+      this.flyStop();//停止
+      break;
+    }
+    default:
+      break;
+  }
+}
   },
   },
 
 
   watch: {
   watch: {
@@ -307,6 +733,14 @@ export default {
     SurfaceTransparency(val) {
     SurfaceTransparency(val) {
       viewer.scene.globe.globeAlpha = parseFloat(val);
       viewer.scene.globe.globeAlpha = parseFloat(val);
     },
     },
+    showRoute(val) {
+      let route = flyManager.currentRoute;
+      if (route) route.isLineVisible = val;
+    },
+    showStop(val) {
+      let route = flyManager.currentRoute;
+      if (route) route.isStopVisible = val;
+    },
   },
   },
 };
 };
 </script>
 </script>

+ 168 - 0
src/components/sceneAtttribute/camera/fly-line-xml.js

@@ -0,0 +1,168 @@
+/*
+ * @Author: juzi.liu 
+ * @Date: 2021-04-09 09:22:43 
+ * @Last Modified by: juzi.liu
+ * @Last Modified time: 2021-04-09 09:45:51
+ */
+
+class flylinexml {
+    constructor() {
+    }
+    /**
+     * 添加飞行的路线节点
+     * @param {*} route ={idnex:0,speed:0, ~~~}
+     */
+    createXMLflyLine(route) {  
+        let parser = new DOMParser()
+        window.parser = parser
+        let xmlDoc = parser.parseFromString("<cusxmlRoute></cusxmlRoute>", "text/xml")
+        window.xmlDoc = xmlDoc
+        let sceneRouteNode = this.createSceneRouteNode(xmlDoc)
+
+        //创建路线
+        let routeNode = this.createRouteNode(xmlDoc, route)
+        let routeStyleNode = this.createRouteStyleNode(xmlDoc)
+        routeNode.appendChild(routeStyleNode)
+        //开始添加站点
+        let _that = this;
+        let stops = route.routeStops;
+        if (stops.length < 2) {
+            console.warn("节点数小于2")
+            return
+        }
+        for (let index = 0; index < stops.length ; index++) {
+            let stop = stops[index];
+            let camerainfo = {
+                longitude: stop.point[0],
+                latitude: stop.point[1],
+                altitude: stop.point[2],
+                heading: stop.heading * (180 / Math.PI),
+                tilt: stop.tilt * (180 / Math.PI)+ 90
+            }
+            
+            let stopnode = _that.createStopNode(xmlDoc, camerainfo,stop, index)
+            routeNode.appendChild(stopnode)
+
+        }
+        sceneRouteNode.appendChild(routeNode)
+        xmlDoc.children[0].appendChild(sceneRouteNode)
+        let flylinexml = xmlDoc.children[0].innerHTML
+        return flylinexml
+
+    }
+    createSceneRouteNode(rootXmlDoc) {
+        let sceneRouteNode = rootXmlDoc.createElement("SceneRoute")
+        sceneRouteNode.setAttribute("xmlns", "http://www.supermap.com.cn/ugc60")
+        return sceneRouteNode
+    }
+
+    createRouteNode(rootXmlDoc, route) {
+        let routeNode = rootXmlDoc.createElement("route")
+        let attrs = {
+            name: route.routeName || "飞行路线" ,
+            speed: route.speed || "200",
+            lineType: "0",
+            showroutestop: "False",
+            showrouteline: "False",
+            altitudefree: "False",
+            headingfree: "False",
+            tiltfree: "False",
+            flycircle: "False",
+            alongline: route.isAlongLine || "False"
+        }
+        for (const key in attrs) {
+            routeNode.setAttribute(key, attrs[key])
+        }
+        return routeNode
+    }
+    createRouteStyleNode(rootXmlDoc) {
+
+        let routeStyleNode = rootXmlDoc.createElement("style")
+        let geostyle3d = rootXmlDoc.createElement("geostyle3d")
+        let linecolor = rootXmlDoc.createElement("linecolor")
+        linecolor.textContent = "RGBA(147,112,219,255)"
+
+        let linewidth = rootXmlDoc.createElement("linewidth")
+        linewidth.textContent = 2
+        let altitudeMode = rootXmlDoc.createElement("altitudeMode")
+        altitudeMode.textContent = "Absolute"
+        let bottomAltitude = rootXmlDoc.createElement("bottomAltitude")
+        bottomAltitude.textContent = 0.00
+        geostyle3d.appendChild(linecolor)
+        geostyle3d.appendChild(linewidth)
+        geostyle3d.appendChild(altitudeMode)
+        geostyle3d.appendChild(bottomAltitude)
+
+        routeStyleNode.appendChild(geostyle3d)
+        return routeStyleNode
+
+    }
+    createStopNode(rootXmlDoc, camerainfo,stop,index) {
+        let stopNode = rootXmlDoc.createElement("routestop");
+        let name = stop.stopName || 'Stop' + ( index + 1)
+        let attris = {
+            name: name,
+            speed: stop.speed,
+            excluded: "False",
+            viewType: "camera"
+        }
+        for (const key in attris) {
+            stopNode.setAttribute(key, attris[key])
+        }
+        let cameranode = this.createStopCameraNode(rootXmlDoc, camerainfo)
+        stopNode.appendChild(cameranode)
+        let stylenode = this.createStopStyleNode(rootXmlDoc)
+        stopNode.appendChild(stylenode)
+
+        let settingnode = this.createStopSettingNode(rootXmlDoc,stop)
+        stopNode.appendChild(settingnode)
+        return stopNode
+
+    }
+    createStopCameraNode(rootXmlDoc, cameraObj) {
+        let stopCameraNode = rootXmlDoc.createElement("camera")
+        for (const key in cameraObj) {
+            let node = rootXmlDoc.createElement(key)
+            node.textContent = cameraObj[key]
+            stopCameraNode.appendChild(node)
+        }
+        return stopCameraNode
+    }
+    createStopStyleNode(rootXmlDoc) {
+        let nodeObj = {
+            icon: "",
+            markersize: 4.8,
+            markericonscale: 1,
+            markercolor: "RGBA(255, 255, 255, 255)"
+        }
+        let stopStyleNode = rootXmlDoc.createElement("style")
+        let geostyle3d = rootXmlDoc.createElement("geostyle3d")
+
+        for (const key in nodeObj) {
+            let node = rootXmlDoc.createElement(key)
+            node.textContent = nodeObj[key]
+            geostyle3d.appendChild(node)
+        }
+        stopStyleNode.appendChild(geostyle3d)
+        return stopStyleNode
+
+    }
+    createStopSettingNode(rootXmlDoc,stop) {
+        let stopSettingNode = rootXmlDoc.createElement("setting");
+        let nodesObj = {
+            turnTime: stop.surroundDuration || 1.5,
+            turnSlowly:  "False",
+            stopPlayMode:stop.stopPlayMode|| "StopPause",
+            autoPlay: "False",
+            pauseTime:stop.waitTime || 0,
+            angularSpeed: 1
+        }
+        for (const key in nodesObj) {
+            let node = rootXmlDoc.createElement(key)
+            node.textContent = nodesObj[key]
+            stopSettingNode.appendChild(node)
+        }
+        return stopSettingNode
+    }
+}
+export default flylinexml

+ 289 - 0
src/components/sceneAtttribute/camera/rotate.vue

@@ -0,0 +1,289 @@
+<template>
+  <div>
+    <div>
+      <div class="row-item">
+        <span>绕点旋转</span>
+        <!-- <div style="width: 12.96rem">
+          <n-switch v-model="state.rotateShow" size="small" />
+        </div> -->
+        <el-switch
+          v-model="state.rotateShow"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        >
+        </el-switch>
+      </div>
+
+      <div v-show="state.rotateShow">
+        <div class="icon-list" style="width: 12.96rem">
+          <span
+            v-for="(item, index) in state.itemOptions"
+            :key="index"
+            class="icon-span"
+            :title="item.lable"
+            :class="item.isSelect ? 'selected-icon' : ''"
+            @click="changleIconItem(item)"
+          >
+            <i
+              class="iconfont iconSize"
+              :class="item.iconName"
+              style="margin-top: 0px"
+            ></i>
+          </span>
+        </div>
+
+        <div class="row-item">
+          <span>旋转速度</span>
+          <div >
+            <input
+                class="min-solider"
+                min="0"
+                max="20"
+                step="0.1"
+                type="range"
+                v-model="state.speedRatio"
+              />
+              <!-- <span>{{state.speedRatio }}</span> -->
+              <input
+                class="min-solider"
+                min="0"
+                max="20"
+                step="0.1"
+                type="number"
+                v-model="state.speedRatio"
+              />
+            <!-- <n-slider
+              style="width: 1.5rem"
+              v-model="state.speedRatio"
+              :step="0.1"
+              :min="0"
+              :max="20"
+            />
+            <n-input-number
+              v-model="state.speedRatio"
+              class="slider-input-number"
+              :update-value-on-input="false"
+              :bordered="false"
+              :show-button="false"
+              :min="0"
+              :max="20"
+              placeholder=""
+              size="small"
+            /> -->
+          </div>
+        </div>
+
+        <div class="row-item" style="margin-bottom: -0.1rem">
+          <span></span>
+          <div class="row-content">
+            <el-checkbox v-model="flyCircleLoop">循环旋转</el-checkbox></el-checkbox>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+let scene, windowPosition, scratchTiltFrame, scratchOldTransform, handlerPoint;
+// let windowPosition = new Cesium.Cartesian2();
+// let scratchTiltFrame = new Cesium.Matrix4();
+// let scratchOldTransform = new Cesium.Matrix4();
+// let handlerPoint = new Cesium.DrawHandler(viewer, Cesium.DrawMode.Point);
+let listener;
+export default {
+  name: "rotate",
+  data() {
+    return {
+      speedRatio: 1, // 旋转速度
+      flyCircleLoop: true, // 是否循环
+      rotateShow: false, // 是否绕点旋转
+      itemOptions: null, // 功能选项
+      position: [], // 绕点选择中心点
+
+      state: {
+        speedRatio: 1,
+        flyCircleLoop: true,
+        rotateShow: false,
+        position: null,
+        itemOptions: [
+          {
+            index: 1,
+            lable: "添加",
+            iconName: "el-icon-circle-plus-outline",
+            isSelect: false,
+          },
+          {
+            index: 2,
+            lable: "播放",
+            iconName: "el-icon-video-play",
+            isSelect: false,
+          },
+          {
+            index: 3,
+            lable: "暂停",
+            iconName: "el-icon-video-pause",
+            isSelect: false,
+          },
+          {
+            index: 4,
+            lable: "刷新",
+            iconName: "el-icon-refresh-right",
+            isSelect: false,
+          },
+        ],
+      },
+    };
+  },
+
+  computed: {},
+  beforeDestroy() {
+    this.clearFlyCircle();
+    handlerPoint.clear();
+  },
+  mounted() {
+    this.$nextTick(() => {
+      console.log(window.viewer, 99999);
+      windowPosition = new Cesium.Cartesian2();
+      scratchTiltFrame = new Cesium.Matrix4();
+      scratchOldTransform = new Cesium.Matrix4();
+      handlerPoint = new Cesium.DrawHandler(
+        window.viewer,
+        Cesium.DrawMode.Point
+      );
+    });
+
+    this.init();
+  },
+  methods: {
+    //初始化
+    init() {
+      if (!window.viewer) return;
+      viewer.scene.camera.flyCircleLoop = this.state.flyCircleLoop;
+      viewer.scene.camera.speedRatio = this.state.speedRatio;
+    },
+    // 功能切换
+    changleIconItem(item) {
+      this.state.itemOptions.map((itemObj) => {
+        if (itemObj.index == item.index) {
+          itemObj.isSelect = true;
+        } else {
+          itemObj.isSelect = false;
+        }
+      });
+
+      switch (item.index) {
+        case 1: {
+          this.addCenter();
+          break;
+        }
+        case 2: {
+          this.startFlyCircle();
+          break;
+        }
+        case 3: {
+          this.clearFlyCircle();
+          break;
+        }
+        case 4: {
+          this.clearFlyCircle();
+          reset();
+          break;
+        }
+        default:
+          break;
+      }
+    },
+    // 绑定监听事件
+    addCenter() {
+      viewer.enableCursorStyle = false;
+      viewer._element.style.cursor = "";
+      document.body.classList.add("measureCur");
+      console.log(viewer,"vhjfhjfhj");
+      console.log(viewer.eventManager,"eventManager");
+      window.viewer.eventManager.addEventListener("CLICK", left_click, true);
+      handlerPoint.activate();
+    },
+
+    // 添加点
+    left_click(e) {
+      this.state.position = e.message.position;
+    },
+
+    // 开始旋转
+    startFlyCircle() {
+      let center = viewer.scene.pickPosition(this.state.position);
+      if (Cesium.defined(center)) viewer.scene.camera.flyCircle(center); // 相机绕中心点旋转
+      document.body.classList.remove("measureCur");
+      window.viewer.eventManager.removeEventListener("CLICK", left_click);
+      handlerPoint.deactivate();
+    },
+
+    // 清除绕点旋转
+    clearFlyCircle() {
+      viewer.scene.camera.stopFlyCircle();
+      document.body.classList.remove("measureCur");
+      window.viewer.eventManager.removeEventListener("CLICK", left_click);
+      handlerPoint.clear();
+    },
+
+    // 复位-指北旋转
+    reset() {
+      windowPosition.x = scene.canvas.clientWidth / 2;
+      windowPosition.y = scene.canvas.clientHeight / 2;
+      let viewCenter = viewer.scene.pickPosition(windowPosition);
+      if (!viewCenter || listener !== undefined) {
+        return;
+      }
+      let tiltFrame = Cesium.Transforms.eastNorthUpToFixedFrame(
+        viewCenter,
+        scene.globe.ellipsoid,
+        scratchTiltFrame
+      );
+      // scene.camera.lookAtTransform(tiltFrame);
+      let rotateAngle;
+      listener = setInterval(function () {
+        let currentHeading = Cesium.Math.toDegrees(scene.camera.heading);
+        let oldTransform = Cesium.Matrix4.clone(
+          scene.camera.transform,
+          scratchOldTransform
+        );
+        scene.camera.lookAtTransform(tiltFrame);
+        if (currentHeading > 180 && currentHeading < 360) {
+          rotateAngle = Cesium.Math.toRadians(360 - currentHeading) / 2;
+          scene.camera.rotateLeft(rotateAngle); //顺时针旋转
+          scene.camera.lookAtTransform(oldTransform);
+          if (360 - currentHeading < 1) {
+            //罗盘指北移除监听
+            clearInterval(listener);
+            listener = undefined;
+          }
+        } else {
+          rotateAngle = Cesium.Math.toRadians(currentHeading) / 2;
+          scene.camera.rotateRight(rotateAngle); //逆时针旋转
+          scene.camera.lookAtTransform(oldTransform);
+          if (1 - currentHeading > 0) {
+            //罗盘指北移除监听
+            clearInterval(listener);
+            listener = undefined;
+          }
+        }
+      }, 100);
+    },
+  },
+
+  watch: {
+    speedRatio(val) {
+      viewer.scene.camera.speedRatio = Cesium.defaultValue(val, 0);
+    },
+    flyCircleLoop(val) {
+      viewer.scene.camera.flyCircleLoop = val;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "camera";
+
+</style>

+ 141 - 0
src/components/sceneAtttribute/camera/tool.js

@@ -0,0 +1,141 @@
+
+
+const Cesium = window.Cesium;
+
+// 获取两点的角度和弧度
+function getAngleAndRadian(pointA, pointB) {
+  // 建立以点A为原点,X轴为east,Y轴为north,Z轴朝上的坐标系
+  const transform = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
+  // 向量AB
+  const positionvector = Cesium.Cartesian3.subtract(pointB, pointA, new Cesium.Cartesian3());
+  // 因transform是将A为原点的eastNorthUp坐标系中的点转换到世界坐标系的矩阵
+  // AB为世界坐标中的向量
+  // 因此将AB向量转换为A原点坐标系中的向量,需乘以transform的逆矩阵。
+  const vector = Cesium.Matrix4.multiplyByPointAsVector(Cesium.Matrix4.inverse(transform, new Cesium.Matrix4()), positionvector, new Cesium.Cartesian3());
+  //归一化
+  const direction = Cesium.Cartesian3.normalize(vector, new Cesium.Cartesian3());
+  // heading
+  const heading1 = Math.atan2(direction.y, direction.x) - Cesium.Math.PI_OVER_TWO;
+
+  let radian = Cesium.Math.TWO_PI - Cesium.Math.zeroToTwoPi(heading1);
+  var angle = radian * (180 / Math.PI);
+  if (angle < 0) {
+    angle = angle + 360;
+  }
+  return { angle, radian };
+}
+
+// 笛卡尔转经纬度
+const CartesiantoDegrees = (Cartesians) => {
+  let array = [].concat(Cartesians);
+  let positions = [];
+  for (let i = 0, len = array.length; i < len; i++) {
+    let cartographic = Cesium.Cartographic.fromCartesian(array[i]);
+    let longitude = Number(Cesium.Math.toDegrees(cartographic.longitude));
+    let latitude = Number(Cesium.Math.toDegrees(cartographic.latitude));
+    let h = Number(cartographic.height);
+    if (positions.indexOf(longitude) == -1 && positions.indexOf(latitude) == -1) {
+      positions.push(longitude);
+      positions.push(latitude);
+      positions.push(h);
+    }
+  }
+  return positions
+};
+
+// 笛卡尔转经纬度 (TS)
+const CartesiantoDegreesTestTS = (Cartesians) => {
+  let array = [].concat(Cartesians);
+  let positions = [];
+  for (let i = 0, len = array.length; i < len; i++) {
+    let cartographic = Cesium.Cartographic.fromCartesian(array[i]);
+    let longitude = Cesium.Math.toDegrees(cartographic.longitude);
+    let latitude = Cesium.Math.toDegrees(cartographic.latitude);
+    let h = cartographic.height;
+    if (positions.indexOf(longitude) == -1 && positions.indexOf(latitude) == -1) {
+      positions.push(longitude);
+      positions.push(latitude);
+      positions.push(h);
+    }
+  }
+  return positions
+};
+
+// 笛卡尔转经纬度(每个点是独立的对象)
+const CartesiantoDegreesObjs = (Cartesians) => {
+  let array = [].concat(Cartesians);
+  let positions = [];
+  for (let i = 0, len = array.length; i < len; i++) {
+    let cartographic = Cesium.Cartographic.fromCartesian(array[i]);
+    let obj = {
+      longitude: Cesium.Math.toDegrees(cartographic.longitude),
+      latitude: Cesium.Math.toDegrees(cartographic.latitude),
+      height: cartographic.height
+    };
+    positions.push(obj);
+  }
+  return positions
+}
+
+// 获取渐变色函数
+function gradientColors(start, end, steps, gamma) {
+  var i, j, ms, me, output = [], so = [];
+  gamma = gamma || 1;
+  var normalize = function (channel) {
+    return Math.pow(channel / 255, gamma);
+  };
+  start = parseColor(start).map(normalize);
+  end = parseColor(end).map(normalize);
+  for (i = 0; i < steps; i++) {
+    ms = i / (steps - 1);
+    me = 1 - ms;
+    for (j = 0; j < 3; j++) {
+      so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16));
+    }
+    output.push('#' + so.join(''));
+  }
+  return output;
+  function parseColor(hexStr) {
+    return hexStr.length === 4 ? hexStr.substr(1).split('').map(function (s) { return 0x11 * parseInt(s, 16); }) : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) { return parseInt(s, 16); })
+  };
+  // zero-pad 1 digit to 2
+  function pad(s) {
+    return (s.length === 1) ? '0' + s : s;
+  }
+};
+
+function getPitch(pointA, pointB) {
+  let transfrom = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
+  const vector = Cesium.Cartesian3.subtract(pointB, pointA, new Cesium.Cartesian3());
+  let direction = Cesium.Matrix4.multiplyByPointAsVector(Cesium.Matrix4.inverse(transfrom, transfrom), vector, vector);
+  Cesium.Cartesian3.normalize(direction, direction);
+  //因为direction已归一化,斜边长度等于1,所以余弦函数等于direction.z
+  let radian = Cesium.Math.PI_OVER_TWO - Cesium.Math.acosClamped(direction.z);
+  var angle = radian * (180 / Math.PI);
+  if (angle < 0) {
+    angle = angle + 360;
+  }
+  return { angle, radian };
+}
+
+// // 设置所有color-pick的初始样式-渐变条纹
+// function setAllColorPickBg(){
+//   document.querySelectorAll('.n-color-picker-trigger__fill div:nth-child(2)').forEach((el:any)=>{
+//     el.style.backgroundImage = "linear-gradient(to right, rgb(255, 191, 112), rgb(52, 153, 229), rgb(1, 212, 219))";
+//   })
+// }
+
+// // 移除指定color-pick的条纹样式
+// function clearColorPickBgByIndex(index:number){
+//   let el:any = document.querySelectorAll('.n-color-picker-trigger__fill div:nth-child(2)')[index];
+//   if(el) el.style.backgroundImage = "none";
+// }
+
+export default {
+  getAngleAndRadian,
+  CartesiantoDegrees,
+  CartesiantoDegreesTestTS,
+  CartesiantoDegreesObjs,
+  gradientColors,
+  getPitch,
+}

+ 5 - 0
src/resource/resourceCN.js

@@ -143,6 +143,11 @@
         threshold: '亮度阈值',
         threshold: '亮度阈值',
         bloomIntensity: '泛光强度',
         bloomIntensity: '泛光强度',
         flyRoute: '飞行线路',
         flyRoute: '飞行线路',
+        createFlyRoute: '创建飞行路线',
+        addedStops: '已添站点',
+        downLoad: '下载',
+        addStopSuccess: '添加站点成功',
+        atLeastTwoStop: '至少需要两个节点才能保存',
         fly: '飞行',
         fly: '飞行',
         startFly: '开始',
         startFly: '开始',
         pauseFly: '暂停',
         pauseFly: '暂停',