三維流體模組API開發-時間軸
[info] 小提示:
程式碼連結:https://doc-3dgdp.colife.org.tw/samplecode/#src/testweb/fluid_timeline/
初始化三維流體模組
三維流體模組需使用WebGL2,注意圖台初始化時要在參數中設定 requestWebGL2: true
。
index.html
:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<meta charset="utf-8" />
<!--引入js-->
<script src="PGWeb3D.min.js"></script>
<!--引入css-->
<link rel="stylesheet" type="text/css" href="PGWeb3D.css"/>
</head>
<body>
<!-- 參數控制面板 -->
<div id="MyControl" style="position: absolute;z-index:1;">
<table style="background-color: black; color: white;">
<tr>
<td>Shape</td>
<td id="shape">
<Input type="radio" name="shapeRadio" value="line" checked>Line</Input>
<Input type="radio" name="shapeRadio" value="arrow">Arrow</Input>
</td>
</tr>
<tr>
<td>MinSpeed</td>
<td><input type="number" id="minSpeed" style="width: 118px" step="0.1"></td>
</tr>
<tr>
<td>Speed Factor</td>
<td><input type="number" id="speedFactor" style="width: 118px"></td>
</tr>
<tr>
<td>Lift</td>
<td><input type="number" id="lift" style="width: 118px"></td>
</tr>
<tr>
<td>ArrowScale</td>
<td><input type="number" id="arrowScale" style="width: 118px"></td>
</tr>
</table>
</div>
<!-- 圖台初始化 -->
<div id="MyMap" style="width:100%;height:100%;position:absolute;top:0;left:0"></div>
<!-- 引用主要 js,邏輯會寫在這個檔案 -->
<script src="main.js"></script>
</body>
</html>
main.js
:
// 初始化圖台, 記得傳入 requestWebGL2: true 參數
var terrainview = new ov.TerrainView("MyMap", { requestWebGL2: true });
// 取得三維流體模組
var fluidModule = terrainview.getModule("fluid");
// 預先建立三維流體模組的參數物件
var fluidParam = {};
// 在使用其他圖層前必須要先讀入地形 其他圖層的新增都會在callback中
terrainview.openTerrain({
url: "http://127.0.0.1:8080",
identifier: "範例地形圖",
callback: openCallback,
urlTemplate: "https://sample.pilotgaea.com.tw/Oview.aspx?{URL}"
});
// 文字資料取得
const getText = async (url) => {
const response = await fetch(url);
return await response.text();
};
// 地形讀取完成後的callback
function openCallback(result) {
//設定初始位置
let initialPos = new GeoPoint(119.87511267160517, 23.069641713992965, 150);
let initialV = new Geo3DPoint(0, 0, -1);
let initialUp = new Geo3DPoint(0, 1, 0);
let initialCamera = new ov.Camera(initialPos, initialV, initialUp);
terrainview.gotoCamera(initialCamera, false);
// 設定地形檔的WMTS影像
// ov.TerrainView.setBaseLayer(param)
// @param {string} param.url WMTS服務網址
// @param {string} param.identifier WMTS圖層名稱
terrainview.setBaseLayer({
url: "BING_MAP",
identifier: "IMAGE"
});
// 設定時間資料
const list = [
"0",
"10",
"20",
"30",
"40",
"50",
"60",
"70",
"80",
"90",
"100"
];
const times = [
"2023-10-25 00:00:00",
"2023-10-25 00:00:10",
"2023-10-25 00:00:20",
"2023-10-25 00:00:30",
"2023-10-25 00:00:40",
"2023-10-25 00:00:50",
"2023-10-25 00:01:00",
"2023-10-25 00:01:10",
"2023-10-25 00:01:20",
"2023-10-25 00:01:30",
"2023-10-25 00:01:40"
];
const dates = times.map((t) => new Date(t));
// 建立時間軸
const Timeline = new ov.Widget.Timeline({
view: terrainview,
startTime: dates[0],
stopTime: dates[dates.length - 1],
style: { left: "70px", width: "calc(90% - 70px)" }
});
// 建立時間軸播放器
const TimelinePlayer = new ov.Widget.TimelinePlayer({
view: terrainview,
timeline: Timeline,
playSpeed: 1
});
// 建立中心座標
const center4326 = new GeoPoint(
119.87433528151684,
23.069218404158807,
0.060315079318631545
);
// 取得所有流體資料
Promise.all(
list.map((s) =>
getText(
`https://sample.pilotgaea.com.tw/demo/index/src/testweb/fluid_timeline/Waterflow/Waterflow_t_${s}s.csv`
)
)
).then((rawData) => {
const data = rawData.map((datum) => {
return datum
.split("\r\n")
.map((line) => line.split(",").map((l) => l.trim()))
.filter((l) => !isNaN(parseFloat(l[0])));
});
const xs = data.map((datum) =>
datum.map((values) => parseFloat(values[0]))
);
const ys = data.map((datum) =>
datum.map((values) => parseFloat(values[1]))
);
const zs = data.map((datum) =>
datum.map((values) => parseFloat(values[2]))
);
const max = [-Infinity, -Infinity, -Infinity];
const min = [Infinity, Infinity, Infinity];
xs.forEach((datum) =>
datum.forEach((value) => {
max[0] = Math.max(max[0], value);
min[0] = Math.min(min[0], value);
})
);
ys.forEach((datum) =>
datum.forEach((value) => {
max[1] = Math.max(max[1], value);
min[1] = Math.min(min[1], value);
})
);
zs.forEach((datum) =>
datum.forEach((value) => {
max[2] = Math.max(max[2], value);
min[2] = Math.min(min[2], value);
})
);
const row = Math.ceil((max[0] - min[0]) * 1);
const col = Math.ceil((max[1] - min[1]) * 1);
const level = Math.ceil((max[2] - min[2]) * 1);
const epsgEngine = GetEPSGEngine();
const center3857 = new GeoPoint(center4326);
epsgEngine.Transfer(4326, 3857, center3857);
const lt = new GeoPoint(min[0], max[1], center3857.z).PlusSelf(center3857);
const rt = new GeoPoint(max[0], max[1], center3857.z).PlusSelf(center3857);
const lb = new GeoPoint(min[0], min[1], center3857.z).PlusSelf(center3857);
const rb = new GeoPoint(max[0], min[1], center3857.z).PlusSelf(center3857);
epsgEngine.Transfer(3857, 4326, lt);
epsgEngine.Transfer(3857, 4326, rt);
epsgEngine.Transfer(3857, 4326, lb);
epsgEngine.Transfer(3857, 4326, rb);
const uData = [];
const vData = [];
const zData = [];
for (let i = 0; i < data.length; i++) {
uData.push(new Array(row * col * level).fill(0));
vData.push(new Array(row * col * level).fill(0));
zData.push(new Array(row * col * level).fill(0));
}
for (let i = 0; i < data.length; i++) {
const length = xs[i].length;
for (let j = 0; j < length; j++) {
const x = Math.min(
Math.round(((xs[i][j] - min[0]) / (max[0] - min[0])) * row),
row
);
const y = Math.min(
Math.round(((max[1] - ys[i][j]) / (max[1] - min[1])) * col),
col
);
const z = Math.min(
Math.round(((zs[i][j] - min[2]) / (max[2] - min[2])) * level),
level
);
const index = (z * col + y) * row + x;
uData[i][index] = parseFloat(data[i][j][4]);
vData[i][index] = parseFloat(data[i][j][5]);
zData[i][index] = parseFloat(data[i][j][6]);
}
}
// 建立流體資料
fluidModule.setFluidDataByUVData({
uData,
vData,
zData,
width: row,
height: col,
depth: level,
lift: -1,
boundary: new GeoBoundary(
Math.min(lt.x, lb.x),
Math.min(lb.y, rb.y),
Math.max(rt.x, rb.x),
Math.max(lt.y, rt.y)
),
offset: [
min[0],
max[0] - min[0],
min[1],
max[1] - min[1],
min[2],
max[2] - min[2]
],
speedFactor: 50,
center: center4326,
date: dates
});
fluidModule.arrowScale = 3;
// 依數據設定操作面板資料
document.getElementById("speedFactor").value = fluidModule.speedFactor;
document.getElementById("minSpeed").value = fluidModule._module.MinSpeed;
document.getElementById("lift").value = fluidModule.lift;
document.getElementById("arrowScale").value = fluidModule.arrowScale;
// 繫結時間軸與流體模組
Timeline.addLink(fluidModule);
});
}
// 設定操作面板事件
document.getElementById("minSpeed").addEventListener("input", function (evt) {
let minSpeed = parseFloat(evt.target.value);
if (!isNaN(minSpeed)) {
fluidModule.setFluidDataByParam({ minSpeed });
}
});
document
.getElementById("speedFactor")
.addEventListener("input", function (evt) {
let speedFactor = parseFloat(evt.target.value);
if (!isNaN(speedFactor)) {
fluidModule.speedFactor = speedFactor;
}
});
document.getElementById("lift").addEventListener("input", function (evt) {
let lift = parseFloat(evt.target.value);
if (!isNaN(lift)) {
fluidModule.lift = lift;
}
});
document.getElementById("arrowScale").addEventListener("input", (evt) => {
let arrowScale = parseFloat(evt.target.value);
if (!isNaN(arrowScale)) {
fluidModule.arrowScale = arrowScale;
}
});
document.getElementById("shape").addEventListener("change", () => {
let elements = document.getElementsByName("shapeRadio");
for (let element of elements) {
if (element.checked) {
switch (element.value) {
case "line":
fluidModule.mode = ov.FLUID_MODE.LINE;
break;
case "arrow":
fluidModule.mode = ov.FLUID_MODE.ARROW;
break;
}
}
}
});