nginx.exe #启动运行
nginx -s stop #停止
nginx -s quit #退出
nginx -s reload #重新加载
二、配置Negix,加载3DTiles地图信息
路径修改为自己的路径
启动Nginx后请求:
http://localhost/out/tileset.json
或者 http://localhost:80/out/tileset.json
返回json
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log logs/access.log main;
error_log logs/error.log warn;
sendfile off;
tcp_nopush off;
keepalive_timeout 65;
server_tokens off;
# 移除了重复的json类型定义,保留唯一特殊3D Tiles类型
types {
application/octet-stream b3dm pnts i3dm cmpt;
model/gltf+json gltf;
model/gltf-binary glb;
}
server {
listen 80;
server_name 192.168.0.138;
root C:/obgs/jhdata;
# 全局CORS设置(带always确保所有响应都包含)
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
location /out/ {
alias C:/obgs/jhdata/out1/;
# OPTIONS预检请求处理
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000 always;
add_header 'Content-Type' 'text/plain; charset=utf-8' always;
add_header 'Content-Length' 0 always;
return 204;
}
# JSON文件单独处理(不再需要重复定义MIME类型)
location ~ \.json$ {
# 确保返回正确的Content-Type
default_type application/json;
}
}
}
}
三、编写html页面加载3dtiles内容
通过调用官网api和本地api俩种方式实现
官网api:
<script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
本地资源api
<script src="./Cesium-1.110.1/Build/Cesium/Cesium.js"></script>
<link href="./Cesium-1.110.1/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
Cesium官网下载地址
html代码如下:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>3D Tiles Viewertitle>
<script src="./Cesium-1.110.1/Build/Cesium/Cesium.js">script>
<link href="./Cesium-1.110.1/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
#cesiumContainer { width: 100%; height: 100%; }
.loading-overlay {
position: absolute; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.9); color: white;
display: flex; flex-direction: column;
justify-content: center; align-items: center;
z-index: 999; font-family: Arial, sans-serif;
backdrop-filter: blur(5px);
}
.progress-container {
width: 80%; max-width: 500px;
margin: 20px 0;
background: rgba(255,255,255,0.1);
border-radius: 10px; overflow: hidden;
}
#progressBar { height: 8px; background: linear-gradient(90deg, #4CAF50, #8BC34A); width: 0%;
transition: width 0.3s ease; }
#statusText { margin-top: 20px; text-align: center; }
.error { color: #ff6b6b; }
#debugInfo {
position: absolute; bottom: 10px; right: 10px;
background: rgba(0,0,0,0.7); padding: 8px;
border-radius: 4px; font-size: 12px;
max-width: 300px; display: none;
}
/* 控制面板样式 */
.control-panel {
position: absolute;
top: 20px;
right: 20px;
background: rgba(42, 42, 42, 0.8);
padding: 12px;
border-radius: 5px;
z-index: 1000;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
color: white;
}
.control-panel h3 {
margin: 0 0 10px 0;
padding: 0;
font-size: 16px;
text-align: center;
}
.control-btn {
background: #4CAF50;
color: white;
border: none;
padding: 8px 12px;
margin: 5px 0;
border-radius: 4px;
cursor: pointer;
width: 100%;
transition: background 0.3s;
}
.control-btn:hover {
background: #45a049;
}
.control-btn.active {
background: #2196F3;
}
.slider-container {
margin: 10px 0;
width: 100%;
}
.slider-container label {
display: block;
margin-bottom: 5px;
font-size: 12px;
}
.speed-slider {
width: 100%;
}
/* 操作提示面板优化 */
.hint-panel {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(42, 42, 42, 0.9);
padding: 12px 15px;
border-radius: 8px;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
backdrop-filter: blur(5px);
font-size: 14px;
line-height: 1.5;
color: #ffffff;
user-select: none;
pointer-events: none;
opacity: 0.9;
transition: opacity 0.3s ease;
}
.hint-panel:hover {
opacity: 1;
}
.hint-panel div {
margin: 6px 0;
display: flex;
align-items: center;
}
.hint-icon {
width: 20px;
height: 20px;
margin-right: 10px;
fill: #ffffff;
}
/* 指南针优化 */
.compass {
position: fixed;
top: 100px;
right: 25px;
width: 50px;
height: 50px;
background: rgba(42, 42, 42, 0.9);
border-radius: 50%;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.3s ease, background 0.3s ease;
backdrop-filter: blur(5px);
}
.compass:hover {
background: rgba(66, 66, 66, 0.9);
transform: scale(1.05);
}
.compass svg {
width: 80%;
height: 80%;
transform-origin: center;
transition: transform 0.2s ease;
}
.compass.auto-rotate {
animation: compassRotate 3s linear infinite;
}
@keyframes compassRotate {
from { transform: rotate(0deg) scale(1.05); }
to { transform: rotate(360deg) scale(1.05); }
}
.compass:hover svg {
transform: scale(1.1);
}
.compass.resetting {
animation: compassReset 0.5s ease-out;
}
@keyframes compassReset {
0% { transform: rotate(var(--current-rotation)) scale(1.1); }
100% { transform: rotate(0deg) scale(1.1); }
}
/* 鼠标拖动样式 */
.drag-cursor {
cursor: grab;
}
.dragging {
cursor: grabbing;
}
style>
head>
<body>
<div id="cesiumContainer">div>
<div class="loading-overlay">
<h1>3D 模型加载器h1>
<div class="progress-container">
<div id="progressBar">div>
div>
<div id="statusText">正在初始化...div>
<div id="debugInfo">div>
div>
<div class="control-panel" id="controlPanel">
<h3>模型控制h3>
<button class="control-btn" id="rotateToggle">开启旋转button>
<button class="control-btn" id="resetView">重置视角button>
<div class="slider-container">
<label for="rotationSpeed">旋转速度: <span id="speedValue">1.0span>xlabel>
<input type="range" min="0.1" max="3" step="0.1" value="1.0" class="speed-slider" id="rotationSpeed">
div>
div>
<div class="hint-panel">
<div><svg class="hint-icon" viewBox="0 0 24 24"><path d="M20 9c-.04-4.39-3.6-7.93-8-7.93S4.04 4.61 4 9v6c0 4.42 3.58 8 8 8s8-3.58 8-8V9zm-2 0h-5V3.16c2.81.47 4.96 2.9 5 5.84zm-7-5.84V9H6c.04-2.94 2.19-5.37 5-5.84zM18 15c0 3.31-2.69 6-6 6s-6-2.69-6-6v-4h12v4z"/>svg>左键拖动 - 旋转视角div>
<div><svg class="hint-icon" viewBox="0 0 24 24"><path d="M13 5.08c3.06.44 5.48 2.86 5.92 5.92h3.03c-.47-4.72-4.23-8.48-8.95-8.95v3.03zM18.92 13c-.44 3.06-2.86 5.48-5.92 5.92v3.03c4.72-.47 8.48-4.23 8.95-8.95h-3.03zM11 18.92c-3.39-.49-6-3.4-6-6.92s2.61-6.43 6-6.92V2.05c-5.05.5-9 4.76-9 9.9 0 5.15 3.95 9.4 9 9.9v-3.03z"/>svg>滚轮 - 缩放视角div>
<div><svg class="hint-icon" viewBox="0 0 24 24"><path d="M15 5l-1.41 1.41L18.17 11H8v2h10.17l-4.59 4.59L15 19l7-7-7-7zM4 5h4V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4v-2H4V5z"/>svg>中键拖动 - 平移视角div>
div>
<div class="compass" id="compass">
<svg viewBox="0 0 36 36">
<circle cx="18" cy="18" r="16" stroke="#ffffff" stroke-width="1" fill="none"/>
<path d="M18 6 L18 30 M18 6 L12 12 M18 6 L24 12" stroke="#ffffff" stroke-width="2" fill="none"/>
<text x="18" y="12" text-anchor="middle" font-size="8" fill="#ffffff">Ntext>
svg>
div>
<script>
// 配置参数
const config = {
// cesiumToken: '替换自己token,在线组件需要',
tilesetUrl: '替换自己json地址',
showDebug: true,
flyToDuration: 3.0,
initialRotation: 45, // 默认向右旋转45度
mouseSensitivity: 1 // 鼠标拖动灵敏度调节 (0.1-1.0)
};
// 初始化Viewer
class CesiumViewer {
constructor() {
this.viewer = null;
this.tileset = null;
this.loadingEl = document.querySelector('.loading-overlay');
this.progressEl = document.getElementById('progressBar');
this.statusEl = document.getElementById('statusText');
this.debugEl = document.getElementById('debugInfo');
this.isRotating = false;
this.rotationSpeed = 1.0;
this.rotateHandler = null;
this.initialView = null;
this.compassListener = null;
// 控制面板元素
this.rotateToggleBtn = document.getElementById('rotateToggle');
this.resetViewBtn = document.getElementById('resetView');
this.speedSlider = document.getElementById('rotationSpeed');
this.speedValueDisplay = document.getElementById('speedValue');
// 鼠标拖动相关变量
this.isDragging = false;
this.lastMousePosition = null;
this.previousAutoRotate = false;
// 指南针元素
this.compass = document.getElementById('compass');
this.init();
}
async init() {
try {
// 设置Ion令牌
Cesium.Ion.defaultAccessToken = config.cesiumToken;
// 初始化UI
this.updateStatus('正在加载Cesium引擎...', 10);
// 检查Cesium是否加载
if (!window.Cesium) throw new Error("Cesium库加载失败");
// 创建Viewer
this.updateStatus('正在创建3D场景...', 30);
this.createViewer();
// 检查并加载3D Tiles
await this.checkTileset();
await this.loadTileset();
// 完成加载
this.completeLoading();
// 设置控制面板事件
this.setupControls();
// 添加鼠标拖动控制
this.setupMouseDrag();
// 禁用默认的相机控制
this.disableDefaultCameraControls();
} catch (error) {
this.handleError(error);
}
}
disableDefaultCameraControls() {
// 禁用原始的左键拖动事件
// this.viewer.scene.screenSpaceCameraController.enableRotate = false;
// this.viewer.scene.screenSpaceCameraController.enableTilt = false;
// this.viewer.scene.screenSpaceCameraController.enableLook = false;
}
createViewer() {
// const terrainProvider = this.createTerrainProvider();
const terrainProvider = new Cesium.EllipsoidTerrainProvider();
this.viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: terrainProvider,
imageryProvider: false, // 不使用在线影像
// imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
// url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
// }),
animation: false,
baseLayerPicker: false,
fullscreenButton: true,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
shouldAnimate: true
});
// 移除默认影像层
this.viewer.imageryLayers.remove(this.viewer.imageryLayers.get(0));
// 设置鼠标悬停样式
this.viewer.scene.canvas.classList.add('drag-cursor');
this.viewer.scene.postProcessStages.fxaa.enabled = false;
this.viewer.scene.screenSpaceCameraController.zoomEventTypes = [Cesium.CameraEventType.WHEEL];
this.viewer.scene.screenSpaceCameraController.tiltEventTypes = [Cesium.CameraEventType.LEFT_DRAG];
this.viewer.scene.screenSpaceCameraController.rotateEventTypes = [Cesium.CameraEventType.RIGHT_DRAG];
}
createTerrainProvider() {
try {
if (typeof Cesium.createWorldTerrain === 'function') {
return Cesium.createWorldTerrain({
requestWaterMask: true,
requestVertexNormals: true
});
}
return new Cesium.EllipsoidTerrainProvider();
} catch (e) {
console.warn("地形初始化失败:", e);
return new Cesium.EllipsoidTerrainProvider();
}
}
async checkTileset() {
this.updateStatus('正在检查3D模型数据...', 50);
try {
const response = await fetch(config.tilesetUrl, {
method: 'GET',
mode: 'cors',
cache: 'no-cache'
});
if (!response.ok) throw new Error(`HTTP错误 (${response.status})`);
const data = await response.json();
if (!data.root) throw new Error("无效的tileset.json格式");
} catch (error) {
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
console.warn("CORS问题检测到,尝试非CORS模式加载");
return;
}
throw new Error(`模型检查失败: ${error.message}`);
}
}
async loadTileset() {
this.updateStatus('正在加载3D模型...', 70);
this.tileset = await Cesium.Cesium3DTileset.fromUrl(config.tilesetUrl, {
dynamicScreenSpaceError: true,
maximumScreenSpaceError: 2,
dynamicScreenSpaceErrorDensity: 0.00278,
dynamicScreenSpaceErrorFactor: 4.0,
dynamicScreenSpaceErrorHeightFalloff: 0.25,
cullWithChildrenBounds: true,
cullRequestsWhileMoving: true,
preloadFlightDestinations: false,
preloadWhenHidden: false,
preferLeaves: true
});
this.viewer.scene.primitives.add(this.tileset);
this.tileset.initialTilesLoaded.addEventListener(() => {
this.updateStatus('模型加载完成,准备渲染...', 90);
// 默认向右旋转60度
this.applyInitialRotation(config.initialRotation);
});
// 等待tileset准备就绪
await this.viewer.zoomTo(this.tileset, new Cesium.HeadingPitchRange(0, -0.5, 0), config.flyToDuration);
// 保存初始视角
this.initialView = {
position: this.viewer.camera.position.clone(),
heading: Cesium.Math.toRadians(config.initialRotation),
pitch: -0.5,
roll: 0
};
// 初始化指南针
this.updateCompass();
// 使用更可靠的tileset准备检测方式
await new Promise(resolve => {
const checkReady = () => {
if (this.tileset.boundingSphere) {
resolve();
} else {
setTimeout(checkReady, 100);
}
};
checkReady();
});
// 应用初始旋转
this.applyInitialRotation(config.initialRotation);
// 保存初始视角
this.initialView = {
position: this.viewer.camera.position.clone(),
heading: Cesium.Math.toRadians(config.initialRotation),
pitch: -0.5,
roll: 0
};
}
applyInitialRotation(degrees) {
if (!this.tileset) return;
const tryApplyRotation = () => {
try {
if (this.tileset.boundingSphere && this.tileset.boundingSphere.center) {
const radians = Cesium.Math.toRadians(degrees);
const boundingSphere = this.tileset.boundingSphere;
const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
boundingSphere.center
);
if (center) {
// 使用 rotate 而不是直接设置朝向
this.viewer.camera.rotate(center, radians);
return true;
}
}
return false;
} catch (e) {
console.warn("旋转应用失败:", e);
return false;
}
};
if (!tryApplyRotation()) {
const retryInterval = setInterval(() => {
if (tryApplyRotation()) {
clearInterval(retryInterval);
}
}, 100);
setTimeout(() => clearInterval(retryInterval), 15000);
}
}
setupControls() {
const self = this;
// 旋转开关
this.rotateToggleBtn.addEventListener('click', () => {
self.toggleRotation();
});
// 重置视角
this.resetViewBtn.addEventListener('click', () => {
self.resetView();
self.compass.classList.remove('auto-rotate');
});
// 旋转速度调节
this.speedSlider.addEventListener('input', (e) => {
self.rotationSpeed = parseFloat(e.target.value);
self.speedValueDisplay.textContent = self.rotationSpeed.toFixed(1);
if (self.isRotating) {
self.stopRotation();
self.startRotation();
}
});
// 指南针点击事件
this.compass.addEventListener('click', () => this.resetToNorth());
this.compass.addEventListener('touchstart', (e) => {
e.preventDefault();
this.resetToNorth();
});
}
updateCompass() {
if (!this.viewer) return;
// 移除之前的监听器
if (this.compassListener) {
this.viewer.scene.postRender.removeEventListener(this.compassListener);
}
this.compassListener = this.viewer.scene.postRender.addEventListener(() => {
if (this.viewer && this.viewer.camera) {
// 获取当前相机朝向角度(弧度)
const heading = this.viewer.camera.heading;
// 转换为角度并取反(因为指南针需要反向旋转)
const degrees = -Cesium.Math.toDegrees(heading);
// 设置自定义属性用于复位动画
this.compass.style.setProperty('--current-rotation', `${degrees}deg`);
// 如果不是自动旋转状态,更新指南针角度
if (!this.isRotating) {
this.compass.style.transform = `rotate(${degrees}deg)`;
}
}
});
}
resetToNorth() {
if (!this.viewer || !this.tileset) return;
this.stopRotation();
this.compass.classList.remove('auto-rotate');
this.compass.classList.add('resetting');
// 计算模型中心点
const boundingSphere = this.tileset.boundingSphere;
const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
boundingSphere.center
);
// 重置朝向正北
this.viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(
0, // 朝向正北
this.viewer.camera.pitch, // 保持当前俯仰角
this.viewer.camera.positionCartographic.height // 保持当前高度
));
// 动画结束后移除复位状态
setTimeout(() => {
this.compass.classList.remove('resetting');
}, 500);
}
setupMouseDrag() {
const scene = this.viewer.scene;
const canvas = scene.canvas;
// 新增惯性效果相关变量
this.inertia = {
enabled: true,
friction: 0.90, // 摩擦系数 (0.9 = 10%速度损失/帧)
velocityX: 0,
velocityY: 0,
lastTime: 0
};
// 鼠标按下事件
canvas.addEventListener('mousedown', (e) => {
if (e.button !== 0) return;
// 停止任何惯性动画
cancelAnimationFrame(this.inertia.animationFrame);
if (this.isRotating) {
this.previousAutoRotate = true;
this.stopRotation();
this.compass.classList.remove('auto-rotate');
} else {
this.previousAutoRotate = false;
}
this.isDragging = true;
document.body.classList.add('dragging');
this.lastMousePosition = new Cesium.Cartesian2(e.clientX, e.clientY);
// 重置惯性速度
this.inertia.velocityX = 0;
this.inertia.velocityY = 0;
this.inertia.lastTime = performance.now();
e.preventDefault();
}, false);
// 鼠标移动事件
canvas.addEventListener('mousemove', (e) => {
if (!this.isDragging) return;
const currentTime = performance.now();
const timeDelta = currentTime - this.inertia.lastTime;
this.inertia.lastTime = currentTime;
const currentPosition = new Cesium.Cartesian2(e.clientX, e.clientY);
const movement = new Cesium.Cartesian2();
Cesium.Cartesian2.subtract(currentPosition, this.lastMousePosition, movement);
// 计算当前速度 (像素/毫秒)
if (timeDelta > 0) {
this.inertia.velocityX = movement.x / timeDelta;
this.inertia.velocityY = movement.y / timeDelta;
}
this.rotateCamera(movement.x, movement.y);
this.lastMousePosition = currentPosition;
}, false);
// 鼠标释放事件 - 添加惯性动画
document.addEventListener('mouseup', (e) => {
if (!this.isDragging || e.button !== 0) return;
this.isDragging = false;
document.body.classList.remove('dragging');
// 如果有足够速度,启动惯性动画
if (this.inertia.enabled &&
(Math.abs(this.inertia.velocityX) > 0.1 || Math.abs(this.inertia.velocityY) > 0.1)) {
this.startInertiaAnimation();
}
// 恢复自动旋转
if (this.previousAutoRotate) {
this.startRotation();
this.compass.classList.add('auto-rotate');
}
}, false);
// 鼠标移出时停止
canvas.addEventListener('mouseleave', () => {
if (this.isDragging) {
this.isDragging = false;
document.body.classList.remove('dragging');
cancelAnimationFrame(this.inertia.animationFrame);
}
}, false);
// 鼠标悬停样式
canvas.addEventListener('mouseenter', () => {
if (!this.isDragging) {
document.body.classList.add('drag-cursor');
}
});
canvas.addEventListener('mouseleave', () => {
if (!this.isDragging) {
document.body.classList.remove('drag-cursor');
}
});
}
// 新增旋转相机方法
rotateCamera(deltaX, deltaY) {
if (!this.tileset || !this.tileset.boundingSphere) return;
try {
const boundingSphere = this.tileset.boundingSphere;
const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
boundingSphere.center
);
if (!center) return;
// 计算新的朝向角度(考虑当前角度)
const headingDelta = -deltaX * 0.002 * config.mouseSensitivity;
const pitchDelta = -deltaY * 0.002 * config.mouseSensitivity;
let newHeading = this.viewer.camera.heading + headingDelta;
let newPitch = this.viewer.camera.pitch + pitchDelta;
// 限制俯仰角范围 (-85° 到 +85°)
const maxPitch = Cesium.Math.toRadians(85);
const minPitch = Cesium.Math.toRadians(-85);
newPitch = Cesium.Math.clamp(newPitch, minPitch, maxPitch);
// 获取当前相机位置
const position = this.viewer.camera.positionCartographic;
// 计算新的相机视角
this.viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(
Cesium.Math.toDegrees(position.longitude),
Cesium.Math.toDegrees(position.latitude),
position.height
),
orientation: {
heading: newHeading,
pitch: newPitch,
roll: this.viewer.camera.roll
}
});
} catch (e) {
console.warn("旋转相机失败:", e);
}
}
// 新增惯性动画方法
startInertiaAnimation() {
const animate = () => {
// 应用速度衰减
this.inertia.velocityX *= this.inertia.friction;
this.inertia.velocityY *= this.inertia.friction;
// 如果速度足够小,停止动画
if (Math.abs(this.inertia.velocityX) < 0.01 && Math.abs(this.inertia.velocityY) < 0.01) {
return;
}
// 计算基于时间的移动量
const currentTime = performance.now();
const timeDelta = currentTime - this.inertia.lastTime;
this.inertia.lastTime = currentTime;
const deltaX = this.inertia.velocityX * timeDelta;
const deltaY = this.inertia.velocityY * timeDelta;
this.rotateCamera(deltaX, deltaY);
// 继续动画
this.inertia.animationFrame = requestAnimationFrame(animate);
};
this.inertia.lastTime = performance.now();
this.inertia.animationFrame = requestAnimationFrame(animate);
}
toggleRotation() {
this.isRotating ? this.stopRotation() : this.startRotation();
this.rotateToggleBtn.textContent = this.isRotating ? "停止旋转" : "开启旋转";
this.rotateToggleBtn.classList.toggle('active');
this.compass.classList.toggle('auto-rotate', this.isRotating);
}
startRotation() {
if (!this.tileset || this.isRotating) return;
this.isRotating = true;
this.rotateToggleBtn.textContent = "停止旋转";
this.rotateToggleBtn.classList.add('active');
const boundingSphere = this.tileset.boundingSphere;
const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
boundingSphere.center
);
this.rotateHandler = this.viewer.clock.onTick.addEventListener(() => {
if (this.isRotating && center) {
const angle = Cesium.Math.toRadians(0.3 * this.rotationSpeed);
this.viewer.camera.rotate(center, angle);
}
});
this.compass.classList.add('auto-rotate');
}
stopRotation() {
if (!this.isRotating) return;
this.isRotating = false;
this.rotateToggleBtn.textContent = "开启旋转";
this.rotateToggleBtn.classList.remove('active');
if (this.rotateHandler) {
this.viewer.clock.onTick.removeEventListener(this.rotateHandler);
this.rotateHandler = null;
}
this.compass.classList.remove('auto-rotate');
}
resetView() {
this.stopRotation();
if (this.initialView) {
this.viewer.camera.flyTo({
destination: this.initialView.position,
orientation: {
heading: this.initialView.heading,
pitch: this.initialView.pitch,
roll: this.initialView.roll
},
duration: 1.0
});
}
}
updateStatus(message, progress) {
this.statusEl.textContent = message;
if (progress !== undefined) {
this.progressEl.style.width = `${progress}%`;
}
if (config.showDebug) {
this.debugEl.innerHTML = `
状态: ${message}
进度: ${progress || 0}%
3D Tiles: ${this.tileset ? '已加载' : '未加载'}
`;
this.debugEl.style.display = 'block';
}
}
completeLoading() {
this.updateStatus('加载完成', 100);
setTimeout(() => {
this.loadingEl.style.opacity = '0';
setTimeout(() => {
this.loadingEl.style.display = 'none';
}, 500);
}, 1000);
}
handleError(error) {
console.error('加载失败:', error);
this.statusEl.innerHTML = `
错误: ${error.message}
建议操作:
- 检查服务器是否运行
- 确认CORS配置正确
- 查看控制台获取详细信息
`;
this.progressEl.style.background = '#ff5252';
const retryBtn = document.createElement('button');
retryBtn.textContent = '重试加载';
retryBtn.style.marginTop = '15px';
retryBtn.style.padding = '8px 16px';
retryBtn.style.borderRadius = '4px';
retryBtn.style.border = 'none';
retryBtn.style.background = '#4CAF50';
retryBtn.style.color = 'white';
retryBtn.style.cursor = 'pointer';
retryBtn.onclick = () => window.location.reload();
this.statusEl.appendChild(retryBtn);
if (config.showDebug) {
this.debugEl.innerHTML += `
错误详情: ${error.message}
堆栈: ${error.stack || '无堆栈信息'}
`;
this.debugEl.style.display = 'block';
}
}
}
document.addEventListener('DOMContentLoaded', () => {
new CesiumViewer();
});
script>
body>
html>
详细代码及组件已上传