Files
dbstorage/dbapp/mainapp/views/map.py

441 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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):
import json
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:source_list")
# Get polygon filter from URL if present
polygon_coords_str = request.GET.get("polygon", "").strip()
polygon_coords = None
if polygon_coords_str:
try:
polygon_coords = json.loads(polygon_coords_str)
except (json.JSONDecodeError, ValueError, TypeError):
polygon_coords = None
context = {
"groups": groups,
"polygon_coords": json.dumps(polygon_coords) if polygon_coords else None,
}
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:source_list")
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}] МГц",
"timestamp": obj.geo_obj.timestamp.isoformat() if obj.geo_obj.timestamp else None,
}
)
# Сортируем точки по времени (от старой к новой)
all_gl_points.sort(key=lambda x: x["timestamp"] if x["timestamp"] else "")
# Добавляем все точки ГЛ одним цветом (красный)
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:source_list")
# Получаем все 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": "ок"})