You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>风速剖面三维图</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
background: linear-gradient(to bottom, #1a237e, #283593);
color: white;
}
#container {
position: relative;
width: 100vw;
height: 100vh;
}
#info-panel {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
max-width: 300px;
z-index: 100;
}
#controls {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
z-index: 100;
}
button {
background: #3949ab;
color: white;
border: none;
padding: 8px 15px;
margin: 5px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #5c6bc0;
}
.axis-label {
position: absolute;
color: white;
font-size: 14px;
z-index: 10;
}
#time-label {
bottom: 50px;
left: 50%;
transform: translateX(-50%);
}
#height-label {
top: 50%;
left: 30px;
transform: translateY(-50%) rotate(-90deg);
}
#wind-label {
top: 20px;
right: 30px;
}
.legend {
position: absolute;
right: 20px;
bottom: 100px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 8px;
width: 150px;
}
.legend-item {
display: flex;
align-items: center;
margin: 5px 0;
}
.legend-color {
width: 20px;
height: 20px;
margin-right: 10px;
border-radius: 3px;
}
</style>
</head>
<body>
<div id="container">
<div id="info-panel">
<h3>风速剖面数据</h3>
<p>日期: 2018-10-05 03:00:00</p>
<p>数据来源: CMA</p>
<p>参数: CAMP, MULTI, BRAZY, HONOR, PERSONAL, SERVICE, VOLUME, VARIABILITY</p>
</div>
<div id="controls">
<button id="reset-view">重置视图</button>
<button id="auto-rotate">自动旋转</button>
<button id="toggle-labels">切换标签</button>
</div>
<div class="axis-label" id="time-label">时间轴</div>
<div class="axis-label" id="height-label">高度轴 (m)</div>
<div class="axis-label" id="wind-label">风速 (m/s)</div>
<div class="legend">
<h4>风速图例</h4>
<div class="legend-item">
<div class="legend-color" style="background-color: #00b0ff;"></div>
<span>0-5 m/s</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #00e676;"></div>
<span>5-10 m/s</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ffeb3b;"></div>
<span>10-15 m/s</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #ff9800;"></div>
<span>15-20 m/s</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #f44336;"></div>
<span>20+ m/s</span>
</div>
</div>
</div>
<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.js"></script>
<script>
// 初始化场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a237e);
// 初始化相机 - 调整视角以更好地查看贴地效果
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(20, 15, 30);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('container').appendChild(renderer.domElement);
// 添加轨道控制器
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 50, 50);
scene.add(directionalLight);
// 创建坐标轴
const axesHelper = new THREE.AxesHelper(20);
scene.add(axesHelper);
// 生成模拟风速数据
function generateWindData() {
const data = [];
const timePoints = 24; // 24小时
const heightPoints = 20; // 20个高度点
for (let t = 0; t < timePoints; t++) {
const timeData = [];
for (let h = 0; h < heightPoints; h++) {
// 模拟风速数据,随时间和高度变化
const baseWind = 5 + Math.sin(t / 3) * 3;
const heightEffect = h * 0.5;
const turbulence = Math.sin(t * 0.5 + h * 0.3) * 2;
const windSpeed = baseWind + heightEffect + turbulence;
timeData.push(Math.max(0, windSpeed));
}
data.push(timeData);
}
return data;
}
// 根据风速获取颜色
function getWindColor(speed) {
if (speed < 5) return new THREE.Color(0x00b0ff);
if (speed < 10) return new THREE.Color(0x00e676);
if (speed < 15) return new THREE.Color(0xffeb3b);
if (speed < 20) return new THREE.Color(0xff9800);
return new THREE.Color(0xf44336);
}
// 创建风速剖面可视化
function createWindProfile(data) {
const group = new THREE.Group();
// 创建地面网格平面作为参考
const gridHelper = new THREE.GridHelper(30, 24, 0x444444, 0x222222);
scene.add(gridHelper);
// 创建风速曲面
const geometry = new THREE.BufferGeometry();
const vertices = [];
const colors = [];
const indices = [];
const timePoints = data.length;
const heightPoints = data[0].length;
// 创建顶点和颜色 - 调整坐标系使图形贴合地面
for (let t = 0; t < timePoints; t++) {
for (let h = 0; h < heightPoints; h++) {
const x = t - timePoints / 2; // 时间轴
const z = h * 1.5; // 高度轴 (沿Z轴向上)
const y = data[t][h] * 0.5; // 风速轴 (沿Y轴延伸贴近地面)
vertices.push(x, y, z);
const color = getWindColor(data[t][h]);
colors.push(color.r, color.g, color.b);
}
}
// 创建索引(三角形)
for (let t = 0; t < timePoints - 1; t++) {
for (let h = 0; h < heightPoints - 1; h++) {
const a = t * heightPoints + h;
const b = t * heightPoints + h + 1;
const c = (t + 1) * heightPoints + h;
const d = (t + 1) * heightPoints + h + 1;
indices.push(a, b, d);
indices.push(a, d, c);
}
}
geometry.setIndex(indices);
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
// 计算法向量以获得更好的光照效果
geometry.computeVertexNormals();
const material = new THREE.MeshLambertMaterial({
vertexColors: true,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.8
});
const mesh = new THREE.Mesh(geometry, material);
group.add(mesh);
// 添加数据点标记
for (let t = 0; t < timePoints; t += 3) {
for (let h = 0; h < heightPoints; h += 3) {
const x = t - timePoints / 2;
const z = h * 1.5;
const y = data[t][h] * 0.5;
const sphereGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const sphereMaterial = new THREE.MeshBasicMaterial({
color: getWindColor(data[t][h])
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(x, y, z);
group.add(sphere);
}
}
scene.add(group);
return group;
}
// 生成数据并创建可视化
const windData = generateWindData();
const windProfile = createWindProfile(windData);
// 添加坐标轴标签
function addAxisLabels() {
// 时间轴标签
for (let i = 0; i <= 24; i += 6) {
const text = document.createElement('div');
text.className = 'axis-label';
text.style.position = 'absolute';
text.style.bottom = '30px';
text.style.left = `${50 + (i - 12) * 10}px`;
text.textContent = `${i}:00`;
document.getElementById('container').appendChild(text);
}
// 高度轴标签
for (let i = 0; i <= 30; i += 10) {
const text = document.createElement('div');
text.className = 'axis-label';
text.style.position = 'absolute';
text.style.top = `${50 + i * 5}px`;
text.style.left = '10px';
text.textContent = `${i * 100}m`;
document.getElementById('container').appendChild(text);
}
}
addAxisLabels();
// 动画循环
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
// 窗口大小调整
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 控制按钮功能
document.getElementById('reset-view').addEventListener('click', () => {
controls.reset();
});
let autoRotate = false;
document.getElementById('auto-rotate').addEventListener('click', () => {
autoRotate = !autoRotate;
controls.autoRotate = autoRotate;
document.getElementById('auto-rotate').textContent =
autoRotate ? '停止旋转' : '自动旋转';
});
let labelsVisible = true;
document.getElementById('toggle-labels').addEventListener('click', () => {
labelsVisible = !labelsVisible;
const labels = document.querySelectorAll('.axis-label');
labels.forEach(label => {
if (!label.id) { // 只切换数据标签,不切换轴标题
label.style.display = labelsVisible ? 'block' : 'none';
}
});
document.getElementById('toggle-labels').textContent =
labelsVisible ? '隐藏标签' : '显示标签';
});
</script>
</body>
</html>