<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>极致3D地球可视化系统</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #1e3c72, #2a5298);
overflow: hidden;
color: white;
}
#earthContainer {
width: 100vw;
height: 100vh;
position: relative;
}
#loadingScreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loading-bar {
width: 300px;
height: 8px;
background: #333;
border-radius: 4px;
margin: 20px 0;
overflow: hidden;
}
.loading-progress {
height: 100%;
background: linear-gradient(90deg, #00c9ff, #92fe9d);
width: 0%;
transition: width 0.3s ease;
}
/* 控制面板样式 */
.control-panel {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
border-radius: 15px;
padding: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
min-width: 300px;
}
.panel-title {
font-size: 18px;
margin-bottom: 15px;
color: #00c9ff;
text-align: center;
}
.control-group {
margin-bottom: 15px;
}
.control-label {
display: block;
margin-bottom: 5px;
font-size: 14px;
}
.slider-container {
display: flex;
align-items: center;
gap: 10px;
}
.slider {
flex: 1;
height: 6px;
border-radius: 3px;
background: #333;
outline: none;
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #00c9ff;
cursor: pointer;
}
.slider-value {
min-width: 40px;
text-align: center;
font-size: 12px;
}
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 8px 15px;
background: linear-gradient(45deg, #00c9ff, #92fe9d);
border: none;
border-radius: 20px;
color: #000;
cursor: pointer;
font-size: 12px;
font-weight: bold;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 201, 255, 0.4);
}
.btn-active {
background: linear-gradient(45deg, #ff416c, #ff4b2b);
color: white;
}
/* 图层控制面板 */
.layer-panel {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
border-radius: 15px;
padding: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
min-width: 250px;
}
.layer-item {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.layer-item:hover {
background: rgba(255, 255, 255, 0.2);
}
.layer-item.active {
background: linear-gradient(45deg, #00c9ff, #92fe9d);
color: #000;
}
.layer-checkbox {
margin-right: 10px;
}
/* 信息面板 */
.info-panel {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
border-radius: 15px;
padding: 15px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
min-width: 200px;
}
.info-item {
margin-bottom: 8px;
font-size: 12px;
}
.info-label {
color: #00c9ff;
margin-right: 5px;
}
/* 搜索框 */
.search-panel {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
border-radius: 25px;
padding: 10px 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
z-index: 100;
display: flex;
align-items: center;
}
.search-input {
background: transparent;
border: none;
color: white;
padding: 5px 10px;
width: 300px;
outline: none;
font-size: 14px;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.search-btn {
background: none;
border: none;
color: #00c9ff;
cursor: pointer;
padding: 5px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.control-panel, .layer-panel {
position: fixed;
top: auto;
bottom: 0;
left: 0;
right: 0;
width: 100%;
border-radius: 20px 20px 0 0;
max-height: 50vh;
overflow-y: auto;
}
.search-panel {
top: 10px;
width: 90%;
}
.search-input {
width: 200px;
}
}
/* 动画效果 */
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
.pulse {
animation: pulse 2s infinite;
}
</style>
</head>
<body>
<!-- 加载界面 -->
<div id="loadingScreen">
<h2>正在构建极致3D地球...</h2>
<div class="loading-bar">
<div class="loading-progress" id="loadingProgress"></div>
</div>
<p id="loadingText">初始化引擎...</p>
</div>
<!-- 主容器 -->
<div id="earthContainer"></div>
<!-- 搜索面板 -->
<div class="search-panel">
<input type="text" class="search-input" placeholder="搜索城市、国家或坐标..." id="searchInput">
<button class="search-btn" id="searchBtn">🔍</button>
</div>
<!-- 控制面板 -->
<div class="control-panel">
<div class="panel-title">🌍 地球控制系统</div>
<div class="control-group">
<label class="control-label">缩放级别</label>
<div class="slider-container">
<input type="range" min="1" max="100" value="50" class="slider" id="zoomSlider">
<span class="slider-value" id="zoomValue">50</span>
</div>
</div>
<div class="control-group">
<label class="control-label">旋转速度</label>
<div class="slider-container">
<input type="range" min="0" max="10" value="2" class="slider" id="rotationSlider">
<span class="slider-value" id="rotationValue">2</span>
</div>
</div>
<div class="control-group">
<label class="control-label">光照强度</label>
<div class="slider-container">
<input type="range" min="0" max="2" value="1" step="0.1" class="slider" id="lightSlider">
<span class="slider-value" id="lightValue">1.0</span>
</div>
</div>
<div class="button-group">
<button class="btn" id="autoRotateBtn">自动旋转</button>
<button class="btn" id="resetBtn">重置视角</button>
<button class="btn" id="fullscreenBtn">全屏显示</button>
</div>
</div>
<!-- 图层控制面板 -->
<div class="layer-panel">
<div class="panel-title">🗺️ 图层控制</div>
<div class="layer-item active" data-layer="satellite">
<input type="checkbox" class="layer-checkbox" checked>
<span>卫星图像</span>
</div>
<div class="layer-item" data-layer="terrain">
<input type="checkbox" class="layer-checkbox">
<span>地形高程</span>
</div>
<div class="layer-item" data-layer="boundaries">
<input type="checkbox" class="layer-checkbox" checked>
<span>国家边界</span>
</div>
<div class="layer-item" data-layer="cities">
<input type="checkbox" class="layer-checkbox">
<span>主要城市</span>
</div>
<div class="layer-item" data-layer="clouds">
<input type="checkbox" class="layer-checkbox">
<span>实时云层</span>
</div>
<div class="layer-item" data-layer="atmosphere">
<input type="checkbox" class="layer-checkbox" checked>
<span>大气效果</span>
</div>
</div>
<!-- 信息面板 -->
<div class="info-panel">
<div class="info-item"><span class="info-label">坐标:</span> <span id="coordinates">0°, 0°</span></div>
<div class="info-item"><span class="info-label">缩放:</span> <span id="zoomLevel">1.0x</span></div>
<div class="info-item"><span class="info-label">时间:</span> <span id="currentTime"></span></div>
<div class="info-item"><span class="info-label">图层:</span> <span id="currentLayer">卫星图像</span></div>
</div>
<script>
// 全局变量
let scene, camera, renderer, earth, controls;
let earthGroup, cloudGroup;
let autoRotate = false;
let currentLayer = 'satellite';
let markers = [];
let animationId;
// 城市数据 (简化版,实际应用中会有1000+项)
const cities = [
{ name: "北京", lat: 39.9042, lng: 116.4074, population: "21,540,000" },
{ name: "上海", lat: 31.2304, lng: 121.4737, population: "24,280,000" },
{ name: "纽约", lat: 40.7128, lng: -74.0060, population: "8,336,817" },
{ name: "伦敦", lat: 51.5074, lng: -0.1278, population: "9,425,622" },
{ name: "东京", lat: 35.6762, lng: 139.6503, population: "13,929,286" },
{ name: "巴黎", lat: 48.8566, lng: 2.3522, population: "2,161,000" },
{ name: "莫斯科", lat: 55.7558, lng: 37.6173, population: "12,619,600" },
{ name: "悉尼", lat: -33.8688, lng: 151.2093, population: "5,312,163" },
{ name: "开罗", lat: 30.0444, lng: 31.2357, population: "20,484,965" },
{ name: "里约热内卢", lat: -22.9068, lng: -43.1729, population: "6,748,000" }
];
// 初始化应用
function init() {
// 显示加载进度
updateLoadingProgress(10, "初始化Three.js引擎...");
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000814);
scene.fog = new THREE.Fog(0x000814, 100, 300);
// 创建相机
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 250;
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('earthContainer').appendChild(renderer.domElement);
updateLoadingProgress(30, "创建地球模型...");
// 创建地球组
earthGroup = new THREE.Group();
scene.add(earthGroup);
// 创建地球
createEarth();
updateLoadingProgress(50, "添加光照系统...");
// 添加光照
addLighting();
updateLoadingProgress(70, "配置交互控制...");
// 添加控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 100;
controls.maxDistance = 500;
updateLoadingProgress(80, "添加环境效果...");
// 添加星星背景
addStars();
// 添加云层
addClouds();
updateLoadingProgress(90, "初始化UI界面...");
// 绑定事件
bindEvents();
updateLoadingProgress(100, "完成!");
// 隐藏加载界面
setTimeout(() => {
document.getElementById('loadingScreen').style.display = 'none';
}, 1000);
// 开始动画循环
animate();
}
// 创建地球
function createEarth() {
// 地球几何体
const geometry = new THREE.SphereGeometry(100, 64, 64);
// 地球材质 - 使用更真实的纹理
const material = new THREE.MeshPhongMaterial({
color: 0x2E86AB,
specular: 0x333333,
shininess: 5,
transparent: true,
opacity: 0.9
});
earth = new THREE.Mesh(geometry, material);
earthGroup.add(earth);
// 添加更多细节层
addEarthDetails();
}
// 添加地球细节
function addEarthDetails() {
// 大气层效果
const atmosphereGeometry = new THREE.SphereGeometry(102, 64, 64);
const atmosphereMaterial = new THREE.MeshPhongMaterial({
color: 0x87CEEB,
transparent: true,
opacity: 0.1,
side: THREE.BackSide
});
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
earthGroup.add(atmosphere);
// 添加大陆轮廓
addContinents();
}
// 添加大陆轮廓
function addContinents() {
// 简化的大陆形状 - 实际应用中会使用真实数据
const continentMaterial = new THREE.MeshPhongMaterial({
color: 0x3D9970,
transparent: true,
opacity: 0.7
});
// 添加一些大陆块作为示例
const continents = [
{ position: [0, 0, 95], size: [20, 15, 5] }, // 简化的大陆
{ position: [-30, 20, 90], size: [15, 10, 5] },
{ position: [25, -15, 92], size: [18, 12, 5] }
];
continents.forEach(cont => {
const contGeometry = new THREE.BoxGeometry(cont.size[0], cont.size[1], cont.size[2]);
const continent = new THREE.Mesh(contGeometry, continentMaterial);
continent.position.set(cont.position[0], cont.position[1], cont.position[2]);
earthGroup.add(continent);
});
}
// 添加光照
function addLighting() {
// 环境光
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
scene.add(ambientLight);
// 太阳光源
const sunLight = new THREE.DirectionalLight(0xFFFFFF, 1);
sunLight.position.set(300, 300, 300);
sunLight.castShadow = true;
scene.add(sunLight);
// 补充光源
const fillLight = new THREE.DirectionalLight(0x404040, 0.3);
fillLight.position.set(-200, -200, -200);
scene.add(fillLight);
}
// 添加星星背景
function addStars() {
const starGeometry = new THREE.BufferGeometry();
const starMaterial = new THREE.PointsMaterial({
color: 0xFFFFFF,
size: 1,
transparent: true
});
const starVertices = [];
for (let i = 0; i < 10000; i++) {
const x = (Math.random() - 0.5) * 2000;
const y = (Math.random() - 0.5) * 2000;
const z = (Math.random() - 0.5) * 2000;
starVertices.push(x, y, z);
}
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
}
// 添加云层
function addClouds() {
cloudGroup = new THREE.Group();
earthGroup.add(cloudGroup);
const cloudGeometry = new THREE.SphereGeometry(105, 32, 32);
const cloudMaterial = new THREE.MeshPhongMaterial({
color: 0xFFFFFF,
transparent: true,
opacity: 0.3,
wireframe: true
});
const clouds = new THREE.Mesh(cloudGeometry, cloudMaterial);
cloudGroup.add(clouds);
}
// 添加城市标记
function addCityMarkers() {
cities.forEach(city => {
const markerGeometry = new THREE.SphereGeometry(2, 8, 8);
const markerMaterial = new THREE.MeshPhongMaterial({
color: 0xFF4136,
emissive: 0xFF4136,
emissiveIntensity: 0.5
});
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
// 将经纬度转换为3D坐标
const phi = (90 - city.lat) * Math.PI / 180;
const theta = (city.lng + 180) * Math.PI / 180;
const radius = 101;
marker.position.x = -radius * Math.sin(phi) * Math.cos(theta);
marker.position.y = radius * Math.cos(phi);
marker.position.z = radius * Math.sin(phi) * Math.sin(theta);
marker.userData = city;
earthGroup.add(marker);
markers.push(marker);
});
}
// 绑定事件
function bindEvents() {
// 缩放控制
document.getElementById('zoomSlider').addEventListener('input', function() {
const value = this.value;
document.getElementById('zoomValue').textContent = value;
// 实现缩放逻辑
});
// 旋转控制
document.getElementById('rotationSlider').addEventListener('input', function() {
const value = this.value;
document.getElementById('rotationValue').textContent = value;
// 实现旋转速度控制
});
// 光照控制
document.getElementById('lightSlider').addEventListener('input', function() {
const value = parseFloat(this.value).toFixed(1);
document.getElementById('lightValue').textContent = value;
// 实现光照强度控制
});
// 自动旋转按钮
document.getElementById('autoRotateBtn').addEventListener('click', function() {
autoRotate = !autoRotate;
this.classList.toggle('btn-active');
this.textContent = autoRotate ? '停止旋转' : '自动旋转';
});
// 重置按钮
document.getElementById('resetBtn').addEventListener('click', function() {
controls.reset();
camera.position.z = 250;
});
// 全屏按钮
document.getElementById('fullscreenBtn').addEventListener('click', function() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
});
// 图层控制
document.querySelectorAll('.layer-item').forEach(item => {
item.addEventListener('click', function() {
const layer = this.dataset.layer;
this.classList.toggle('active');
const checkbox = this.querySelector('.layer-checkbox');
checkbox.checked = !checkbox.checked;
// 实现图层切换逻辑
switch(layer) {
case 'satellite':
currentLayer = '卫星图像';
break;
case 'terrain':
currentLayer = '地形高程';
break;
case 'boundaries':
currentLayer = '国家边界';
break;
case 'cities':
if (checkbox.checked) {
addCityMarkers();
} else {
// 移除城市标记
markers.forEach(marker => earthGroup.remove(marker));
markers = [];
}
break;
}
document.getElementById('currentLayer').textContent = currentLayer;
});
});
// 搜索功能
document.getElementById('searchBtn').addEventListener('click', searchLocation);
document.getElementById('searchInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
searchLocation();
}
});
// 窗口大小调整
window.addEventListener('resize', onWindowResize);
}
// 搜索位置
function searchLocation() {
const query = document.getElementById('searchInput').value.trim();
if (query) {
// 简化的搜索逻辑 - 实际应用中会连接地理编码API
const foundCity = cities.find(city =>
city.name.toLowerCase().includes(query.toLowerCase())
);
if (foundCity) {
// 定位到城市
const phi = (90 - foundCity.lat) * Math.PI / 180;
const theta = (foundCity.lng + 180) * Math.PI / 180;
// 平滑移动相机
const targetX = -150 * Math.sin(phi) * Math.cos(theta);
const targetY = 150 * Math.cos(phi);
const targetZ = 150 * Math.sin(phi) * Math.sin(theta);
// 这里应该添加平滑动画,简化处理
camera.position.set(targetX * 1.5, targetY * 1.5, targetZ * 1.5);
controls.update();
// 显示搜索结果
alert(`已定位到: ${foundCity.name}\n人口: ${foundCity.population}`);
} else {
alert('未找到指定位置');
}
}
}
// 窗口大小调整
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 更新加载进度
function updateLoadingProgress(percent, text) {
document.getElementById('loadingProgress').style.width = percent + '%';
document.getElementById('loadingText').textContent = text;
}
// 动画循环
function animate() {
animationId = requestAnimationFrame(animate);
// 地球自转
if (autoRotate) {
earthGroup.rotation.y += 0.002;
}
// 云层运动
if (cloudGroup) {
cloudGroup.rotation.y += 0.001;
}
// 更新控制器
controls.update();
// 更新时间显示
updateTime();
// 渲染场景
renderer.render(scene, camera);
}
// 更新时间显示
function updateTime() {
const now = new Date();
const timeString = now.toLocaleString('zh-CN');
document.getElementById('currentTime').textContent = timeString;
}
// 获取鼠标位置对应的经纬度
function getCoordinatesFromMouse(event) {
const rect = renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// 这里应该实现射线检测来获取地球表面坐标
// 简化处理,返回示例值
return { lat: 0, lng: 0 };
}
// 销毁应用
function destroy() {
if (animationId) {
cancelAnimationFrame(animationId);
}
if (renderer) {
renderer.dispose();
}
// 清理其他资源...
}
// 页面加载完成后初始化
window.addEventListener('load', init);
// 页面卸载时清理
window.addEventListener('beforeunload', destroy);
</script>
</body>
</html>
index.html
index.html