426 lines
15 KiB
Python
426 lines
15 KiB
Python
"""
|
||
Map related views for displaying objects on maps.
|
||
"""
|
||
|
||
from collections import defaultdict
|
||
|
||
from django.contrib.admin.views.decorators import staff_member_required
|
||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||
from django.http import JsonResponse
|
||
from django.shortcuts import redirect, render
|
||
from django.utils.decorators import method_decorator
|
||
from django.views import View
|
||
|
||
from ..clusters import get_clusters
|
||
from ..mixins import RoleRequiredMixin
|
||
from ..models import ObjItem
|
||
|
||
|
||
@method_decorator(staff_member_required, name="dispatch")
|
||
class ShowMapView(RoleRequiredMixin, View):
|
||
"""View for displaying objects on map (admin interface)."""
|
||
|
||
required_roles = ["admin", "moderator"]
|
||
|
||
def get(self, request):
|
||
ids = request.GET.get("ids", "")
|
||
points = []
|
||
if ids:
|
||
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
|
||
locations = ObjItem.objects.filter(id__in=id_list).select_related(
|
||
"parameter_obj",
|
||
"parameter_obj__id_satellite",
|
||
"parameter_obj__polarization",
|
||
"parameter_obj__modulation",
|
||
"parameter_obj__standard",
|
||
"geo_obj",
|
||
)
|
||
for obj in locations:
|
||
if (
|
||
not hasattr(obj, "geo_obj")
|
||
or not obj.geo_obj
|
||
or not obj.geo_obj.coords
|
||
):
|
||
continue
|
||
param = getattr(obj, "parameter_obj", None)
|
||
if not param:
|
||
continue
|
||
points.append(
|
||
{
|
||
"name": f"{obj.name}",
|
||
"freq": f"{param.frequency} [{param.freq_range}] МГц",
|
||
"point": (obj.geo_obj.coords.x, obj.geo_obj.coords.y),
|
||
}
|
||
)
|
||
else:
|
||
return redirect("admin")
|
||
|
||
grouped = defaultdict(list)
|
||
for p in points:
|
||
grouped[p["name"]].append({"point": p["point"], "frequency": p["freq"]})
|
||
|
||
groups = [
|
||
{"name": name, "points": coords_list}
|
||
for name, coords_list in grouped.items()
|
||
]
|
||
|
||
context = {
|
||
"groups": groups,
|
||
}
|
||
return render(request, "admin/map_custom.html", context)
|
||
|
||
|
||
class ShowSelectedObjectsMapView(LoginRequiredMixin, View):
|
||
"""View for displaying selected objects on map."""
|
||
|
||
def get(self, request):
|
||
ids = request.GET.get("ids", "")
|
||
points = []
|
||
if ids:
|
||
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
|
||
locations = ObjItem.objects.filter(id__in=id_list).select_related(
|
||
"parameter_obj",
|
||
"parameter_obj__id_satellite",
|
||
"parameter_obj__polarization",
|
||
"parameter_obj__modulation",
|
||
"parameter_obj__standard",
|
||
"geo_obj",
|
||
)
|
||
for obj in locations:
|
||
if (
|
||
not hasattr(obj, "geo_obj")
|
||
or not obj.geo_obj
|
||
or not obj.geo_obj.coords
|
||
):
|
||
continue
|
||
param = getattr(obj, "parameter_obj", None)
|
||
if not param:
|
||
continue
|
||
points.append(
|
||
{
|
||
"name": f"{obj.name}",
|
||
"freq": f"{param.frequency} [{param.freq_range}] МГц",
|
||
"point": (obj.geo_obj.coords.x, obj.geo_obj.coords.y),
|
||
}
|
||
)
|
||
else:
|
||
return redirect("mainapp:objitem_list")
|
||
|
||
# Group points by object name
|
||
grouped = defaultdict(list)
|
||
for p in points:
|
||
grouped[p["name"]].append({"point": p["point"], "frequency": p["freq"]})
|
||
|
||
groups = [
|
||
{"name": name, "points": coords_list}
|
||
for name, coords_list in grouped.items()
|
||
]
|
||
|
||
context = {
|
||
"groups": groups,
|
||
}
|
||
return render(request, "mainapp/objitem_map.html", context)
|
||
|
||
|
||
class ShowSourcesMapView(LoginRequiredMixin, View):
|
||
"""View for displaying selected sources on map."""
|
||
|
||
def get(self, request):
|
||
from ..models import Source
|
||
|
||
ids = request.GET.get("ids", "")
|
||
groups = []
|
||
|
||
if ids:
|
||
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
|
||
sources = Source.objects.filter(id__in=id_list)
|
||
|
||
# Define coordinate types with their labels and colors
|
||
coord_types = [
|
||
("coords_average", "Усредненные координаты", "blue"),
|
||
("coords_kupsat", "Координаты Кубсата", "orange"),
|
||
("coords_valid", "Координаты оперативников", "green"),
|
||
("coords_reference", "Координаты справочные", "violet"),
|
||
]
|
||
|
||
# Group points by coordinate type
|
||
for coord_field, label, color in coord_types:
|
||
points = []
|
||
for source in sources:
|
||
coords = getattr(source, coord_field)
|
||
if coords:
|
||
# coords is a Point object with x (longitude) and y (latitude)
|
||
points.append(
|
||
{
|
||
"point": (coords.x, coords.y), # (lon, lat)
|
||
"source_id": f"Источник #{source.id}",
|
||
}
|
||
)
|
||
|
||
if points:
|
||
groups.append(
|
||
{
|
||
"name": label,
|
||
"points": points,
|
||
"color": color,
|
||
}
|
||
)
|
||
else:
|
||
return redirect("mainapp:home")
|
||
|
||
context = {
|
||
"groups": groups,
|
||
}
|
||
return render(request, "mainapp/source_map.html", context)
|
||
|
||
|
||
class ShowSourceWithPointsMapView(LoginRequiredMixin, View):
|
||
"""View for displaying a single source with all its related ObjItem points."""
|
||
|
||
def get(self, request, source_id):
|
||
from ..models import Source
|
||
|
||
try:
|
||
source = Source.objects.prefetch_related(
|
||
"source_objitems",
|
||
"source_objitems__parameter_obj",
|
||
"source_objitems__geo_obj",
|
||
).get(id=source_id)
|
||
except Source.DoesNotExist:
|
||
return redirect("mainapp:home")
|
||
|
||
groups = []
|
||
|
||
# Цвета для разных типов координат источника
|
||
source_coord_types = [
|
||
("coords_average", "Усредненные координаты", "blue"),
|
||
("coords_kupsat", "Координаты Кубсата", "orange"),
|
||
("coords_valid", "Координаты оперативников", "green"),
|
||
("coords_reference", "Координаты справочные", "violet"),
|
||
]
|
||
|
||
# Добавляем координаты источника
|
||
for coord_field, label, color in source_coord_types:
|
||
coords = getattr(source, coord_field)
|
||
if coords:
|
||
groups.append(
|
||
{
|
||
"name": label,
|
||
"points": [
|
||
{
|
||
"point": (coords.x, coords.y),
|
||
"source_id": f"Источник #{source.id}",
|
||
}
|
||
],
|
||
"color": color,
|
||
}
|
||
)
|
||
|
||
# Добавляем все точки ГЛ одной группой
|
||
gl_points = source.source_objitems.select_related(
|
||
"parameter_obj", "geo_obj"
|
||
).all()
|
||
|
||
# Собираем все точки ГЛ в одну группу
|
||
all_gl_points = []
|
||
for obj in gl_points:
|
||
if (
|
||
not hasattr(obj, "geo_obj")
|
||
or not obj.geo_obj
|
||
or not obj.geo_obj.coords
|
||
):
|
||
continue
|
||
param = getattr(obj, "parameter_obj", None)
|
||
if not param:
|
||
continue
|
||
|
||
all_gl_points.append(
|
||
{
|
||
"point": (obj.geo_obj.coords.x, obj.geo_obj.coords.y),
|
||
"name": obj.name,
|
||
"frequency": f"{param.frequency} [{param.freq_range}] МГц",
|
||
}
|
||
)
|
||
|
||
# Добавляем все точки ГЛ одним цветом (красный)
|
||
if all_gl_points:
|
||
groups.append(
|
||
{"name": "Точки ГЛ", "points": all_gl_points, "color": "red"}
|
||
)
|
||
|
||
context = {
|
||
"groups": groups,
|
||
"source_id": source_id,
|
||
}
|
||
return render(request, "mainapp/source_with_points_map.html", context)
|
||
|
||
|
||
class ShowSourceAveragingStepsMapView(LoginRequiredMixin, View):
|
||
"""View for displaying source averaging steps visualization."""
|
||
|
||
def get(self, request, source_id):
|
||
from ..models import Source
|
||
from ..utils import calculate_mean_coords, RANGE_DISTANCE
|
||
|
||
try:
|
||
source = Source.objects.prefetch_related(
|
||
"source_objitems",
|
||
"source_objitems__parameter_obj",
|
||
"source_objitems__geo_obj",
|
||
).get(id=source_id)
|
||
except Source.DoesNotExist:
|
||
return redirect("mainapp:home")
|
||
|
||
# Получаем все ObjItem, отсортированные по ID (порядок добавления)
|
||
objitems = source.source_objitems.select_related(
|
||
"parameter_obj", "geo_obj"
|
||
).order_by("id")
|
||
|
||
# Собираем координаты всех точек
|
||
original_points = []
|
||
for obj in objitems:
|
||
if (
|
||
not hasattr(obj, "geo_obj")
|
||
or not obj.geo_obj
|
||
or not obj.geo_obj.coords
|
||
):
|
||
continue
|
||
param = getattr(obj, "parameter_obj", None)
|
||
if not param:
|
||
continue
|
||
|
||
original_points.append(
|
||
{
|
||
"point": (obj.geo_obj.coords.x, obj.geo_obj.coords.y),
|
||
"name": obj.name,
|
||
"frequency": f"{param.frequency} [{param.freq_range}] МГц",
|
||
"objitem_id": obj.id,
|
||
}
|
||
)
|
||
|
||
# Воспроизводим алгоритм усреднения
|
||
averaging_steps = []
|
||
|
||
if original_points:
|
||
# Первая точка становится начальным средним
|
||
current_avg = original_points[0]["point"]
|
||
|
||
# Обрабатываем остальные точки
|
||
for i, point_data in enumerate(original_points[1:], start=1):
|
||
current_coord = point_data["point"]
|
||
|
||
# Вычисляем новое среднее и расстояние
|
||
new_avg, distance = calculate_mean_coords(current_avg, current_coord)
|
||
|
||
# Сохраняем шаг усреднения
|
||
averaging_steps.append(
|
||
{
|
||
"point": new_avg,
|
||
"step": i,
|
||
"distance": round(distance, 2),
|
||
"within_range": distance <= RANGE_DISTANCE,
|
||
}
|
||
)
|
||
|
||
# Обновляем текущее среднее
|
||
current_avg = new_avg
|
||
|
||
# Формируем группы для отображения на карте
|
||
groups = []
|
||
|
||
# 1. Исходные точки ObjItem (красный)
|
||
if original_points:
|
||
groups.append(
|
||
{
|
||
"name": "Исходные точки ГЛ",
|
||
"points": original_points,
|
||
"color": "red",
|
||
}
|
||
)
|
||
|
||
# 2. Промежуточные точки усреднения (оранжевый)
|
||
if averaging_steps:
|
||
intermediate_points = [
|
||
{
|
||
"point": step["point"],
|
||
"step": f"Шаг {step['step']}",
|
||
"distance": f"{step['distance']} км",
|
||
}
|
||
for step in averaging_steps[:-1] # Все кроме последней
|
||
]
|
||
if intermediate_points:
|
||
groups.append(
|
||
{
|
||
"name": "Промежуточные шаги усреднения",
|
||
"points": intermediate_points,
|
||
"color": "orange",
|
||
}
|
||
)
|
||
|
||
# 3. Финальная усредненная точка (синий)
|
||
if averaging_steps:
|
||
final_step = averaging_steps[-1]
|
||
groups.append(
|
||
{
|
||
"name": "Финальная усредненная координата",
|
||
"points": [
|
||
{
|
||
"point": final_step["point"],
|
||
"step": f"Шаг {final_step['step']} (финальный)",
|
||
"distance": f"{final_step['distance']} км",
|
||
}
|
||
],
|
||
"color": "blue",
|
||
}
|
||
)
|
||
|
||
# 4. Координаты источника для сравнения (если есть)
|
||
source_coord_types = [
|
||
("coords_average", "Сохраненные усредненные координаты", "green"),
|
||
("coords_kupsat", "Координаты Кубсата", "purple"),
|
||
("coords_valid", "Координаты оперативников", "cyan"),
|
||
("coords_reference", "Координаты справочные", "violet"),
|
||
]
|
||
|
||
for coord_field, label, color in source_coord_types:
|
||
coords = getattr(source, coord_field)
|
||
if coords:
|
||
groups.append(
|
||
{
|
||
"name": label,
|
||
"points": [
|
||
{
|
||
"point": (coords.x, coords.y),
|
||
"source_id": f"Источник #{source.id}",
|
||
}
|
||
],
|
||
"color": color,
|
||
}
|
||
)
|
||
|
||
context = {
|
||
"groups": groups,
|
||
"source_id": source_id,
|
||
"total_points": len(original_points),
|
||
"total_steps": len(averaging_steps),
|
||
}
|
||
return render(request, "mainapp/source_averaging_map.html", context)
|
||
|
||
|
||
class ClusterTestView(LoginRequiredMixin, View):
|
||
"""Test view for clustering functionality."""
|
||
|
||
def get(self, request):
|
||
objs = ObjItem.objects.filter(
|
||
name__icontains="! Astra 4A 12654,040 [1,962] МГц H"
|
||
)
|
||
coords = []
|
||
for obj in objs:
|
||
if hasattr(obj, "geo_obj") and obj.geo_obj and obj.geo_obj.coords:
|
||
coords.append(
|
||
(obj.geo_obj.coords.coords[1], obj.geo_obj.coords.coords[0])
|
||
)
|
||
get_clusters(coords)
|
||
|
||
return JsonResponse({"success": "ок"})
|