246 lines
8.8 KiB
JavaScript
246 lines
8.8 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
||
const stationInput = document.getElementById('station-input');
|
||
const daysInput = document.getElementById('days-input');
|
||
const fetchButton = document.getElementById('fetch-button');
|
||
const loadingDiv = document.getElementById('loading');
|
||
const errorDiv = document.getElementById('error');
|
||
const resultsDiv = document.getElementById('results');
|
||
const resultsTitle = document.getElementById('results-title');
|
||
const analysisSummary = document.getElementById('analysis-summary');
|
||
const rawDataContainer = document.getElementById('raw-data-container');
|
||
|
||
// Устанавливаем значения по умолчанию
|
||
// stationInput.value = 'UIII'; // Иркутск
|
||
daysInput.value = '14';
|
||
|
||
const charts = {};
|
||
|
||
const fetchAndDrawData = async () => {
|
||
const station = stationInput.value.toUpperCase() || 'UIII';
|
||
const days = daysInput.value || '14';
|
||
|
||
loadingDiv.classList.remove('hidden');
|
||
resultsDiv.classList.add('hidden');
|
||
errorDiv.classList.add('hidden');
|
||
|
||
try {
|
||
const response = await fetch(`/api/metar?station=${station}&days=${days}`);
|
||
if (!response.ok) {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||
}
|
||
const apiResponse = await response.json();
|
||
|
||
if (!apiResponse.data || apiResponse.data.length === 0) {
|
||
throw new Error('Данные для указанной станции или периода не найдены.');
|
||
}
|
||
|
||
processData(apiResponse.data, station, days);
|
||
|
||
loadingDiv.classList.add('hidden');
|
||
resultsDiv.classList.remove('hidden');
|
||
|
||
} catch (e) {
|
||
loadingDiv.classList.add('hidden');
|
||
errorDiv.textContent = `Ошибка: ${e.message}`;
|
||
errorDiv.classList.remove('hidden');
|
||
}
|
||
};
|
||
|
||
const processData = (data, station, days) => {
|
||
resultsTitle.textContent = `Результаты для ${station} за последние ${days} дней`;
|
||
|
||
const labels = [];
|
||
const temperatures = [];
|
||
const dewPoints = [];
|
||
const pressures = [];
|
||
const windSpeeds = [];
|
||
const windDirections = [];
|
||
|
||
const fToC = (f) => f !== null ? ((f - 32) * 5 / 9).toFixed(1) : null;
|
||
const inHgToHpa = (inHg) => inHg !== null ? (inHg * 33.8639).toFixed(1) : null;
|
||
const knotsToMs = (knots) => knots !== null ? (knots * 0.514444).toFixed(1) : null;
|
||
|
||
data.forEach(report => {
|
||
labels.push(new Date(report.valid).toLocaleString('ru-RU', { timeZone: 'UTC' }));
|
||
temperatures.push(fToC(report.tmpf));
|
||
dewPoints.push(fToC(report.dwpf));
|
||
pressures.push(inHgToHpa(report.alti));
|
||
windSpeeds.push(knotsToMs(report.sknt));
|
||
if (report.drct) {
|
||
windDirections.push(report.drct);
|
||
}
|
||
});
|
||
|
||
// графики
|
||
drawChart('temp-chart', 'line', {
|
||
labels,
|
||
datasets: [
|
||
{
|
||
label: 'Температура (°C)',
|
||
data: temperatures,
|
||
borderColor: 'rgba(255, 99, 132, 1)',
|
||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||
fill: false,
|
||
tension: 0.1
|
||
},
|
||
{
|
||
label: 'Точка росы (°C)',
|
||
data: dewPoints,
|
||
borderColor: 'rgba(54, 162, 235, 1)',
|
||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||
fill: false,
|
||
tension: 0.1
|
||
}
|
||
]
|
||
}, 'Температура и точка росы');
|
||
|
||
drawChart('pressure-chart', 'line', {
|
||
labels,
|
||
datasets: [{
|
||
label: 'Давление (гПа)',
|
||
data: pressures,
|
||
borderColor: 'rgba(75, 192, 192, 1)',
|
||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||
fill: false,
|
||
tension: 0.1
|
||
}]
|
||
}, 'Атмосферное давление (QNH)');
|
||
|
||
drawChart('wind-speed-chart', 'line', {
|
||
labels,
|
||
datasets: [{
|
||
label: 'Скорость ветра (м/с)',
|
||
data: windSpeeds,
|
||
borderColor: 'rgba(153, 102, 255, 1)',
|
||
backgroundColor: 'rgba(153, 102, 255, 0.2)',
|
||
fill: false,
|
||
tension: 0.1
|
||
}]
|
||
}, 'Скорость ветра');
|
||
|
||
// анализ
|
||
performAnalysis(temperatures, windSpeeds, windDirections);
|
||
|
||
// отображение данных тлько последние 50
|
||
displayRawData(data.slice(-20));
|
||
};
|
||
|
||
const drawChart = (canvasId, type, data, title) => {
|
||
const ctx = document.getElementById(canvasId).getContext('2d');
|
||
|
||
if (charts[canvasId]) {
|
||
charts[canvasId].destroy();
|
||
}
|
||
|
||
charts[canvasId] = new Chart(ctx, {
|
||
type: type,
|
||
data: data,
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: title,
|
||
font: { size: 16 }
|
||
}
|
||
},
|
||
scales: {
|
||
x: {
|
||
ticks: {
|
||
maxTicksLimit: 15
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const performAnalysis = (temps, speeds, directions) => {
|
||
const validTemps = temps.filter(t => t !== null).map(Number);
|
||
const validSpeeds = speeds.filter(s => s !== null).map(Number);
|
||
|
||
const maxTemp = Math.max(...validTemps);
|
||
const minTemp = Math.min(...validTemps);
|
||
const avgTemp = (validTemps.reduce((a, b) => a + b, 0) / validTemps.length).toFixed(1);
|
||
|
||
const maxWind = Math.max(...validSpeeds);
|
||
|
||
const getWindDirection = (deg) => {
|
||
if (deg > 337.5 || deg <= 22.5) return 'С';
|
||
if (deg > 22.5 && deg <= 67.5) return 'СВ';
|
||
if (deg > 67.5 && deg <= 112.5) return 'В';
|
||
if (deg > 112.5 && deg <= 157.5) return 'ЮВ';
|
||
if (deg > 157.5 && deg <= 202.5) return 'Ю';
|
||
if (deg > 202.5 && deg <= 247.5) return 'ЮЗ';
|
||
if (deg > 247.5 && deg <= 292.5) return 'З';
|
||
if (deg > 292.5 && deg <= 337.5) return 'СЗ';
|
||
return null;
|
||
};
|
||
|
||
const dirCounts = directions.map(getWindDirection)
|
||
.filter(d => d !== null)
|
||
.reduce((acc, dir) => {
|
||
acc[dir] = (acc[dir] || 0) + 1;
|
||
return acc;
|
||
}, {});
|
||
|
||
const prevailingDir = Object.keys(dirCounts).length > 0 ? Object.keys(dirCounts).reduce((a, b) => dirCounts[a] > dirCounts[b] ? a : b) : 'Нет данных';
|
||
|
||
analysisSummary.innerHTML = `
|
||
<ul>
|
||
<li><strong>Температура:</strong></li>
|
||
<ul>
|
||
<li>Максимальная: <strong>${maxTemp}°C</strong></li>
|
||
<li>Минимальная: <strong>${minTemp}°C</strong></li>
|
||
<li>Средняя: <strong>${avgTemp}°C</strong></li>
|
||
</ul>
|
||
<li><strong>Ветер:</strong></li>
|
||
<ul>
|
||
<li>Максимальная скорость: <strong>${maxWind} м/с</strong></li>
|
||
<li>Преобладающее направление: <strong>${prevailingDir}</strong></li>
|
||
</ul>
|
||
</ul>
|
||
`;
|
||
};
|
||
|
||
const displayRawData = (data) => {
|
||
let tableHTML = `
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Время (UTC)</th>
|
||
<th>Сводка METAR</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
data.forEach(report => {
|
||
const date = new Date(report.valid);
|
||
|
||
const formattedDate = date.toLocaleString('ru-RU', {
|
||
timeZone: 'UTC',
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
|
||
tableHTML += `
|
||
<tr>
|
||
<td>${formattedDate}</td>
|
||
<td><code>${report.metar}</code></td>
|
||
</tr>
|
||
`;
|
||
});
|
||
tableHTML += `</tbody></table>`;
|
||
rawDataContainer.innerHTML = tableHTML;
|
||
};
|
||
|
||
|
||
fetchButton.addEventListener('click', fetchAndDrawData);
|
||
|
||
fetchAndDrawData();
|
||
}); |