diff --git a/dbapp/lyngsatapp/async_utils.py b/dbapp/lyngsatapp/async_utils.py
index daadaa8..e5c3c45 100644
--- a/dbapp/lyngsatapp/async_utils.py
+++ b/dbapp/lyngsatapp/async_utils.py
@@ -53,9 +53,13 @@ def process_single_satellite(
logger.info(f"Найдено {len(sources)} источников для {sat_name}")
- # Находим спутник в базе
+ # Находим спутник в базе по имени или альтернативному имени (lowercase)
+ from django.db.models import Q
+ sat_name_lower = sat_name.lower()
try:
- sat_obj = Satellite.objects.get(name__icontains=sat_name)
+ sat_obj = Satellite.objects.get(
+ Q(name__icontains=sat_name_lower) | Q(alternative_name__icontains=sat_name_lower)
+ )
logger.debug(f"Спутник {sat_name} найден в базе (ID: {sat_obj.id})")
except Satellite.DoesNotExist:
error_msg = f"Спутник '{sat_name}' не найден в базе данных"
diff --git a/dbapp/lyngsatapp/utils.py b/dbapp/lyngsatapp/utils.py
index 851a0d7..aad0d5f 100644
--- a/dbapp/lyngsatapp/utils.py
+++ b/dbapp/lyngsatapp/utils.py
@@ -1,175 +1,179 @@
-import logging
-from .parser import LyngSatParser
-from .models import LyngSat
-from mainapp.models import Polarization, Standard, Modulation, Satellite
-
-logger = logging.getLogger(__name__)
-
-
-def fill_lyngsat_data(
- target_sats: list[str],
- regions: list[str] = None,
- task_id: str = None,
- update_progress=None
-):
- """
- Заполняет данные Lyngsat для указанных спутников и регионов.
-
- Args:
- target_sats: Список названий спутников для обработки
- regions: Список регионов для парсинга (по умолчанию все)
- task_id: ID задачи Celery для логирования
- update_progress: Функция для обновления прогресса (current, total, status)
-
- Returns:
- dict: Статистика обработки с ключами:
- - total_satellites: общее количество спутников
- - total_sources: общее количество источников
- - created: количество созданных записей
- - updated: количество обновленных записей
- - errors: список ошибок
- """
- log_prefix = f"[Task {task_id}]" if task_id else "[Lyngsat]"
- stats = {
- 'total_satellites': 0,
- 'total_sources': 0,
- 'created': 0,
- 'updated': 0,
- 'errors': []
- }
-
- if regions is None:
- regions = ["europe", "asia", "america", "atlantic"]
-
- logger.info(f"{log_prefix} Начало парсинга данных")
- logger.info(f"{log_prefix} Спутники: {', '.join(target_sats)}")
- logger.info(f"{log_prefix} Регионы: {', '.join(regions)}")
-
- if update_progress:
- update_progress(0, len(target_sats), "Инициализация парсера...")
-
- try:
- parser = LyngSatParser(
- flaresolver_url="http://localhost:8191/v1",
- target_sats=target_sats,
- regions=regions
- )
-
- logger.info(f"{log_prefix} Получение данных со спутников...")
- if update_progress:
- update_progress(0, len(target_sats), "Получение данных со спутников...")
-
- lyngsat_data = parser.get_satellites_data()
- stats['total_satellites'] = len(lyngsat_data)
-
- logger.info(f"{log_prefix} Получено данных по {stats['total_satellites']} спутникам")
-
- for idx, (sat_name, data) in enumerate(lyngsat_data.items(), 1):
- logger.info(f"{log_prefix} Обработка спутника {idx}/{stats['total_satellites']}: {sat_name}")
-
- if update_progress:
- update_progress(idx, stats['total_satellites'], f"Обработка {sat_name}...")
-
- url = data['url']
- sources = data['sources']
- stats['total_sources'] += len(sources)
-
- logger.info(f"{log_prefix} Найдено {len(sources)} источников для {sat_name}")
-
- # Находим спутник в базе
- try:
- sat_obj = Satellite.objects.get(name__icontains=sat_name)
- logger.debug(f"{log_prefix} Спутник {sat_name} найден в базе (ID: {sat_obj.id})")
- except Satellite.DoesNotExist:
- error_msg = f"Спутник '{sat_name}' не найден в базе данных"
- logger.warning(f"{log_prefix} {error_msg}")
- stats['errors'].append(error_msg)
- continue
- except Satellite.MultipleObjectsReturned:
- error_msg = f"Найдено несколько спутников с именем '{sat_name}'"
- logger.warning(f"{log_prefix} {error_msg}")
- stats['errors'].append(error_msg)
- continue
-
- for source_idx, source in enumerate(sources, 1):
- try:
- # Парсим частоту
- try:
- freq = float(source['freq'])
- except (ValueError, TypeError):
- freq = -1.0
- error_msg = f"Некорректная частота для {sat_name}: {source.get('freq')}"
- logger.debug(f"{log_prefix} {error_msg}")
- stats['errors'].append(error_msg)
-
- last_update = source['last_update']
- fec = source['metadata'].get('fec')
- modulation_name = source['metadata'].get('modulation')
- standard_name = source['metadata'].get('standard')
- symbol_velocity = source['metadata'].get('symbol_rate')
- polarization_name = source['pol']
- channel_info = source['provider_name']
-
- # Создаем или получаем связанные объекты
- pol_obj, _ = Polarization.objects.get_or_create(
- name=polarization_name if polarization_name else "-"
- )
-
- mod_obj, _ = Modulation.objects.get_or_create(
- name=modulation_name if modulation_name else "-"
- )
-
- standard_obj, _ = Standard.objects.get_or_create(
- name=standard_name if standard_name else "-"
- )
-
- # Создаем или обновляем запись Lyngsat
- lyng_obj, created = LyngSat.objects.update_or_create(
- id_satellite=sat_obj,
- frequency=freq,
- polarization=pol_obj,
- defaults={
- "modulation": mod_obj,
- "standard": standard_obj,
- "sym_velocity": symbol_velocity if symbol_velocity else 0,
- "channel_info": channel_info[:20] if channel_info else "",
- "last_update": last_update,
- "fec": fec[:30] if fec else "",
- "url": url
- }
- )
-
- if created:
- stats['created'] += 1
- logger.debug(f"{log_prefix} Создана запись для {sat_name} {freq} МГц")
- else:
- stats['updated'] += 1
- logger.debug(f"{log_prefix} Обновлена запись для {sat_name} {freq} МГц")
-
- # Логируем прогресс каждые 10 источников
- if source_idx % 10 == 0:
- logger.info(f"{log_prefix} Обработано {source_idx}/{len(sources)} источников для {sat_name}")
-
- except Exception as e:
- error_msg = f"Ошибка при обработке источника {sat_name}: {str(e)}"
- logger.error(f"{log_prefix} {error_msg}", exc_info=True)
- stats['errors'].append(error_msg)
- continue
-
- logger.info(f"{log_prefix} Завершена обработка {sat_name}: создано {stats['created']}, обновлено {stats['updated']}")
-
- except Exception as e:
- error_msg = f"Критическая ошибка: {str(e)}"
- logger.error(f"{log_prefix} {error_msg}", exc_info=True)
- stats['errors'].append(error_msg)
-
- logger.info(f"{log_prefix} Обработка завершена. Итого: создано {stats['created']}, обновлено {stats['updated']}, ошибок {len(stats['errors'])}")
-
- if update_progress:
- update_progress(stats['total_satellites'], stats['total_satellites'], "Завершено")
-
- return stats
-
-
-def link_lyngsat_to_sources():
+import logging
+from .parser import LyngSatParser
+from .models import LyngSat
+from mainapp.models import Polarization, Standard, Modulation, Satellite
+
+logger = logging.getLogger(__name__)
+
+
+def fill_lyngsat_data(
+ target_sats: list[str],
+ regions: list[str] = None,
+ task_id: str = None,
+ update_progress=None
+):
+ """
+ Заполняет данные Lyngsat для указанных спутников и регионов.
+
+ Args:
+ target_sats: Список названий спутников для обработки
+ regions: Список регионов для парсинга (по умолчанию все)
+ task_id: ID задачи Celery для логирования
+ update_progress: Функция для обновления прогресса (current, total, status)
+
+ Returns:
+ dict: Статистика обработки с ключами:
+ - total_satellites: общее количество спутников
+ - total_sources: общее количество источников
+ - created: количество созданных записей
+ - updated: количество обновленных записей
+ - errors: список ошибок
+ """
+ log_prefix = f"[Task {task_id}]" if task_id else "[Lyngsat]"
+ stats = {
+ 'total_satellites': 0,
+ 'total_sources': 0,
+ 'created': 0,
+ 'updated': 0,
+ 'errors': []
+ }
+
+ if regions is None:
+ regions = ["europe", "asia", "america", "atlantic"]
+
+ logger.info(f"{log_prefix} Начало парсинга данных")
+ logger.info(f"{log_prefix} Спутники: {', '.join(target_sats)}")
+ logger.info(f"{log_prefix} Регионы: {', '.join(regions)}")
+
+ if update_progress:
+ update_progress(0, len(target_sats), "Инициализация парсера...")
+
+ try:
+ parser = LyngSatParser(
+ flaresolver_url="http://localhost:8191/v1",
+ target_sats=target_sats,
+ regions=regions
+ )
+
+ logger.info(f"{log_prefix} Получение данных со спутников...")
+ if update_progress:
+ update_progress(0, len(target_sats), "Получение данных со спутников...")
+
+ lyngsat_data = parser.get_satellites_data()
+ stats['total_satellites'] = len(lyngsat_data)
+
+ logger.info(f"{log_prefix} Получено данных по {stats['total_satellites']} спутникам")
+
+ for idx, (sat_name, data) in enumerate(lyngsat_data.items(), 1):
+ logger.info(f"{log_prefix} Обработка спутника {idx}/{stats['total_satellites']}: {sat_name}")
+
+ if update_progress:
+ update_progress(idx, stats['total_satellites'], f"Обработка {sat_name}...")
+
+ url = data['url']
+ sources = data['sources']
+ stats['total_sources'] += len(sources)
+
+ logger.info(f"{log_prefix} Найдено {len(sources)} источников для {sat_name}")
+
+ # Находим спутник в базе по имени или альтернативному имени (lowercase)
+ from django.db.models import Q
+ sat_name_lower = sat_name.lower()
+ try:
+ sat_obj = Satellite.objects.get(
+ Q(name__icontains=sat_name_lower) | Q(alternative_name__icontains=sat_name_lower)
+ )
+ logger.debug(f"{log_prefix} Спутник {sat_name} найден в базе (ID: {sat_obj.id})")
+ except Satellite.DoesNotExist:
+ error_msg = f"Спутник '{sat_name}' не найден в базе данных"
+ logger.warning(f"{log_prefix} {error_msg}")
+ stats['errors'].append(error_msg)
+ continue
+ except Satellite.MultipleObjectsReturned:
+ error_msg = f"Найдено несколько спутников с именем '{sat_name}'"
+ logger.warning(f"{log_prefix} {error_msg}")
+ stats['errors'].append(error_msg)
+ continue
+
+ for source_idx, source in enumerate(sources, 1):
+ try:
+ # Парсим частоту
+ try:
+ freq = float(source['freq'])
+ except (ValueError, TypeError):
+ freq = -1.0
+ error_msg = f"Некорректная частота для {sat_name}: {source.get('freq')}"
+ logger.debug(f"{log_prefix} {error_msg}")
+ stats['errors'].append(error_msg)
+
+ last_update = source['last_update']
+ fec = source['metadata'].get('fec')
+ modulation_name = source['metadata'].get('modulation')
+ standard_name = source['metadata'].get('standard')
+ symbol_velocity = source['metadata'].get('symbol_rate')
+ polarization_name = source['pol']
+ channel_info = source['provider_name']
+
+ # Создаем или получаем связанные объекты
+ pol_obj, _ = Polarization.objects.get_or_create(
+ name=polarization_name if polarization_name else "-"
+ )
+
+ mod_obj, _ = Modulation.objects.get_or_create(
+ name=modulation_name if modulation_name else "-"
+ )
+
+ standard_obj, _ = Standard.objects.get_or_create(
+ name=standard_name if standard_name else "-"
+ )
+
+ # Создаем или обновляем запись Lyngsat
+ lyng_obj, created = LyngSat.objects.update_or_create(
+ id_satellite=sat_obj,
+ frequency=freq,
+ polarization=pol_obj,
+ defaults={
+ "modulation": mod_obj,
+ "standard": standard_obj,
+ "sym_velocity": symbol_velocity if symbol_velocity else 0,
+ "channel_info": channel_info[:20] if channel_info else "",
+ "last_update": last_update,
+ "fec": fec[:30] if fec else "",
+ "url": url
+ }
+ )
+
+ if created:
+ stats['created'] += 1
+ logger.debug(f"{log_prefix} Создана запись для {sat_name} {freq} МГц")
+ else:
+ stats['updated'] += 1
+ logger.debug(f"{log_prefix} Обновлена запись для {sat_name} {freq} МГц")
+
+ # Логируем прогресс каждые 10 источников
+ if source_idx % 10 == 0:
+ logger.info(f"{log_prefix} Обработано {source_idx}/{len(sources)} источников для {sat_name}")
+
+ except Exception as e:
+ error_msg = f"Ошибка при обработке источника {sat_name}: {str(e)}"
+ logger.error(f"{log_prefix} {error_msg}", exc_info=True)
+ stats['errors'].append(error_msg)
+ continue
+
+ logger.info(f"{log_prefix} Завершена обработка {sat_name}: создано {stats['created']}, обновлено {stats['updated']}")
+
+ except Exception as e:
+ error_msg = f"Критическая ошибка: {str(e)}"
+ logger.error(f"{log_prefix} {error_msg}", exc_info=True)
+ stats['errors'].append(error_msg)
+
+ logger.info(f"{log_prefix} Обработка завершена. Итого: создано {stats['created']}, обновлено {stats['updated']}, ошибок {len(stats['errors'])}")
+
+ if update_progress:
+ update_progress(stats['total_satellites'], stats['total_satellites'], "Завершено")
+
+ return stats
+
+
+def link_lyngsat_to_sources():
pass
\ No newline at end of file
diff --git a/dbapp/mainapp/admin.py b/dbapp/mainapp/admin.py
index 36f5a16..f954a39 100644
--- a/dbapp/mainapp/admin.py
+++ b/dbapp/mainapp/admin.py
@@ -573,6 +573,7 @@ class SatelliteAdmin(BaseAdmin):
list_display = (
"name",
+ "alternative_name",
"norad",
"international_code",
"undersat_point",
@@ -580,7 +581,7 @@ class SatelliteAdmin(BaseAdmin):
"created_at",
"updated_at",
)
- search_fields = ("name", "norad", "international_code")
+ search_fields = ("name", "alternative_name", "norad", "international_code")
ordering = ("name",)
filter_horizontal = ("band",)
autocomplete_fields = ("band",)
diff --git a/dbapp/mainapp/forms.py b/dbapp/mainapp/forms.py
index c66c6ca..9755e0f 100644
--- a/dbapp/mainapp/forms.py
+++ b/dbapp/mainapp/forms.py
@@ -815,6 +815,7 @@ class SatelliteForm(forms.ModelForm):
model = Satellite
fields = [
'name',
+ 'alternative_name',
'norad',
'international_code',
'band',
@@ -829,6 +830,10 @@ class SatelliteForm(forms.ModelForm):
'placeholder': 'Введите название спутника',
'required': True
}),
+ 'alternative_name': forms.TextInput(attrs={
+ 'class': 'form-control',
+ 'placeholder': 'Введите альтернативное название (необязательно)'
+ }),
'norad': forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Введите NORAD ID'
@@ -862,6 +867,7 @@ class SatelliteForm(forms.ModelForm):
}
labels = {
'name': 'Название спутника',
+ 'alternative_name': 'Альтернативное название',
'norad': 'NORAD ID',
'international_code': 'Международный код',
'band': 'Диапазоны работы',
@@ -872,6 +878,7 @@ class SatelliteForm(forms.ModelForm):
}
help_texts = {
'name': 'Уникальное название спутника',
+ 'alternative_name': 'Альтернативное название спутника (например, на другом языке)',
'norad': 'Идентификатор NORAD для отслеживания спутника',
'international_code': 'Международный идентификатор спутника (например, 2011-074A)',
'band': 'Выберите диапазоны работы спутника (удерживайте Ctrl для множественного выбора)',
diff --git a/dbapp/mainapp/migrations/0017_add_satellite_alternative_name.py b/dbapp/mainapp/migrations/0017_add_satellite_alternative_name.py
new file mode 100644
index 0000000..ec3fedf
--- /dev/null
+++ b/dbapp/mainapp/migrations/0017_add_satellite_alternative_name.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.7 on 2025-12-01 08:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mainapp', '0016_alter_satellite_international_code_techanalyze'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='satellite',
+ name='alternative_name',
+ field=models.CharField(blank=True, db_index=True, help_text='Альтернативное название спутника (например, из скобок)', max_length=100, null=True, verbose_name='Альтернативное имя'),
+ ),
+ migrations.AlterField(
+ model_name='standard',
+ name='name',
+ field=models.CharField(db_index=True, help_text='Стандарт передачи данных (DVB-S, DVB-S2, DVB-S2X и т.д.)', max_length=80, unique=True, verbose_name='Стандарт'),
+ ),
+ ]
diff --git a/dbapp/mainapp/models.py b/dbapp/mainapp/models.py
index 3c96cde..a16374e 100644
--- a/dbapp/mainapp/models.py
+++ b/dbapp/mainapp/models.py
@@ -344,6 +344,14 @@ class Satellite(models.Model):
db_index=True,
help_text="Название спутника",
)
+ alternative_name = models.CharField(
+ max_length=100,
+ blank=True,
+ null=True,
+ verbose_name="Альтернативное имя",
+ db_index=True,
+ help_text="Альтернативное название спутника (например, из скобок)",
+ )
norad = models.IntegerField(
blank=True,
null=True,
diff --git a/dbapp/mainapp/templates/mainapp/components/_satellite_modal.html b/dbapp/mainapp/templates/mainapp/components/_satellite_modal.html
index b5d603c..c64be58 100644
--- a/dbapp/mainapp/templates/mainapp/components/_satellite_modal.html
+++ b/dbapp/mainapp/templates/mainapp/components/_satellite_modal.html
@@ -43,8 +43,13 @@ function showSatelliteModal(satelliteId) {
'
' +
'' +
'
' +
- '| Название: | ' + data.name + ' |
' +
- '| NORAD ID: | ' + data.norad + ' |
' +
+ '| Название: | ' + data.name + ' |
';
+
+ if (data.alternative_name && data.alternative_name !== '-') {
+ html += '| Альтернативное название: | ' + data.alternative_name + ' |
';
+ }
+
+ html += '| NORAD ID: | ' + data.norad + ' |
' +
'| Подспутниковая точка: | ' + data.undersat_point + ' |
' +
'| Диапазоны: | ' + data.bands + ' |
' +
'
' +
diff --git a/dbapp/mainapp/templates/mainapp/points_averaging.html b/dbapp/mainapp/templates/mainapp/points_averaging.html
index c44c87d..ffa083d 100644
--- a/dbapp/mainapp/templates/mainapp/points_averaging.html
+++ b/dbapp/mainapp/templates/mainapp/points_averaging.html
@@ -180,6 +180,9 @@
+
@@ -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 ``;
+ return `
+ `;
},
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();
});
diff --git a/dbapp/mainapp/templates/mainapp/satellite_form.html b/dbapp/mainapp/templates/mainapp/satellite_form.html
index 925d601..3cee18e 100644
--- a/dbapp/mainapp/templates/mainapp/satellite_form.html
+++ b/dbapp/mainapp/templates/mainapp/satellite_form.html
@@ -86,6 +86,25 @@
+
+
+
+ {{ form.alternative_name }}
+ {% if form.alternative_name.errors %}
+
+ {{ form.alternative_name.errors.0 }}
+
+ {% endif %}
+ {% if form.alternative_name.help_text %}
+
{{ form.alternative_name.help_text }}
+ {% endif %}
+
+
+
+
+
-
-
-
+
-
+
+
+
+
+
+
+
+ {{ form.launch_date }}
+ {% if form.launch_date.errors %}
+
+ {{ form.launch_date.errors.0 }}
+
+ {% endif %}
+ {% if form.launch_date.help_text %}
+
{{ form.launch_date.help_text }}
+ {% endif %}
+
+
@@ -160,24 +196,7 @@
-
-
-
- {{ form.launch_date }}
- {% if form.launch_date.errors %}
-
- {{ form.launch_date.errors.0 }}
-
- {% endif %}
- {% if form.launch_date.help_text %}
-
{{ form.launch_date.help_text }}
- {% endif %}
-
-
-
-
+