Переделал страницу с ObjItem. Теперь работает корректно.
This commit is contained in:
@@ -8,92 +8,121 @@
|
|||||||
4. Иначе создать новый Source с coords_average = координаты geo_obj
|
4. Иначе создать новый Source с coords_average = координаты geo_obj
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
# import os
|
||||||
import django
|
# import django
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dbapp.settings")
|
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dbapp.settings")
|
||||||
django.setup()
|
# django.setup()
|
||||||
|
|
||||||
from mainapp.models import ObjItem, Source, CustomUser
|
# from mainapp.models import ObjItem, Source, CustomUser
|
||||||
from django.contrib.gis.geos import Point
|
# from django.contrib.gis.geos import Point
|
||||||
from django.contrib.gis.measure import D
|
# from django.contrib.gis.measure import D
|
||||||
from django.contrib.gis.db.models.functions import Distance
|
# from django.contrib.gis.db.models.functions import Distance
|
||||||
|
|
||||||
|
|
||||||
def calculate_distance_degrees(coord1, coord2):
|
# def calculate_distance_degrees(coord1, coord2):
|
||||||
"""Вычисляет расстояние между двумя координатами в градусах."""
|
# """Вычисляет расстояние между двумя координатами в градусах."""
|
||||||
import math
|
# import math
|
||||||
|
|
||||||
|
# lon1, lat1 = coord1
|
||||||
|
# lon2, lat2 = coord2
|
||||||
|
|
||||||
|
# return math.sqrt((lon2 - lon1) ** 2 + (lat2 - lat1) ** 2)
|
||||||
|
|
||||||
|
|
||||||
|
# def fix_objitems_without_source():
|
||||||
|
# """Исправляет ObjItems без связи с Source."""
|
||||||
|
|
||||||
|
# # Получаем пользователя по умолчанию
|
||||||
|
# default_user = CustomUser.objects.get(id=1)
|
||||||
|
|
||||||
|
# # Получаем все ObjItems без source
|
||||||
|
# objitems_without_source = ObjItem.objects.filter(source__isnull=True)
|
||||||
|
# total_count = objitems_without_source.count()
|
||||||
|
|
||||||
|
# print(f"Найдено {total_count} ObjItems без source")
|
||||||
|
|
||||||
|
# if total_count == 0:
|
||||||
|
# print("Нечего исправлять!")
|
||||||
|
# return
|
||||||
|
|
||||||
|
# fixed_count = 0
|
||||||
|
# new_sources_count = 0
|
||||||
|
|
||||||
|
# for objitem in objitems_without_source:
|
||||||
|
# # Проверяем, есть ли geo_obj
|
||||||
|
# if not hasattr(objitem, 'geo_obj') or not objitem.geo_obj or not objitem.geo_obj.coords:
|
||||||
|
# print(f"ObjItem {objitem.id} не имеет geo_obj или координат, пропускаем")
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# geo_coords = objitem.geo_obj.coords
|
||||||
|
# coord_tuple = (geo_coords.x, geo_coords.y)
|
||||||
|
|
||||||
|
# # Ищем ближайший Source
|
||||||
|
# sources_with_coords = Source.objects.filter(coords_average__isnull=False)
|
||||||
|
|
||||||
|
# closest_source = None
|
||||||
|
# min_distance = float('inf')
|
||||||
|
|
||||||
|
# for source in sources_with_coords:
|
||||||
|
# source_coord = (source.coords_average.x, source.coords_average.y)
|
||||||
|
# distance = calculate_distance_degrees(coord_tuple, source_coord)
|
||||||
|
|
||||||
|
# if distance < min_distance:
|
||||||
|
# min_distance = distance
|
||||||
|
# closest_source = source
|
||||||
|
|
||||||
|
# # Если нашли близкий Source (расстояние <= 0.5 градуса)
|
||||||
|
# if closest_source and min_distance <= 0.5:
|
||||||
|
# objitem.source = closest_source
|
||||||
|
# objitem.save()
|
||||||
|
# print(f"ObjItem {objitem.id} связан с Source {closest_source.id} (расстояние: {min_distance:.4f}°)")
|
||||||
|
# fixed_count += 1
|
||||||
|
# else:
|
||||||
|
# # Создаем новый Source
|
||||||
|
# new_source = Source.objects.create(
|
||||||
|
# coords_average=Point(coord_tuple, srid=4326),
|
||||||
|
# created_by=default_user
|
||||||
|
# )
|
||||||
|
# objitem.source = new_source
|
||||||
|
# objitem.save()
|
||||||
|
# print(f"ObjItem {objitem.id} связан с новым Source {new_source.id}")
|
||||||
|
# fixed_count += 1
|
||||||
|
# new_sources_count += 1
|
||||||
|
|
||||||
|
# print(f"\nГотово!")
|
||||||
|
# print(f"Исправлено ObjItems: {fixed_count}")
|
||||||
|
# print(f"Создано новых Source: {new_sources_count}")
|
||||||
|
|
||||||
|
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# fix_objitems_without_source()
|
||||||
|
|
||||||
|
|
||||||
|
from geographiclib.geodesic import Geodesic
|
||||||
|
|
||||||
|
def calculate_mean_coords(coord1: tuple, coord2: tuple) -> tuple[tuple, float]:
|
||||||
|
"""
|
||||||
|
Вычисляет среднюю точку между двумя координатами с использованием геодезических вычислений (с учётом эллипсоида).
|
||||||
|
|
||||||
|
:param lat1: Широта первой точки в градусах.
|
||||||
|
:param lon1: Долгота первой точки в градусах.
|
||||||
|
:param lat2: Широта второй точки в градусах.
|
||||||
|
:param lon2: Долгота второй точки в градусах.
|
||||||
|
:return: Словарь с ключами 'lat' и 'lon' для средней точки, и расстояние(dist) в КМ.
|
||||||
|
"""
|
||||||
lon1, lat1 = coord1
|
lon1, lat1 = coord1
|
||||||
lon2, lat2 = coord2
|
lon2, lat2 = coord2
|
||||||
|
geod_inv = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2)
|
||||||
return math.sqrt((lon2 - lon1) ** 2 + (lat2 - lat1) ** 2)
|
azimuth1 = geod_inv['azi1']
|
||||||
|
distance = geod_inv['s12']
|
||||||
|
geod_direct = Geodesic.WGS84.Direct(lat1, lon1, azimuth1, distance / 2)
|
||||||
|
return (geod_direct['lon2'], geod_direct['lat2']), distance/1000
|
||||||
|
|
||||||
|
# Пример использования
|
||||||
|
lat1, lon1 = 56.15465080269812, 38.140518028837285
|
||||||
|
lat2, lon2 = 56.0852, 38.0852
|
||||||
|
midpoint = calculate_mean_coords((lat1, lon1), (lat2, lon2)) #56.15465080269812, 38.140518028837285
|
||||||
|
|
||||||
def fix_objitems_without_source():
|
print(f"Средняя точка: {midpoint[0]}")
|
||||||
"""Исправляет ObjItems без связи с Source."""
|
print(f"Расстояние: {midpoint[1]} км")
|
||||||
|
|
||||||
# Получаем пользователя по умолчанию
|
|
||||||
default_user = CustomUser.objects.get(id=1)
|
|
||||||
|
|
||||||
# Получаем все ObjItems без source
|
|
||||||
objitems_without_source = ObjItem.objects.filter(source__isnull=True)
|
|
||||||
total_count = objitems_without_source.count()
|
|
||||||
|
|
||||||
print(f"Найдено {total_count} ObjItems без source")
|
|
||||||
|
|
||||||
if total_count == 0:
|
|
||||||
print("Нечего исправлять!")
|
|
||||||
return
|
|
||||||
|
|
||||||
fixed_count = 0
|
|
||||||
new_sources_count = 0
|
|
||||||
|
|
||||||
for objitem in objitems_without_source:
|
|
||||||
# Проверяем, есть ли geo_obj
|
|
||||||
if not hasattr(objitem, 'geo_obj') or not objitem.geo_obj or not objitem.geo_obj.coords:
|
|
||||||
print(f"ObjItem {objitem.id} не имеет geo_obj или координат, пропускаем")
|
|
||||||
continue
|
|
||||||
|
|
||||||
geo_coords = objitem.geo_obj.coords
|
|
||||||
coord_tuple = (geo_coords.x, geo_coords.y)
|
|
||||||
|
|
||||||
# Ищем ближайший Source
|
|
||||||
sources_with_coords = Source.objects.filter(coords_average__isnull=False)
|
|
||||||
|
|
||||||
closest_source = None
|
|
||||||
min_distance = float('inf')
|
|
||||||
|
|
||||||
for source in sources_with_coords:
|
|
||||||
source_coord = (source.coords_average.x, source.coords_average.y)
|
|
||||||
distance = calculate_distance_degrees(coord_tuple, source_coord)
|
|
||||||
|
|
||||||
if distance < min_distance:
|
|
||||||
min_distance = distance
|
|
||||||
closest_source = source
|
|
||||||
|
|
||||||
# Если нашли близкий Source (расстояние <= 0.5 градуса)
|
|
||||||
if closest_source and min_distance <= 0.5:
|
|
||||||
objitem.source = closest_source
|
|
||||||
objitem.save()
|
|
||||||
print(f"ObjItem {objitem.id} связан с Source {closest_source.id} (расстояние: {min_distance:.4f}°)")
|
|
||||||
fixed_count += 1
|
|
||||||
else:
|
|
||||||
# Создаем новый Source
|
|
||||||
new_source = Source.objects.create(
|
|
||||||
coords_average=Point(coord_tuple, srid=4326),
|
|
||||||
created_by=default_user
|
|
||||||
)
|
|
||||||
objitem.source = new_source
|
|
||||||
objitem.save()
|
|
||||||
print(f"ObjItem {objitem.id} связан с новым Source {new_source.id}")
|
|
||||||
fixed_count += 1
|
|
||||||
new_sources_count += 1
|
|
||||||
|
|
||||||
print(f"\nГотово!")
|
|
||||||
print(f"Исправлено ObjItems: {fixed_count}")
|
|
||||||
print(f"Создано новых Source: {new_sources_count}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
fix_objitems_without_source()
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{% comment %}
|
||||||
|
Компонент для элемента переключения видимости столбца
|
||||||
|
Использование:
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=0 column_label="Выбрать" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=1 column_label="Имя" checked=True %}
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="{{ column_index }}" {% if checked %}checked{% endif %}
|
||||||
|
onchange="toggleColumn(this)"> {{ column_label }}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{% comment %}
|
||||||
|
Компонент для выпадающего списка видимости столбцов
|
||||||
|
Использование:
|
||||||
|
{% include 'mainapp/components/_column_visibility_dropdown.html' %}
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<div class="dropdown">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
|
||||||
|
id="columnVisibilityDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-gear"></i> Колонки
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="columnVisibilityDropdown" style="z-index: 1050; max-height: 300px; overflow-y: auto;">
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" id="select-all-columns" unchecked
|
||||||
|
onchange="toggleAllColumns(this)"> Выбрать всё
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=0 column_label="Выбрать" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=1 column_label="Имя" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=2 column_label="Спутник" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=3 column_label="Част, МГц" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=4 column_label="Полоса, МГц" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=5 column_label="Поляризация" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=6 column_label="Сим. V" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=7 column_label="Модул" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=8 column_label="ОСШ" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=9 column_label="Время ГЛ" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=10 column_label="Местоположение" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=11 column_label="Геолокация" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=12 column_label="Обновлено" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=13 column_label="Кем (обновление)" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=14 column_label="Создано" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=15 column_label="Кем (создание)" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=16 column_label="Комментарий" checked=False %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=17 column_label="Усреднённое" checked=False %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=18 column_label="Стандарт" checked=False %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=19 column_label="Тип источника" checked=True %}
|
||||||
|
{% include 'mainapp/components/_column_toggle_item.html' with column_index=20 column_label="Sigma" checked=True %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<ul class="navbar-nav me-auto">
|
<ul class="navbar-nav me-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:home' %}">Объекты</a>
|
<a class="nav-link" href="{% url 'mainapp:objitem_list' %}">Объекты</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>
|
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>
|
||||||
|
|||||||
@@ -92,179 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Column visibility toggle button -->
|
<!-- Column visibility toggle button -->
|
||||||
<div class="dropdown">
|
{% include 'mainapp/components/_column_visibility_dropdown.html' %}
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
|
|
||||||
id="columnVisibilityDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<i class="bi bi-gear"></i> Колонки
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="columnVisibilityDropdown" style="z-index: 1050; max-height: 300px; overflow-y: auto;">
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" id="select-all-columns" unchecked
|
|
||||||
onchange="toggleAllColumns(this)"> Выбрать всё
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<hr class="dropdown-divider">
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="0" checked
|
|
||||||
onchange="toggleColumn(this)"> Выбрать
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="1" checked
|
|
||||||
onchange="toggleColumn(this)"> Имя
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="2" checked
|
|
||||||
onchange="toggleColumn(this)"> Спутник
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="3" checked
|
|
||||||
onchange="toggleColumn(this)"> Част, МГц
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="4" checked
|
|
||||||
onchange="toggleColumn(this)"> Полоса, МГц
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="5" checked
|
|
||||||
onchange="toggleColumn(this)"> Поляризация
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="6" checked
|
|
||||||
onchange="toggleColumn(this)"> Сим. V
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="7" checked
|
|
||||||
onchange="toggleColumn(this)"> Модул
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="8" checked
|
|
||||||
onchange="toggleColumn(this)"> ОСШ
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="9" checked
|
|
||||||
onchange="toggleColumn(this)"> Время ГЛ
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="10" checked
|
|
||||||
onchange="toggleColumn(this)"> Местоположение
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="11" checked
|
|
||||||
onchange="toggleColumn(this)"> Геолокация
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="12" checked
|
|
||||||
onchange="toggleColumn(this)"> Кубсат
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="13" checked
|
|
||||||
onchange="toggleColumn(this)"> Опер. отд
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="14" checked
|
|
||||||
onchange="toggleColumn(this)"> Гео-куб, км
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="15" checked
|
|
||||||
onchange="toggleColumn(this)"> Гео-опер, км
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="16" checked
|
|
||||||
onchange="toggleColumn(this)"> Куб-опер, км
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="17" checked
|
|
||||||
onchange="toggleColumn(this)"> Обновлено
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="18" checked
|
|
||||||
onchange="toggleColumn(this)"> Кем (обновление)
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="19" checked
|
|
||||||
onchange="toggleColumn(this)"> Создано
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="20" checked
|
|
||||||
onchange="toggleColumn(this)"> Кем (создание)
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="21" unchecked
|
|
||||||
onchange="toggleColumn(this)"> Комментарий
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="22" unchecked
|
|
||||||
onchange="toggleColumn(this)"> Усреднённое
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="23" unchecked
|
|
||||||
onchange="toggleColumn(this)"> Стандарт
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="24" checked
|
|
||||||
onchange="toggleColumn(this)"> Тип источника
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label class="dropdown-item">
|
|
||||||
<input type="checkbox" class="column-toggle" data-column="25" checked
|
|
||||||
onchange="toggleColumn(this)"> Sigma
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<div class="ms-auto">
|
<div class="ms-auto">
|
||||||
@@ -497,11 +325,6 @@
|
|||||||
{% include 'mainapp/components/_table_header.html' with label="Время ГЛ" field="geo_timestamp" sort=sort %}
|
{% include 'mainapp/components/_table_header.html' with label="Время ГЛ" field="geo_timestamp" sort=sort %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Местоположение" field="" sortable=False %}
|
{% include 'mainapp/components/_table_header.html' with label="Местоположение" field="" sortable=False %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Геолокация" field="" sortable=False %}
|
{% include 'mainapp/components/_table_header.html' with label="Геолокация" field="" sortable=False %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Кубсат" field="" sortable=False %}
|
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Опер. отд" field="" sortable=False %}
|
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Гео-куб, км" field="" sortable=False %}
|
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Гео-опер, км" field="" sortable=False %}
|
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Куб-опер, км" field="" sortable=False %}
|
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Обновлено" field="updated_at" sort=sort %}
|
{% include 'mainapp/components/_table_header.html' with label="Обновлено" field="updated_at" sort=sort %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Кем(обн)" field="" sortable=False %}
|
{% include 'mainapp/components/_table_header.html' with label="Кем(обн)" field="" sortable=False %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Создано" field="created_at" sort=sort %}
|
{% include 'mainapp/components/_table_header.html' with label="Создано" field="created_at" sort=sort %}
|
||||||
@@ -531,11 +354,6 @@
|
|||||||
<td>{{ item.geo_timestamp|date:"d.m.Y H:i" }}</td>
|
<td>{{ item.geo_timestamp|date:"d.m.Y H:i" }}</td>
|
||||||
<td>{{ item.geo_location}}</td>
|
<td>{{ item.geo_location}}</td>
|
||||||
<td>{{ item.geo_coords }}</td>
|
<td>{{ item.geo_coords }}</td>
|
||||||
<td>{{ item.kupsat_coords }}</td>
|
|
||||||
<td>{{ item.valid_coords }}</td>
|
|
||||||
<td>{{ item.distance_geo_kup }}</td>
|
|
||||||
<td>{{ item.distance_geo_valid }}</td>
|
|
||||||
<td>{{ item.distance_kup_valid }}</td>
|
|
||||||
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
|
||||||
<td>{{ item.updated_by }}</td>
|
<td>{{ item.updated_by }}</td>
|
||||||
<td>{{ item.obj.created_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ item.obj.created_at|date:"d.m.Y H:i" }}</td>
|
||||||
@@ -900,19 +718,8 @@
|
|||||||
|
|
||||||
// Initialize column visibility - hide creation columns by default
|
// Initialize column visibility - hide creation columns by default
|
||||||
function initColumnVisibility() {
|
function initColumnVisibility() {
|
||||||
const creationDateCheckbox = document.querySelector('input[data-column="19"]');
|
const creationDateCheckbox = document.querySelector('input[data-column="12"]');
|
||||||
const creationUserCheckbox = document.querySelector('input[data-column="20"]');
|
const creationUserCheckbox = document.querySelector('input[data-column="13"]');
|
||||||
const creationDistanceGOpCheckbox = document.querySelector('input[data-column="15"]');
|
|
||||||
const creationDistanceKubOpCheckbox = document.querySelector('input[data-column="16"]');
|
|
||||||
if (creationDistanceGOpCheckbox) {
|
|
||||||
creationDistanceGOpCheckbox.checked = false;
|
|
||||||
toggleColumn(creationDistanceGOpCheckbox);
|
|
||||||
}
|
|
||||||
if (creationDistanceKubOpCheckbox) {
|
|
||||||
creationDistanceKubOpCheckbox.checked = false;
|
|
||||||
toggleColumn(creationDistanceKubOpCheckbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (creationDateCheckbox) {
|
if (creationDateCheckbox) {
|
||||||
creationDateCheckbox.checked = false;
|
creationDateCheckbox.checked = false;
|
||||||
toggleColumn(creationDateCheckbox);
|
toggleColumn(creationDateCheckbox);
|
||||||
@@ -924,9 +731,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide comment, is_average, and standard columns by default
|
// Hide comment, is_average, and standard columns by default
|
||||||
const commentCheckbox = document.querySelector('input[data-column="21"]');
|
const commentCheckbox = document.querySelector('input[data-column="14"]');
|
||||||
const isAverageCheckbox = document.querySelector('input[data-column="22"]');
|
const isAverageCheckbox = document.querySelector('input[data-column="15"]');
|
||||||
const standardCheckbox = document.querySelector('input[data-column="23"]');
|
const standardCheckbox = document.querySelector('input[data-column="16"]');
|
||||||
|
|
||||||
if (commentCheckbox) {
|
if (commentCheckbox) {
|
||||||
commentCheckbox.checked = false;
|
commentCheckbox.checked = false;
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ from . import views
|
|||||||
app_name = 'mainapp'
|
app_name = 'mainapp'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.SourceListView.as_view(), name='home'), # Source list page
|
path('', views.SourceListView.as_view(), name='home'),
|
||||||
path('objitems/', views.ObjItemListView.as_view(), name='objitem_list'), # Objects list page
|
path('objitems/', views.ObjItemListView.as_view(), name='objitem_list'),
|
||||||
path('actions/', views.ActionsPageView.as_view(), name='actions'), # Move actions to a separate page
|
path('actions/', views.ActionsPageView.as_view(), name='actions'),
|
||||||
path('excel-data', views.LoadExcelDataView.as_view(), name='load_excel_data'),
|
path('excel-data', views.LoadExcelDataView.as_view(), name='load_excel_data'),
|
||||||
path('satellites', views.AddSatellitesView.as_view(), name='add_sats'),
|
path('satellites', views.AddSatellitesView.as_view(), name='add_sats'),
|
||||||
path('api/locations/<int:sat_id>/geojson/', views.GetLocationsView.as_view(), name='locations_by_id'),
|
path('api/locations/<int:sat_id>/geojson/', views.GetLocationsView.as_view(), name='locations_by_id'),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from django.db.models import F
|
|||||||
|
|
||||||
# Third-party imports
|
# Third-party imports
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
from geographiclib.geodesic import Geodesic
|
||||||
# Local imports
|
# Local imports
|
||||||
from mapsapp.models import Transponders
|
from mapsapp.models import Transponders
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ MAX_ITEMS_PER_PAGE = 10000
|
|||||||
DEFAULT_NUMERIC_VALUE = -1.0
|
DEFAULT_NUMERIC_VALUE = -1.0
|
||||||
MINIMUM_BANDWIDTH_MHZ = 0.08
|
MINIMUM_BANDWIDTH_MHZ = 0.08
|
||||||
|
|
||||||
|
RANGE_DISTANCE = 56
|
||||||
|
|
||||||
def get_all_constants():
|
def get_all_constants():
|
||||||
sats = [sat.name for sat in Satellite.objects.all()]
|
sats = [sat.name for sat in Satellite.objects.all()]
|
||||||
@@ -160,15 +161,9 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
|||||||
|
|
||||||
# Вычислить расстояние от координаты до coords_average
|
# Вычислить расстояние от координаты до coords_average
|
||||||
current_avg = (source.coords_average.x, source.coords_average.y)
|
current_avg = (source.coords_average.x, source.coords_average.y)
|
||||||
distance = calculate_distance_degrees(current_avg, current_coord)
|
new_avg, distance = calculate_mean_coords(current_avg, current_coord)
|
||||||
|
|
||||||
# Если расстояние <= 0.5 градуса
|
|
||||||
if distance <= 0.5:
|
|
||||||
# Вычислить новое среднее ИНКРЕМЕНТАЛЬНО
|
|
||||||
new_avg = calculate_average_coords_incremental(
|
|
||||||
current_avg, current_coord
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if distance <= RANGE_DISTANCE:
|
||||||
# Обновить coords_average в Source
|
# Обновить coords_average в Source
|
||||||
source.coords_average = Point(new_avg, srid=4326)
|
source.coords_average = Point(new_avg, srid=4326)
|
||||||
source.save()
|
source.save()
|
||||||
@@ -255,7 +250,6 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts):
|
|||||||
comment = row["Комментарий"]
|
comment = row["Комментарий"]
|
||||||
source_name = row["Объект наблюдения"]
|
source_name = row["Объект наблюдения"]
|
||||||
|
|
||||||
# Создаем Geo объект (БЕЗ coords_kupsat и coords_valid)
|
|
||||||
geo, _ = Geo.objects.get_or_create(
|
geo, _ = Geo.objects.get_or_create(
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
coords=geo_point,
|
coords=geo_point,
|
||||||
@@ -469,7 +463,7 @@ def get_points_from_csv(file_content, current_user=None):
|
|||||||
|
|
||||||
for source in existing_sources:
|
for source in existing_sources:
|
||||||
source_coord = (source.coords_average.x, source.coords_average.y)
|
source_coord = (source.coords_average.x, source.coords_average.y)
|
||||||
distance = calculate_distance_degrees(source_coord, current_coord)
|
new_avg, distance = calculate_mean_coords(source_coord, current_coord)
|
||||||
|
|
||||||
if distance < min_distance:
|
if distance < min_distance:
|
||||||
min_distance = distance
|
min_distance = distance
|
||||||
@@ -479,7 +473,7 @@ def get_points_from_csv(file_content, current_user=None):
|
|||||||
if closest_source and min_distance <= 0.5:
|
if closest_source and min_distance <= 0.5:
|
||||||
# Обновить coords_average инкрементально
|
# Обновить coords_average инкрементально
|
||||||
current_avg = (closest_source.coords_average.x, closest_source.coords_average.y)
|
current_avg = (closest_source.coords_average.x, closest_source.coords_average.y)
|
||||||
new_avg = calculate_average_coords_incremental(current_avg, current_coord)
|
# new_avg = calculate_average_coords_incremental(current_avg, current_coord)
|
||||||
closest_source.coords_average = Point(new_avg, srid=4326)
|
closest_source.coords_average = Point(new_avg, srid=4326)
|
||||||
closest_source.save()
|
closest_source.save()
|
||||||
|
|
||||||
@@ -507,7 +501,7 @@ def get_points_from_csv(file_content, current_user=None):
|
|||||||
return new_sources_count
|
return new_sources_count
|
||||||
|
|
||||||
|
|
||||||
def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.001):
|
def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.1):
|
||||||
"""
|
"""
|
||||||
Проверяет, существует ли уже ObjItem с такими же координатами и частотой.
|
Проверяет, существует ли уже ObjItem с такими же координатами и частотой.
|
||||||
|
|
||||||
@@ -515,7 +509,7 @@ def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.001):
|
|||||||
coord_tuple: кортеж (lon, lat) координат
|
coord_tuple: кортеж (lon, lat) координат
|
||||||
frequency: частота в МГц
|
frequency: частота в МГц
|
||||||
freq_range: полоса частот в МГц
|
freq_range: полоса частот в МГц
|
||||||
tolerance: допуск для сравнения координат в градусах (по умолчанию 0.001 ≈ 100м)
|
tolerance: допуск для сравнения координат в километрах
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True если дубликат найден, False иначе
|
bool: True если дубликат найден, False иначе
|
||||||
@@ -531,7 +525,7 @@ def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.001):
|
|||||||
|
|
||||||
# Проверяем расстояние между координатами
|
# Проверяем расстояние между координатами
|
||||||
geo_coord = (objitem.geo_obj.coords.x, objitem.geo_obj.coords.y)
|
geo_coord = (objitem.geo_obj.coords.x, objitem.geo_obj.coords.y)
|
||||||
distance = calculate_distance_degrees(coord_tuple, geo_coord)
|
_, distance = calculate_mean_coords(coord_tuple, geo_coord)
|
||||||
|
|
||||||
if distance <= tolerance:
|
if distance <= tolerance:
|
||||||
# Координаты совпадают, проверяем частоту
|
# Координаты совпадают, проверяем частоту
|
||||||
@@ -873,40 +867,24 @@ def kub_report(data_in: io.StringIO) -> pd.DataFrame:
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
def calculate_distance_degrees(coord1: tuple, coord2: tuple) -> float:
|
def calculate_mean_coords(coord1: tuple, coord2: tuple) -> tuple[tuple, float]:
|
||||||
"""
|
"""
|
||||||
Вычисляет расстояние между двумя координатами в градусах.
|
Вычисляет среднюю точку между двумя координатами с использованием геодезических вычислений (с учётом эллипсоида).
|
||||||
|
|
||||||
Использует простую евклидову метрику для малых расстояний.
|
:param lat1: Широта первой точки в градусах.
|
||||||
Подходит для определения близости точек в радиусе до нескольких градусов.
|
:param lon1: Долгота первой точки в градусах.
|
||||||
|
:param lat2: Широта второй точки в градусах.
|
||||||
Args:
|
:param lon2: Долгота второй точки в градусах.
|
||||||
coord1 (tuple): Первая координата в формате (longitude, latitude)
|
:return: Словарь с ключами 'lat' и 'lon' для средней точки, и расстояние(dist) в КМ.
|
||||||
coord2 (tuple): Вторая координата в формате (longitude, latitude)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
float: Расстояние в градусах
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> dist = calculate_distance_degrees((37.62, 55.75), (37.63, 55.76))
|
|
||||||
>>> print(f"{dist:.4f}") # ~0.0141 градусов
|
|
||||||
0.0141
|
|
||||||
|
|
||||||
>>> dist = calculate_distance_degrees((37.62, 55.75), (37.62, 55.75))
|
|
||||||
>>> print(dist) # Одинаковые координаты
|
|
||||||
0.0
|
|
||||||
"""
|
"""
|
||||||
lon1, lat1 = coord1
|
lon1, lat1 = coord1
|
||||||
lon2, lat2 = coord2
|
lon2, lat2 = coord2
|
||||||
|
geod_inv = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2)
|
||||||
|
azimuth1 = geod_inv['azi1']
|
||||||
|
distance = geod_inv['s12']
|
||||||
|
geod_direct = Geodesic.WGS84.Direct(lat1, lon1, azimuth1, distance / 2)
|
||||||
|
return (geod_direct['lon2'], geod_direct['lat2']), distance/1000
|
||||||
|
|
||||||
# Простая евклидова метрика для малых расстояний
|
|
||||||
# Для более точных расчетов на больших расстояниях можно использовать формулу гаверсинуса
|
|
||||||
delta_lon = lon2 - lon1
|
|
||||||
delta_lat = lat2 - lat1
|
|
||||||
|
|
||||||
distance = (delta_lon**2 + delta_lat**2) ** 0.5
|
|
||||||
|
|
||||||
return distance
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_average_coords_incremental(
|
def calculate_average_coords_incremental(
|
||||||
|
|||||||
@@ -915,6 +915,7 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
objects = (
|
objects = (
|
||||||
ObjItem.objects.select_related(
|
ObjItem.objects.select_related(
|
||||||
"geo_obj",
|
"geo_obj",
|
||||||
|
"source",
|
||||||
"updated_by__user",
|
"updated_by__user",
|
||||||
"created_by__user",
|
"created_by__user",
|
||||||
"lyngsat_source",
|
"lyngsat_source",
|
||||||
@@ -933,6 +934,7 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
else:
|
else:
|
||||||
objects = ObjItem.objects.select_related(
|
objects = ObjItem.objects.select_related(
|
||||||
"geo_obj",
|
"geo_obj",
|
||||||
|
"source",
|
||||||
"updated_by__user",
|
"updated_by__user",
|
||||||
"created_by__user",
|
"created_by__user",
|
||||||
"lyngsat_source",
|
"lyngsat_source",
|
||||||
@@ -1021,14 +1023,14 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if has_kupsat == "1":
|
if has_kupsat == "1":
|
||||||
objects = objects.filter(geo_obj__coords_kupsat__isnull=False)
|
objects = objects.filter(source__coords_kupsat__isnull=False)
|
||||||
elif has_kupsat == "0":
|
elif has_kupsat == "0":
|
||||||
objects = objects.filter(geo_obj__coords_kupsat__isnull=True)
|
objects = objects.filter(source__coords_kupsat__isnull=True)
|
||||||
|
|
||||||
if has_valid == "1":
|
if has_valid == "1":
|
||||||
objects = objects.filter(geo_obj__coords_valid__isnull=False)
|
objects = objects.filter(source__coords_valid__isnull=False)
|
||||||
elif has_valid == "0":
|
elif has_valid == "0":
|
||||||
objects = objects.filter(geo_obj__coords_valid__isnull=True)
|
objects = objects.filter(source__coords_valid__isnull=True)
|
||||||
|
|
||||||
# Date filter for geo_obj timestamp
|
# Date filter for geo_obj timestamp
|
||||||
date_from = request.GET.get("date_from")
|
date_from = request.GET.get("date_from")
|
||||||
@@ -1145,33 +1147,6 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||||
geo_coords = f"{lat} {lon}"
|
geo_coords = f"{lat} {lon}"
|
||||||
|
|
||||||
if obj.geo_obj.coords_kupsat:
|
|
||||||
longitude = obj.geo_obj.coords_kupsat.coords[0]
|
|
||||||
latitude = obj.geo_obj.coords_kupsat.coords[1]
|
|
||||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
|
||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
|
||||||
kupsat_coords = f"{lat} {lon}"
|
|
||||||
elif obj.geo_obj.coords_kupsat is not None:
|
|
||||||
kupsat_coords = "-"
|
|
||||||
|
|
||||||
if obj.geo_obj.coords_valid:
|
|
||||||
longitude = obj.geo_obj.coords_valid.coords[0]
|
|
||||||
latitude = obj.geo_obj.coords_valid.coords[1]
|
|
||||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
|
||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
|
||||||
valid_coords = f"{lat} {lon}"
|
|
||||||
elif obj.geo_obj.coords_valid is not None:
|
|
||||||
valid_coords = "-"
|
|
||||||
|
|
||||||
if obj.geo_obj.distance_coords_kup is not None:
|
|
||||||
distance_geo_kup = f"{obj.geo_obj.distance_coords_kup:.3f}"
|
|
||||||
|
|
||||||
if obj.geo_obj.distance_coords_valid is not None:
|
|
||||||
distance_geo_valid = f"{obj.geo_obj.distance_coords_valid:.3f}"
|
|
||||||
|
|
||||||
if obj.geo_obj.distance_kup_valid is not None:
|
|
||||||
distance_kup_valid = f"{obj.geo_obj.distance_kup_valid:.3f}"
|
|
||||||
|
|
||||||
satellite_name = "-"
|
satellite_name = "-"
|
||||||
frequency = "-"
|
frequency = "-"
|
||||||
freq_range = "-"
|
freq_range = "-"
|
||||||
|
|||||||
Reference in New Issue
Block a user