模型軌跡走動功能 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 <= new Date(list[left].t).getTime()) return left;
if (t >= new Date(list[right].t).getTime()) return right;
list.every((d, i, a) => {
const ts = new Date(d.t).getTime(),
te = new Date(i < a.length - 1 ? a[i + 1].t : d.t).getTime();
if (t < ts || t > te) return true;
idx = i;
return false;
});
return idx;
},
updateListTrack() {
listTrack.forEach((o, i) => {
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) => {
initBasemap(); // 設定底圖
initPosition(); // 設定初始位置
//# 初始渲染圖層
setTimeout(() => {
this.trackTable();
this.trackMap();
this.cow();
}, 1000);
};
//# 設定底圖
const initBasemap = () => {
terrainview.setBaseLayer({
url: "GOOGLE_MAP",
identifier: "IMAGE",
urlTemplate: "{URL}",
});
};
//# 設定初始位置
const initPosition = () => {
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) => {
const addDdlAct = () => {
const selected0 = o.act === 0 ? "selected" : "";
const selected1 = o.act === 1 ? "selected" : "";
let opts = "";
opts += `<option value="0" ${selected0}>吃</option>`;
opts += `<option value="1" ${selected1}>走</option>`;
return `<select class="form-select">${opts}</select>`;
};
const addRangeRotae = () => {
let range = "";
range += `<input type="range" min="-359" max="359" value="${o.rotate}" onchange="Events.changeRotate(this)" />`;
range += `<div class="text-centet">${o.rotate}</div>`;
return range;
};
let tr = "";
tr += `<td>${i + 1}</td>`;
tr += `<td><input type="text" class="w-80p" name="lon" placeholder="請填寫經度" value="${o.lon}" /></td>`;
tr += `<td><input type="text" class="w-80p" name="lat" placeholder="請填寫緯度" value="${o.lat}" /></td>`;
tr += `<td>${addDdlAct()}</td>`;
tr += `<td>${addRangeRotae()}</td>`;
tr += `<td><input type="button" class="btn" name="update" value="更新" onClick="Setting.btnUpdate(${i})" /></td>`;
tr += `<td><input type="button" class="btn btn-red" name="delete" value="刪除" onClick="Setting.btnDelete(${i})" /></td>`;
tbody.innerHTML += tr;
});
},
trackMap() {
if (layerTrack != null) Events.removeTrackPolylin();
const initialize = (sucess, layer) => {
if (sucess) {
const pts = getPts();
addPolyline(layer, pts);
addPoints(layer, pts);
} else console.log("track layer initialize failed!");
};
const getPts = () => {
let pts = [];
listTrack.forEach((item) => {
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) => {
pts.forEach((p, idx) => {
layer.addPointEntity({
geo: p,
color: new ov.Color("#0000FF"),
absHeight: false,
label: {
text: (idx + 1).toString(),
size: 18,
},
});
});
};
const addPolyline = (layer, pts) => {
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) => {
if (success) addModel(layer);
else console.error("custom layer initialize failed!");
};
const addModel = (layer) => {
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(() => {
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 > 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 = () => {
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 = () => {
//# 檢查坐標格式
const checkCoord = (lon, lat) => {
let errMsg = "";
if (lon === "" || lat === "") errMsg = "經緯度不可為空值!";
else if (
!/^(-?\d+)(\.\d+)?$/.test(lon) ||
!/^(-?\d+)(\.\d+)?$/.test(lat)
)
errMsg = "經緯度格式有誤!";
else if (
Math.abs(parseFloat(lon)) > 180 ||
Math.abs(parseFloat(lat)) > 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 = () => {
const GetGpsPosition = (pos) => {
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 = () => {
document.querySelector(".box-track").classList.add("show");
Events.switchBoxTool(false);
};
},
// 按鈕_切換軌跡列表視窗
btnSwitch2() {
document.querySelector(".box-track .btnSwitch2").onclick = () => {
Events.switchBoxTrack2();
};
},
// 按鈕_關閉軌跡列表視窗
btnClose() {
document.querySelector(".box-track .btnClose2").onclick = () => {
Events.switchBoxTrack(false);
};
},
// 按鈕_播放時間軸
btnPlay() {
const btn = document.querySelector('.btn[name="play"');
btn.onclick = () => {
if (listTrack.length === 0) {
alert("請先新增軌跡");
return;
}
Events.switchTrackBtns(true);
Events.switchBoxTrack2();
Render.timeAxis(true);
};
},
// 按鈕_暫停時間軸
btnPause() {
const btn = document.querySelector('.btn[name="pause"');
btn.onclick = () => {
Events.switchTrackBtns(false);
Render.timeAxis(false);
};
},
btnAdd() {
document.querySelector('.btn[name="add"').onclick = () => {
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) => {
o.classList.add("hide");
});
btnDels.forEach((o) => {
o.classList.add("hide");
});
} else {
btnAdd.classList.remove("hide");
btnPlay.classList.remove("hide");
btnPause.classList.add("hide");
btnUds.forEach((o) => {
o.classList.remove("hide");
});
btnDels.forEach((o) => {
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 = () => {
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(() => {
const getNearData = (idx, t) => {
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) => {
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(() => {
TimeAxis.sw = false;
}, 100);
};
const addLastTimeEvent = () => {
const data = list[list.length - 1],
end = data.t;
const lastEvent = () => {
TimeAxis.sw = true;
timeAxis.timelinePlayer.stop();
timeAxis.act = -1;
Gltf.ent.stopAll();
Gltf.ent.play(data.act);
setTimeout(() => {
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) => {
const evt = terrainview.addTimeEvent({
start: new Date(d.t),
end: new Date(i < 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 = () => {
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
加入圖臺中。