Compare commits
3 Commits
ad479a2069
...
a18071b7ec
| Author | SHA1 | Date | |
|---|---|---|---|
| a18071b7ec | |||
| b9e17df32c | |||
| 96f961b0f8 |
File diff suppressed because it is too large
Load Diff
@@ -203,10 +203,17 @@ def find_mirror_satellites(mirror_names: list) -> list:
|
|||||||
|
|
||||||
Алгоритм:
|
Алгоритм:
|
||||||
1. Для каждого имени зеркала:
|
1. Для каждого имени зеркала:
|
||||||
- Обрезать пробелы и привести к нижнему регистру
|
- Обрезать пробелы
|
||||||
|
- Извлечь первую часть имени (до скобки), если есть двойное имя
|
||||||
|
- Привести к нижнему регистру
|
||||||
- Найти все спутники, в имени или альтернативном имени которых содержится это имя
|
- Найти все спутники, в имени или альтернативном имени которых содержится это имя
|
||||||
2. Вернуть список найденных спутников
|
2. Вернуть список найденных спутников
|
||||||
|
|
||||||
|
Примеры обработки:
|
||||||
|
- "DSN-3 (SUPERBIRD-C2)" -> "dsn-3"
|
||||||
|
- "Turksat 3A" -> "turksat 3a"
|
||||||
|
- " Amos 4 " -> "amos 4"
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mirror_names: список имен зеркал
|
mirror_names: список имен зеркал
|
||||||
|
|
||||||
@@ -221,15 +228,26 @@ def find_mirror_satellites(mirror_names: list) -> list:
|
|||||||
if not mirror_name or mirror_name == "-":
|
if not mirror_name or mirror_name == "-":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Обрезаем пробелы и приводим к нижнему регистру
|
# Обрезаем пробелы
|
||||||
mirror_name_clean = mirror_name.strip().lower()
|
mirror_name_clean = mirror_name.strip()
|
||||||
|
|
||||||
if not mirror_name_clean:
|
if not mirror_name_clean or mirror_name_clean == "-":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Извлекаем первую часть имени (до скобки), если есть двойное имя
|
||||||
|
# Например: "DSN-3 (SUPERBIRD-C2)" -> "DSN-3"
|
||||||
|
if "(" in mirror_name_clean:
|
||||||
|
mirror_name_clean = mirror_name_clean.split("(")[0].strip()
|
||||||
|
|
||||||
|
# Приводим к нижнему регистру для поиска
|
||||||
|
mirror_name_lower = mirror_name_clean.lower()
|
||||||
|
|
||||||
|
if not mirror_name_lower:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Ищем спутники, в имени или альтернативном имени которых содержится имя зеркала
|
# Ищем спутники, в имени или альтернативном имени которых содержится имя зеркала
|
||||||
satellites = Satellite.objects.filter(
|
satellites = Satellite.objects.filter(
|
||||||
Q(name__icontains=mirror_name_clean) | Q(alternative_name__icontains=mirror_name_clean)
|
Q(name__icontains=mirror_name_lower) | Q(alternative_name__icontains=mirror_name_lower)
|
||||||
)
|
)
|
||||||
|
|
||||||
found_satellites.extend(satellites)
|
found_satellites.extend(satellites)
|
||||||
@@ -1395,27 +1413,28 @@ def kub_report(data_in: io.StringIO) -> pd.DataFrame:
|
|||||||
from pyproj import CRS, Transformer
|
from pyproj import CRS, Transformer
|
||||||
|
|
||||||
|
|
||||||
def get_gauss_kruger_zone(longitude: float) -> int:
|
def get_gauss_kruger_zone(longitude: float) -> int | None:
|
||||||
"""
|
"""
|
||||||
Определяет номер зоны Гаусса-Крюгера по долготе.
|
Определяет номер зоны Гаусса-Крюгера по долготе.
|
||||||
|
|
||||||
Зоны ГК нумеруются от 1 до 60, каждая зона охватывает 6° долготы.
|
Зоны ГК (Пулково 1942) имеют EPSG коды 28404-28432 (зоны 4-32).
|
||||||
Центральный меридиан зоны N: (6*N - 3)°
|
Каждая зона охватывает 6° долготы.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
longitude: Долгота в градусах (от -180 до 180)
|
longitude: Долгота в градусах (от -180 до 180)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Номер зоны ГК (1-60)
|
int | None: Номер зоны ГК (4-32) или None если координаты вне зон ГК
|
||||||
"""
|
"""
|
||||||
# Нормализуем долготу к диапазону 0-360
|
# Нормализуем долготу к диапазону 0-360
|
||||||
lon_normalized = longitude if longitude >= 0 else longitude + 360
|
lon_normalized = longitude if longitude >= 0 else longitude + 360
|
||||||
# Вычисляем номер зоны (1-60)
|
# Вычисляем номер зоны (1-60)
|
||||||
zone = int((lon_normalized + 6) / 6)
|
zone = int((lon_normalized + 6) / 6)
|
||||||
if zone > 60:
|
|
||||||
zone = 60
|
# EPSG коды Пулково 1942 существуют только для зон 4-32
|
||||||
if zone < 1:
|
if zone < 4 or zone > 32:
|
||||||
zone = 1
|
return None
|
||||||
|
|
||||||
return zone
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@@ -1423,14 +1442,8 @@ def get_gauss_kruger_epsg(zone: int) -> int:
|
|||||||
"""
|
"""
|
||||||
Возвращает EPSG код для зоны Гаусса-Крюгера (Pulkovo 1942 / Gauss-Kruger).
|
Возвращает EPSG код для зоны Гаусса-Крюгера (Pulkovo 1942 / Gauss-Kruger).
|
||||||
|
|
||||||
EPSG коды для Pulkovo 1942 GK зон:
|
|
||||||
- Зона 4: EPSG:28404
|
|
||||||
- Зона 5: EPSG:28405
|
|
||||||
- ...
|
|
||||||
- Зона N: EPSG:28400 + N
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
zone: Номер зоны ГК (1-60)
|
zone: Номер зоны ГК (4-32)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: EPSG код проекции
|
int: EPSG код проекции
|
||||||
@@ -1438,13 +1451,50 @@ def get_gauss_kruger_epsg(zone: int) -> int:
|
|||||||
return 28400 + zone
|
return 28400 + zone
|
||||||
|
|
||||||
|
|
||||||
|
def get_utm_zone(longitude: float) -> int:
|
||||||
|
"""
|
||||||
|
Определяет номер зоны UTM по долготе.
|
||||||
|
|
||||||
|
UTM зоны нумеруются от 1 до 60, каждая зона охватывает 6° долготы.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
longitude: Долгота в градусах (от -180 до 180)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Номер зоны UTM (1-60)
|
||||||
|
"""
|
||||||
|
zone = int((longitude + 180) / 6) + 1
|
||||||
|
if zone > 60:
|
||||||
|
zone = 60
|
||||||
|
if zone < 1:
|
||||||
|
zone = 1
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
|
def get_utm_epsg(zone: int, is_northern: bool = True) -> int:
|
||||||
|
"""
|
||||||
|
Возвращает EPSG код для зоны UTM (WGS 84 / UTM).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
zone: Номер зоны UTM (1-60)
|
||||||
|
is_northern: True для северного полушария, False для южного
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: EPSG код проекции
|
||||||
|
"""
|
||||||
|
if is_northern:
|
||||||
|
return 32600 + zone
|
||||||
|
else:
|
||||||
|
return 32700 + zone
|
||||||
|
|
||||||
|
|
||||||
def transform_wgs84_to_gk(coord: tuple, zone: int = None) -> tuple:
|
def transform_wgs84_to_gk(coord: tuple, zone: int = None) -> tuple:
|
||||||
"""
|
"""
|
||||||
Преобразует координаты из WGS84 (EPSG:4326) в проекцию Гаусса-Крюгера.
|
Преобразует координаты из WGS84 в проекцию Гаусса-Крюгера.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coord: Координаты в формате (longitude, latitude) в WGS84
|
coord: Координаты в формате (longitude, latitude) в WGS84
|
||||||
zone: Номер зоны ГК (если None, определяется автоматически по долготе)
|
zone: Номер зоны ГК (если None, определяется автоматически)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: Координаты (x, y) в метрах в проекции ГК
|
tuple: Координаты (x, y) в метрах в проекции ГК
|
||||||
@@ -1454,9 +1504,11 @@ def transform_wgs84_to_gk(coord: tuple, zone: int = None) -> tuple:
|
|||||||
if zone is None:
|
if zone is None:
|
||||||
zone = get_gauss_kruger_zone(lon)
|
zone = get_gauss_kruger_zone(lon)
|
||||||
|
|
||||||
|
if zone is None:
|
||||||
|
raise ValueError(f"Координаты ({lon}, {lat}) вне зон Гаусса-Крюгера (4-32)")
|
||||||
|
|
||||||
epsg_gk = get_gauss_kruger_epsg(zone)
|
epsg_gk = get_gauss_kruger_epsg(zone)
|
||||||
|
|
||||||
# Создаём трансформер WGS84 -> GK
|
|
||||||
transformer = Transformer.from_crs(
|
transformer = Transformer.from_crs(
|
||||||
CRS.from_epsg(4326),
|
CRS.from_epsg(4326),
|
||||||
CRS.from_epsg(epsg_gk),
|
CRS.from_epsg(epsg_gk),
|
||||||
@@ -1469,7 +1521,7 @@ def transform_wgs84_to_gk(coord: tuple, zone: int = None) -> tuple:
|
|||||||
|
|
||||||
def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
|
def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
|
||||||
"""
|
"""
|
||||||
Преобразует координаты из проекции Гаусса-Крюгера в WGS84 (EPSG:4326).
|
Преобразует координаты из проекции Гаусса-Крюгера в WGS84.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coord: Координаты (x, y) в метрах в проекции ГК
|
coord: Координаты (x, y) в метрах в проекции ГК
|
||||||
@@ -1481,7 +1533,6 @@ def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
|
|||||||
x, y = coord
|
x, y = coord
|
||||||
epsg_gk = get_gauss_kruger_epsg(zone)
|
epsg_gk = get_gauss_kruger_epsg(zone)
|
||||||
|
|
||||||
# Создаём трансформер GK -> WGS84
|
|
||||||
transformer = Transformer.from_crs(
|
transformer = Transformer.from_crs(
|
||||||
CRS.from_epsg(epsg_gk),
|
CRS.from_epsg(epsg_gk),
|
||||||
CRS.from_epsg(4326),
|
CRS.from_epsg(4326),
|
||||||
@@ -1492,37 +1543,126 @@ def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
|
|||||||
return (lon, lat)
|
return (lon, lat)
|
||||||
|
|
||||||
|
|
||||||
def calculate_distance_gk(coord1_gk: tuple, coord2_gk: tuple) -> float:
|
def transform_wgs84_to_utm(coord: tuple, zone: int = None, is_northern: bool = None) -> tuple:
|
||||||
"""
|
"""
|
||||||
Вычисляет расстояние между двумя точками в проекции ГК (в километрах).
|
Преобразует координаты из WGS84 в проекцию UTM.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coord1_gk: Первая точка (x, y) в метрах
|
coord: Координаты в формате (longitude, latitude) в WGS84
|
||||||
coord2_gk: Вторая точка (x, y) в метрах
|
zone: Номер зоны UTM (если None, определяется автоматически)
|
||||||
|
is_northern: Северное полушарие (если None, определяется по широте)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: Расстояние в километрах
|
tuple: Координаты (x, y) в метрах в проекции UTM
|
||||||
"""
|
"""
|
||||||
import math
|
lon, lat = coord
|
||||||
x1, y1 = coord1_gk
|
|
||||||
x2, y2 = coord2_gk
|
if zone is None:
|
||||||
distance_m = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
zone = get_utm_zone(lon)
|
||||||
return distance_m / 1000
|
|
||||||
|
if is_northern is None:
|
||||||
|
is_northern = lat >= 0
|
||||||
|
|
||||||
|
epsg_utm = get_utm_epsg(zone, is_northern)
|
||||||
|
|
||||||
|
transformer = Transformer.from_crs(
|
||||||
|
CRS.from_epsg(4326),
|
||||||
|
CRS.from_epsg(epsg_utm),
|
||||||
|
always_xy=True
|
||||||
|
)
|
||||||
|
|
||||||
|
x, y = transformer.transform(lon, lat)
|
||||||
|
return (x, y)
|
||||||
|
|
||||||
|
|
||||||
def average_coords_in_gk(coords: list[tuple], zone: int = None) -> tuple:
|
def transform_utm_to_wgs84(coord: tuple, zone: int, is_northern: bool = True) -> tuple:
|
||||||
"""
|
"""
|
||||||
Вычисляет среднее арифметическое координат в проекции Гаусса-Крюгера.
|
Преобразует координаты из проекции UTM в WGS84.
|
||||||
|
|
||||||
Алгоритм:
|
Args:
|
||||||
1. Определяет зону ГК по первой точке (если не указана)
|
coord: Координаты (x, y) в метрах в проекции UTM
|
||||||
2. Преобразует все координаты в проекцию ГК
|
zone: Номер зоны UTM
|
||||||
3. Вычисляет среднее арифметическое X и Y
|
is_northern: Северное полушарие
|
||||||
4. Преобразует результат обратно в WGS84
|
|
||||||
|
Returns:
|
||||||
|
tuple: Координаты (longitude, latitude) в WGS84
|
||||||
|
"""
|
||||||
|
x, y = coord
|
||||||
|
epsg_utm = get_utm_epsg(zone, is_northern)
|
||||||
|
|
||||||
|
transformer = Transformer.from_crs(
|
||||||
|
CRS.from_epsg(epsg_utm),
|
||||||
|
CRS.from_epsg(4326),
|
||||||
|
always_xy=True
|
||||||
|
)
|
||||||
|
|
||||||
|
lon, lat = transformer.transform(x, y)
|
||||||
|
return (lon, lat)
|
||||||
|
|
||||||
|
|
||||||
|
def average_coords_in_gk(coords: list[tuple], zone: int = None) -> tuple[tuple, str]:
|
||||||
|
"""
|
||||||
|
Вычисляет среднее арифметическое координат в проекции.
|
||||||
|
|
||||||
|
Приоритет:
|
||||||
|
1. Гаусс-Крюгер (Пулково 1942) для зон 4-32
|
||||||
|
2. UTM для координат вне зон ГК
|
||||||
|
3. Геодезическое усреднение как последний fallback
|
||||||
|
|
||||||
|
Args:
|
||||||
|
coords: Список координат в формате [(lon1, lat1), (lon2, lat2), ...]
|
||||||
|
zone: Номер зоны (если None, определяется по первой точке)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (координаты (lon, lat), тип_усреднения)
|
||||||
|
тип_усреднения: "ГК" | "UTM" | "Геод"
|
||||||
|
"""
|
||||||
|
if not coords:
|
||||||
|
return (0, 0), "ГК"
|
||||||
|
|
||||||
|
if len(coords) == 1:
|
||||||
|
return coords[0], "ГК"
|
||||||
|
|
||||||
|
first_lon, first_lat = coords[0]
|
||||||
|
|
||||||
|
# Пытаемся использовать Гаусс-Крюгер
|
||||||
|
if zone is None:
|
||||||
|
gk_zone = get_gauss_kruger_zone(first_lon)
|
||||||
|
else:
|
||||||
|
gk_zone = zone if 4 <= zone <= 32 else None
|
||||||
|
|
||||||
|
# Если координаты в зонах ГК (4-32), используем ГК
|
||||||
|
if gk_zone is not None:
|
||||||
|
try:
|
||||||
|
coords_projected = [transform_wgs84_to_gk(c, gk_zone) for c in coords]
|
||||||
|
avg_x = sum(c[0] for c in coords_projected) / len(coords_projected)
|
||||||
|
avg_y = sum(c[1] for c in coords_projected) / len(coords_projected)
|
||||||
|
return transform_gk_to_wgs84((avg_x, avg_y), gk_zone), "ГК"
|
||||||
|
except Exception:
|
||||||
|
pass # Fallback на UTM
|
||||||
|
|
||||||
|
# Fallback на UTM для координат вне зон ГК
|
||||||
|
try:
|
||||||
|
utm_zone = get_utm_zone(first_lon)
|
||||||
|
is_northern = first_lat >= 0
|
||||||
|
|
||||||
|
coords_utm = [transform_wgs84_to_utm(c, utm_zone, is_northern) for c in coords]
|
||||||
|
avg_x = sum(c[0] for c in coords_utm) / len(coords_utm)
|
||||||
|
avg_y = sum(c[1] for c in coords_utm) / len(coords_utm)
|
||||||
|
return transform_utm_to_wgs84((avg_x, avg_y), utm_zone, is_northern), "UTM"
|
||||||
|
except Exception:
|
||||||
|
# Последний fallback - геодезическое усреднение
|
||||||
|
return _average_coords_geodesic(coords), "Геод"
|
||||||
|
|
||||||
|
|
||||||
|
def _average_coords_geodesic(coords: list[tuple]) -> tuple:
|
||||||
|
"""
|
||||||
|
Вычисляет среднее координат через последовательное геодезическое усреднение.
|
||||||
|
|
||||||
|
Используется как fallback при ошибках проекции.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
coords: Список координат в формате [(lon1, lat1), (lon2, lat2), ...]
|
coords: Список координат в формате [(lon1, lat1), (lon2, lat2), ...]
|
||||||
zone: Номер зоны ГК (если None, определяется по первой точке)
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: Средние координаты (longitude, latitude) в WGS84
|
tuple: Средние координаты (longitude, latitude) в WGS84
|
||||||
@@ -1533,19 +1673,12 @@ def average_coords_in_gk(coords: list[tuple], zone: int = None) -> tuple:
|
|||||||
if len(coords) == 1:
|
if len(coords) == 1:
|
||||||
return coords[0]
|
return coords[0]
|
||||||
|
|
||||||
# Определяем зону по первой точке
|
# Последовательно усредняем точки
|
||||||
if zone is None:
|
result = coords[0]
|
||||||
zone = get_gauss_kruger_zone(coords[0][0])
|
for i in range(1, len(coords)):
|
||||||
|
result, _ = calculate_mean_coords(result, coords[i])
|
||||||
|
|
||||||
# Преобразуем все координаты в ГК
|
return result
|
||||||
coords_gk = [transform_wgs84_to_gk(c, zone) for c in coords]
|
|
||||||
|
|
||||||
# Вычисляем среднее арифметическое
|
|
||||||
avg_x = sum(c[0] for c in coords_gk) / len(coords_gk)
|
|
||||||
avg_y = sum(c[1] for c in coords_gk) / len(coords_gk)
|
|
||||||
|
|
||||||
# Преобразуем обратно в WGS84
|
|
||||||
return transform_gk_to_wgs84((avg_x, avg_y), zone)
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_mean_coords(coord1: tuple, coord2: tuple) -> tuple[tuple, float]:
|
def calculate_mean_coords(coord1: tuple, coord2: tuple) -> tuple[tuple, float]:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Points averaging view for satellite data grouping by day/night intervals.
|
Points averaging view for satellite data grouping by day/night intervals.
|
||||||
|
Groups points by Source, then by time intervals within each Source.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
@@ -8,7 +9,7 @@ from django.shortcuts import render
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from ..models import ObjItem, Satellite
|
from ..models import ObjItem, Satellite, Source
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
calculate_mean_coords,
|
calculate_mean_coords,
|
||||||
calculate_distance_wgs84,
|
calculate_distance_wgs84,
|
||||||
@@ -29,8 +30,9 @@ class PointsAveragingView(LoginRequiredMixin, View):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
# Get satellites that have points with geo data
|
# Get satellites that have sources with points with geo data
|
||||||
satellites = Satellite.objects.filter(
|
satellites = Satellite.objects.filter(
|
||||||
|
parameters__objitem__source__isnull=False,
|
||||||
parameters__objitem__geo_obj__coords__isnull=False
|
parameters__objitem__geo_obj__coords__isnull=False
|
||||||
).distinct().order_by('name')
|
).distinct().order_by('name')
|
||||||
|
|
||||||
@@ -44,13 +46,14 @@ class PointsAveragingView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
class PointsAveragingAPIView(LoginRequiredMixin, View):
|
class PointsAveragingAPIView(LoginRequiredMixin, View):
|
||||||
"""
|
"""
|
||||||
API endpoint for grouping and averaging points by day/night intervals.
|
API endpoint for grouping and averaging points by Source and day/night intervals.
|
||||||
|
|
||||||
Groups points into:
|
Groups points into:
|
||||||
- Day: 08:00 - 19:00
|
- Day: 08:00 - 19:00
|
||||||
- Night: 19:00 - 08:00 (next day)
|
- Night: 19:00 - 08:00 (next day)
|
||||||
|
- Weekend: Friday 19:00 - Monday 08:00
|
||||||
|
|
||||||
For each group, calculates average coordinates and checks for outliers (>56 km).
|
For each group within each Source, calculates average coordinates and checks for outliers (>56 km).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
@@ -76,9 +79,50 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return JsonResponse({'error': 'Неверный формат даты'}, status=400)
|
return JsonResponse({'error': 'Неверный формат даты'}, status=400)
|
||||||
|
|
||||||
# Get all points for the satellite in the date range
|
# Get all Sources for the satellite that have points in the date range
|
||||||
objitems = ObjItem.objects.filter(
|
sources = Source.objects.filter(
|
||||||
parameter_obj__id_satellite=satellite,
|
source_objitems__parameter_obj__id_satellite=satellite,
|
||||||
|
source_objitems__geo_obj__coords__isnull=False,
|
||||||
|
source_objitems__geo_obj__timestamp__gte=date_from_obj,
|
||||||
|
source_objitems__geo_obj__timestamp__lt=date_to_obj,
|
||||||
|
).distinct().prefetch_related(
|
||||||
|
'source_objitems',
|
||||||
|
'source_objitems__geo_obj',
|
||||||
|
'source_objitems__geo_obj__mirrors',
|
||||||
|
'source_objitems__parameter_obj',
|
||||||
|
'source_objitems__parameter_obj__polarization',
|
||||||
|
'source_objitems__parameter_obj__modulation',
|
||||||
|
'source_objitems__parameter_obj__standard',
|
||||||
|
)
|
||||||
|
|
||||||
|
if not sources.exists():
|
||||||
|
return JsonResponse({'error': 'Источники не найдены в указанном диапазоне'}, status=404)
|
||||||
|
|
||||||
|
# Process each source
|
||||||
|
result_sources = []
|
||||||
|
for source in sources:
|
||||||
|
source_data = self._process_source(source, date_from_obj, date_to_obj)
|
||||||
|
if source_data['groups']: # Only add if has groups with points
|
||||||
|
result_sources.append(source_data)
|
||||||
|
|
||||||
|
if not result_sources:
|
||||||
|
return JsonResponse({'error': 'Точки не найдены в указанном диапазоне'}, status=404)
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'success': True,
|
||||||
|
'satellite': satellite.name,
|
||||||
|
'date_from': date_from,
|
||||||
|
'date_to': date_to,
|
||||||
|
'sources': result_sources,
|
||||||
|
'total_sources': len(result_sources),
|
||||||
|
})
|
||||||
|
|
||||||
|
def _process_source(self, source, date_from_obj, date_to_obj):
|
||||||
|
"""
|
||||||
|
Process a single Source: get its points and group them by time intervals.
|
||||||
|
"""
|
||||||
|
# Get all points for this source in the date range
|
||||||
|
objitems = source.source_objitems.filter(
|
||||||
geo_obj__coords__isnull=False,
|
geo_obj__coords__isnull=False,
|
||||||
geo_obj__timestamp__gte=date_from_obj,
|
geo_obj__timestamp__gte=date_from_obj,
|
||||||
geo_obj__timestamp__lt=date_to_obj,
|
geo_obj__timestamp__lt=date_to_obj,
|
||||||
@@ -89,16 +133,12 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
'parameter_obj__modulation',
|
'parameter_obj__modulation',
|
||||||
'parameter_obj__standard',
|
'parameter_obj__standard',
|
||||||
'geo_obj',
|
'geo_obj',
|
||||||
'source',
|
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'geo_obj__mirrors'
|
'geo_obj__mirrors'
|
||||||
).order_by('geo_obj__timestamp')
|
).order_by('geo_obj__timestamp')
|
||||||
|
|
||||||
if not objitems.exists():
|
# Group points by day/night intervals
|
||||||
return JsonResponse({'error': 'Точки не найдены в указанном диапазоне'}, status=404)
|
groups = self._group_points_by_intervals(list(objitems))
|
||||||
|
|
||||||
# Group points by source name and day/night intervals
|
|
||||||
groups = self._group_points_by_intervals(objitems)
|
|
||||||
|
|
||||||
# Process each group: calculate average and check for outliers
|
# Process each group: calculate average and check for outliers
|
||||||
result_groups = []
|
result_groups = []
|
||||||
@@ -106,21 +146,27 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
group_result = self._process_group(group_key, points)
|
group_result = self._process_group(group_key, points)
|
||||||
result_groups.append(group_result)
|
result_groups.append(group_result)
|
||||||
|
|
||||||
return JsonResponse({
|
# Get source name from first point or use ID
|
||||||
'success': True,
|
source_name = f"Источник #{source.id}"
|
||||||
'satellite': satellite.name,
|
if objitems.exists():
|
||||||
'date_from': date_from,
|
first_point = objitems.first()
|
||||||
'date_to': date_to,
|
if first_point.name:
|
||||||
|
source_name = first_point.name
|
||||||
|
|
||||||
|
return {
|
||||||
|
'source_id': source.id,
|
||||||
|
'source_name': source_name,
|
||||||
|
'total_points': sum(len(g['points']) for g in result_groups),
|
||||||
'groups': result_groups,
|
'groups': result_groups,
|
||||||
'total_groups': len(result_groups),
|
}
|
||||||
})
|
|
||||||
|
|
||||||
def _group_points_by_intervals(self, objitems):
|
def _group_points_by_intervals(self, objitems):
|
||||||
"""
|
"""
|
||||||
Group points by source name and day/night intervals.
|
Group points by day/night intervals.
|
||||||
|
|
||||||
Day: 08:00 - 19:00
|
Day: 08:00 - 19:00
|
||||||
Night: 19:00 - 08:00 (next day)
|
Night: 19:00 - 08:00 (next day)
|
||||||
|
Weekend: Friday 19:00 - Monday 08:00
|
||||||
"""
|
"""
|
||||||
groups = {}
|
groups = {}
|
||||||
|
|
||||||
@@ -129,19 +175,14 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
timestamp = timezone.localtime(objitem.geo_obj.timestamp)
|
timestamp = timezone.localtime(objitem.geo_obj.timestamp)
|
||||||
# timestamp = objitem.geo_obj.timestamp
|
|
||||||
source_name = objitem.name or f"Объект #{objitem.id}"
|
|
||||||
|
|
||||||
# Determine interval
|
# Determine interval
|
||||||
interval_key = self._get_interval_key(timestamp)
|
interval_key = self._get_interval_key(timestamp)
|
||||||
|
|
||||||
# Create group key: (source_name, interval_key)
|
if interval_key not in groups:
|
||||||
group_key = (source_name, interval_key)
|
groups[interval_key] = []
|
||||||
|
|
||||||
if group_key not in groups:
|
groups[interval_key].append(objitem)
|
||||||
groups[group_key] = []
|
|
||||||
|
|
||||||
groups[group_key].append(objitem)
|
|
||||||
|
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
@@ -208,7 +249,7 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
return date - timedelta(days=3)
|
return date - timedelta(days=3)
|
||||||
return date
|
return date
|
||||||
|
|
||||||
def _process_group(self, group_key, points):
|
def _process_group(self, interval_key, points):
|
||||||
"""
|
"""
|
||||||
Process a group of points: calculate average and check for outliers.
|
Process a group of points: calculate average and check for outliers.
|
||||||
|
|
||||||
@@ -218,8 +259,6 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
3. Iteratively add points within 56 km of current average
|
3. Iteratively add points within 56 km of current average
|
||||||
4. Points not within 56 km of final average are outliers
|
4. Points not within 56 km of final average are outliers
|
||||||
"""
|
"""
|
||||||
source_name, interval_key = group_key
|
|
||||||
|
|
||||||
# Parse interval info
|
# Parse interval info
|
||||||
date_str, interval_type = interval_key.rsplit('_', 1)
|
date_str, interval_type = interval_key.rsplit('_', 1)
|
||||||
interval_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
interval_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||||
@@ -278,7 +317,7 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Apply clustering algorithm
|
# Apply clustering algorithm
|
||||||
avg_coord, valid_indices = self._find_cluster_center(points_data)
|
avg_coord, valid_indices, avg_type = self._find_cluster_center(points_data)
|
||||||
|
|
||||||
# Mark outliers and calculate distances
|
# Mark outliers and calculate distances
|
||||||
outliers = []
|
outliers = []
|
||||||
@@ -322,7 +361,7 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
# Calculate median time from valid points using timestamp_objects array
|
# Calculate median time from valid points using timestamp_objects array
|
||||||
valid_timestamps = []
|
valid_timestamps = []
|
||||||
for i in valid_indices:
|
for i in valid_indices:
|
||||||
if timestamp_objects[i]:
|
if i < len(timestamp_objects) and timestamp_objects[i]:
|
||||||
valid_timestamps.append(timestamp_objects[i])
|
valid_timestamps.append(timestamp_objects[i])
|
||||||
|
|
||||||
median_time_str = '-'
|
median_time_str = '-'
|
||||||
@@ -344,7 +383,6 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
median_time_str = timezone.localtime(median_datetime).strftime("%d.%m.%Y %H:%M")
|
median_time_str = timezone.localtime(median_datetime).strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'source_name': source_name,
|
|
||||||
'interval_key': interval_key,
|
'interval_key': interval_key,
|
||||||
'interval_label': interval_label,
|
'interval_label': interval_label,
|
||||||
'total_points': len(points_data),
|
'total_points': len(points_data),
|
||||||
@@ -353,6 +391,7 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
'has_outliers': len(outliers) > 0,
|
'has_outliers': len(outliers) > 0,
|
||||||
'avg_coordinates': avg_coords_str,
|
'avg_coordinates': avg_coords_str,
|
||||||
'avg_coord_tuple': avg_coord,
|
'avg_coord_tuple': avg_coord,
|
||||||
|
'avg_type': avg_type,
|
||||||
'avg_time': median_time_str,
|
'avg_time': median_time_str,
|
||||||
'frequency': first_point.get('frequency', '-'),
|
'frequency': first_point.get('frequency', '-'),
|
||||||
'freq_range': first_point.get('freq_range', '-'),
|
'freq_range': first_point.get('freq_range', '-'),
|
||||||
@@ -376,13 +415,13 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
If only 1 point, return it as center.
|
If only 1 point, return it as center.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (avg_coord, set of valid point indices)
|
tuple: (avg_coord, set of valid point indices, avg_type)
|
||||||
"""
|
"""
|
||||||
if len(points_data) == 0:
|
if len(points_data) == 0:
|
||||||
return (0, 0), set()
|
return (0, 0), set(), "ГК"
|
||||||
|
|
||||||
if len(points_data) == 1:
|
if len(points_data) == 1:
|
||||||
return points_data[0]['coord_tuple'], {0}
|
return points_data[0]['coord_tuple'], {0}, "ГК"
|
||||||
|
|
||||||
# Step 1: Take first point as reference
|
# Step 1: Take first point as reference
|
||||||
first_coord = points_data[0]['coord_tuple']
|
first_coord = points_data[0]['coord_tuple']
|
||||||
@@ -397,35 +436,32 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
|
|||||||
valid_indices.add(i)
|
valid_indices.add(i)
|
||||||
|
|
||||||
# Step 3: Calculate average of all valid points using Gauss-Kruger projection
|
# Step 3: Calculate average of all valid points using Gauss-Kruger projection
|
||||||
avg_coord = self._calculate_average_from_indices(points_data, valid_indices)
|
avg_coord, avg_type = self._calculate_average_from_indices(points_data, valid_indices)
|
||||||
|
|
||||||
return avg_coord, valid_indices
|
return avg_coord, valid_indices, avg_type
|
||||||
|
|
||||||
def _calculate_average_from_indices(self, points_data, indices):
|
def _calculate_average_from_indices(self, points_data, indices):
|
||||||
"""
|
"""
|
||||||
Calculate average coordinate from points at given indices.
|
Calculate average coordinate from points at given indices.
|
||||||
Uses arithmetic averaging in Gauss-Kruger projection.
|
Uses arithmetic averaging in Gauss-Kruger or UTM projection.
|
||||||
|
|
||||||
Algorithm:
|
Returns:
|
||||||
1. Determine GK zone from the first point
|
tuple: (avg_coord, avg_type) where avg_type is "ГК", "UTM" or "Геод"
|
||||||
2. Transform all coordinates to GK projection
|
|
||||||
3. Calculate arithmetic mean of X and Y
|
|
||||||
4. Transform result back to WGS84
|
|
||||||
"""
|
"""
|
||||||
indices_list = sorted(indices)
|
indices_list = sorted(indices)
|
||||||
if not indices_list:
|
if not indices_list:
|
||||||
return (0, 0)
|
return (0, 0), "ГК"
|
||||||
|
|
||||||
if len(indices_list) == 1:
|
if len(indices_list) == 1:
|
||||||
return points_data[indices_list[0]]['coord_tuple']
|
return points_data[indices_list[0]]['coord_tuple'], "ГК"
|
||||||
|
|
||||||
# Collect coordinates for averaging
|
# Collect coordinates for averaging
|
||||||
coords = [points_data[idx]['coord_tuple'] for idx in indices_list]
|
coords = [points_data[idx]['coord_tuple'] for idx in indices_list]
|
||||||
|
|
||||||
# Use Gauss-Kruger projection for averaging
|
# Use Gauss-Kruger/UTM projection for averaging
|
||||||
avg_coord = average_coords_in_gk(coords)
|
avg_coord, avg_type = average_coords_in_gk(coords)
|
||||||
|
|
||||||
return avg_coord
|
return avg_coord, avg_type
|
||||||
|
|
||||||
|
|
||||||
class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
||||||
@@ -451,7 +487,7 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
|||||||
# If include_all is False, use only non-outlier points and apply clustering
|
# If include_all is False, use only non-outlier points and apply clustering
|
||||||
if include_all:
|
if include_all:
|
||||||
# Average all points - no outliers, all points are valid
|
# Average all points - no outliers, all points are valid
|
||||||
avg_coord = self._calculate_average_from_indices(points, set(range(len(points))))
|
avg_coord, avg_type = self._calculate_average_from_indices(points, set(range(len(points))))
|
||||||
valid_indices = set(range(len(points)))
|
valid_indices = set(range(len(points)))
|
||||||
else:
|
else:
|
||||||
# Filter out outliers first
|
# Filter out outliers first
|
||||||
@@ -461,7 +497,7 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
|||||||
return JsonResponse({'error': 'No valid points after filtering'}, status=400)
|
return JsonResponse({'error': 'No valid points after filtering'}, status=400)
|
||||||
|
|
||||||
# Apply clustering algorithm
|
# Apply clustering algorithm
|
||||||
avg_coord, valid_indices = self._find_cluster_center(points)
|
avg_coord, valid_indices, avg_type = self._find_cluster_center(points)
|
||||||
|
|
||||||
# Mark outliers and calculate distances
|
# Mark outliers and calculate distances
|
||||||
for i, point in enumerate(points):
|
for i, point in enumerate(points):
|
||||||
@@ -522,6 +558,7 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
|||||||
'success': True,
|
'success': True,
|
||||||
'avg_coordinates': avg_coords_str,
|
'avg_coordinates': avg_coords_str,
|
||||||
'avg_coord_tuple': avg_coord,
|
'avg_coord_tuple': avg_coord,
|
||||||
|
'avg_type': avg_type,
|
||||||
'total_points': len(points),
|
'total_points': len(points),
|
||||||
'valid_points_count': len(valid_points),
|
'valid_points_count': len(valid_points),
|
||||||
'outliers_count': len(outliers),
|
'outliers_count': len(outliers),
|
||||||
@@ -537,13 +574,13 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
|||||||
1. Take the first point as reference
|
1. Take the first point as reference
|
||||||
2. Find all points within 56 km of the first point
|
2. Find all points within 56 km of the first point
|
||||||
3. Calculate average of all found points using Gauss-Kruger projection
|
3. Calculate average of all found points using Gauss-Kruger projection
|
||||||
4. Return final average and indices of valid points
|
4. Return final average, indices of valid points, and averaging type
|
||||||
"""
|
"""
|
||||||
if len(points) == 0:
|
if len(points) == 0:
|
||||||
return (0, 0), set()
|
return (0, 0), set(), "ГК"
|
||||||
|
|
||||||
if len(points) == 1:
|
if len(points) == 1:
|
||||||
return tuple(points[0]['coord_tuple']), {0}
|
return tuple(points[0]['coord_tuple']), {0}, "ГК"
|
||||||
|
|
||||||
# Step 1: Take first point as reference
|
# Step 1: Take first point as reference
|
||||||
first_coord = tuple(points[0]['coord_tuple'])
|
first_coord = tuple(points[0]['coord_tuple'])
|
||||||
@@ -557,27 +594,30 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
|
|||||||
if distance <= RANGE_DISTANCE:
|
if distance <= RANGE_DISTANCE:
|
||||||
valid_indices.add(i)
|
valid_indices.add(i)
|
||||||
|
|
||||||
# Step 3: Calculate average of all valid points using Gauss-Kruger projection
|
# Step 3: Calculate average of all valid points
|
||||||
avg_coord = self._calculate_average_from_indices(points, valid_indices)
|
avg_coord, avg_type = self._calculate_average_from_indices(points, valid_indices)
|
||||||
|
|
||||||
return avg_coord, valid_indices
|
return avg_coord, valid_indices, avg_type
|
||||||
|
|
||||||
def _calculate_average_from_indices(self, points, indices):
|
def _calculate_average_from_indices(self, points, indices):
|
||||||
"""
|
"""
|
||||||
Calculate average coordinate from points at given indices.
|
Calculate average coordinate from points at given indices.
|
||||||
Uses arithmetic averaging in Gauss-Kruger projection.
|
Uses arithmetic averaging in Gauss-Kruger or UTM projection.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (avg_coord, avg_type)
|
||||||
"""
|
"""
|
||||||
indices_list = sorted(indices)
|
indices_list = sorted(indices)
|
||||||
if not indices_list:
|
if not indices_list:
|
||||||
return (0, 0)
|
return (0, 0), "ГК"
|
||||||
|
|
||||||
if len(indices_list) == 1:
|
if len(indices_list) == 1:
|
||||||
return tuple(points[indices_list[0]]['coord_tuple'])
|
return tuple(points[indices_list[0]]['coord_tuple']), "ГК"
|
||||||
|
|
||||||
# Collect coordinates for averaging
|
# Collect coordinates for averaging
|
||||||
coords = [tuple(points[idx]['coord_tuple']) for idx in indices_list]
|
coords = [tuple(points[idx]['coord_tuple']) for idx in indices_list]
|
||||||
|
|
||||||
# Use Gauss-Kruger projection for averaging
|
# Use Gauss-Kruger/UTM projection for averaging
|
||||||
avg_coord = average_coords_in_gk(coords)
|
avg_coord, avg_type = average_coords_in_gk(coords)
|
||||||
|
|
||||||
return avg_coord
|
return avg_coord, avg_type
|
||||||
|
|||||||
Reference in New Issue
Block a user