From c72bf12d41dbc19e3792d590490ee88ad83ff66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=88=D0=BA=D0=B8=D0=BD=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9?= Date: Mon, 1 Dec 2025 12:19:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D1=82=D0=B5=D1=80=D0=BD=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D0=B2=D0=BD=D0=BE=D0=B5=20=D0=B8=D0=BC=D1=8F=20=D1=83=20=D1=81?= =?UTF-8?q?=D0=BF=D1=83=D1=82=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbapp/lyngsatapp/async_utils.py | 8 +- dbapp/lyngsatapp/utils.py | 352 +++++++++--------- dbapp/mainapp/admin.py | 3 +- dbapp/mainapp/forms.py | 7 + .../0017_add_satellite_alternative_name.py | 23 ++ dbapp/mainapp/models.py | 8 + .../mainapp/components/_satellite_modal.html | 9 +- .../templates/mainapp/points_averaging.html | 281 +++++++++++++- .../templates/mainapp/satellite_form.html | 63 ++-- .../templates/mainapp/satellite_list.html | 13 +- dbapp/mainapp/utils.py | 8 +- dbapp/mainapp/views/api.py | 1 + dbapp/mainapp/views/satellite.py | 6 +- dbapp/mapsapp/utils.py | 158 +++++++- 14 files changed, 720 insertions(+), 220 deletions(-) create mode 100644 dbapp/mainapp/migrations/0017_add_satellite_alternative_name.py 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) { '
' + '
Основная информация
' + '
' + - '' + - '' + + ''; + + if (data.alternative_name && data.alternative_name !== '-') { + html += ''; + } + + html += '' + '' + '' + '
Название:' + data.name + '
NORAD ID:' + data.norad + '
Название:' + data.name + '
Альтернативное название:' + data.alternative_name + '
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 %} -
-
- -
+