模型軌跡走動功能 Widget 教學


[info] 小提示:

SandBox連結:https://codesandbox.io/s/tu-tai-fan-li-nqzp77?file=/index.html


[info] 小提示:

構成本 Widget 使用之 Function:
ov.TerrainView.addCustomLayer
ov.TerrainView.removeAllEntity
ov.TerrainView.removeLayer
ov.CustomLayer.addGLTFEntity
ov.CustomGLTFEntity.play
ov.CustomGLTFEntity.stopAll
ov.CustomGLTFEntity.update
ov.Widget.Timeline
ov.Widget.TimelinePlayer


初始化 Widget

先撰寫一個最基本的圖臺。 Widget初始化須依賴核心生成的div,所以需要寫在地形開啟後的CallBack中。

這裡先撰寫一個最基本的圖台(div)。

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <meta charset="utf-8" />
    <title></title>
    <link rel="stylesheet" type="text/css" href="index.css" />
    <link rel="stylesheet" type="text/css" href="https://icons.getbootstrap.com/assets/font/bootstrap-icons.min.css" />
    <link rel="stylesheet" type="text/css" href="https://sample.pilotgaea.com.tw/demo/index/src/PGWebJS/13.0/css/PGWeb3D.css" />
    <link rel="stylesheet" type="text/css" href="https://sample.pilotgaea.com.tw/demo/index/css/main.css" />
    <script src="https://sample.pilotgaea.com.tw/demo/index/src/PGWebJS/13.0/PGWeb3D.min.js"></script>
    <script src="https://sample.pilotgaea.com.tw/demo/index/src/PGWebJS/13.0/PGWeb3DMilitary.min.js"></script>
    <script src="index.js"></script>
  </head>
  <body onload="WebInit()">
    <div class="map-form" id="MyMap"></div>
    <div class="box-col-left">
      <div class="box-tool">
        <div class="header">
          <div class="box-col-1">功能範例</div>
          <div class="box-col-3">
            <a href="javascript:void(0)">
              <i class="bi bi-caret-down-fill"></i>
            </a>
          </div>
        </div>
        <div class="content">
          <table>
            <tr>
              <td class="col-l" rowspan="2">定位</td>
              <td class="col-r">
                <span>經度:</span>
                <input type="text" class="w-80p" id="txtLon" placeholder="請填寫經度" />
                <span>緯度:</span>
                <input type="text" class="w-80p" id="txtLat" placeholder="請填寫緯度" />
              </td>
            </tr>
            <tr>
              <td class="col-r">
                <input type="button" class="btn btn-locate" value="坐標定位" />
                <input type="button" class="btn btn-gps btn-green" value="GPS 定位" />
              </td>
            </tr>
            <tr>
              <td class="col-l">軌跡</td>
              <td class="col-r">
                <input type="button" class="btn btn-tarck" value="開啟軌跡列表" />
              </td>
            </tr>
          </table>
        </div>
      </div>
      <div class="box-track">
        <div class="header">
          <div class="box-col-1">軌跡列表</div>
          <div class="box-col-2">
            <input type="button" class="btn" name="add" value="新增" />
            <input type="button" class="btn btn-green" name="play" value="播放" />
            <input type="button" class="btn btn-green hide" name="pause" value="暫停" />
          </div>
          <div class="box-col-3">
            <a href="javascript:void(0)" class="btnSwitch2">
              <i class="bi bi-caret-down-fill"></i>
            </a>
            <a href="javascript:void(0)" class="btnClose2">
              <i class="bi bi-x-circle"></i>
            </a>
          </div>
        </div>
        <div class="content">
          <table class="tb1">
            <thead>
              <tr>
                <th></th>
                <th>經度</th>
                <th>緯度</th>
                <th>動作</th>
                <th>旋轉</th>
                <th>更新</th>
                <th>刪除</th>
              </tr>
            </thead>
          </table>
          <table class="tb2">
            <tbody></tbody>
          </table>
        </div>
      </div>
    </div>
  </body>
</html>

CSS 樣式表,請放到 <head> 裡 。

index.css:

body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  font-size: 1rem;
  background-color: #072635;
  margin: 0px;
  padding: 0px;
}
.map-form {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
.ov-widget-basemap + .ov-widget-corner {
  overflow-y: hidden;
}
.box-col-left {
  position: absolute;
  top: 5px;
  left: 5px;
}
.box-tool,
.box-track {
  z-index: 1;
  position: relative;
  background-color: #ffffff99;
  padding: 0.375rem 0.75rem;
  border-radius: 10px;
  border: 1px solid #000000;
  display: table;
}
.box-tool.open {
  padding: 0.375rem;
}
.open .header {
  text-align: center;
  padding-bottom: 5px;
  border-bottom: 1px solid;
}
.box-tool .header a,
.box-track .header a {
  color: #000;
  text-decoration: none;
}
.box-tool .content {
  display: none;
}
.open .content {
  padding-top: 5px;
  display: block;
}
.txt-coord {
  width: 80px;
  border-radius: 5px;
  padding: 0.375rem;
}
.w-80p {
  width: 80px;
}
.text-centet {
  text-align: center !important;
}
.btn {
  display: inline-block;
  text-align: center;
  text-decoration: none;
  border: 1px solid #0d6efd;
  border-radius: 0.375rem;
  color: #ffffff;
  background-color: #0d6efd;
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
    border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
  padding: 0.375rem 0.75rem;
  cursor: pointer;
}
.btn.hide {
  display: none;
}
.btn-red {
  background-color: #dc3545;
  border-color: #dc3545;
}
.btn-green {
  background-color: #28a745;
  border-color: #28a745;
}
input[type="range"] {
  width: 5rem;
}
td.col-l {
  padding-right: 5px;
  border-right: 1px solid;
}
td.col-r {
  padding-left: 5px;
}
.box-track {
  display: none;
  top: 5px;
  max-width: 620px;
}
.box-track.show {
  display: inline-block;
}
.header div {
  display: inline-block;
  cursor: context-menu;
}
.box-track .content,
.box-track .header .box-col-2 {
  display: none;
}
.header .box-col-1 {
  margin-right: 4px;
}
.box-track.open .header {
  text-align: unset;
}
.box-track.open .header .box-col-2 {
  display: inline-block;
}
.open .header .box-col-3 {
  position: absolute;
  top: 14px;
  right: 15px;
}
.box-track.open .content {
  display: block;
}

.box-track table {
  width: 100%;
}
.box-track table th {
  border-bottom: 1px solid;
  padding-bottom: 5px;
}
.box-track table tbody {
  max-height: 220px;
  overflow-x: hidden;
  overflow-y: auto;
  display: block;
  width: 100%;
}
.box-track table th:nth-child(1),
.box-track table td:nth-child(1) {
  width: 1.5rem;
}

.box-track table th:nth-child(2),
.box-track table td:nth-child(2),
.box-track table th:nth-child(3),
.box-track table td:nth-child(3) {
  width: 5.5rem;
}

.box-track table th:nth-child(4),
.box-track table td:nth-child(4) {
  width: 2.5rem;
}

.box-track table th:nth-child(5),
.box-track table td:nth-child(5) {
  width: 5.5rem;
}

/* 網頁捲軸【寬度】 */
::-webkit-scrollbar {
  width: 5px;
}

/* 網頁捲軸【背景】顏色 */
::-webkit-scrollbar-track {
  background: #15151566;
  border-radius: 5px;
}

/* 網頁捲軸【把手】顏色 */
::-webkit-scrollbar-thumb {
  background: #15151566;
  border-radius: 5px;
}

/* 網頁捲軸【滑過時】把手的顏色 */
::-webkit-scrollbar-thumb:hover {
  background: #151515;
}

Widget 初始化加進我們撰寫好的 index.js 中。

index.js

let terrainview = null;
let listTrack = [];
let Gltf = null;
let layerTrack = null;
let timeAxis = null;
const Data = {
  init() {
    listTrack.push({
      lon: 120.6553499,
      lat: 24.1780933,
      rotate: 0,
      act: 1,
      t: "2023-01-01 00:00:00",
    });
    listTrack.push({
      lon: 120.6552425,
      lat: 24.1774975,
      rotate: -3,
      act: 1,
      t: "2023-01-01 00:00:03",
    });
    listTrack.push({
      lon: 120.6550951,
      lat: 24.176514,
      rotate: -8,
      act: 1,
      t: "2023-01-01 00:00:06",
    });
    listTrack.push({
      lon: 120.655086,
      lat: 24.1761523,
      rotate: 78,
      act: 1,
      t: "2023-01-01 00:00:09",
    });
    listTrack.push({
      lon: 120.6538496,
      lat: 24.1761653,
      rotate: 80,
      act: 0,
      t: "2023-01-01 00:00:12",
    });
    listTrack.push({
      lon: 120.6526356,
      lat: 24.1761578,
      rotate: 80,
      act: 1,
      t: "2023-01-01 00:00:15",
    });
  },
  // 取得最近時間軌跡
  getTimeAxisNearIndex(t) {
    const list = listTrack;
    let idx = null;
    let left = 0,
      right = list.length - 1;
    if (t &lt;= new Date(list[left].t).getTime()) return left;
    if (t &gt;= new Date(list[right].t).getTime()) return right;
    list.every((d, i, a) =&gt; {
      const ts = new Date(d.t).getTime(),
        te = new Date(i &lt; a.length - 1 ? a[i + 1].t : d.t).getTime();
      if (t &lt; ts || t &gt; te) return true;
      idx = i;
      return false;
    });
    return idx;
  },
  updateListTrack() {
    listTrack.forEach((o, i) =&gt; {
      o.t = new Date(
        new Date("2023-01-01 00:00:00").getTime() + i * 1000 * 3,
      ).format("yyyy-MM-dd HH:mm:ss");
    });
  },
};
const Render = {
  init() {
    this.map();
  },
  map() {
    //# 初始化
    const initialize = (res) =&gt; {
      initBasemap(); // 設定底圖
      initPosition(); // 設定初始位置
      //# 初始渲染圖層
      setTimeout(() =&gt; {
        this.trackTable();
        this.trackMap();
        this.cow();
      }, 1000);
    };
    //# 設定底圖
    const initBasemap = () =&gt; {
      terrainview.setBaseLayer({
        url: "GOOGLE_MAP",
        identifier: "IMAGE",
        urlTemplate: "{URL}",
      });
    };
    //# 設定初始位置
    const initPosition = () =&gt; {
      let pos = new window.GeoPoint(
        120.65574656663023,
        24.17841723956431,
        130.01970158180166,
      );
      let v = new window.Geo3DPoint(
        -0.5172117265996201,
        -0.7833157772855182,
        -0.34483100632545255,
      );
      let up = new window.Geo3DPoint(
        -0.19000461418265122,
        -0.28776148024488296,
        0.9386647841889967,
      );
      let camera = new window.ov.Camera(pos, v, up);
      terrainview.gotoCamera(camera, false);
    };
    terrainview = new window.ov.TerrainView("MyMap");
    terrainview.openTerrain({
      url: "http://127.0.0.1:8080",
      identifier: "terrain",
      callback: initialize,
      urlTemplate: "https://sample.pilotgaea.com.tw/Oview.aspx?{URL}",
    });
  },
  trackTable() {
    const tbody = document.querySelector("table.tb2 tbody");
    tbody.innerHTML = "";
    listTrack.forEach((o, i) =&gt; {
      const addDdlAct = () =&gt; {
        const selected0 = o.act === 0 ? "selected" : "";
        const selected1 = o.act === 1 ? "selected" : "";
        let opts = "";
        opts += `&lt;option value="0" ${selected0}&gt;吃&lt;/option&gt;`;
        opts += `&lt;option value="1" ${selected1}&gt;走&lt;/option&gt;`;
        return `&lt;select class="form-select"&gt;${opts}&lt;/select&gt;`;
      };
      const addRangeRotae = () =&gt; {
        let range = "";
        range += `&lt;input type="range" min="-359" max="359" value="${o.rotate}" onchange="Events.changeRotate(this)" /&gt;`;
        range += `&lt;div class="text-centet"&gt;${o.rotate}&lt;/div&gt;`;
        return range;
      };
      let tr = "";
      tr += `&lt;td&gt;${i + 1}&lt;/td&gt;`;
      tr += `&lt;td&gt;&lt;input type="text" class="w-80p" name="lon" placeholder="請填寫經度" value="${o.lon}" /&gt;&lt;/td&gt;`;
      tr += `&lt;td&gt;&lt;input type="text" class="w-80p" name="lat" placeholder="請填寫緯度" value="${o.lat}" /&gt;&lt;/td&gt;`;
      tr += `&lt;td&gt;${addDdlAct()}&lt;/td&gt;`;
      tr += `&lt;td&gt;${addRangeRotae()}&lt;/td&gt;`;
      tr += `&lt;td&gt;&lt;input type="button" class="btn" name="update" value="更新" onClick="Setting.btnUpdate(${i})" /&gt;&lt;/td&gt;`;
      tr += `&lt;td&gt;&lt;input type="button" class="btn btn-red" name="delete" value="刪除" onClick="Setting.btnDelete(${i})" /&gt;&lt;/td&gt;`;
      tbody.innerHTML += tr;
    });
  },
  trackMap() {
    if (layerTrack != null) Events.removeTrackPolylin();
    const initialize = (sucess, layer) =&gt; {
      if (sucess) {
        const pts = getPts();
        addPolyline(layer, pts);
        addPoints(layer, pts);
      } else console.log("track layer initialize failed!");
    };
    const getPts = () =&gt; {
      let pts = [];
      listTrack.forEach((item) =&gt; {
        const pos = new window.GeoPoint(item.lon, item.lat, 0);
        pts.push(new window.GeoPoint(item.lon, item.lat, 0));
      });
      return pts;
    };
    const addPoints = (layer, pts) =&gt; {
      pts.forEach((p, idx) =&gt; {
        layer.addPointEntity({
          geo: p,
          color: new ov.Color("#0000FF"),
          absHeight: false,
          label: {
            text: (idx + 1).toString(),
            size: 18,
          },
        });
      });
    };
    const addPolyline = (layer, pts) =&gt; {
      layer.addGroundPolylineEntity({
        geo: new window.GeoPolyline(pts),
        opacity: 1.0,
        color: new window.ov.Color("#FF0000"),
        size: 4,
      });
    };
    layerTrack = terrainview.addCustomLayer({
      layername: "Track",
      callback: initialize,
    });
  },
  cow() {
    if (Gltf != null) Events.removeGltf();
    const initialize = (success, layer) =&gt; {
      if (success) addModel(layer);
      else console.error("custom layer initialize failed!");
    };
    const addModel = (layer) =&gt; {
      const url =
        "https://sample.pilotgaea.com.tw/demo/index/src/commonSrc/glb/乳牛_1011_2.glb";
      let lon = 0,
        lat = 0,
        z = 0,
        scale = 4;
      lon = listTrack[0].lon;
      lat = listTrack[0].lat;
      const pos = new window.GeoPoint(lon, lat, 0);
      z = terrainview.getHeight(pos);
      model.ent = layer.addGLTFEntity({
        src: url,
        position: new window.Geo3DPoint(lon, lat, z),
        scale: scale,
        rotate: { x: 0, y: 1, z: 0, w: 10 },
      });
    };
    const model = { layer: null, ent: [] };
    model.layer = terrainview.addCustomLayer({
      layername: "Gltf",
      callback: initialize,
    });
    Gltf = model;
  },
  timeAxis(sw) {
    if (sw) {
      const timeS = new Date("2023-01-01 00:00:00"),
        timeE = new Date(timeS.getTime() + 1000 * (listTrack.length - 1) * 3),
        range = timeE - timeS;
      timeAxis = {};
      timeAxis.sw = false;
      timeAxis.act = -1;
      timeAxis.start = timeS;
      timeAxis.end = timeE;
      timeAxis.event = [];
      const opt1 = {
        view: terrainview,
        style: { left: "70px", width: "calc(95% - 50px)" },
        timeRange: range,
        leftTime: timeS,
        startTime: timeS,
        stopTime: timeE,
        hideDate: true,
      };
      timeAxis.timeline = new ov.Widget.Timeline(opt1);
      const opt2 = {
        view: terrainview,
        timeline: timeAxis.timeline,
        playSpeed: 1.0,
        minUpdateInterval: 10,
      };
      timeAxis.timelinePlayer = new ov.Widget.TimelinePlayer(opt2);
      //# 防呆
      setTimeout(() =&gt; {
        const event1 = terrainview.addTimeEvent({
          start: new Date("1911-01-01"),
          end: new Date(timeS.getTime() - 1),
          title: "日期超過範圍",
          onEnter() {
            timeAxis.timeline.toStart();
          }, // 進入時間區間時執行的事件
          onLeave() {}, // 離開時間區間時執行的事件,
        });
        const event2 = terrainview.addTimeEvent({
          start: new Date(timeE.getTime() + 1),
          end: timeE.addDays(30),
          title: "日期超過範圍",
          onEnter() {
            timeAxis.timeline.updateTime(timeE);
          }, // 進入時間區間時執行的事件
          onLeave() {}, // 離開時間區間時執行的事件
        });
        timeAxis.event.push(event1);
        timeAxis.event.push(event2);
      }, 1500);
    } else {
      while (timeAxis.event.length &gt; 0) {
        terrainview.removeTimeEvent(timeAxis.event[0]);
        timeAxis.event.splice(0, 1);
      }
      timeAxis.timeline.removeLink();
      timeAxis.timelinePlayer.remove();
      timeAxis.timeline.remove();
      timeAxis = null;
    }
  },
};
const Setting = {
  // 初始化
  init() {
    this.boxTool();
    this.btnLocate();
    this.btnGps();
    this.btnTrack();
    this.btnSwitch2();
    this.btnClose();
    this.btnPlay();
    this.btnPause();
    this.timelinePlayer();
    this.btnAdd();
  },
  //
  boxTool() {
    document.querySelector(".box-tool .header a").onclick = () =&gt; {
      const i = document.querySelector(".box-tool .header a i");
      i.classList.toggle("bi-caret-down-fill");
      i.classList.toggle("bi-caret-up-fill");
      document.querySelector(".box-tool").classList.toggle("open");
      //Events.switchBoxTrack(false);
    };
  },
  // 按鈕_坐標定位
  btnLocate() {
    document.querySelector(".btn.btn-locate").onclick = () =&gt; {
      //# 檢查坐標格式
      const checkCoord = (lon, lat) =&gt; {
        let errMsg = "";
        if (lon === "" || lat === "") errMsg = "經緯度不可為空值!";
        else if (
          !/^(-?\d+)(\.\d+)?$/.test(lon) ||
          !/^(-?\d+)(\.\d+)?$/.test(lat)
        )
          errMsg = "經緯度格式有誤!";
        else if (
          Math.abs(parseFloat(lon)) &gt; 180 ||
          Math.abs(parseFloat(lat)) &gt; 90
        )
          errMsg = "經緯度不可超過範圍!";
        return errMsg;
      };
      const lon = document.querySelector("#txtLon").value,
        lat = document.querySelector("#txtLat").value,
        errMsg = checkCoord(lon, lat);
      if (errMsg != "") {
        alert(errMsg);
        return;
      }
      Events.gotoCoord(lon, lat);
    };
  },
  // 按鈕_GPS 定位
  btnGps() {
    document.querySelector(".btn.btn-gps").onclick = () =&gt; {
      const GetGpsPosition = (pos) =&gt; {
        const lon = pos.coords.longitude,
          lat = pos.coords.latitude;
        Events.gotoCoord(lon, lat);
      };
      if (navigator.geolocation)
        navigator.geolocation.getCurrentPosition(GetGpsPosition);
      else alert("無法進行定位!");
    };
  },
  // 按鈕_開啟軌跡列表
  btnTrack() {
    document.querySelector(".btn.btn-tarck").onclick = () =&gt; {
      document.querySelector(".box-track").classList.add("show");
      Events.switchBoxTool(false);
    };
  },
  // 按鈕_切換軌跡列表視窗
  btnSwitch2() {
    document.querySelector(".box-track .btnSwitch2").onclick = () =&gt; {
      Events.switchBoxTrack2();
    };
  },
  // 按鈕_關閉軌跡列表視窗
  btnClose() {
    document.querySelector(".box-track .btnClose2").onclick = () =&gt; {
      Events.switchBoxTrack(false);
    };
  },
  // 按鈕_播放時間軸
  btnPlay() {
    const btn = document.querySelector('.btn[name="play"');
    btn.onclick = () =&gt; {
      if (listTrack.length === 0) {
        alert("請先新增軌跡");
        return;
      }
      Events.switchTrackBtns(true);
      Events.switchBoxTrack2();
      Render.timeAxis(true);
    };
  },
  // 按鈕_暫停時間軸
  btnPause() {
    const btn = document.querySelector('.btn[name="pause"');
    btn.onclick = () =&gt; {
      Events.switchTrackBtns(false);
      Render.timeAxis(false);
    };
  },
  btnAdd() {
    document.querySelector('.btn[name="add"').onclick = () =&gt; {
      const pos = terrainview.camera.pos;
      listTrack.push({
        lon: pos.x,
        lat: pos.y,
        rotate: 0,
        act: 1,
        t: "",
      });
      Data.updateListTrack();
      Render.trackTable();
      Render.trackMap();
      Render.cow();
    };
  },
  btnUpdate(idx) {
    const tr = document.querySelectorAll("table.tb2 tr")[idx];
    if (tr === null) return;
    const lon = tr.querySelector('input[name="lon"]').value.trim();
    const lat = tr.querySelector('input[name="lat"]').value.trim();
    const act = tr.querySelector("select").value;
    const rotate = tr.querySelector('input[type="range"]').value;
    const item = listTrack[idx];
    item.lon = parseFloat(lon);
    item.lat = parseFloat(lat);
    item.act = parseInt(act);
    item.rotate = parseInt(rotate);
    Data.updateListTrack();
    Render.trackTable();
    Render.trackMap();
    Render.cow();
  },
  btnDelete(idx) {
    if (!confirm("您確定要刪除該節點嗎?")) return;
    listTrack.splice(idx, 1);
    Data.updateListTrack();
    Render.trackTable();
    Render.trackMap();
    Render.cow();
  },
  // 設定時間軸播放器
  timelinePlayer() {
    ov.Widget.TimelinePlayer.prototype.play = function () {
      this._Play ||
        ((this._Play = !0),
        this._PlayBtn.classList.toggle("Play"),
        (this._LastTime = GetTickCount()),
        this._dispatchEvent("Play"),
        requestAnimationFrame(this._do.bind(this)));
      Events.startViewingMode();
      Events.timeAxisEvent();
    };
  },
};
const Events = {
  // 開關功能範例
  switchBoxTool(sw) {
    const i = document.querySelector(".box-tool .header a i");
    const box = document.querySelector(".box-tool");
    if (sw) {
      i.classList.add("bi-caret-up-fill");
      i.classList.remove("bi-caret-down-fill");
      box.classList.add("open");
    } else {
      i.classList.remove("bi-caret-up-fill");
      i.classList.add("bi-caret-down-fill");
      box.classList.remove("open");
    }
  },
  // 開關軌跡列表
  switchBoxTrack(sw) {
    const box = document.querySelector(".box-track");
    const i = document.querySelector(".box-track .btnSwitch2 i");
    if (sw) {
      box.classList.add("show");
      i.classList.remove("bi-caret-down-fill");
      i.classList.add("bi-caret-up-fill");
    } else {
      box.classList.remove("show");
      box.classList.remove("open");
      i.classList.add("bi-caret-down-fill");
      i.classList.remove("bi-caret-up-fill");
    }
  },
  // 顯示/隱藏軌跡列表
  switchBoxTrack2() {
    document.querySelector(".box-track").classList.toggle("open");
    const i = document.querySelector(".box-track .btnSwitch2 i");
    i.classList.toggle("bi-caret-down-fill");
    i.classList.toggle("bi-caret-up-fill");
  },
  // 顯示/隱藏軌跡列表按鈕
  switchTrackBtns(sw) {
    const btnAdd = document.querySelector('.btn[name="add"'),
      btnPlay = document.querySelector('.btn[name="play"'),
      btnPause = document.querySelector('.btn[name="pause"'),
      btnUds = document.querySelectorAll('.btn[name="update"'),
      btnDels = document.querySelectorAll('.btn[name="delete"');
    if (sw) {
      btnAdd.classList.add("hide");
      btnPlay.classList.add("hide");
      btnPause.classList.remove("hide");
      btnUds.forEach((o) =&gt; {
        o.classList.add("hide");
      });
      btnDels.forEach((o) =&gt; {
        o.classList.add("hide");
      });
    } else {
      btnAdd.classList.remove("hide");
      btnPlay.classList.remove("hide");
      btnPause.classList.add("hide");
      btnUds.forEach((o) =&gt; {
        o.classList.remove("hide");
      });
      btnDels.forEach((o) =&gt; {
        o.classList.remove("hide");
      });
    }
  },
  // 移動坐標
  gotoCoord(lon, lat) {
    const pos = new window.GeoPoint(
      parseFloat(lon),
      parseFloat(lat),
      terrainview.getHeightAboveGround(),
    );
    const v = new window.Geo3DPoint(0, 0, -1);
    const up = new window.Geo3DPoint(0, 1, 0);
    const camera = new window.ov.Camera(pos, v, up);
    terrainview.gotoCamera(camera, false);
  },
  //  移除軌跡圖層
  removeTrackPolylin() {
    if (layerTrack === null) return;
    layerTrack.removeAllEntity();
    terrainview.removeLayer(layerTrack);
    layerTrack = null;
  },
  // 移除牛隻模型
  removeGltf() {
    if (Gltf === null) return;
    Gltf.layer.removeAllEntity();
    terrainview.removeLayer(Gltf.layer);
    Gltf = null;
  },
  startViewingMode() {
    const model = Gltf,
      list = listTrack;
    const setMoveEvent = () =&gt; {
      const d1 = list[0],
        d2 = list[1];
      const pos1 = new window.GeoPoint(d1.lon, d1.lat, 0),
        pos2 = new window.GeoPoint(d2.lon, d2.lat, 0);
      pos1.z = terrainview.getHeight(pos1);
      pos2.z = terrainview.getHeight(pos2);
      model.moveEvent = terrainview.addMoveEvent({
        steps: [
          {
            startPosition: pos1,
            endPosition: pos2,
          },
        ],
        entity: model.ent,
        defaultFunction(ent, pos) {},
      });
    };
    setMoveEvent();
  },
  // 時間軸事件(範圍內)
  timeAxisEvent() {
    const TimeAxis = timeAxis,
      list = listTrack;
    setTimeout(() =&gt; {
      const getNearData = (idx, t) =&gt; {
        if (idx === list.length - 1) return list[idx];
        const d1 = list[idx],
          d2 = list[idx + 1],
          ratio =
            (t - new Date(d1.t).getTime()) /
            (new Date(d2.t).getTime() - new Date(d1.t).getTime());
        const lon = d1.lon + (d2.lon - d1.lon) * ratio,
          lat = d1.lat + (d2.lat - d1.lat) * ratio;
        const pos = new window.GeoPoint(lon, lat, 0),
          z = terrainview.getHeight(pos);
        return {
          lon: lon,
          lat: lat,
          z: z,
          rotate: d1.rotate,
          heading: 0,
          act: d1.act,
        };
      };
      const renderItem = (name, now) =&gt; {
        if (TimeAxis.sw) return;
        TimeAxis.sw = true;
        const idx = Data.getTimeAxisNearIndex(now);
        TimeAxis.idx = idx;
        const data = getNearData(idx, now);
        Events.updateGltf(
          new window.GeoPoint(data.lon, data.lat, data.z),
          data.rotate,
          data.heading,
          data.act,
        );
        setTimeout(() =&gt; {
          TimeAxis.sw = false;
        }, 100);
      };
      const addLastTimeEvent = () =&gt; {
        const data = list[list.length - 1],
          end = data.t;
        const lastEvent = () =&gt; {
          TimeAxis.sw = true;
          timeAxis.timelinePlayer.stop();
          timeAxis.act = -1;
          Gltf.ent.stopAll();
          Gltf.ent.play(data.act);
          setTimeout(() =&gt; {
            TimeAxis.sw = false;
          }, 100);
        };
        const evt = terrainview.addTimeEvent({
          start: new Date(end),
          end: new Date(new Date(end).getTime() + 1),
          title: "影片最後範圍",
          onPlay(time) {
            lastEvent();
          },
          onEnter() {
            lastEvent();
          },
        });
        timeAxis.event.push(evt);
      };
      list.forEach((d, i, a) =&gt; {
        const evt = terrainview.addTimeEvent({
          start: new Date(d.t),
          end: new Date(i &lt; a.length - 1 ? a[i + 1].t : d.t),
          title: "影片範圍",
          onPlay(time) {
            renderItem("onPlay", new Date(time).getTime());
          },
          onEnter() {
            renderItem("onEnter", timeAxis.timeline.getNowTime().getTime());
          }, // 進入時間區間時執行的事件
        });
        TimeAxis.event.push(evt);
      });
      addLastTimeEvent();
    }, 500);
  },
  updateGltf(pos, rotate, heading, act) {
    let rotateX = 0,
      rotateZ = 0;
    if (Array.isArray(heading)) {
      rotateX = heading[0];
      rotateZ = heading[1];
    }
    Gltf.ent.update({
      position: pos,
      rotate: { x: rotateX, y: 1, z: rotateZ, w: rotate },
    });
    if (timeAxis.act === act) return;
    Gltf.ent.stopAll();
    Gltf.ent.play(act);
    timeAxis.act = act;
  },
  changeRotate(range) {
    const val = parseInt(range.value);
    range.nextSibling.innerHTML = val;
    Gltf.ent.update({ rotate: { x: 0, y: 1, z: 0, w: val } });
  },
};
var WebInit = () =&gt; {
  Setting.init();
  Data.init();
  Render.init();
};
// 時間格式
Date.prototype.format = function (formatstring) {
  var yyyy = this.getFullYear().toString(),
    MM = (this.getMonth() + 1).toString(), // getMonth() is zero-based
    dd = this.getDate().toString(),
    HH = this.getHours().toString(),
    mm = this.getMinutes().toString(),
    ss = this.getSeconds().toString();

  MM = MM[1] ? MM : "0" + MM[0];
  dd = dd[1] ? dd : "0" + dd[0];
  HH = HH[1] ? HH : "0" + HH[0];
  mm = mm[1] ? mm : "0" + mm[0];
  ss = ss[1] ? ss : "0" + ss[0];

  formatstring = formatstring
    .replace("yyyy", yyyy)
    .replace("MM", MM)
    .replace("dd", dd)
    .replace("HH", HH)
    .replace("mm", mm)
    .replace("ss", ss);
  return formatstring;
};
// 增減天數
Date.prototype.addDays = function (days) {
  let dat = new Date(this.valueOf());
  dat.setDate(dat.getDate() + days);
  return dat;
};

到目前為止,我們已成功將初步的模型軌跡走動圖展示 Widget 加入圖臺中。

Copyright © NCHC 2022 Version:13.0 all right reserved,powered by Gitbook修訂時間: 2024-11-20 13:52:09