mirror of https://github.com/CesiumGS/cesium.git
556 lines
20 KiB
HTML
556 lines
20 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
<meta
|
|
name="viewport"
|
|
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
|
/>
|
|
<meta
|
|
name="description"
|
|
content="Mars terrain visualized using 3D Tiles, tiled and hosted by Cesium ion, and shown with points of interest and additional data layers."
|
|
/>
|
|
<meta name="cesium-sandcastle-labels" content="Showcases, ion Assets, 3D Tiles" />
|
|
<title>Cesium Mars</title>
|
|
<script type="text/javascript" src="../Sandcastle-header.js"></script>
|
|
<script
|
|
type="text/javascript"
|
|
src="../../../Build/CesiumUnminified/Cesium.js"
|
|
nomodule
|
|
></script>
|
|
<script type="module" src="../load-cesium-es6.js"></script>
|
|
</head>
|
|
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
|
|
<style>
|
|
@import url(../templates/bucket.css);
|
|
#toolbar {
|
|
background: rgba(42, 42, 42, 0.8);
|
|
padding: 4px;
|
|
border-radius: 4px;
|
|
}
|
|
#toolbar input {
|
|
vertical-align: middle;
|
|
padding-top: 2px;
|
|
padding-bottom: 2px;
|
|
}
|
|
#toolbar .header {
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Styles for indicating to the user to press the play button */
|
|
.cesium-animation-rectButton.highlight-animation .cesium-animation-buttonGlow {
|
|
display: block;
|
|
fill: #fff;
|
|
/* keep the built-in blur and add extra glow layers */
|
|
filter: url(#animation_blurred) drop-shadow(0 0 3px #aef)
|
|
drop-shadow(0 0 3px #fff);
|
|
animation: highlight-animation-button 1.2s ease-in-out infinite;
|
|
}
|
|
.cesium-animation-rectButton.highlight-animation .cesium-animation-buttonMain {
|
|
stroke: #fff;
|
|
stroke-width: 3;
|
|
}
|
|
.cesium-animation-rectButton.highlight-animation .cesium-animation-buttonPath {
|
|
fill: #fff;
|
|
}
|
|
|
|
.cesium-animation-shuttleRingG.highlight-animation
|
|
.cesium-animation-shuttleRingBack {
|
|
stroke: #fff;
|
|
stroke-width: 6;
|
|
filter: drop-shadow(0 0 3px #aef) drop-shadow(0 0 3px rgba(174, 238, 255, 0.95));
|
|
animation: highlight-animation-ring 1.2s ease-in-out infinite;
|
|
}
|
|
.cesium-animation-shuttleRingG.highlight-animation
|
|
.cesium-animation-shuttleRingSwoosh
|
|
line {
|
|
stroke: #fff;
|
|
stroke-opacity: 0.8;
|
|
}
|
|
|
|
@keyframes highlight-animation-button {
|
|
0% {
|
|
opacity: 0.3;
|
|
stroke-width: 0;
|
|
}
|
|
50% {
|
|
opacity: 1;
|
|
stroke-width: 4;
|
|
}
|
|
100% {
|
|
opacity: 0.3;
|
|
stroke-width: 0;
|
|
}
|
|
}
|
|
@keyframes highlight-animation-ring {
|
|
0% {
|
|
stroke-opacity: 0.25;
|
|
stroke: #333;
|
|
}
|
|
50% {
|
|
stroke-opacity: 1;
|
|
stroke: #fff;
|
|
}
|
|
100% {
|
|
stroke-opacity: 0.25;
|
|
stroke: #333;
|
|
}
|
|
}
|
|
</style>
|
|
<div id="cesiumContainer" class="fullSize"></div>
|
|
<div id="loadingOverlay"><h1>Loading...</h1></div>
|
|
<div id="toolbar"></div>
|
|
<template id="roverHelpRowTemplate1">
|
|
<tr>
|
|
<td>
|
|
<img
|
|
data-src="Widgets/Images/NavigationHelp/MouseLeft.svg"
|
|
style="height: 48px; width: 48px"
|
|
alt="Left mouse button"
|
|
/>
|
|
</td>
|
|
<td>
|
|
<div class="cesium-navigation-help-pan">Track Rover</div>
|
|
<div class="cesium-navigation-help-detail">
|
|
Double click on a rover to track it
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<template id="roverHelpRowTemplate2">
|
|
<tr>
|
|
<td>
|
|
<svg width="48" height="48" viewBox="0 0 32 32" aria-label="Play" role="img">
|
|
<path
|
|
transform="translate(32,32) scale(0.85) translate(-32,-32)"
|
|
d="M6.684,25.682L24.316,15.5L6.684,5.318V25.682z"
|
|
fill="#ffffff"
|
|
/>
|
|
</svg>
|
|
</td>
|
|
<td>
|
|
<div class="cesium-navigation-help-zoom">Play Animation</div>
|
|
<div class="cesium-navigation-help-detail">
|
|
Press play on the timeline to watch the rover move
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<script id="cesium_sandcastle_script">
|
|
window.startup = async function (Cesium) {
|
|
"use strict";
|
|
//Sandcastle_Begin
|
|
|
|
Cesium.Ellipsoid.default = Cesium.Ellipsoid.MARS;
|
|
const viewer = new Cesium.Viewer("cesiumContainer", {
|
|
terrainProvider: false,
|
|
baseLayer: false,
|
|
baseLayerPicker: false,
|
|
geocoder: false,
|
|
shadows: false,
|
|
globe: new Cesium.Globe(Cesium.Ellipsoid.MARS),
|
|
skyBox: Cesium.SkyBox.createEarthSkyBox(),
|
|
skyAtmosphere: new Cesium.SkyAtmosphere(Cesium.Ellipsoid.MARS),
|
|
});
|
|
viewer.scene.globe.show = false;
|
|
const scene = viewer.scene;
|
|
const clock = viewer.clock;
|
|
const navHelp = viewer.navigationHelpButton;
|
|
|
|
// Adjust the default atmosphere coefficients to be more Mars-like
|
|
scene.skyAtmosphere.atmosphereMieCoefficient = new Cesium.Cartesian3(
|
|
9.0e-5,
|
|
2.0e-5,
|
|
1.0e-5,
|
|
);
|
|
scene.skyAtmosphere.atmosphereRayleighCoefficient = new Cesium.Cartesian3(
|
|
9.0e-6,
|
|
2.0e-6,
|
|
1.0e-6,
|
|
);
|
|
scene.skyAtmosphere.atmosphereRayleighScaleHeight = 9000;
|
|
scene.skyAtmosphere.atmosphereMieScaleHeight = 2700.0;
|
|
scene.skyAtmosphere.saturationShift = -0.1;
|
|
scene.skyAtmosphere.perFragmentAtmosphere = true;
|
|
|
|
// Adjust postprocess settings for brighter and richer features
|
|
const bloom = viewer.scene.postProcessStages.bloom;
|
|
bloom.enabled = true;
|
|
bloom.uniforms.brightness = -0.5;
|
|
bloom.uniforms.stepSize = 1.0;
|
|
bloom.uniforms.sigma = 3.0;
|
|
bloom.uniforms.delta = 1.5;
|
|
scene.highDynamicRange = true;
|
|
viewer.scene.postProcessStages.exposure = 1.5;
|
|
|
|
// Load Mars tileset
|
|
try {
|
|
const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(3644333, {
|
|
enableCollision: true,
|
|
});
|
|
viewer.scene.primitives.add(tileset);
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
|
|
// Load the rovers and path from The Martian (by Andy Weir), from CZML data source.
|
|
let curiosity, perseverance, ingenuity, theMartianJourney;
|
|
try {
|
|
const dataSource = await Cesium.CzmlDataSource.load(
|
|
"../../SampleData/Mars.czml",
|
|
);
|
|
viewer.dataSources.add(dataSource);
|
|
|
|
const roverMenuEntries = [
|
|
{
|
|
text: "Fly to rover...",
|
|
onselect: () => {},
|
|
},
|
|
];
|
|
|
|
const onSelectRover = (rover) => {
|
|
reset();
|
|
const roverAnimStartIso = rover.properties.animationStartTime.getValue(
|
|
Cesium.JulianDate.now(),
|
|
);
|
|
clock.multiplier = 604800;
|
|
clock.currentTime = new Cesium.JulianDate.fromIso8601(roverAnimStartIso);
|
|
viewer.timeline.zoomTo(rover.availability.start, rover.availability.stop);
|
|
|
|
const boundingSphere = new Cesium.BoundingSphere(
|
|
rover.position.getValue(clock.currentTime),
|
|
5000.0,
|
|
);
|
|
|
|
scene.camera.flyToBoundingSphere(boundingSphere, {
|
|
offset: new Cesium.HeadingPitchRoll(4.9791, -0.5294, 0.0),
|
|
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
|
|
maximumHeight: 5e6,
|
|
pitchAdjustHeight: 2.5e6,
|
|
duration: 3.0,
|
|
complete: function () {
|
|
highlightAnimationViewModel(); // Draw attention to the play button
|
|
navHelp.viewModel.showInstructions = true;
|
|
},
|
|
});
|
|
};
|
|
|
|
const setupRover = function (entityId, startSol, outRover) {
|
|
outRover = dataSource.entities.getById(entityId);
|
|
|
|
const julianDateToSol = createJulianDateToSolConverter(
|
|
outRover.availability.start,
|
|
startSol,
|
|
);
|
|
outRover.label.text = new Cesium.CallbackProperty(function (time) {
|
|
return julianDateToSol(time);
|
|
}, false);
|
|
|
|
const roverPath = dataSource.entities.getById(`${entityId}Path`);
|
|
roverPath.polyline.width = createWidthCallbackProperty(
|
|
new Cesium.NearFarScalar(0.0, 15.0, 1.0e5, 0.0),
|
|
);
|
|
|
|
roverMenuEntries.push({
|
|
text: entityId,
|
|
onselect: () => onSelectRover(outRover),
|
|
});
|
|
|
|
return outRover;
|
|
};
|
|
|
|
curiosity = setupRover("Curiosity", 3, curiosity);
|
|
perseverance = setupRover("Perseverance", 13, perseverance);
|
|
ingenuity = dataSource.entities.getById("Ingenuity"); // Only for viewing - no data for flight paths
|
|
theMartianJourney = dataSource.entities.getById("TheMartianJourney");
|
|
theMartianJourney.polyline.width = createWidthCallbackProperty(
|
|
new Cesium.NearFarScalar(0.0, 10.0, 1.0e7, 0.0),
|
|
);
|
|
theMartianJourney.rectangle.material = new Cesium.ImageMaterialProperty({
|
|
image: createCanvasAsTexture('Mark Watney\'s Journey in "The Martian"'),
|
|
transparent: true,
|
|
});
|
|
|
|
roverMenuEntries.push({
|
|
text: '"The Martian" Journey',
|
|
onselect: () => {
|
|
reset();
|
|
viewer.zoomTo(theMartianJourney);
|
|
},
|
|
});
|
|
|
|
Sandcastle.addToolbarMenu(roverMenuEntries);
|
|
} catch (error) {
|
|
console.log(`Error loading CZML: ${error}`);
|
|
}
|
|
|
|
// For changing the width of polylines based on distance from the camera
|
|
function createWidthCallbackProperty(nearFarScalar) {
|
|
return new Cesium.CallbackProperty(function () {
|
|
const distance = viewer.camera.positionCartographic.height;
|
|
let t =
|
|
(distance - nearFarScalar.near) / (nearFarScalar.far - nearFarScalar.near);
|
|
t = Cesium.Math.clamp(t, 0.0, 1.0);
|
|
return Cesium.Math.lerp(nearFarScalar.nearValue, nearFarScalar.farValue, t);
|
|
}, false);
|
|
}
|
|
|
|
// Converts a Julian date to a Mars Sol number, given a start date / sol number
|
|
function createJulianDateToSolConverter(startJulianDate, startSol) {
|
|
return function (julianDate) {
|
|
const secondsPerSol = 24 * 60 * 60 + 39 * 60 + 35;
|
|
const differenceInSeconds = Cesium.JulianDate.secondsDifference(
|
|
julianDate,
|
|
startJulianDate,
|
|
);
|
|
const solNumber = Math.floor(differenceInSeconds / secondsPerSol) + startSol;
|
|
return `Sol ${solNumber}`;
|
|
};
|
|
}
|
|
|
|
// Load points of interest from GeoJSON data source
|
|
try {
|
|
const dataSource = await Cesium.GeoJsonDataSource.load(
|
|
"../../SampleData/MarsPointsOfInterest.geojson",
|
|
);
|
|
viewer.dataSources.add(dataSource);
|
|
|
|
const onSelectLandmark = (landmark) => {
|
|
reset();
|
|
scene.camera.flyTo(landmark);
|
|
};
|
|
|
|
const landmarkMenuEntries = [
|
|
{
|
|
text: "Fly to landmark...",
|
|
onselect: () => {},
|
|
},
|
|
];
|
|
|
|
const entities = dataSource.entities.values;
|
|
entities.forEach((entity) => {
|
|
entity.label = new Cesium.LabelGraphics({
|
|
text: entity.properties.text,
|
|
font: "18pt Verdana",
|
|
outlineColor: Cesium.Color.DARKSLATEGREY,
|
|
outlineWidth: 2,
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
pixelOffset: new Cesium.Cartesian2(0, -22),
|
|
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1.0, 1.5e7, 0.5),
|
|
translucencyByDistance: new Cesium.NearFarScalar(2.5e7, 1.0, 4.0e7, 0.0),
|
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
disableDepthTestDistance: new Cesium.CallbackProperty(() => {
|
|
return Cesium.Cartesian3.magnitude(scene.camera.positionWC);
|
|
}, false),
|
|
});
|
|
|
|
entity.point = new Cesium.PointGraphics({
|
|
pixelSize: 10,
|
|
color: Cesium.Color.fromBytes(243, 242, 99),
|
|
outlineColor: Cesium.Color.fromBytes(219, 218, 111),
|
|
outlineWidth: 2,
|
|
scaleByDistance: new Cesium.NearFarScalar(1.5e3, 1.0, 4.0e7, 0.1),
|
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
disableDepthTestDistance: new Cesium.CallbackProperty(() => {
|
|
return Cesium.Cartesian3.magnitude(scene.camera.positionWC);
|
|
}, false),
|
|
});
|
|
|
|
entity.name = entity.properties.text.getValue();
|
|
entity.description = createPickedFeatureDescription(entity);
|
|
|
|
const flyToDestination = new Cesium.Cartesian3.fromArray(
|
|
entity.properties.destination.getValue(),
|
|
);
|
|
const orientationArray = entity.properties.orientation.getValue();
|
|
const flyToOrientation = new Cesium.HeadingPitchRoll(
|
|
orientationArray[0],
|
|
orientationArray[1],
|
|
orientationArray[2],
|
|
);
|
|
|
|
landmarkMenuEntries.push({
|
|
text: entity.properties.text.getValue(),
|
|
onselect: () =>
|
|
onSelectLandmark({
|
|
destination: flyToDestination,
|
|
orientation: flyToOrientation,
|
|
easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT,
|
|
maximumHeight: 5e6,
|
|
pitchAdjustHeight: 2.5e6,
|
|
duration: 3.0,
|
|
complete: function () {
|
|
viewer.selectedEntity = entity;
|
|
viewer.infoBox.viewModel.showInfo = true;
|
|
},
|
|
}),
|
|
});
|
|
});
|
|
|
|
Sandcastle.addToolbarMenu(landmarkMenuEntries);
|
|
} catch (error) {
|
|
console.log(`Error loading GeoJSON: ${error}`);
|
|
}
|
|
|
|
// Spin Mars on first load but disable the spinning upon any input
|
|
const rotationSpeed = Cesium.Math.toRadians(0.1);
|
|
const removeRotation = viewer.scene.postRender.addEventListener(
|
|
function (scene, time) {
|
|
viewer.scene.camera.rotateRight(rotationSpeed);
|
|
},
|
|
);
|
|
|
|
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|
handler.setInputAction(
|
|
() => removeRotation(),
|
|
Cesium.ScreenSpaceEventType.LEFT_DOWN,
|
|
);
|
|
handler.setInputAction(
|
|
() => removeRotation(),
|
|
Cesium.ScreenSpaceEventType.RIGHT_DOWN,
|
|
);
|
|
handler.setInputAction(
|
|
() => removeRotation(),
|
|
Cesium.ScreenSpaceEventType.MIDDLE_DOWN,
|
|
);
|
|
handler.setInputAction(() => removeRotation(), Cesium.ScreenSpaceEventType.WHEEL);
|
|
|
|
// For drawing attention to the play button in the animation view model
|
|
function highlightAnimationViewModel() {
|
|
if (clock.shouldAnimate) {
|
|
// Animation already playing
|
|
return;
|
|
}
|
|
|
|
const playPath =
|
|
viewer.animation.container.querySelector("#animation_pathPlay");
|
|
const playButton = playPath.closest("g.cesium-animation-rectButton");
|
|
const ringG = viewer.animation.container.querySelector(
|
|
".cesium-animation-shuttleRingG",
|
|
);
|
|
playButton.classList.add("highlight-animation");
|
|
ringG.classList.add("highlight-animation");
|
|
|
|
playButton.addEventListener("click", removeHighlight, { once: true });
|
|
setTimeout(removeHighlight, 30000); // Remove after 30 seconds if not clicked
|
|
}
|
|
|
|
function removeHighlight() {
|
|
const playPath =
|
|
viewer.animation.container.querySelector("#animation_pathPlay");
|
|
const playButton = playPath.closest("g.cesium-animation-rectButton");
|
|
const ringG = viewer.animation.container.querySelector(
|
|
".cesium-animation-shuttleRingG",
|
|
);
|
|
playButton.classList.remove("highlight-animation");
|
|
ringG.classList.remove("highlight-animation");
|
|
}
|
|
|
|
function reset() {
|
|
clock.multiplier = 1;
|
|
viewer.selectedEntity = undefined;
|
|
viewer.trackedEntity = undefined;
|
|
viewer.timeline.zoomTo(clock.startTime, clock.stopTime);
|
|
removeRotation();
|
|
removeHighlight();
|
|
}
|
|
|
|
// Add a listener for when the home button is clicked.
|
|
viewer.homeButton.viewModel.command.beforeExecute.addEventListener(
|
|
function (commandInfo) {
|
|
reset();
|
|
},
|
|
);
|
|
|
|
// When animating, if the multiplier is very high (which is necessary to see rover movement),
|
|
// model lighting flickers distractingly, so disable it
|
|
const entitiesToDisableLightingFor = [curiosity, perseverance, ingenuity];
|
|
Cesium.knockout
|
|
.getObservable(viewer.clockViewModel, "shouldAnimate")
|
|
.subscribe(function (shouldAnimate) {
|
|
if (shouldAnimate && clock.multiplier >= 100000) {
|
|
entitiesToDisableLightingFor.forEach(function (entity) {
|
|
entity.model.lightColor = new Cesium.Color(0, 0, 0);
|
|
});
|
|
} else {
|
|
entitiesToDisableLightingFor.forEach(function (entity) {
|
|
entity.model.lightColor = new Cesium.Color(1, 1, 1);
|
|
});
|
|
}
|
|
});
|
|
|
|
// To create a rectangle with text that conforms to the terrain, we can create a canvas
|
|
// with text and use it as a texture on a rectangle entity.
|
|
function createCanvasAsTexture(text) {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = 1024;
|
|
canvas.height = 256;
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
// Background
|
|
ctx.fillStyle = "rgba(0, 0, 0, 0)";
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Text
|
|
ctx.font = "36px sans-serif";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
ctx.strokeStyle = "rgba(0,0,0,0.1)";
|
|
ctx.lineWidth = 1;
|
|
ctx.fillStyle = "#ffffff";
|
|
|
|
ctx.strokeText(text, canvas.width / 2, canvas.height / 2);
|
|
ctx.fillText(text, canvas.width / 2, canvas.height / 2);
|
|
|
|
return canvas;
|
|
}
|
|
|
|
// Create the HTML that will be put into the info box that shows
|
|
// information about the currently selected feature
|
|
function createPickedFeatureDescription(entity) {
|
|
return `<img\
|
|
width="50%"\
|
|
style="float:left; margin: 0 1em 1em 0;"\
|
|
src=${entity.properties.imageURL}>\
|
|
<p>${entity.properties.description}</p>\
|
|
<p>\
|
|
Source: \
|
|
<a style="color: WHITE"\
|
|
target="_blank"\
|
|
href="${entity.properties.sourceURL}">${entity.properties.source}</a>\
|
|
</p>`;
|
|
}
|
|
|
|
// Inject instructions for interacting with the rovers into the navigation help menu
|
|
function addRoverInstructionsToNavMenu() {
|
|
const div = document.querySelector(
|
|
".cesium-click-navigation-help.cesium-navigation-help-instructions",
|
|
);
|
|
const table = div.querySelector("table");
|
|
|
|
const instructions1 = document.getElementById("roverHelpRowTemplate1");
|
|
const instructions1Clone = instructions1.content.cloneNode(true);
|
|
const img = instructions1Clone.querySelector("img[data-src]");
|
|
img.src = Cesium.buildModuleUrl(img.dataset.src);
|
|
table.tBodies[0].appendChild(instructions1Clone);
|
|
|
|
const instructions2 = document.getElementById("roverHelpRowTemplate2");
|
|
const instructions2Clone = instructions2.content.cloneNode(true);
|
|
table.tBodies[0].appendChild(instructions2Clone);
|
|
}
|
|
addRoverInstructionsToNavMenu();
|
|
//Sandcastle_End
|
|
};
|
|
if (typeof Cesium !== "undefined") {
|
|
window.startupCalled = true;
|
|
window.startup(Cesium).catch((error) => {
|
|
"use strict";
|
|
console.error(error);
|
|
});
|
|
Sandcastle.finishedLoading();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|