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.

2013 lines
73 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 全局变量
let currentModule = 'data-reception';
let dataStreamInterval;
let timeUpdateInterval;
let processingTaskInterval;
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
});
// 应用初始化
function initializeApp() {
setupNavigation();
setupTimeDisplay();
setupDataStream();
setupProcessingNodes();
setupVisualizationControls();
setupDataPoints();
console.log('海洋数据系统DEMO已启动');
}
// 设置导航功能
function setupNavigation() {
const navItems = document.querySelectorAll('.nav-item');
const modules = document.querySelectorAll('.module');
navItems.forEach(item => {
item.addEventListener('click', function() {
const targetModule = this.getAttribute('data-module');
// 更新导航状态
navItems.forEach(nav => nav.classList.remove('active'));
this.classList.add('active');
// 切换模块显示
modules.forEach(module => module.classList.remove('active'));
document.getElementById(targetModule).classList.add('active');
currentModule = targetModule;
// 根据模块执行特定初始化
switch(targetModule) {
case 'data-reception':
startDataReception();
break;
case 'data-processing':
startProcessingMonitor();
break;
case 'data-visualization':
startVisualization();
break;
}
console.log(`切换到模块: ${targetModule}`);
});
});
}
// 设置时间显示
function setupTimeDisplay() {
const timeDisplay = document.getElementById('currentTime');
function updateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
timeDisplay.textContent = timeString;
}
updateTime();
timeUpdateInterval = setInterval(updateTime, 1000);
}
// 设置数据流显示
// 数据流管理类
class DataStreamManager {
constructor() {
this.container = null;
this.interval = null;
this.maxItems = 12;
this.updateFrequency = 500; // 1秒更新一次
this.isRunning = false;
}
init() {
this.container = document.querySelector('.data-stream');
if (!this.container) {
console.error('数据流容器未找到');
return false;
}
// 清空容器
this.container.innerHTML = '';
// 初始化几个数据项
this.generateInitialData();
// 开始定期更新
this.start();
console.log('数据流管理器初始化完成');
return true;
}
generateInitialData() {
// 生成3-5个初始数据项
const initialCount = Math.floor(Math.random() * 3) + 3;
for (let i = 0; i < initialCount; i++) {
setTimeout(() => {
this.addDataItem();
}, i * 300);
}
}
addDataItem() {
if (!this.container) return;
// 随机选择设备
const device = oceanDevices[Math.floor(Math.random() * oceanDevices.length)];
const param = device.parameters[Math.floor(Math.random() * device.parameters.length)];
const value = this.generateValue(param);
const timestamp = new Date().toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// 创建数据项
const item = this.createDataItem(device, param, value, timestamp);
// 添加到容器顶部
this.container.insertBefore(item, this.container.firstChild);
// 限制数量
this.limitItems();
// 触发进入动画
requestAnimationFrame(() => {
item.style.opacity = '1';
item.style.transform = 'translateX(0)';
});
}
createDataItem(device, param, value, timestamp) {
const item = document.createElement('div');
item.className = `stream-item ${this.getDeviceClass(device.type)}`;
// 参数名称映射
const paramNames = {
'time': '时间', 'lng_lat': '经纬度', 'temp': '温度', 'cond': '电导率',
'depth': '深度', 'salinity': '盐度', 'flow_velocity': '流速', 'flow_direction': '流向',
'gravity': '重力', 'magnetic_field_1': '磁场强度1', 'magnetic_field_2': '磁场强度2',
'wave_height_max': '最大波高', 'wave_height_effective': '有效波高', 'cycle_max': '最大周期',
'wind_speed': '风速', 'wind_direction': '风向', 'air_pressure': '气压',
'humidity': '湿度', 'visibility': '能见度', 'cloud_height_1': '云高1',
'irradiance': '辐照度', 'turbidity': '浊度', 'chlorophyll': '叶绿素'
};
item.innerHTML = `
<div class="stream-device-info">
<div class="stream-device-icon">${this.getDeviceIcon(device.type)}</div>
<div class="stream-device-details">
<div class="stream-device-name">${device.name}</div>
<div class="stream-parameter">${paramNames[param] || param}</div>
</div>
</div>
<div class="stream-value-section">
<div class="stream-value" style="color: ${device.color}">${value}</div>
<div class="stream-timestamp">${timestamp}</div>
</div>
`;
// 添加点击事件
item.addEventListener('click', () => {
this.showItemDetails(device, param, value, timestamp);
});
return item;
}
limitItems() {
const items = this.container.querySelectorAll('.stream-item');
if (items.length > this.maxItems) {
const excess = items.length - this.maxItems;
for (let i = 0; i < excess; i++) {
const lastItem = items[items.length - 1 - i];
lastItem.classList.add('removing');
setTimeout(() => {
if (lastItem.parentNode) {
lastItem.remove();
}
}, 300);
}
}
}
generateValue(param) {
const valueRanges = {
'temp': () => (Math.random() * 30 + 5).toFixed(1) + '°C',
'depth': () => (Math.random() * 6000 + 10).toFixed(0) + 'm',
'flow_velocity': () => (Math.random() * 2 + 0.1).toFixed(2) + 'm/s',
'wind_speed': () => (Math.random() * 20 + 2).toFixed(1) + 'm/s',
'air_pressure': () => (Math.random() * 50 + 1000).toFixed(1) + 'hPa',
'humidity': () => (Math.random() * 40 + 40).toFixed(0) + '%',
'wave_height_max': () => (Math.random() * 8 + 0.5).toFixed(1) + 'm',
'salinity': () => (Math.random() * 5 + 30).toFixed(2) + 'psu',
'gravity': () => (Math.random() * 0.1 + 9.8).toFixed(3) + 'm/s²',
'visibility': () => (Math.random() * 20 + 5).toFixed(1) + 'km',
'cloud_height_1': () => (Math.random() * 3000 + 500).toFixed(0) + 'm'
};
return valueRanges[param] ? valueRanges[param]() : (Math.random() * 100).toFixed(2);
}
getDeviceClass(type) {
const classMap = {
'CTD': 'device-ctd', 'ADCP_38K': 'device-adcp', 'ADCP_150K': 'device-adcp',
'GRAVITY': 'device-gravity', 'MAGNETIC': 'device-magnetic', 'WAVE_GAUGE': 'device-wave',
'DEPTH_SOUNDER': 'device-depth', 'WAVE_RADAR': 'device-radar', 'MULTI_SENSOR': 'device-multi',
'WAVE_BUOY': 'device-buoy', 'BEIDOU': 'device-beidou', 'WIND_PROFILER': 'device-wind',
'WEATHER_STATION': 'device-weather', 'SKY_SCANNER': 'device-sky', 'TURBULENCE': 'device-turbulence',
'LIDAR_WIND': 'device-lidar', 'VISIBILITY': 'device-visibility', 'CLOUD_HEIGHT': 'device-cloud',
'NAVIGATION': 'device-navigation'
};
return classMap[type] || 'device-default';
}
getDeviceIcon(type) {
const iconMap = {
'CTD': '🌊', 'ADCP_38K': '🌀', 'ADCP_150K': '🌀', 'GRAVITY': '⚖️', 'MAGNETIC': '🧲',
'WAVE_GAUGE': '〰️', 'DEPTH_SOUNDER': '📏', 'WAVE_RADAR': '📡', 'MULTI_SENSOR': '🔬',
'WAVE_BUOY': '🛟', 'BEIDOU': '🛰️', 'WIND_PROFILER': '💨', 'WEATHER_STATION': '🌤️',
'SKY_SCANNER': '🌌', 'TURBULENCE': '🌪️', 'LIDAR_WIND': '🔦', 'VISIBILITY': '👁️',
'CLOUD_HEIGHT': '☁️', 'NAVIGATION': '🧭'
};
return iconMap[type] || '📊';
}
showItemDetails(device, param, value, timestamp) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'stream-detail-modal';
modal.style.cssText = `
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0, 0, 0, 0.8); display: flex; align-items: center;
justify-content: center; z-index: 1000; backdrop-filter: blur(5px);
`;
const content = document.createElement('div');
content.style.cssText = `
background: linear-gradient(135deg, #1e3a8a 0%, #0f172a 100%);
border-radius: 16px; padding: 24px; max-width: 400px; width: 90%;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
`;
content.innerHTML = `
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="font-size: 24px;">${this.getDeviceIcon(device.type)}</div>
<div>
<h3 style="margin: 0; color: white; font-size: 1.2rem;">${device.name}</h3>
<p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 0.9rem;">设备ID: ${device.id}</p>
</div>
</div>
<div style="background: rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span style="color: #94a3b8;">参数</span>
<span style="color: white; font-weight: 600;">${param}</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span style="color: #94a3b8;">数值</span>
<span style="color: ${device.color}; font-weight: 700; font-family: monospace;">${value}</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span style="color: #94a3b8;">时间</span>
<span style="color: white; font-family: monospace;">${timestamp}</span>
</div>
</div>
<button onclick="this.closest('.stream-detail-modal').remove()"
style="width: 100%; padding: 12px; background: ${device.color}; color: white;
border: none; border-radius: 8px; font-weight: 600; cursor: pointer;">
关闭
</button>
`;
modal.appendChild(content);
document.body.appendChild(modal);
// 点击背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
}
start() {
if (this.isRunning) return;
this.isRunning = true;
this.interval = setInterval(() => {
this.addDataItem();
}, this.updateFrequency);
console.log('数据流开始运行');
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
this.isRunning = false;
console.log('数据流已停止');
}
restart() {
this.stop();
setTimeout(() => {
this.start();
}, 100);
}
}
// 创建全局数据流管理器实例
const dataStreamManager = new DataStreamManager();
// 重新定义setupDataStream函数
function setupDataStream() {
return dataStreamManager.init();
}
// 设置数据点交互
function setupDataPoints() {
const dataPoints = document.querySelectorAll('.data-point');
const sourceDetails = document.getElementById('sourceDetails');
const sourceInfo = {
'浮标-001': {
location: '北纬 25.2°, 东经 120.5°',
status: '在线',
lastUpdate: '2分钟前',
type: '海洋浮标'
},
'卫星-A': {
location: '轨道高度 705km',
status: '在线',
lastUpdate: '1分钟前',
type: '海洋观测卫星'
},
'监测站-B': {
location: '北纬 22.8°, 东经 118.3°',
status: '在线',
lastUpdate: '30秒前',
type: '海岸监测站'
},
'船舶-C': {
location: '北纬 24.1°, 东经 119.7°',
status: '在线',
lastUpdate: '5分钟前',
type: '科考船'
}
};
dataPoints.forEach(point => {
point.addEventListener('click', function() {
const sourceName = this.getAttribute('data-source');
const info = sourceInfo[sourceName];
if (info && sourceDetails) {
sourceDetails.innerHTML = `
<div class="detail-item">
<span class="detail-label">数据源:</span>
<span class="detail-value">${sourceName}</span>
</div>
<div class="detail-item">
<span class="detail-label">类型:</span>
<span class="detail-value">${info.type}</span>
</div>
<div class="detail-item">
<span class="detail-label">位置:</span>
<span class="detail-value">${info.location}</span>
</div>
<div class="detail-item">
<span class="detail-label">状态:</span>
<span class="detail-value status-online">${info.status}</span>
</div>
<div class="detail-item">
<span class="detail-label">最后更新:</span>
<span class="detail-value">${info.lastUpdate}</span>
</div>
`;
}
// 高亮效果
dataPoints.forEach(p => p.classList.remove('highlighted'));
this.classList.add('highlighted');
});
});
}
// 设置处理节点交互
function setupProcessingNodes() {
const nodes = document.querySelectorAll('.pipeline-node');
const nodeTitle = document.getElementById('nodeTitle');
const nodeDetails = document.getElementById('nodeDetails');
const nodeInfo = {
'input': {
title: '原始数据输入',
details: `
<div class="node-info">
<p><strong>功能:</strong> 接收来自各种数据源的原始海洋数据</p>
<p><strong>数据类型:</strong> 温度、盐度、叶绿素、波高等</p>
<p><strong>当前状态:</strong> <span style="color: #10b981;">正常运行</span></p>
<p><strong>处理速率:</strong> 15.2 MB/s</p>
</div>
`
},
'clean': {
title: '数据清洗',
details: `
<div class="node-info">
<p><strong>功能:</strong> 去除异常值、填补缺失数据</p>
<p><strong>质量指标:</strong></p>
<ul>
<li>数据完整性: 98.5%</li>
<li>异常值检出率: 2.1%</li>
<li>处理成功率: 99.8%</li>
</ul>
<p><strong>算法:</strong> 3σ准则 + 卡尔曼滤波</p>
</div>
`
},
'format': {
title: '数据格式化',
details: `
<div class="node-info">
<p><strong>功能:</strong> 统一数据格式和坐标系统</p>
<p><strong>输出格式:</strong> NetCDF-4</p>
<p><strong>坐标系:</strong> WGS84</p>
<p><strong>时间标准:</strong> UTC</p>
<p><strong>处理延迟:</strong> < 5秒</p>
</div>
`
},
'interpolation': {
title: '三维插值',
details: `
<div class="node-info">
<p><strong>功能:</strong> 生成规则网格的三维温度场</p>
<p><strong>插值方法:</strong> 克里金插值</p>
<p><strong>质量评估:</strong></p>
<ul>
<li>相关系数: 0.95</li>
<li>RMSE: 0.2°C</li>
<li>标准差: 0.15</li>
</ul>
<p><strong>网格分辨率:</strong> 0.1° × 0.1°</p>
</div>
`
},
'analysis': {
title: '叶绿素反演',
details: `
<div class="node-info">
<p><strong>功能:</strong> 基于多光谱数据反演叶绿素浓度</p>
<p><strong>算法:</strong> OC4v6 + 神经网络</p>
<p><strong>波段:</strong> 443, 490, 510, 555 nm</p>
<p><strong>精度:</strong> ±0.3 mg/m³</p>
<p><strong>覆盖范围:</strong> 全球海域</p>
</div>
`
},
'output': {
title: '产品数据库',
details: `
<div class="node-info">
<p><strong>功能:</strong> 存储最终处理产品</p>
<p><strong>存储格式:</strong> HDF5 + 元数据</p>
<p><strong>数据产品:</strong></p>
<ul>
<li>海表温度场</li>
<li>叶绿素分布图</li>
<li>三维温盐结构</li>
</ul>
<p><strong>更新频率:</strong> 每小时</p>
</div>
`
}
};
nodes.forEach(node => {
node.addEventListener('click', function() {
const step = this.getAttribute('data-step');
const info = nodeInfo[step];
if (info && nodeTitle && nodeDetails) {
nodeTitle.textContent = info.title;
nodeDetails.innerHTML = info.details;
}
// 高亮效果
nodes.forEach(n => n.classList.remove('highlighted'));
this.classList.add('highlighted');
});
});
}
// 设置可视化控件
function setupVisualizationControls() {
const playButton = document.querySelector('#data-visualization .btn-primary');
const dataSelect = document.querySelector('#data-visualization .control-select');
if (playButton) {
playButton.addEventListener('click', function() {
const icon = this.querySelector('i');
const text = this.querySelector('span') || this.childNodes[this.childNodes.length - 1];
if (icon.classList.contains('fa-play')) {
icon.classList.remove('fa-play');
icon.classList.add('fa-pause');
if (text) text.textContent = ' 暂停动画';
startTemperatureAnimation();
} else {
icon.classList.remove('fa-pause');
icon.classList.add('fa-play');
if (text) text.textContent = ' 播放动画';
stopTemperatureAnimation();
}
});
}
if (dataSelect) {
dataSelect.addEventListener('change', function() {
updateVisualizationData(this.value);
});
}
}
// 启动数据接收
function startDataReception() {
console.log('数据接收模块已激活');
// 重新启动数据流(如果已停止)
if (!dataStreamInterval) {
setupDataStream();
}
}
// 启动处理监控
function startProcessingMonitor() {
console.log('数据处理模块已激活');
startProcessingTask();
}
// 启动可视化
function startVisualization() {
console.log('数据可视化模块已激活');
updateMetrics();
}
// 启动处理任务演示
function startProcessingTask() {
const nodes = document.querySelectorAll('.pipeline-node');
const logContent = document.getElementById('logContent');
function addLogEntry(message, type = 'info') {
if (!logContent) return;
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}`;
logEntry.textContent = `[${timeString}] ${message}`;
logContent.insertBefore(logEntry, logContent.firstChild);
// 限制日志条数
while (logContent.children.length > 20) {
logContent.removeChild(logContent.lastChild);
}
}
function simulateProcessing() {
const messages = [
{ msg: '数据块 #A7B2 处理完成', type: 'info' },
{ msg: '三维插值算法执行成功', type: 'info' },
{ msg: '叶绿素反演计算完成', type: 'info' },
{ msg: '警告: 数据源 浮标-003 信号弱', type: 'warning' },
{ msg: '产品数据已存储到数据库', type: 'info' },
{ msg: '质量检验通过率: 98.5%', type: 'info' }
];
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
addLogEntry(randomMessage.msg, randomMessage.type);
}
// 定期添加日志
processingTaskInterval = setInterval(simulateProcessing, 5000);
// 初始日志
setTimeout(() => addLogEntry('南海温度场与叶绿素产品生成任务启动'), 1000);
}
// 温度动画控制
let temperatureAnimationInterval;
function startTemperatureAnimation() {
const temperatureZones = document.querySelectorAll('.temperature-zone');
temperatureAnimationInterval = setInterval(() => {
temperatureZones.forEach(zone => {
// 随机改变温度区域的大小和位置
const newTop = Math.random() * 70 + 10;
const newLeft = Math.random() * 70 + 10;
const newWidth = Math.random() * 20 + 15;
const newHeight = Math.random() * 15 + 10;
zone.style.top = newTop + '%';
zone.style.left = newLeft + '%';
zone.style.width = newWidth + '%';
zone.style.height = newHeight + '%';
});
}, 2000);
}
function stopTemperatureAnimation() {
if (temperatureAnimationInterval) {
clearInterval(temperatureAnimationInterval);
temperatureAnimationInterval = null;
}
}
// 更新可视化数据
function updateVisualizationData(dataType) {
const metrics = document.querySelectorAll('.metric-value');
const dataValues = {
'温度数据': ['24.5°C', '2.8m', '3'],
'盐度数据': ['34.2‰', '1.9m', '2'],
'叶绿素数据': ['0.8mg/m³', '2.1m', '4']
};
const values = dataValues[dataType] || dataValues['温度数据'];
metrics.forEach((metric, index) => {
if (values[index]) {
metric.textContent = values[index];
}
});
console.log(`切换到${dataType}显示`);
}
// 更新指标数据
function updateMetrics() {
const statValues = document.querySelectorAll('.stat-value');
// 模拟数据更新
setInterval(() => {
statValues.forEach(stat => {
const currentText = stat.textContent;
if (currentText.includes('MB/s')) {
const newValue = (Math.random() * 10 + 10).toFixed(1);
stat.textContent = newValue + ' MB/s';
} else if (currentText.includes('°C')) {
const newValue = (Math.random() * 5 + 22).toFixed(1);
stat.textContent = newValue + '°C';
} else if (!isNaN(parseInt(currentText))) {
const baseValue = parseInt(currentText);
const variation = Math.floor(Math.random() * 10 - 5);
stat.textContent = Math.max(0, baseValue + variation);
}
});
}, 10000);
}
// 添加CSS类用于高亮效果
const style = document.createElement('style');
style.textContent = `
.data-point.highlighted {
transform: scale(1.5);
box-shadow: 0 0 20px var(--accent-green);
z-index: 20;
}
.pipeline-node.highlighted {
transform: translateY(-5px) scale(1.05);
box-shadow: 0 15px 40px rgba(0, 212, 255, 0.4);
border-color: var(--accent-blue);
}
.node-info {
line-height: 1.6;
}
.node-info ul {
margin: 0.5rem 0;
padding-left: 1rem;
}
.node-info li {
margin: 0.25rem 0;
color: var(--text-secondary);
}
.node-info strong {
color: var(--accent-blue);
}
`;
document.head.appendChild(style);
// 清理函数
function cleanup() {
if (dataStreamInterval) clearInterval(dataStreamInterval);
if (timeUpdateInterval) clearInterval(timeUpdateInterval);
if (processingTaskInterval) clearInterval(processingTaskInterval);
if (temperatureAnimationInterval) clearInterval(temperatureAnimationInterval);
}
// 页面卸载时清理
window.addEventListener('beforeunload', cleanup);
// 导出主要函数供调试使用
window.OceanDemo = {
switchModule: function(moduleName) {
const navItem = document.querySelector(`[data-module="${moduleName}"]`);
if (navItem) navItem.click();
},
startProcessingDemo: startProcessingTask,
updateVisualization: updateVisualizationData,
cleanup: cleanup
};
console.log('海洋数据系统DEMO脚本加载完成');
// 真实海洋设备数据配置
const oceanDevices = [
{
id: 'ctd_001',
name: 'CTD温盐深仪',
type: 'CTD',
position: { lat: 35.2, lng: 120.5 },
parameters: ['time', 'lng_lat', 'temp', 'cond', 'depth'],
status: 'online',
color: '#00ff88'
},
{
id: 'adcp_38k_001',
name: '船载ADCP系统(38k)',
type: 'ADCP_38K',
position: { lat: 36.1, lng: 121.2 },
parameters: ['data_time', 'lng_lat', 'depth', 'flow_velocity', 'flow_direction', 'flow_velocity_e', 'flow_velocity_n', 'flow_velocity_v'],
status: 'online',
color: '#0088ff'
},
{
id: 'adcp_150k_001',
name: '船载ADCP系统(150k)',
type: 'ADCP_150K',
position: { lat: 34.8, lng: 119.8 },
parameters: ['data_time', 'lng_lat', 'depth', 'flow_velocity', 'flow_direction', 'flow_velocity_e', 'flow_velocity_n', 'flow_velocity_v'],
status: 'online',
color: '#0066ff'
},
{
id: 'gravity_001',
name: '重力仪',
type: 'GRAVITY',
position: { lat: 35.5, lng: 120.8 },
parameters: ['data_time', 'lng_lat', 'gravity'],
status: 'online',
color: '#ff6600'
},
{
id: 'magnetic_001',
name: '磁力仪',
type: 'MAGNETIC',
position: { lat: 36.3, lng: 121.5 },
parameters: ['data_time', 'lng_lat', 'magnetic_field_1', 'magnetic_field_2'],
status: 'online',
color: '#ff0066'
},
{
id: 'wave_gauge_001',
name: '波潮仪',
type: 'WAVE_GAUGE',
position: { lat: 35.8, lng: 120.2 },
parameters: ['data_time', 'lng_lat', 'wave_height_max', 'cycle_max', 'wave_height_1_10', 'cycle_1_10', 'wave_height_effective', 'cycle_effective', 'wave_height_average', 'cycle_average'],
status: 'online',
color: '#00ffff'
},
{
id: 'depth_sounder_001',
name: '6000米测深仪',
type: 'DEPTH_SOUNDER',
position: { lat: 34.5, lng: 119.5 },
parameters: ['data_time', 'lng_lat', 'depth_f', 'depth_m'],
status: 'online',
color: '#8800ff'
},
{
id: 'wave_radar_001',
name: '测波雷达',
type: 'WAVE_RADAR',
position: { lat: 36.5, lng: 122.1 },
parameters: ['data_time', 'lng_lat', 'wave_height_effective', 'cycle_tm2', 'direction_peak', 'cycle_peak', 'wavelength_peak', 'direction_swell_primary', 'cycle_swell_primary', 'wavelength_swell_primary', 'direction_wind_wave', 'cycle_wind_wave', 'wavelength_wind_wave', 'current_direction', 'current_velocity', 'encounter_current_direction', 'encounter_current_velocity'],
status: 'online',
color: '#ffff00'
},
{
id: 'multi_sensor_001',
name: '表层多要素自动测定系统',
type: 'MULTI_SENSOR',
position: { lat: 35.1, lng: 119.9 },
parameters: ['data_time', 'lng_lat', 'temp', 'cond', 'salinity', 'chlorophyll', 'cdom', 'turbidity'],
status: 'online',
color: '#00ff00'
},
{
id: 'wave_buoy_001',
name: '小型波浪浮标',
type: 'WAVE_BUOY',
position: { lat: 34.9, lng: 120.7 },
parameters: ['time', 'lng_lat', 'wave_height_average', 'cycle_average', 'wave_height_max', 'cycle_max', 'wave_height_1_10', 'cycle_1_10', 'wave_height_effective'],
status: 'online',
color: '#ff8800'
},
{
id: 'beidou_001',
name: '北斗探测系统',
type: 'BEIDOU',
position: { lat: 36.0, lng: 121.8 },
parameters: ['data_time', 'lng_lat', 'temp', 'relative_humidity', 'air_pressure', 'wind_speed', 'height', 'wind_direction'],
status: 'online',
color: '#ff4400'
},
{
id: 'wind_profiler_001',
name: '舰载风廓线雷达',
type: 'WIND_PROFILER',
position: { lat: 35.7, lng: 121.0 },
parameters: ['time', 'lng_lat', 'height', 'wind_direction', 'wind_speed', 'wind_speed_v', 'reliability_l', 'reliability_v', 'cn2'],
status: 'online',
color: '#4400ff'
},
{
id: 'weather_station_001',
name: '船载自动气象站',
type: 'WEATHER_STATION',
position: { lat: 34.7, lng: 119.3 },
parameters: ['data_time', 'lng_lat', 'wind_speed_realtime', 'wind_direction_realtime', 'wind_speed_average', 'wind_direction_average', 'wind_speed_max', 'wind_direction_max', 'time_wind_speed_max', 'temperature', 'humidity', 'pressure', 'rainfall_minute', 'rainfall_hour', 'rainfall_day', 'status_sensor'],
status: 'online',
color: '#88ff00'
},
{
id: 'sky_scanner_001',
name: '全天空背景扫描仪',
type: 'SKY_SCANNER',
position: { lat: 36.2, lng: 120.3 },
parameters: ['data_time', 'lng_lat', 'irradiance', 'radiance'],
status: 'online',
color: '#ff0088'
},
{
id: 'turbulence_001',
name: '大气湍流谱仪',
type: 'TURBULENCE',
position: { lat: 35.4, lng: 121.7 },
parameters: ['data_time', 'lng_lat', 'temp', 'air_pressure', 'cn2'],
status: 'online',
color: '#0044ff'
},
{
id: 'lidar_wind_001',
name: '激光测风雷达',
type: 'LIDAR_WIND',
position: { lat: 34.6, lng: 120.1 },
parameters: ['data_time', 'lng_lat', 'height', 'wind_speed_l', 'wind_direction_l', 'wind_speed_v'],
status: 'online',
color: '#ff6600'
},
{
id: 'visibility_001',
name: '天气现象及能见度仪',
type: 'VISIBILITY',
position: { lat: 35.9, lng: 119.6 },
parameters: ['data_time', 'lng_lat', 'height', 'visibility', 'azimuth_angle', 'elevation'],
status: 'online',
color: '#66ff00'
},
{
id: 'cloud_height_001',
name: '激光云高仪',
type: 'CLOUD_HEIGHT',
position: { lat: 36.4, lng: 120.9 },
parameters: ['data_time', 'lng_lat', 'cloud_height_1', 'cloud_height_2', 'cloud_height_3', 'cloud_height_4', 'cloud_height_5', 'cloud_thickness_1', 'cloud_thickness_2', 'cloud_thickness_3', 'cloud_thickness_4', 'cloud_thickness_5'],
status: 'online',
color: '#00aaff'
},
{
id: 'navigation_001',
name: '船舶航行数据',
type: 'NAVIGATION',
position: { lat: 35.3, lng: 121.3 },
parameters: ['POS惯导', 'GNSS', '计程仪', '电罗经', '测深仪'],
status: 'online',
color: '#aa00ff'
}
];
function initDataReception() {
const earthContainer = document.getElementById('earth-container');
if (!earthContainer) return;
// 清空容器
earthContainer.innerHTML = '';
// 创建地球
const earth = document.createElement('div');
earth.className = 'earth';
earthContainer.appendChild(earth);
// 添加真实设备数据源点
oceanDevices.forEach((device, index) => {
const point = document.createElement('div');
point.className = 'data-source-point';
point.style.backgroundColor = device.color;
point.style.boxShadow = `0 0 20px ${device.color}`;
// 根据经纬度计算位置(简化的球面投影)
const x = 50 + (device.position.lng - 120) * 8; // 相对于120度经线
const y = 50 - (device.position.lat - 35) * 8; // 相对于35度纬线
point.style.left = `${Math.max(10, Math.min(90, x))}%`;
point.style.top = `${Math.max(10, Math.min(90, y))}%`;
point.setAttribute('data-device-id', device.id);
point.setAttribute('data-device-name', device.name);
point.setAttribute('data-device-type', device.type);
// 添加点击事件
point.addEventListener('click', () => showDeviceDetails(device));
earthContainer.appendChild(point);
// 创建数据流线
const streamLine = document.createElement('div');
streamLine.className = 'data-stream-line';
streamLine.style.background = `linear-gradient(45deg, transparent, ${device.color}, transparent)`;
streamLine.style.left = point.style.left;
streamLine.style.top = point.style.top;
streamLine.style.animationDelay = `${index * 0.2}s`;
earthContainer.appendChild(streamLine);
});
// 更新统计信息
updateReceptionStats();
}
function updateReceptionStats() {
const totalDevices = oceanDevices.length;
const onlineDevices = oceanDevices.filter(d => d.status === 'online').length;
const totalParams = oceanDevices.reduce((sum, device) => sum + device.parameters.length, 0);
// 更新统计显示
const statsElements = {
'total-received': `${(Math.random() * 50000 + 150000).toFixed(0)}`,
'reception-rate': `${(Math.random() * 200 + 800).toFixed(0)} 包/秒`,
'online-sources': `${onlineDevices}/${totalDevices}`,
'data-types': `${totalParams} 种参数`
};
Object.entries(statsElements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) element.textContent = value;
});
}
// 底部数据流面板管理
class BottomDataStreamManager {
constructor() {
this.isCollapsed = false;
this.dataStreamManager = new DataStreamManager();
this.init();
}
init() {
this.setupEventListeners();
this.startDataStream();
}
setupEventListeners() {
const streamHeader = document.querySelector('.stream-header');
const streamToggle = document.querySelector('.stream-toggle');
if (streamHeader) {
streamHeader.addEventListener('click', () => this.togglePanel());
}
if (streamToggle) {
streamToggle.addEventListener('click', (e) => {
e.stopPropagation();
this.togglePanel();
});
}
}
togglePanel() {
const panel = document.querySelector('.bottom-data-stream');
if (panel) {
this.isCollapsed = !this.isCollapsed;
panel.classList.toggle('collapsed', this.isCollapsed);
// 更新按钮文本
const toggleText = document.querySelector('.stream-toggle span');
if (toggleText) {
toggleText.textContent = this.isCollapsed ? '展开' : '收起';
}
}
}
startDataStream() {
// 启动数据流管理器
this.dataStreamManager.start();
// 每1秒添加新数据 - 加快生成速度
setInterval(() => {
this.addNewDataItem();
}, 1000);
}
addNewDataItem() {
const container = document.querySelector('.data-stream.horizontal');
if (!container) return;
const newItem = this.dataStreamManager.generateDataItem();
const itemElement = this.createStreamItemElement(newItem);
// 为新卡片添加特殊类名
itemElement.classList.add('new-item');
// 为现有卡片添加推动效果
const existingItems = container.querySelectorAll('.stream-item');
existingItems.forEach((item, index) => {
if (index < 5) { // 对前5个卡片添加推动效果
// 先移除可能存在的重置类
item.classList.remove('reset-position');
// 添加推动效果
setTimeout(() => {
item.classList.add('push-right');
}, 50); // 小延迟确保DOM更新
}
});
// 添加到容器开头
container.insertBefore(itemElement, container.firstChild);
// 延迟重置卡片位置,创建流动效果
setTimeout(() => {
const allItems = container.querySelectorAll('.stream-item');
allItems.forEach((item, index) => {
if (item.classList.contains('push-right')) {
item.classList.remove('push-right');
item.classList.add('reset-position');
}
});
}, 100);
// 限制显示的数据项数量,为超出的卡片添加淡出动画
setTimeout(() => {
const items = container.querySelectorAll('.stream-item');
if (items.length > 20) {
const itemToRemove = items[items.length - 1];
itemToRemove.classList.add('fade-out');
// 动画完成后移除元素
setTimeout(() => {
if (itemToRemove.parentNode) {
itemToRemove.remove();
}
}, 500);
}
}, 200);
// 清理类名
setTimeout(() => {
itemElement.classList.remove('new-item');
const allItems = container.querySelectorAll('.stream-item');
allItems.forEach(item => {
item.classList.remove('reset-position');
});
}, 800);
// 自动滚动到最新数据
container.scrollLeft = 0;
}
createStreamItemElement(data) {
const item = document.createElement('div');
item.className = 'stream-item';
// 设备类型颜色
const deviceColors = {
'temperature': { bg: 'rgba(239, 68, 68, 0.2)', border: '#ef4444', icon: '🌡️' },
'pressure': { bg: 'rgba(59, 130, 246, 0.2)', border: '#3b82f6', icon: '📊' },
'salinity': { bg: 'rgba(34, 197, 94, 0.2)', border: '#22c55e', icon: '🧂' },
'ph': { bg: 'rgba(168, 85, 247, 0.2)', border: '#a855f7', icon: '⚗️' },
'oxygen': { bg: 'rgba(6, 182, 212, 0.2)', border: '#06b6d4', icon: '💨' },
'turbidity': { bg: 'rgba(245, 158, 11, 0.2)', border: '#f59e0b', icon: '🌊' }
};
const colorInfo = deviceColors[data.type] || deviceColors['temperature'];
item.innerHTML = `
<div class="stream-device-info">
<div class="stream-device-icon" style="background: ${colorInfo.bg}; border-color: ${colorInfo.border}; color: ${colorInfo.border};">
${colorInfo.icon}
</div>
<div class="stream-device-details">
<div class="stream-device-name">${data.deviceName}</div>
<div class="stream-parameter">${data.parameter}</div>
</div>
</div>
<div class="stream-value-section">
<div class="stream-value" style="color: ${colorInfo.border};">${data.value}</div>
<div class="stream-timestamp">${data.timestamp}</div>
</div>
`;
return item;
}
}
// 初始化底部数据流面板
document.addEventListener('DOMContentLoaded', () => {
// 检查是否存在底部数据流面板
if (document.querySelector('.bottom-data-stream')) {
new BottomDataStreamManager();
}
});
function showDeviceDetails(device) {
// 创建设备详情弹窗
const modal = document.createElement('div');
modal.className = 'device-modal';
modal.innerHTML = `
<div class="device-modal-content">
<div class="device-modal-header">
<h3>${device.name}</h3>
<span class="device-modal-close">&times;</span>
</div>
<div class="device-modal-body">
<div class="device-info">
<p><strong>设备ID:</strong> ${device.id}</p>
<p><strong>设备类型:</strong> ${device.type}</p>
<p><strong>位置:</strong> ${device.position.lat}°N, ${device.position.lng}°E</p>
<p><strong>状态:</strong> <span class="status-${device.status}">${device.status === 'online' ? '在线' : '离线'}</span></p>
</div>
<div class="device-parameters">
<h4>测量参数:</h4>
<div class="parameters-grid">
${device.parameters.map(param => `<span class="parameter-tag">${param}</span>`).join('')}
</div>
</div>
<div class="device-stats">
<h4>实时数据:</h4>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">数据包数:</span>
<span class="stat-value">${Math.floor(Math.random() * 1000 + 500)}</span>
</div>
<div class="stat-item">
<span class="stat-label">传输速率:</span>
<span class="stat-value">${Math.floor(Math.random() * 50 + 20)} KB/s</span>
</div>
<div class="stat-item">
<span class="stat-label">数据质量:</span>
<span class="stat-value">${(Math.random() * 10 + 90).toFixed(1)}%</span>
</div>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// 关闭弹窗事件
const closeBtn = modal.querySelector('.device-modal-close');
closeBtn.addEventListener('click', () => {
document.body.removeChild(modal);
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
}
// 更新数据流显示函数
function updateDataStream() {
const streamContainer = document.querySelector('.data-stream');
if (!streamContainer) return;
// 随机选择一个设备
const device = oceanDevices[Math.floor(Math.random() * oceanDevices.length)];
const param = device.parameters[Math.floor(Math.random() * device.parameters.length)];
const value = generateRandomValue(param);
const now = new Date();
const timestamp = now.toLocaleTimeString('zh-CN', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// 获取参数的中文名称
const paramNames = {
'time': '时间',
'lng_lat': '经纬度',
'temp': '温度',
'cond': '电导率',
'depth': '深度',
'salinity': '盐度',
'flow_velocity': '流速',
'flow_direction': '流向',
'gravity': '重力',
'magnetic_field_1': '磁场强度1',
'magnetic_field_2': '磁场强度2',
'wave_height_max': '最大波高',
'wave_height_effective': '有效波高',
'cycle_max': '最大周期',
'wind_speed': '风速',
'wind_direction': '风向',
'air_pressure': '气压',
'humidity': '湿度',
'visibility': '能见度',
'cloud_height_1': '云高1',
'irradiance': '辐照度',
'turbidity': '浊度',
'chlorophyll': '叶绿素'
};
// 创建数据流项目
const streamItem = document.createElement('div');
streamItem.className = `stream-item ${getDeviceTypeClass(device.type)}`;
streamItem.innerHTML = `
<div class="stream-device-info">
<div class="stream-device-icon">${deviceTypeIcons[device.type] || '📊'}</div>
<div class="stream-device-details">
<div class="stream-device-name">${device.name}</div>
<div class="stream-parameter">${paramNames[param] || param}</div>
</div>
</div>
<div class="stream-value-section">
<div class="stream-value" style="color: ${device.color}">${value}</div>
<div class="stream-timestamp">${timestamp}</div>
</div>
`;
// 添加点击事件
streamItem.addEventListener('click', () => {
showStreamItemDetails(device, param, value, timestamp);
});
// 添加到流中
streamContainer.insertBefore(streamItem, streamContainer.firstChild);
// 限制显示的项目数量
const items = streamContainer.querySelectorAll('.stream-item');
if (items.length > 12) {
const lastItem = items[items.length - 1];
lastItem.classList.add('removing');
setTimeout(() => {
if (lastItem.parentNode) {
lastItem.remove();
}
}, 300);
}
}
// 显示数据流项目详细信息
function showStreamItemDetails(device, param, value, timestamp) {
const modal = document.createElement('div');
modal.className = 'stream-detail-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(5px);
`;
const content = document.createElement('div');
content.style.cssText = `
background: linear-gradient(135deg, #1e3a8a 0%, #0f172a 100%);
border-radius: 16px;
padding: 24px;
max-width: 400px;
width: 90%;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
`;
content.innerHTML = `
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
<div style="font-size: 24px;">${deviceTypeIcons[device.type] || '📊'}</div>
<div>
<h3 style="margin: 0; color: white; font-size: 1.2rem;">${device.name}</h3>
<p style="margin: 4px 0 0 0; color: #94a3b8; font-size: 0.9rem;">设备ID: ${device.id}</p>
</div>
</div>
<div style="background: rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="color: #94a3b8;">参数</span>
<span style="color: white; font-weight: 600;">${param}</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="color: #94a3b8;">数值</span>
<span style="color: ${device.color}; font-weight: 700; font-family: 'Courier New', monospace;">${value}</span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="color: #94a3b8;">时间</span>
<span style="color: white; font-family: 'Courier New', monospace;">${timestamp}</span>
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<span style="color: #94a3b8;">位置</span>
<span style="color: white; font-family: 'Courier New', monospace;">${device.position.lat.toFixed(2)}°N, ${device.position.lng.toFixed(2)}°E</span>
</div>
<button onclick="this.closest('.stream-detail-modal').remove()"
style="width: 100%; padding: 12px; background: ${device.color}; color: white; border: none; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.3s ease;">
关闭
</button>
`;
modal.appendChild(content);
document.body.appendChild(modal);
// 点击背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
}
// 更新数据源点渲染函数
function renderDataSourcePoints() {
const earthSphere = document.querySelector('.earth-sphere');
if (!earthSphere) return;
// 清除现有的数据点
earthSphere.querySelectorAll('.data-source-point').forEach(point => point.remove());
earthSphere.querySelectorAll('.data-stream-line').forEach(line => line.remove());
oceanDevices.forEach((device, index) => {
// 创建数据源点
const point = document.createElement('div');
point.className = 'data-source-point';
point.style.backgroundColor = deviceTypeColors[device.type] || '#00d4ff';
point.style.color = deviceTypeColors[device.type] || '#00d4ff';
// 根据经纬度计算位置(简化的球面投影)
const x = ((device.longitude + 180) / 360) * 100;
const y = ((90 - device.latitude) / 180) * 100;
point.style.left = `${x}%`;
point.style.top = `${y}%`;
// 添加点击事件
point.addEventListener('click', () => showDeviceDetails(device));
// 添加悬停提示
point.title = `${device.name} (${device.type})`;
earthSphere.appendChild(point);
// 创建数据流线
if (Math.random() < 0.3) { // 30%的概率显示数据流
const streamLine = document.createElement('div');
streamLine.className = 'data-stream-line';
streamLine.style.background = `linear-gradient(to top, ${deviceTypeColors[device.type] || '#00d4ff'}, transparent)`;
streamLine.style.left = `${x}%`;
streamLine.style.top = `${y}%`;
streamLine.style.animationDelay = `${Math.random() * 3}s`;
earthSphere.appendChild(streamLine);
}
});
}
// 更新设备详情显示函数
function showDeviceDetails(device) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'device-modal';
modal.innerHTML = `
<div class="device-modal-content">
<div class="device-modal-header">
<h3>${deviceTypeIcons[device.type] || '📊'} ${device.name}</h3>
<span class="device-modal-close">&times;</span>
</div>
<div class="device-info">
<p><strong>设备类型:</strong> ${device.type}</p>
<p><strong>位置:</strong> ${device.latitude.toFixed(4)}°N, ${device.longitude.toFixed(4)}°E</p>
<p><strong>状态:</strong> <span class="${device.status === 'online' ? 'status-online' : 'status-offline'}">${device.status === 'online' ? '在线' : '离线'}</span></p>
<p><strong>设备ID:</strong> ${device.id}</p>
</div>
<div class="device-parameters">
<h4>监测参数</h4>
<div class="parameters-grid">
${device.parameters.map(param => `<span class="parameter-tag">${param}</span>`).join('')}
</div>
</div>
<div class="device-stats">
<h4>实时数据</h4>
<div class="stats-grid">
${device.parameters.slice(0, 6).map(param => `
<div class="stat-item">
<span class="stat-label">${param}</span>
<span class="stat-value">${generateRandomValue(param)}</span>
</div>
`).join('')}
</div>
</div>
</div>
`;
// 添加关闭事件
modal.querySelector('.device-modal-close').addEventListener('click', () => {
modal.remove();
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
document.body.appendChild(modal);
}
function generateRandomValue(param) {
const valueMap = {
'temp': `${(Math.random() * 30 + 5).toFixed(2)}°C`,
'temperature': `${(Math.random() * 30 + 5).toFixed(2)}°C`,
'depth': `${(Math.random() * 6000).toFixed(1)}m`,
'wind_speed': `${(Math.random() * 20 + 2).toFixed(1)}m/s`,
'wave_height_max': `${(Math.random() * 8 + 1).toFixed(2)}m`,
'pressure': `${(Math.random() * 50 + 1000).toFixed(1)}hPa`,
'humidity': `${(Math.random() * 40 + 40).toFixed(1)}%`,
'salinity': `${(Math.random() * 5 + 30).toFixed(2)}`,
'flow_velocity': `${(Math.random() * 3).toFixed(2)}m/s`,
'gravity': `${(Math.random() * 0.1 + 9.8).toFixed(4)}m/s²`,
'visibility': `${(Math.random() * 20 + 5).toFixed(1)}km`,
'irradiance': `${(Math.random() * 1000 + 200).toFixed(1)}W/m²`
};
// 检查参数名是否包含特定关键词
for (const [key, value] of Object.entries(valueMap)) {
if (param.includes(key)) {
return value;
}
}
// 默认返回数值
return `${(Math.random() * 100).toFixed(2)}`;
}
// 数据处理模块
function initDataProcessing() {
const pipelineContainer = document.querySelector('.pipeline-container');
if (!pipelineContainer) return;
// 清空现有内容
pipelineContainer.innerHTML = '';
// 创建处理流水线
const pipelineFlow = document.createElement('div');
pipelineFlow.className = 'pipeline-flow';
// 定义处理节点(基于真实海洋数据处理流程)
const processingNodes = [
{
id: 'data-ingestion',
type: 'input-node',
icon: 'fas fa-download',
title: '数据接收',
description: '接收来自19种海洋观测设备的原始数据'
},
{
id: 'data-validation',
type: 'process-node',
icon: 'fas fa-check-circle',
title: '数据验证',
description: '验证CTD、ADCP、重力仪等设备数据的完整性和准确性'
},
{
id: 'quality-control',
type: 'process-node',
icon: 'fas fa-filter',
title: '质量控制',
description: '对温度、盐度、流速等参数进行质量检查和异常值过滤'
},
{
id: 'data-calibration',
type: 'compute-node',
icon: 'fas fa-cogs',
title: '数据校准',
description: '校准传感器漂移,统一时间基准和坐标系统'
},
{
id: 'oceanographic-analysis',
type: 'compute-node',
icon: 'fas fa-chart-line',
title: '海洋学分析',
description: '计算海水密度、声速、地转流等海洋学参数'
},
{
id: 'data-fusion',
type: 'compute-node',
icon: 'fas fa-layer-group',
title: '数据融合',
description: '融合多源观测数据,生成综合海洋环境场'
},
{
id: 'data-output',
type: 'output-node',
icon: 'fas fa-upload',
title: '数据输出',
description: '输出标准格式的海洋数据产品和可视化结果'
}
];
// 创建节点元素
processingNodes.forEach((node, index) => {
const nodeElement = document.createElement('div');
nodeElement.className = `pipeline-node ${node.type}`;
nodeElement.id = node.id;
nodeElement.innerHTML = `
<i class="${node.icon}"></i>
<span>${node.title}</span>
<div class="node-pulse"></div>
`;
// 添加点击事件
nodeElement.addEventListener('click', () => showNodeDetails(node));
pipelineFlow.appendChild(nodeElement);
// 添加连接线(除了最后一个节点)
if (index < processingNodes.length - 1) {
const connection = document.createElement('div');
connection.className = 'pipeline-connection';
connection.style.left = `${(index + 1) * 150 - 75}px`;
connection.style.width = '150px';
pipelineFlow.appendChild(connection);
}
});
pipelineContainer.appendChild(pipelineFlow);
// 更新处理统计
updateProcessingStats();
// 启动处理监控
startProcessingMonitor();
}
// 显示节点详情
function showNodeDetails(node) {
const detailsPanel = document.querySelector('.node-details');
if (!detailsPanel) return;
// 根据节点类型显示不同的详细信息
let detailsContent = '';
switch(node.id) {
case 'data-ingestion':
detailsContent = `
<h4>数据接收节点</h4>
<p>${node.description}</p>
<ul>
<li>CTD数据温度、盐度、深度</li>
<li>ADCP数据流速、流向</li>
<li>重力仪:重力异常</li>
<li>磁力仪:磁场强度</li>
<li>气象数据:风速、气压、湿度</li>
<li>波浪数据:波高、周期、方向</li>
</ul>
<p><strong>处理速度:</strong> 1000条/秒</p>
<p><strong>数据格式:</strong> NetCDF, ASCII, Binary</p>
`;
break;
case 'data-validation':
detailsContent = `
<h4>数据验证节点</h4>
<p>${node.description}</p>
<ul>
<li>时间戳连续性检查</li>
<li>地理坐标合理性验证</li>
<li>物理参数范围检查</li>
<li>数据完整性验证</li>
</ul>
<p><strong>验证规则:</strong> 127项</p>
<p><strong>通过率:</strong> 98.5%</p>
`;
break;
case 'quality-control':
detailsContent = `
<h4>质量控制节点</h4>
<p>${node.description}</p>
<ul>
<li>统计异常值检测</li>
<li>物理一致性检查</li>
<li>时间序列平滑</li>
<li>空间插值质量评估</li>
</ul>
<p><strong>QC标准</strong> IOC/UNESCO</p>
<p><strong>处理算法:</strong> 3σ准则, Hampel滤波</p>
`;
break;
case 'data-calibration':
detailsContent = `
<h4>数据校准节点</h4>
<p>${node.description}</p>
<ul>
<li>传感器漂移校正</li>
<li>时间同步校准</li>
<li>坐标系统转换</li>
<li>单位标准化</li>
</ul>
<p><strong>校准精度:</strong> ±0.001°C (温度)</p>
<p><strong>时间精度:</strong> ±1ms</p>
`;
break;
case 'oceanographic-analysis':
detailsContent = `
<h4>海洋学分析节点</h4>
<p>${node.description}</p>
<ul>
<li>海水状态方程计算</li>
<li>地转流计算</li>
<li>混合层深度估算</li>
<li>海洋锋面识别</li>
</ul>
<p><strong>计算模型:</strong> TEOS-10</p>
<p><strong>分析算法:</strong> 动力高度法, 梯度检测</p>
`;
break;
case 'data-fusion':
detailsContent = `
<h4>数据融合节点</h4>
<p>${node.description}</p>
<ul>
<li>多源数据时空配准</li>
<li>最优插值算法</li>
<li>卡尔曼滤波融合</li>
<li>不确定性评估</li>
</ul>
<p><strong>融合方法:</strong> 变分同化</p>
<p><strong>网格分辨率:</strong> 0.1° × 0.1°</p>
`;
break;
case 'data-output':
detailsContent = `
<h4>数据输出节点</h4>
<p>${node.description}</p>
<ul>
<li>标准格式转换</li>
<li>元数据生成</li>
<li>可视化产品制作</li>
<li>质量报告生成</li>
</ul>
<p><strong>输出格式:</strong> NetCDF-4, HDF5, GeoTIFF</p>
<p><strong>产品类型:</strong> L1, L2, L3级数据产品</p>
`;
break;
default:
detailsContent = `<h4>${node.title}</h4><p>${node.description}</p>`;
}
detailsPanel.innerHTML = detailsContent;
}
// 更新处理统计
function updateProcessingStats() {
const stats = [
{ label: '处理任务', value: Math.floor(Math.random() * 50) + 150, id: 'processing-tasks' },
{ label: '完成任务', value: Math.floor(Math.random() * 30) + 120, id: 'completed-tasks' },
{ label: '等待队列', value: Math.floor(Math.random() * 20) + 10, id: 'pending-tasks' },
{ label: '错误任务', value: Math.floor(Math.random() * 5) + 2, id: 'error-tasks', error: true }
];
stats.forEach(stat => {
const element = document.getElementById(stat.id);
if (element) {
const numberElement = element.querySelector('.stat-number');
if (numberElement) {
numberElement.textContent = stat.value;
if (stat.error) {
element.classList.add('error');
}
}
}
});
// 更新资源监控
updateResourceMonitor();
}
// 更新资源监控
function updateResourceMonitor() {
const resources = [
{ id: 'cpu-usage', value: Math.floor(Math.random() * 30) + 45 },
{ id: 'memory-usage', value: Math.floor(Math.random() * 20) + 60 },
{ id: 'storage-usage', value: Math.floor(Math.random() * 15) + 35 }
];
resources.forEach(resource => {
const element = document.getElementById(resource.id);
if (element) {
const progressRing = element.querySelector('.progress-ring');
const progressValue = element.querySelector('.progress-value');
if (progressRing && progressValue) {
// 更新环形进度条
const percentage = resource.value;
progressRing.style.background = `conic-gradient(var(--accent-blue) ${percentage * 3.6}deg, rgba(255, 255, 255, 0.1) ${percentage * 3.6}deg)`;
progressValue.textContent = `${percentage}%`;
}
}
});
}
// 启动处理监控
function startProcessingMonitor() {
// 模拟处理日志
const logContent = document.querySelector('.log-content');
if (!logContent) return;
const logMessages = [
{ type: 'info', message: '[INFO] 开始处理CTD数据批次 #2024-001' },
{ type: 'info', message: '[INFO] 数据验证完成,通过率: 98.7%' },
{ type: 'warning', message: '[WARN] ADCP设备 #03 数据延迟 5分钟' },
{ type: 'info', message: '[INFO] 质量控制完成,标记异常值 12个' },
{ type: 'info', message: '[INFO] 海洋学参数计算完成' },
{ type: 'error', message: '[ERROR] 重力仪数据校准失败,重试中...' },
{ type: 'info', message: '[INFO] 数据融合完成生成L3级产品' },
{ type: 'info', message: '[INFO] 输出NetCDF文件: ocean_data_20240115.nc' }
];
let logIndex = 0;
const addLogEntry = () => {
if (logIndex < logMessages.length) {
const log = logMessages[logIndex];
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${log.type}`;
logEntry.textContent = `${new Date().toLocaleTimeString()} ${log.message}`;
logContent.appendChild(logEntry);
logContent.scrollTop = logContent.scrollHeight;
logIndex++;
setTimeout(addLogEntry, Math.random() * 3000 + 2000);
} else {
// 重置日志
setTimeout(() => {
logContent.innerHTML = '';
logIndex = 0;
addLogEntry();
}, 5000);
}
};
addLogEntry();
}
// 设备类型颜色映射
const deviceTypeColors = {
'CTD': '#00ff88',
'ADCP': '#0088ff',
'Gravity': '#ff6600',
'Magnetic': '#ff0066',
'Wave': '#00ffff',
'Depth': '#8800ff',
'Radar': '#ffff00',
'Multi': '#00ff00',
'Buoy': '#ff8800',
'Beidou': '#ff4400',
'Wind': '#4400ff',
'Weather': '#88ff00',
'Sky': '#ff0088',
'Turbulence': '#0044ff',
'Lidar': '#ff6600',
'Visibility': '#66ff00',
'Cloud': '#00aaff',
'Navigation': '#aa00ff'
};
// 设备类型图标映射
const deviceTypeIcons = {
'CTD': '🌊',
'ADCP': '🌀',
'Gravity': '⚖️',
'Magnetic': '🧲',
'Wave': '〰️',
'Depth': '📏',
'Radar': '📡',
'Multi': '🔬',
'Buoy': '🛟',
'Beidou': '🛰️',
'Wind': '💨',
'Weather': '🌤️',
'Sky': '🌌',
'Turbulence': '🌪️',
'Lidar': '🔦',
'Visibility': '👁️',
'Cloud': '☁️',
'Navigation': '🧭'
};
// 获取设备类型的CSS类名
function getDeviceTypeClass(deviceType) {
const typeMap = {
'CTD': 'ctd',
'ADCP': 'adcp',
'Gravity': 'gravity',
'Magnetic': 'magnetic',
'Wave': 'wave',
'Depth': 'depth',
'Radar': 'radar',
'Multi': 'multi',
'Buoy': 'buoy',
'Beidou': 'beidou',
'Wind': 'wind',
'Weather': 'weather',
'Sky': 'sky',
'Turbulence': 'turbulence',
'Lidar': 'lidar',
'Visibility': 'visibility',
'Cloud': 'cloud',
'Navigation': 'navigation'
};
return `device-${typeMap[deviceType] || 'default'}`;
}
// 更新数据流显示函数
function updateDataStream() {
const streamContainer = document.querySelector('.data-stream');
if (!streamContainer) return;
// 随机选择一个设备
const device = oceanDevices[Math.floor(Math.random() * oceanDevices.length)];
const param = device.parameters[Math.floor(Math.random() * device.parameters.length)];
const value = generateRandomValue(param);
const time = new Date().toLocaleTimeString();
// 创建数据流项目
const streamItem = document.createElement('div');
streamItem.className = `stream-item ${getDeviceTypeClass(device.type)}`;
streamItem.innerHTML = `
<div class="stream-time">${time}</div>
<div class="stream-device">
<span class="device-icon">${deviceTypeIcons[device.type] || '📊'}</span>
${device.name}
</div>
<div class="stream-param">${param}</div>
<div class="stream-value" style="color: ${deviceTypeColors[device.type] || '#00d4ff'}">${value}</div>
`;
// 添加到流中
streamContainer.insertBefore(streamItem, streamContainer.firstChild);
// 限制显示的项目数量
const items = streamContainer.querySelectorAll('.stream-item');
if (items.length > 10) {
items[items.length - 1].remove();
}
// 添加进入动画
streamItem.style.opacity = '0';
streamItem.style.transform = 'translateX(-20px)';
setTimeout(() => {
streamItem.style.transition = 'all 0.3s ease';
streamItem.style.opacity = '1';
streamItem.style.transform = 'translateX(0)';
}, 50);
}
// 更新数据源点渲染函数
function renderDataSourcePoints() {
const earthSphere = document.querySelector('.earth-sphere');
if (!earthSphere) return;
// 清除现有的数据点
earthSphere.querySelectorAll('.data-source-point').forEach(point => point.remove());
earthSphere.querySelectorAll('.data-stream-line').forEach(line => line.remove());
oceanDevices.forEach((device, index) => {
// 创建数据源点
const point = document.createElement('div');
point.className = 'data-source-point';
point.style.backgroundColor = deviceTypeColors[device.type] || '#00d4ff';
point.style.color = deviceTypeColors[device.type] || '#00d4ff';
// 根据经纬度计算位置(简化的球面投影)
const x = ((device.longitude + 180) / 360) * 100;
const y = ((90 - device.latitude) / 180) * 100;
point.style.left = `${x}%`;
point.style.top = `${y}%`;
// 添加点击事件
point.addEventListener('click', () => showDeviceDetails(device));
// 添加悬停提示
point.title = `${device.name} (${device.type})`;
earthSphere.appendChild(point);
// 创建数据流线
if (Math.random() < 0.3) { // 30%的概率显示数据流
const streamLine = document.createElement('div');
streamLine.className = 'data-stream-line';
streamLine.style.background = `linear-gradient(to top, ${deviceTypeColors[device.type] || '#00d4ff'}, transparent)`;
streamLine.style.left = `${x}%`;
streamLine.style.top = `${y}%`;
streamLine.style.animationDelay = `${Math.random() * 3}s`;
earthSphere.appendChild(streamLine);
}
});
}
// 更新设备详情显示函数
function showDeviceDetails(device) {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'device-modal';
modal.innerHTML = `
<div class="device-modal-content">
<div class="device-modal-header">
<h3>${deviceTypeIcons[device.type] || '📊'} ${device.name}</h3>
<span class="device-modal-close">&times;</span>
</div>
<div class="device-info">
<p><strong>设备类型:</strong> ${device.type}</p>
<p><strong>位置:</strong> ${device.latitude.toFixed(4)}°N, ${device.longitude.toFixed(4)}°E</p>
<p><strong>状态:</strong> <span class="${device.status === 'online' ? 'status-online' : 'status-offline'}">${device.status === 'online' ? '在线' : '离线'}</span></p>
<p><strong>设备ID:</strong> ${device.id}</p>
</div>
<div class="device-parameters">
<h4>监测参数</h4>
<div class="parameters-grid">
${device.parameters.map(param => `<span class="parameter-tag">${param}</span>`).join('')}
</div>
</div>
<div class="device-stats">
<h4>实时数据</h4>
<div class="stats-grid">
${device.parameters.slice(0, 6).map(param => `
<div class="stat-item">
<span class="stat-label">${param}</span>
<span class="stat-value">${generateRandomValue(param)}</span>
</div>
`).join('')}
</div>
</div>
</div>
`;
// 添加关闭事件
modal.querySelector('.device-modal-close').addEventListener('click', () => {
modal.remove();
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.remove();
}
});
document.body.appendChild(modal);
}