сравнение TLE
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
.tle-analysis-page {
|
||||
height: calc(100vh - 4.75rem);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tle-analysis-layout {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tle-analysis-layout > [class*="col-"] {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tle-analysis-sidebar,
|
||||
.tle-analysis-results-card {
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tle-analysis-satellites-wrap,
|
||||
.tle-analysis-results-wrap {
|
||||
overflow: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tle-analysis-satellites-wrap {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.tle-analysis-results-wrap {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.tle-analysis-satellites-table tbody tr,
|
||||
.tle-analysis-results-table tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tle-analysis-satellites-table tbody tr.active {
|
||||
--bs-table-bg: rgba(13, 110, 253, 0.12);
|
||||
--bs-table-hover-bg: rgba(13, 110, 253, 0.18);
|
||||
}
|
||||
|
||||
.tle-analysis-vector {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 0.82rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tle-analysis-discrepancy {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.tle-analysis-page {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tle-analysis-satellites-wrap,
|
||||
.tle-analysis-results-wrap {
|
||||
max-height: 60vh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
(function () {
|
||||
const state = {
|
||||
satellites: [],
|
||||
selectedSatelliteId: null,
|
||||
analysis: null,
|
||||
loading: false
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
bindEvents();
|
||||
await loadSatellites();
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
document.getElementById('tle-analysis-refresh')?.addEventListener('click', async () => {
|
||||
await loadSatellites();
|
||||
if (state.selectedSatelliteId) {
|
||||
await loadAnalysis(state.selectedSatelliteId);
|
||||
}
|
||||
});
|
||||
document.getElementById('tle-analysis-satellite-filter')?.addEventListener('input', renderSatellites);
|
||||
}
|
||||
|
||||
async function loadSatellites() {
|
||||
showAlert('Загрузка списка спутников...', 'info');
|
||||
try {
|
||||
const response = await fetch('/api/catalog/satellites');
|
||||
if (!response.ok) {
|
||||
throw new Error(await errorText(response));
|
||||
}
|
||||
state.satellites = await response.json();
|
||||
renderSatellites();
|
||||
clearAlert();
|
||||
} catch (error) {
|
||||
showAlert(`Не удалось загрузить спутники: ${error.message}`, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAnalysis(satelliteId) {
|
||||
state.loading = true;
|
||||
state.analysis = null;
|
||||
renderResults();
|
||||
showAlert('Формирование анализа TLE...', 'info');
|
||||
try {
|
||||
const response = await fetch(`/api/tle-analysis/satellites/${encodeURIComponent(satelliteId)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(await errorText(response));
|
||||
}
|
||||
state.analysis = await response.json();
|
||||
renderResults();
|
||||
clearAlert();
|
||||
} catch (error) {
|
||||
state.analysis = null;
|
||||
renderResults();
|
||||
showAlert(`Не удалось сформировать анализ TLE: ${error.message}`, 'danger');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderSatellites() {
|
||||
const tbody = document.getElementById('tle-analysis-satellites-body');
|
||||
if (!tbody) return;
|
||||
|
||||
const filter = normalize(document.getElementById('tle-analysis-satellite-filter')?.value || '');
|
||||
const satellites = state.satellites.filter((satellite) => {
|
||||
if (!filter) return true;
|
||||
return normalize([
|
||||
satellite.id,
|
||||
satellite.noradId,
|
||||
satellite.code,
|
||||
satellite.name,
|
||||
satellite.typeCode
|
||||
].join(' ')).includes(filter);
|
||||
});
|
||||
|
||||
if (satellites.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="3" class="catalog-empty">Спутники не найдены</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = satellites.map((satellite) => {
|
||||
const active = Number(satellite.id) === Number(state.selectedSatelliteId) ? 'active' : '';
|
||||
const title = escapeHtml(satellite.name || satellite.code || `КА ${satellite.id}`);
|
||||
return `
|
||||
<tr class="${active}" data-satellite-id="${escapeHtml(satellite.id)}">
|
||||
<td>${escapeHtml(satellite.id)}</td>
|
||||
<td>
|
||||
<div class="catalog-main-text">${title}</div>
|
||||
<div class="catalog-helper">${escapeHtml(satellite.code || '')} ${satellite.typeCode ? ' / ' + escapeHtml(satellite.typeCode) : ''}</div>
|
||||
</td>
|
||||
<td>${satellite.noradId == null ? '—' : escapeHtml(satellite.noradId)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
tbody.querySelectorAll('tr[data-satellite-id]').forEach((row) => {
|
||||
row.addEventListener('click', async () => {
|
||||
const satelliteId = Number(row.dataset.satelliteId);
|
||||
state.selectedSatelliteId = satelliteId;
|
||||
state.analysis = null;
|
||||
renderSatellites();
|
||||
renderSelectedSatellite();
|
||||
await loadAnalysis(satelliteId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderSelectedSatellite() {
|
||||
const label = document.getElementById('tle-analysis-selected-satellite');
|
||||
if (!label) return;
|
||||
const satellite = state.satellites.find((item) => Number(item.id) === Number(state.selectedSatelliteId));
|
||||
if (!satellite) {
|
||||
label.textContent = 'Выберите спутник слева.';
|
||||
return;
|
||||
}
|
||||
const name = satellite.name || satellite.code || `КА ${satellite.id}`;
|
||||
label.textContent = `КА ${satellite.id}: ${name}${satellite.noradId == null ? '' : `, NORAD ${satellite.noradId}`}`;
|
||||
}
|
||||
|
||||
function renderResults() {
|
||||
renderSelectedSatellite();
|
||||
const empty = document.getElementById('tle-analysis-empty');
|
||||
const wrap = document.getElementById('tle-analysis-table-wrap');
|
||||
const tbody = document.getElementById('tle-analysis-results-body');
|
||||
const summary = document.getElementById('tle-analysis-summary');
|
||||
if (!empty || !wrap || !tbody || !summary) return;
|
||||
|
||||
if (state.loading) {
|
||||
empty.textContent = 'Формирование анализа TLE...';
|
||||
empty.classList.remove('d-none');
|
||||
wrap.classList.add('d-none');
|
||||
summary.textContent = '';
|
||||
tbody.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = state.analysis?.rows || [];
|
||||
summary.textContent = rows.length ? `Записей TLE: ${rows.length}` : '';
|
||||
|
||||
if (!state.selectedSatelliteId) {
|
||||
empty.textContent = 'Выберите спутник для загрузки TLE.';
|
||||
empty.classList.remove('d-none');
|
||||
wrap.classList.add('d-none');
|
||||
tbody.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
empty.textContent = 'Для выбранного спутника TLE не найдены.';
|
||||
empty.classList.remove('d-none');
|
||||
wrap.classList.add('d-none');
|
||||
tbody.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
empty.classList.add('d-none');
|
||||
wrap.classList.remove('d-none');
|
||||
tbody.innerHTML = rows.map(renderAnalysisRow).join('');
|
||||
}
|
||||
|
||||
function renderAnalysisRow(row) {
|
||||
const r = row.radiusVector || {};
|
||||
const discrepancy = row.discrepancy;
|
||||
const diffText = discrepancy
|
||||
? `<div class="tle-analysis-discrepancy">${formatNumber(discrepancy.norm, 3)}</div>
|
||||
<div class="catalog-helper tle-analysis-vector">Δx=${formatNumber(discrepancy.dx, 3)}, Δy=${formatNumber(discrepancy.dy, 3)}, Δz=${formatNumber(discrepancy.dz, 3)}</div>
|
||||
<div class="catalog-helper">к предыдущей эпохе ${escapeHtml(formatDateTime(discrepancy.previousEpoch))}</div>`
|
||||
: '<span class="catalog-helper">не рассчитывается</span>';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(row.index)}</td>
|
||||
<td>
|
||||
<div>${escapeHtml(formatDateTime(row.epoch))}</div>
|
||||
${row.tleHeader ? `<div class="catalog-helper">${escapeHtml(row.tleHeader)}</div>` : ''}
|
||||
</td>
|
||||
<td>${escapeHtml(row.revolution)}</td>
|
||||
<td>${escapeHtml(row.noradId)}</td>
|
||||
<td class="tle-analysis-vector">
|
||||
x=${formatNumber(r.x, 3)}<br>
|
||||
y=${formatNumber(r.y, 3)}<br>
|
||||
z=${formatNumber(r.z, 3)}
|
||||
</td>
|
||||
<td>${formatNumber(row.radiusNorm, 3)}</td>
|
||||
<td>${diffText}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
function normalize(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '—';
|
||||
return String(value).replace('T', ' ');
|
||||
}
|
||||
|
||||
function formatNumber(value, fractionDigits) {
|
||||
const number = Number(value);
|
||||
if (!Number.isFinite(number)) return '—';
|
||||
return number.toLocaleString('ru-RU', {
|
||||
minimumFractionDigits: fractionDigits,
|
||||
maximumFractionDigits: fractionDigits
|
||||
});
|
||||
}
|
||||
|
||||
async function errorText(response) {
|
||||
const text = await response.text();
|
||||
if (!text) return `${response.status} ${response.statusText}`;
|
||||
try {
|
||||
const json = JSON.parse(text);
|
||||
return json.message || json.error || text;
|
||||
} catch (_) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function showAlert(message, type) {
|
||||
const alert = document.getElementById('tle-analysis-alert');
|
||||
if (!alert) return;
|
||||
alert.innerHTML = `<div class="alert alert-${type} py-2" role="alert">${escapeHtml(message)}</div>`;
|
||||
}
|
||||
|
||||
function clearAlert() {
|
||||
const alert = document.getElementById('tle-analysis-alert');
|
||||
if (alert) alert.innerHTML = '';
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user