Переделал страницу с ObjItem. Теперь работает корректно.

This commit is contained in:
2025-11-13 14:21:02 +03:00
parent 50498166e5
commit d0a53e251e
8 changed files with 204 additions and 358 deletions

View File

@@ -8,92 +8,121 @@
4. Иначе создать новый Source с coords_average = координаты geo_obj
"""
import os
import django
# import os
# import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dbapp.settings")
django.setup()
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dbapp.settings")
# django.setup()
from mainapp.models import ObjItem, Source, CustomUser
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
from django.contrib.gis.db.models.functions import Distance
# from mainapp.models import ObjItem, Source, CustomUser
# from django.contrib.gis.geos import Point
# from django.contrib.gis.measure import D
# from django.contrib.gis.db.models.functions import Distance
def calculate_distance_degrees(coord1, coord2):
"""Вычисляет расстояние между двумя координатами в градусах."""
import math
# def calculate_distance_degrees(coord1, coord2):
# """Вычисляет расстояние между двумя координатами в градусах."""
# 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
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
return math.sqrt((lon2 - lon1) ** 2 + (lat2 - lat1) ** 2)
# Пример использования
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():
"""Исправляет 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()
print(f"Средняя точка: {midpoint[0]}")
print(f"Расстояние: {midpoint[1]} км")

View File

@@ -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>

View File

@@ -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>

View File

@@ -14,7 +14,7 @@
{% if user.is_authenticated %}
<ul class="navbar-nav me-auto">
<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 class="nav-item">
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>

View File

@@ -92,179 +92,7 @@
</div>
<!-- Column visibility toggle button -->
<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>
<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>
{% include 'mainapp/components/_column_visibility_dropdown.html' %}
<!-- Pagination -->
<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="" 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="" sortable=False %}
{% 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_location}}</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.updated_by }}</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
function initColumnVisibility() {
const creationDateCheckbox = document.querySelector('input[data-column="19"]');
const creationUserCheckbox = document.querySelector('input[data-column="20"]');
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);
}
const creationDateCheckbox = document.querySelector('input[data-column="12"]');
const creationUserCheckbox = document.querySelector('input[data-column="13"]');
if (creationDateCheckbox) {
creationDateCheckbox.checked = false;
toggleColumn(creationDateCheckbox);
@@ -924,9 +731,9 @@
}
// Hide comment, is_average, and standard columns by default
const commentCheckbox = document.querySelector('input[data-column="21"]');
const isAverageCheckbox = document.querySelector('input[data-column="22"]');
const standardCheckbox = document.querySelector('input[data-column="23"]');
const commentCheckbox = document.querySelector('input[data-column="14"]');
const isAverageCheckbox = document.querySelector('input[data-column="15"]');
const standardCheckbox = document.querySelector('input[data-column="16"]');
if (commentCheckbox) {
commentCheckbox.checked = false;

View File

@@ -6,9 +6,9 @@ from . import views
app_name = 'mainapp'
urlpatterns = [
path('', views.SourceListView.as_view(), name='home'), # Source list page
path('objitems/', views.ObjItemListView.as_view(), name='objitem_list'), # Objects list page
path('actions/', views.ActionsPageView.as_view(), name='actions'), # Move actions to a separate page
path('', views.SourceListView.as_view(), name='home'),
path('objitems/', views.ObjItemListView.as_view(), name='objitem_list'),
path('actions/', views.ActionsPageView.as_view(), name='actions'),
path('excel-data', views.LoadExcelDataView.as_view(), name='load_excel_data'),
path('satellites', views.AddSatellitesView.as_view(), name='add_sats'),
path('api/locations/<int:sat_id>/geojson/', views.GetLocationsView.as_view(), name='locations_by_id'),

View File

@@ -10,7 +10,7 @@ from django.db.models import F
# Third-party imports
import pandas as pd
from geographiclib.geodesic import Geodesic
# Local imports
from mapsapp.models import Transponders
@@ -40,6 +40,7 @@ MAX_ITEMS_PER_PAGE = 10000
DEFAULT_NUMERIC_VALUE = -1.0
MINIMUM_BANDWIDTH_MHZ = 0.08
RANGE_DISTANCE = 56
def get_all_constants():
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
current_avg = (source.coords_average.x, source.coords_average.y)
distance = calculate_distance_degrees(current_avg, current_coord)
# Если расстояние <= 0.5 градуса
if distance <= 0.5:
# Вычислить новое среднее ИНКРЕМЕНТАЛЬНО
new_avg = calculate_average_coords_incremental(
current_avg, current_coord
)
new_avg, distance = calculate_mean_coords(current_avg, current_coord)
if distance <= RANGE_DISTANCE:
# Обновить coords_average в Source
source.coords_average = Point(new_avg, srid=4326)
source.save()
@@ -255,7 +250,6 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts):
comment = row["Комментарий"]
source_name = row["Объект наблюдения"]
# Создаем Geo объект (БЕЗ coords_kupsat и coords_valid)
geo, _ = Geo.objects.get_or_create(
timestamp=timestamp,
coords=geo_point,
@@ -469,7 +463,7 @@ def get_points_from_csv(file_content, current_user=None):
for source in existing_sources:
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:
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:
# Обновить coords_average инкрементально
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.save()
@@ -507,7 +501,7 @@ def get_points_from_csv(file_content, current_user=None):
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 с такими же координатами и частотой.
@@ -515,7 +509,7 @@ def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.001):
coord_tuple: кортеж (lon, lat) координат
frequency: частота в МГц
freq_range: полоса частот в МГц
tolerance: допуск для сравнения координат в градусах (по умолчанию 0.001 ≈ 100м)
tolerance: допуск для сравнения координат в километрах
Returns:
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)
distance = calculate_distance_degrees(coord_tuple, geo_coord)
_, distance = calculate_mean_coords(coord_tuple, geo_coord)
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]:
"""
Вычисляет расстояние между двумя координатами в градусах.
Вычисляет среднюю точку между двумя координатами с использованием геодезических вычислений (с учётом эллипсоида).
Использует простую евклидову метрику для малых расстояний.
Подходит для определения близости точек в радиусе до нескольких градусов.
Args:
coord1 (tuple): Первая координата в формате (longitude, latitude)
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
:param lat1: Широта первой точки в градусах.
:param lon1: Долгота первой точки в градусах.
:param lat2: Широта второй точки в градусах.
:param lon2: Долгота второй точки в градусах.
:return: Словарь с ключами 'lat' и 'lon' для средней точки, и расстояние(dist) в КМ.
"""
lon1, lat1 = coord1
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(

View File

@@ -915,6 +915,7 @@ class ObjItemListView(LoginRequiredMixin, View):
objects = (
ObjItem.objects.select_related(
"geo_obj",
"source",
"updated_by__user",
"created_by__user",
"lyngsat_source",
@@ -933,6 +934,7 @@ class ObjItemListView(LoginRequiredMixin, View):
else:
objects = ObjItem.objects.select_related(
"geo_obj",
"source",
"updated_by__user",
"created_by__user",
"lyngsat_source",
@@ -1021,14 +1023,14 @@ class ObjItemListView(LoginRequiredMixin, View):
)
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":
objects = objects.filter(geo_obj__coords_kupsat__isnull=True)
objects = objects.filter(source__coords_kupsat__isnull=True)
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":
objects = objects.filter(geo_obj__coords_valid__isnull=True)
objects = objects.filter(source__coords_valid__isnull=True)
# Date filter for geo_obj timestamp
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"
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 = "-"
frequency = "-"
freq_range = "-"