Добавил альтернативное имя у спутника

This commit is contained in:
2025-12-01 12:19:24 +03:00
parent 01871c3e13
commit c72bf12d41
14 changed files with 720 additions and 220 deletions

View File

@@ -43,8 +43,13 @@ function showSatelliteModal(satelliteId) {
'<div class="col-md-6"><div class="card h-100">' +
'<div class="card-header bg-light"><strong><i class="bi bi-info-circle"></i> Основная информация</strong></div>' +
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
'<tr><td class="text-muted" style="width: 40%;">Название:</td><td><strong>' + data.name + '</strong></td></tr>' +
'<tr><td class="text-muted">NORAD ID:</td><td>' + data.norad + '</td></tr>' +
'<tr><td class="text-muted" style="width: 40%;">Название:</td><td><strong>' + data.name + '</strong></td></tr>';
if (data.alternative_name && data.alternative_name !== '-') {
html += '<tr><td class="text-muted">Альтернативное название:</td><td><strong>' + data.alternative_name + '</strong></td></tr>';
}
html += '<tr><td class="text-muted">NORAD ID:</td><td>' + data.norad + '</td></tr>' +
'<tr><td class="text-muted">Подспутниковая точка:</td><td><strong>' + data.undersat_point + '</strong></td></tr>' +
'<tr><td class="text-muted">Диапазоны:</td><td>' + data.bands + '</td></tr>' +
'</tbody></table></div></div></div>' +

View File

@@ -180,6 +180,9 @@
<button id="export-xlsx" class="btn btn-success" disabled>
<i class="bi bi-file-earmark-excel"></i> Сохранить в Excel
</button>
<button id="export-json" class="btn btn-info ms-2" disabled>
<i class="bi bi-filetype-json"></i> Сохранить в JSON
</button>
<button id="clear-table" class="btn btn-danger ms-2">
<i class="bi bi-trash"></i> Очистить таблицу
</button>
@@ -292,18 +295,21 @@ document.addEventListener('DOMContentLoaded', function() {
{
title: "Действия",
field: "actions",
minWidth: 100,
minWidth: 120,
widthGrow: 1,
hozAlign: "center",
formatter: function(cell, formatterParams, onRendered) {
const data = cell.getRow().getData();
const btnClass = data.has_outliers ? 'btn-warning' : 'btn-info';
return `<button class="btn btn-sm ${btnClass} btn-view-details" title="Просмотр точек"><i class="bi bi-eye"></i></button>`;
return `<button class="btn btn-sm ${btnClass} btn-view-details" title="Просмотр точек"><i class="bi bi-eye"></i></button>
<button class="btn btn-sm btn-danger btn-delete-row ms-1" title="Удалить строку"><i class="bi bi-trash"></i></button>`;
},
cellClick: function(e, cell) {
const data = cell.getRow().getData();
if (e.target.closest('.btn-view-details')) {
showGroupDetails(data._groupIndex);
} else if (e.target.closest('.btn-delete-row')) {
deleteGroupRow(data._groupIndex);
}
}
}
@@ -321,6 +327,31 @@ document.addEventListener('DOMContentLoaded', function() {
function updateGroupCount() {
document.getElementById('group-count').textContent = allGroupsData.length;
document.getElementById('export-xlsx').disabled = allGroupsData.length === 0;
document.getElementById('export-json').disabled = allGroupsData.length === 0;
}
// Delete group row
function deleteGroupRow(groupIndex) {
if (!confirm('Удалить эту группу из таблицы?')) {
return;
}
// Удаляем группу из массива
allGroupsData.splice(groupIndex, 1);
// Пересчитываем индексы для оставшихся групп
allGroupsData.forEach((group, idx) => {
group._groupIndex = idx;
});
// Обновляем таблицу
table.setData(allGroupsData.map(g => ({
...g,
status: g.has_outliers ? 'outliers' : 'ok'
})));
updateGroupCount();
updateAllPointsTable();
}
// Show loading overlay
@@ -837,6 +868,252 @@ document.addEventListener('DOMContentLoaded', function() {
return cookieValue;
}
// Generate UUID v4
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Export to JSON
document.getElementById('export-json').addEventListener('click', function() {
if (allGroupsData.length === 0) {
alert('Нет данных для экспорта');
return;
}
// Creator ID - можно изменить на нужное значение
const CREATOR_ID = '6fd12c90-7f17-43d9-a03e-ee14e880f757';
// Начальный объект path (фиксированный)
const pathObject = {
"tacticObjectType": "path",
"captionPosition": "right",
"points": [
{
"id": "b92b9cbb-dd27-49aa-bcb6-e89a147bc02c",
"latitude": 57,
"longitude": -13,
"altitude": 0,
"customActions": [],
"tags": { "creator": CREATOR_ID },
"tacticObjectType": "point"
},
{
"id": "8e3666d4-4990-4cb9-9594-63ad06333489",
"latitude": 57,
"longitude": 64,
"altitude": 0,
"customActions": [],
"tags": { "creator": CREATOR_ID },
"tacticObjectType": "point"
},
{
"id": "5f137485-d2fc-443d-8507-c936f02f3569",
"latitude": 11,
"longitude": 64,
"altitude": 0,
"customActions": [],
"tags": { "creator": CREATOR_ID },
"tacticObjectType": "point"
},
{
"id": "0fb90df7-8eb0-49fa-9d00-336389171bf5",
"latitude": 11,
"longitude": -13,
"altitude": 0,
"customActions": [],
"tags": { "creator": CREATOR_ID },
"tacticObjectType": "point"
},
{
"id": "3ef12637-585e-40a4-b0ee-8f1786c89ce6",
"latitude": 57,
"longitude": -13,
"altitude": 0,
"customActions": [],
"tags": { "creator": CREATOR_ID },
"tacticObjectType": "point"
}
],
"isCycle": false,
"id": "2f604051-4984-4c2f-8c4c-c0cb64008f5f",
"draggable": false,
"selectable": false,
"editable": false,
"caption": "Ограничение для работы с поверхностями",
"line": {
"color": "rgb(148,0,211)",
"thickness": 1,
"dash": "solid",
"border": null
},
"customActions": [],
"tags": { "creator": CREATOR_ID }
};
// Результирующий массив
const result = [pathObject];
// Палитра цветов для групп (RGB формат)
const jsonGroupColors = [
"rgb(0,128,0)", // зелёный
"rgb(0,0,255)", // синий
"rgb(255,0,0)", // красный
"rgb(255,165,0)", // оранжевый
"rgb(128,0,128)", // фиолетовый
"rgb(0,128,128)", // бирюзовый
"rgb(255,20,147)", // розовый
"rgb(139,69,19)", // коричневый
"rgb(0,100,0)", // тёмно-зелёный
"rgb(70,130,180)" // стальной синий
];
// Обрабатываем каждую группу
allGroupsData.forEach((group, groupIndex) => {
// Цвет для текущей группы
const groupColor = jsonGroupColors[groupIndex % jsonGroupColors.length];
// Формируем имя для усреднённой точки с пометкой "(усредн)"
const avgName = group.source_name;
const avgTime = group.avg_time || '-';
const avgCaption = `${avgName} (усредн) - ${avgTime}`;
// Получаем координаты усреднённой точки
const avgCoord = group.avg_coord_tuple;
const avgLat = avgCoord[1];
const avgLon = avgCoord[0];
// ID для усреднённой точки (source)
const avgSourceId = generateUUID();
// Создаём source для усреднённой точки (triangle)
const avgSource = {
"tacticObjectType": "source",
"captionPosition": "right",
"id": avgSourceId,
"icon": {
"type": "triangle",
"color": groupColor
},
"caption": avgCaption,
"name": avgCaption,
"customActions": [],
"trackBehavior": {},
"bearingStyle": {
"color": groupColor,
"thickness": 2,
"dash": "solid",
"border": null
},
"bearingBehavior": {},
"tags": { "creator": CREATOR_ID }
};
result.push(avgSource);
// Создаём position для усреднённой точки
const avgPosition = {
"tacticObjectType": "position",
"id": generateUUID(),
"parentId": avgSourceId,
"timeStamp": Date.now() / 1000,
"latitude": avgLat,
"altitude": 0,
"longitude": avgLon,
"caption": "",
"tooltip": "",
"customActions": [],
"tags": {
"layers": [],
"creator": CREATOR_ID
}
};
result.push(avgPosition);
// Обрабатываем все точки группы (не выбросы)
group.points.forEach(point => {
if (point.is_outlier) return; // Пропускаем выбросы
const pointCoord = point.coord_tuple;
const pointLat = pointCoord[1];
const pointLon = pointCoord[0];
const pointName = point.name || '-';
const pointTime = point.timestamp || '-';
const pointCaption = `${pointName} - ${pointTime}`;
// ID для source точки
const pointSourceId = generateUUID();
// Создаём source для точки (circle) с тем же цветом группы
const pointSource = {
"tacticObjectType": "source",
"captionPosition": "right",
"id": pointSourceId,
"icon": {
"type": "circle",
"color": groupColor
},
"caption": pointCaption,
"name": pointCaption,
"customActions": [],
"trackBehavior": {},
"bearingStyle": {
"color": groupColor,
"thickness": 2,
"dash": "solid",
"border": null
},
"bearingBehavior": {},
"tags": { "creator": CREATOR_ID }
};
result.push(pointSource);
// Создаём position для точки
const pointPosition = {
"tacticObjectType": "position",
"id": generateUUID(),
"parentId": pointSourceId,
"timeStamp": point.timestamp_unix || (Date.now() / 1000),
"latitude": pointLat,
"altitude": 0,
"longitude": pointLon,
"caption": "",
"tooltip": "",
"customActions": [],
"tags": {
"layers": [],
"creator": CREATOR_ID
}
};
result.push(pointPosition);
});
});
// Конвертируем в JSON строку
const jsonString = JSON.stringify(result, null, 2);
// Добавляем BOM для UTF-8
const BOM = '\uFEFF';
const blob = new Blob([BOM + jsonString], { type: 'application/json;charset=utf-8' });
// Генерируем имя файла
const now = new Date();
const dateStr = now.toISOString().slice(0, 10);
const filename = `averaging_${dateStr}.json`;
// Скачиваем файл
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// Initialize
updateGroupCount();
});

View File

@@ -86,6 +86,25 @@
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.alternative_name.id_for_label }}" class="form-label">
{{ form.alternative_name.label }}
</label>
{{ form.alternative_name }}
{% if form.alternative_name.errors %}
<div class="invalid-feedback d-block">
{{ form.alternative_name.errors.0 }}
</div>
{% endif %}
{% if form.alternative_name.help_text %}
<div class="form-text">{{ form.alternative_name.help_text }}</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.norad.id_for_label }}" class="form-label">
@@ -102,9 +121,7 @@
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.international_code.id_for_label }}" class="form-label">
@@ -121,7 +138,9 @@
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.undersat_point.id_for_label }}" class="form-label">
@@ -138,6 +157,23 @@
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.launch_date.id_for_label }}" class="form-label">
{{ form.launch_date.label }}
</label>
{{ form.launch_date }}
{% if form.launch_date.errors %}
<div class="invalid-feedback d-block">
{{ form.launch_date.errors.0 }}
</div>
{% endif %}
{% if form.launch_date.help_text %}
<div class="form-text">{{ form.launch_date.help_text }}</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
@@ -160,24 +196,7 @@
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.launch_date.id_for_label }}" class="form-label">
{{ form.launch_date.label }}
</label>
{{ form.launch_date }}
{% if form.launch_date.errors %}
<div class="invalid-feedback d-block">
{{ form.launch_date.errors.0 }}
</div>
{% endif %}
{% if form.launch_date.help_text %}
<div class="form-text">{{ form.launch_date.help_text }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="col-md-12">
<div class="mb-3">
<label for="{{ form.url.id_for_label }}" class="form-label">
{{ form.url.label }}

View File

@@ -190,6 +190,16 @@
{% endif %}
</a>
</th>
<th scope="col" style="min-width: 150px;">
<a href="javascript:void(0)" onclick="updateSort('alternative_name')" class="text-white text-decoration-none">
Альт. название
{% if sort == 'alternative_name' %}
<i class="bi bi-arrow-up"></i>
{% elif sort == '-alternative_name' %}
<i class="bi bi-arrow-down"></i>
{% endif %}
</a>
</th>
<th scope="col" style="min-width: 100px;">
<a href="javascript:void(0)" onclick="updateSort('norad')" class="text-white text-decoration-none">
NORAD ID
@@ -274,6 +284,7 @@
</td>
<td class="text-center">{{ satellite.id }}</td>
<td>{{ satellite.name }}</td>
<td>{{ satellite.alternative_name|default:"-" }}</td>
<td>{{ satellite.norad }}</td>
<td>{{ satellite.international_code|default:"-" }}</td>
<td>{{ satellite.bands }}</td>
@@ -307,7 +318,7 @@
</tr>
{% empty %}
<tr>
<td colspan="13" class="text-center text-muted">Нет данных для отображения</td>
<td colspan="14" class="text-center text-muted">Нет данных для отображения</td>
</tr>
{% endfor %}
</tbody>