Generate Cesium preview pages from area builds
This commit is contained in:
@@ -28,6 +28,9 @@ if (stages.blender) {
|
||||
if (stages.cesium) {
|
||||
exportCesium(area);
|
||||
}
|
||||
if (stages.preview) {
|
||||
writeCesiumPreview(area);
|
||||
}
|
||||
|
||||
console.log("Done.");
|
||||
|
||||
@@ -76,6 +79,9 @@ function normalizeAreaConfig(raw) {
|
||||
render: path.resolve(outputOverrides.render || path.join(areaDir, `${fileStem}.png`)),
|
||||
glb: path.resolve(outputOverrides.glb || path.join(areaDir, `${fileStem}.glb`)),
|
||||
metadata: path.resolve(outputOverrides.metadata || path.join(areaDir, `${fileStem}.json`)),
|
||||
cesiumPreview: path.resolve(
|
||||
outputOverrides.cesiumPreview || path.join(areaDir, `${fileStem}-cesium-preview.html`),
|
||||
),
|
||||
pipelineDir: path.resolve(outputOverrides.pipelineDir || path.join(areaDir, "_pipeline")),
|
||||
};
|
||||
|
||||
@@ -89,6 +95,7 @@ function normalizeAreaConfig(raw) {
|
||||
intermediates: raw.stages?.intermediates ?? raw.stages?.qgis ?? true,
|
||||
blender: raw.stages?.blender ?? true,
|
||||
cesium: raw.stages?.cesium ?? true,
|
||||
preview: false,
|
||||
},
|
||||
qgis: {
|
||||
arrowScale: raw.qgis?.arrowScale ?? raw.arrowScale ?? 0.8,
|
||||
@@ -141,12 +148,15 @@ function resolveStages(defaults, requested) {
|
||||
scene: ["blender"],
|
||||
cesium: ["cesium"],
|
||||
glb: ["cesium"],
|
||||
preview: ["preview"],
|
||||
html: ["preview"],
|
||||
cesiumPreview: ["preview"],
|
||||
};
|
||||
const out = { intermediates: false, blender: false, cesium: false };
|
||||
const out = { intermediates: false, blender: false, cesium: false, preview: false };
|
||||
for (const stage of requested) {
|
||||
const mapped = aliases[stage];
|
||||
if (!mapped) {
|
||||
throw new Error(`Unknown stage '${stage}'. Use intermediates, blender, cesium, or all.`);
|
||||
throw new Error(`Unknown stage '${stage}'. Use intermediates, blender, cesium, preview, or all.`);
|
||||
}
|
||||
for (const key of mapped) out[key] = true;
|
||||
}
|
||||
@@ -233,6 +243,7 @@ function exportCesium(area) {
|
||||
"--metadata",
|
||||
area.outputs.metadata,
|
||||
], "cesium");
|
||||
writeCesiumPreview(area);
|
||||
}
|
||||
|
||||
function blenderExecutable(area) {
|
||||
@@ -255,3 +266,117 @@ function runCommand(command, commandArgs, stage) {
|
||||
throw new Error(`Stage '${stage}' failed with status=${result.status}${signal}`);
|
||||
}
|
||||
}
|
||||
|
||||
function writeCesiumPreview(area) {
|
||||
ensureFile(area.outputs.glb, "Cesium GLB");
|
||||
ensureFile(area.outputs.metadata, "Cesium metadata");
|
||||
const htmlPath = area.outputs.cesiumPreview;
|
||||
fs.mkdirSync(path.dirname(htmlPath), { recursive: true });
|
||||
const glbName = path.basename(area.outputs.glb);
|
||||
const metadataName = path.basename(area.outputs.metadata);
|
||||
fs.writeFileSync(htmlPath, cesiumPreviewHtml(glbName, metadataName, area.id));
|
||||
console.log(`Cesium preview: ${htmlPath}`);
|
||||
}
|
||||
|
||||
function cesiumPreviewHtml(glbName, metadataName, areaId) {
|
||||
return `<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${escapeHtml(areaId)} Cesium Preview</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/cesium@1.121.1/Build/Cesium/Cesium.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/cesium@1.121.1/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
|
||||
<style>
|
||||
html, body, #cesiumContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
}
|
||||
#status {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
bottom: 12px;
|
||||
z-index: 1;
|
||||
max-width: 520px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(20, 24, 28, 0.78);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cesiumContainer"></div>
|
||||
<div id="status">Loading ${escapeHtml(glbName)}...</div>
|
||||
<script>
|
||||
const statusEl = document.getElementById("status");
|
||||
Cesium.Ion.defaultAccessToken = "";
|
||||
|
||||
async function main() {
|
||||
const metadata = await fetch("${escapeJs(metadataName)}").then((r) => r.json());
|
||||
const anchor = metadata.anchor || {};
|
||||
const longitude = Number(anchor.longitude || 0);
|
||||
const latitude = Number(anchor.latitude || 0);
|
||||
const height = Number(anchor.height || 0);
|
||||
const heading = Number(metadata.heading_correction_degrees || 0);
|
||||
|
||||
const viewer = new Cesium.Viewer("cesiumContainer", {
|
||||
animation: false,
|
||||
timeline: false,
|
||||
baseLayerPicker: false,
|
||||
geocoder: false,
|
||||
navigationHelpButton: false,
|
||||
sceneModePicker: false,
|
||||
homeButton: true,
|
||||
fullscreenButton: true,
|
||||
infoBox: false,
|
||||
selectionIndicator: false,
|
||||
baseLayer: false
|
||||
});
|
||||
viewer.scene.globe.depthTestAgainstTerrain = true;
|
||||
|
||||
const p = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
|
||||
const enu = Cesium.Transforms.eastNorthUpToFixedFrame(p);
|
||||
const correction = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(heading));
|
||||
const modelMatrix = Cesium.Matrix4.multiplyByMatrix3(enu, correction, new Cesium.Matrix4());
|
||||
const model = await Cesium.Model.fromGltfAsync({
|
||||
url: "${escapeJs(glbName)}",
|
||||
modelMatrix,
|
||||
scale: 1.0
|
||||
});
|
||||
viewer.scene.primitives.add(model);
|
||||
|
||||
const center = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);
|
||||
viewer.camera.flyToBoundingSphere(
|
||||
new Cesium.BoundingSphere(center, 900.0),
|
||||
{ duration: 0.0 }
|
||||
);
|
||||
statusEl.textContent = "${escapeJs(glbName)} | " + longitude.toFixed(6) + ", " + latitude.toFixed(6);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
statusEl.textContent = "Failed to load Cesium preview: " + error.message;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
|
||||
function escapeJs(value) {
|
||||
return String(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user