diff --git a/dbapp/KUBSAT_FEATURE.md b/dbapp/KUBSAT_FEATURE.md new file mode 100644 index 0000000..f213d75 --- /dev/null +++ b/dbapp/KUBSAT_FEATURE.md @@ -0,0 +1,142 @@ +# Страница Кубсат + +## Описание +Страница "Кубсат" предназначена для фильтрации источников сигнала (Source) по различным критериям и экспорта результатов в Excel. + +## Доступ +Страница доступна по адресу: `/kubsat/` +Также добавлена в навигационное меню между "Наличие сигнала" и "3D карта". + +## Функциональность + +### Фильтры + +#### Реализованные фильтры: +1. **Спутники** (мультивыбор) - фильтрация по спутникам из связанных ObjItem +2. **Полоса спутника** (выбор) - выбор диапазона частот +3. **Поляризация** (мультивыбор) - фильтрация по поляризации сигнала +4. **Центральная частота** (диапазон) - фильтрация по частоте от/до в МГц +5. **Полоса** (диапазон) - фильтрация по полосе частот от/до в МГц +6. **Модуляция** (мультивыбор) - фильтрация по типу модуляции +7. **Тип объекта** (мультивыбор) - фильтрация по типу объекта (ObjectInfo) +8. **Количество привязанных ObjItem** (radio button): + - Все + - 1 + - 2 и более + +#### Фиктивные фильтры (заглушки): +9. **Принадлежность объекта** (мультивыбор) - пока не реализовано +10. **Планы на** (radio да/нет) - фиктивный фильтр +11. **Успех 1** (radio да/нет) - фиктивный фильтр +12. **Успех 2** (radio да/нет) - фиктивный фильтр +13. **Диапазон дат** (от/до) - фиктивный фильтр + +### Таблица результатов + +После применения фильтров отображается таблица на всю ширину страницы с колонками: +- ID Source - идентификатор источника (объединяет несколько точек) +- Тип объекта - тип источника +- Кол-во точек - количество точек источника (автоматически пересчитывается при удалении) +- Имя точки - название точки (ObjItem) +- Спутник (имя и NORAD ID) +- Частота (МГц) +- Полоса (МГц) +- Поляризация +- Модуляция +- Координаты ГЛ - координаты из geo_obj точки +- Дата ГЛ - дата геолокации точки +- Действия (кнопки удаления) + +**Важно**: +- Таблица показывает все точки (ObjItem) для каждого источника (Source) +- Точки одного источника группируются вместе +- Колонки ID Source, Тип объекта и Кол-во точек объединены для всех строк источника (rowspan) +- Количество точек автоматически пересчитывается при удалении строк +- Таблица имеет фиксированную высоту с прокруткой и sticky заголовок + +### Выделение по дате +Строки, у которых дата ГЛ попадает в выбранный диапазон дат, **выделяются зеленым цветом**. При этом все объекты остаются в таблице, независимо от фильтра по дате. + +### Управление данными в таблице + +**Кнопки удаления:** +- **Кнопка с иконкой корзины** (для каждой точки) - удаляет конкретную точку (ObjItem) из таблицы +- **Кнопка с иконкой заполненной корзины** (для первой точки источника) - удаляет все точки источника (Source) из таблицы + +**Кнопка "Оставить только подходящие по дате":** +- Удаляет из таблицы все точки, которые НЕ подходят по заданному диапазону дат +- Оставляет только зеленые (выделенные) строки +- Запрашивает подтверждение перед удалением + +**Важно**: Все удаления происходят только из таблицы, **БЕЗ удаления из базы данных**. Это позволяет пользователю исключить ненужные записи перед экспортом. + +### Экспорт в Excel + +Кнопка "Экспорт в Excel" создает файл со следующими колонками: +1. **Дата** - текущая дата (без времени) +2. **Широта, град** - рассчитывается как **инкрементальное среднее** из координат оставшихся в таблице точек +3. **Долгота, град** - рассчитывается как **инкрементальное среднее** из координат оставшихся в таблице точек +4. **Высота, м** - всегда 0 +5. **Местоположение** - из geo_obj.location первого ObjItem +6. **ИСЗ** - имя спутника и NORAD ID в скобках +7. **Прямой канал, МГц** - частота + перенос из транспондера +8. **Обратный канал, МГц** - частота источника +9. **Перенос** - из объекта Transponder +10. **Получено координат, раз** - количество точек (ObjItem), оставшихся в таблице для данного источника +11. **Дата** - пока заполняется как "-" +12. **Зеркала** - все имена зеркал через перенос строки (из оставшихся точек) +13. **СКО, км** - не заполняется +14. **Примечание** - не заполняется +15. **Оператор** - имя текущего пользователя + +**Важно**: +- Экспортируются только точки (ObjItem), оставшиеся в таблице после удалений +- Координаты рассчитываются по алгоритму инкрементального среднего из функции `calculate_mean_coords` (аналогично `fill_data_from_df`) +- Если пользователь удалил некоторые точки, координаты будут рассчитаны только по оставшимся + +Файл сохраняется с именем `kubsat_YYYYMMDD_HHMMSS.xlsx`. + +## Технические детали + +### Файлы +- **Форма**: `dbapp/mainapp/forms.py` - класс `KubsatFilterForm` +- **Представления**: `dbapp/mainapp/views/kubsat.py` - классы `KubsatView` и `KubsatExportView` +- **Шаблон**: `dbapp/mainapp/templates/mainapp/kubsat.html` +- **URL**: `/kubsat/` и `/kubsat/export/` + +### Зависимости +- openpyxl - для создания Excel файлов +- Django GIS - для работы с координатами + +### Оптимизация запросов +Используется `select_related` и `prefetch_related` для минимизации количества запросов к базе данных: +```python +queryset = Source.objects.select_related('info').prefetch_related( + 'source_objitems__parameter_obj__id_satellite', + 'source_objitems__parameter_obj__polarization', + 'source_objitems__parameter_obj__modulation', + 'source_objitems__transponder__sat_id' +) +``` + +## Использование + +1. Откройте страницу "Кубсат" из навигационного меню +2. Выберите нужные фильтры (спутники, поляризация, частота и т.д.) +3. Опционально укажите диапазон дат для выделения подходящих точек +4. Нажмите "Применить фильтры" +5. В таблице отобразятся все точки (ObjItem) сгруппированные по источникам (Source) +6. Точки, подходящие по дате, будут выделены зеленым цветом +7. Опционально нажмите "Оставить только подходящие по дате" для быстрого удаления неподходящих точек +8. При необходимости удалите отдельные точки или целые объекты кнопками в колонке "Действия" +9. Нажмите "Экспорт в Excel" для скачивания файла с оставшимися данными +10. Форма не сбрасывается после экспорта - можно продолжить работу + +## Примечания + +- Форма не сбрасывается после экспорта +- Удаление точек/объектов из таблицы не влияет на базу данных +- Экспортируются только оставшиеся в таблице точки +- Координаты в Excel рассчитываются как инкрементальное среднее из оставшихся точек +- Фильтр по дате не скрывает объекты, а только выделяет их цветом +- Каждая строка таблицы = одна точка (ObjItem), строки группируются по источникам (Source) diff --git a/dbapp/mainapp/forms.py b/dbapp/mainapp/forms.py index ef33198..4bebaaa 100644 --- a/dbapp/mainapp/forms.py +++ b/dbapp/mainapp/forms.py @@ -545,6 +545,124 @@ class SourceForm(forms.ModelForm): +class KubsatFilterForm(forms.Form): + """Форма фильтров для страницы Кубсат""" + + satellites = forms.ModelMultipleChoiceField( + queryset=Satellite.objects.all().order_by('name'), + label='Спутники', + required=False, + widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '5'}) + ) + + band = forms.ModelChoiceField( + queryset=None, + label='Полоса спутника', + required=False, + widget=forms.Select(attrs={'class': 'form-select'}) + ) + + polarization = forms.ModelMultipleChoiceField( + queryset=Polarization.objects.all().order_by('name'), + label='Поляризация', + required=False, + widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'}) + ) + + frequency_min = forms.FloatField( + label='Центральная частота от (МГц)', + required=False, + widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}) + ) + + frequency_max = forms.FloatField( + label='Центральная частота до (МГц)', + required=False, + widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}) + ) + + freq_range_min = forms.FloatField( + label='Полоса от (МГц)', + required=False, + widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}) + ) + + freq_range_max = forms.FloatField( + label='Полоса до (МГц)', + required=False, + widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}) + ) + + modulation = forms.ModelMultipleChoiceField( + queryset=Modulation.objects.all().order_by('name'), + label='Модуляция', + required=False, + widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'}) + ) + + object_type = forms.ModelMultipleChoiceField( + queryset=None, + label='Тип объекта', + required=False, + widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'}) + ) + + # Заглушка для принадлежности объекта + object_ownership = forms.MultipleChoiceField( + choices=[], + label='Принадлежность объекта', + required=False, + widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'}) + ) + + objitem_count = forms.ChoiceField( + choices=[('', 'Все'), ('1', '1'), ('2+', '2 и более')], + label='Количество привязанных ObjItem', + required=False, + widget=forms.RadioSelect() + ) + + # Фиктивные фильтры + has_plans = forms.ChoiceField( + choices=[('', 'Все'), ('yes', 'Да'), ('no', 'Нет')], + label='Планы на', + required=False, + widget=forms.RadioSelect() + ) + + success_1 = forms.ChoiceField( + choices=[('', 'Все'), ('yes', 'Да'), ('no', 'Нет')], + label='Успех 1', + required=False, + widget=forms.RadioSelect() + ) + + success_2 = forms.ChoiceField( + choices=[('', 'Все'), ('yes', 'Да'), ('no', 'Нет')], + label='Успех 2', + required=False, + widget=forms.RadioSelect() + ) + + date_from = forms.DateField( + label='Дата от', + required=False, + widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}) + ) + + date_to = forms.DateField( + label='Дата до', + required=False, + widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}) + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + from mainapp.models import Band, ObjectInfo + self.fields['band'].queryset = Band.objects.all().order_by('name') + self.fields['object_type'].queryset = ObjectInfo.objects.all().order_by('name') + + class TransponderForm(forms.ModelForm): """ Форма для создания и редактирования транспондеров. diff --git a/dbapp/mainapp/templates/mainapp/components/_navbar.html b/dbapp/mainapp/templates/mainapp/components/_navbar.html index 6e27b89..dcc5d6f 100644 --- a/dbapp/mainapp/templates/mainapp/components/_navbar.html +++ b/dbapp/mainapp/templates/mainapp/components/_navbar.html @@ -34,6 +34,9 @@ + diff --git a/dbapp/mainapp/templates/mainapp/kubsat.html b/dbapp/mainapp/templates/mainapp/kubsat.html new file mode 100644 index 0000000..e43d216 --- /dev/null +++ b/dbapp/mainapp/templates/mainapp/kubsat.html @@ -0,0 +1,584 @@ +{% extends 'mainapp/base.html' %} +{% load static %} + +{% block title %}Кубсат{% endblock %} + +{% block content %} +
+
+
+

Кубсат

+
+
+ + +
+ {% csrf_token %} +
+
+
Фильтры
+
+
+
+ +
+ + {{ form.satellites }} + Удерживайте Ctrl для выбора нескольких +
+ + +
+ + {{ form.band }} +
+ + +
+ + {{ form.polarization }} +
+ + +
+ + {{ form.modulation }} +
+
+ +
+ +
+ +
+ {{ form.frequency_min }} + + {{ form.frequency_max }} +
+
+ + +
+ +
+ {{ form.freq_range_min }} + + {{ form.freq_range_max }} +
+
+ + +
+ + {{ form.object_type }} +
+ + +
+ + {{ form.object_ownership }} +
+
+ +
+ +
+ +
+ {% for radio in form.objitem_count %} +
+ {{ radio.tag }} + +
+ {% endfor %} +
+
+ + +
+ +
+ {% for radio in form.has_plans %} +
+ {{ radio.tag }} + +
+ {% endfor %} +
+
+ + +
+ +
+ {% for radio in form.success_1 %} +
+ {{ radio.tag }} + +
+ {% endfor %} +
+
+ + +
+ +
+ {% for radio in form.success_2 %} +
+ {{ radio.tag }} + +
+ {% endfor %} +
+
+
+ +
+ +
+ +
+ {{ form.date_from }} + + {{ form.date_to }} +
+
+
+ +
+
+ + Сбросить +
+
+
+
+
+ + + {% if sources_with_date_info %} +
+
+
+
+
+ + + +
+
+
+
+
+ {% endif %} + + + {% if sources_with_date_info %} +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + {% for source_data in sources_with_date_info %} + {% for objitem_data in source_data.objitems_data %} + + + + {% if forloop.first %} + + {% endif %} + + + {% if forloop.first %} + + {% endif %} + + + {% if forloop.first %} + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% endfor %} + {% endfor %} + +
ID SourceТип объектаКол-во точекИмя точкиСпутникЧастота (МГц)Полоса (МГц)ПоляризацияМодуляцияКоординаты ГЛДата ГЛДействия
{{ source_data.source.id }}{{ source_data.source.info.name|default:"-" }}{{ source_data.objitems_data|length }}{{ objitem_data.objitem.name|default:"-" }} + {% if objitem_data.objitem.parameter_obj and objitem_data.objitem.parameter_obj.id_satellite %} + {{ objitem_data.objitem.parameter_obj.id_satellite.name }} + {% if objitem_data.objitem.parameter_obj.id_satellite.norad %} + ({{ objitem_data.objitem.parameter_obj.id_satellite.norad }}) + {% endif %} + {% else %} + - + {% endif %} + + {% if objitem_data.objitem.parameter_obj %} + {{ objitem_data.objitem.parameter_obj.frequency|default:"-" }} + {% else %} + - + {% endif %} + + {% if objitem_data.objitem.parameter_obj %} + {{ objitem_data.objitem.parameter_obj.freq_range|default:"-" }} + {% else %} + - + {% endif %} + + {% if objitem_data.objitem.parameter_obj and objitem_data.objitem.parameter_obj.polarization %} + {{ objitem_data.objitem.parameter_obj.polarization.name }} + {% else %} + - + {% endif %} + + {% if objitem_data.objitem.parameter_obj and objitem_data.objitem.parameter_obj.modulation %} + {{ objitem_data.objitem.parameter_obj.modulation.name }} + {% else %} + - + {% endif %} + + {% if objitem_data.objitem.geo_obj and objitem_data.objitem.geo_obj.coords %} + {{ objitem_data.objitem.geo_obj.coords.y|floatformat:6 }}, {{ objitem_data.objitem.geo_obj.coords.x|floatformat:6 }} + {% else %} + - + {% endif %} + + {% if objitem_data.geo_date %} + {{ objitem_data.geo_date|date:"d.m.Y" }} + {% else %} + - + {% endif %} + +
+ + {% if forloop.first %} + + {% endif %} +
+
+
+
+
+
+
+ {% elif request.GET %} +
+ По заданным критериям ничего не найдено. +
+ {% endif %} +
+ + + + +{% endblock %} diff --git a/dbapp/mainapp/templates/mainapp/source_list.html b/dbapp/mainapp/templates/mainapp/source_list.html index c589ebb..f68695e 100644 --- a/dbapp/mainapp/templates/mainapp/source_list.html +++ b/dbapp/mainapp/templates/mainapp/source_list.html @@ -195,7 +195,7 @@
- +
Имя Спутник Тип объекта - Усредненные координаты + Координаты ГЛ + + {% include 'mainapp/components/_sort_header.html' with field='objitem_count' label='Кол-во ГЛ(точек)' current_sort=sort %} + Координаты Кубсата - Координаты оперативников + Координаты визуального наблюдения Координаты справочные Наличие сигнала ТВ или нет - - {% include 'mainapp/components/_sort_header.html' with field='objitem_count' label='Кол-во точек' current_sort=sort %} - {% include 'mainapp/components/_sort_header.html' with field='created_at' label='Создано' current_sort=sort %} @@ -474,6 +474,7 @@ {{ source.info }} {{ source.coords_average }} + {{ source.objitem_count }} {{ source.coords_kupsat }} {{ source.coords_valid }} {{ source.coords_reference }} @@ -510,7 +511,7 @@ - {% endif %} - {{ source.objitem_count }} + {{ source.created_at|date:"d.m.Y H:i" }} {{ source.updated_at|date:"d.m.Y H:i" }} @@ -704,8 +705,8 @@ {% endblock %} {% block extra_js %} - - + +