- Нет данных. Выберите спутник и диапазон дат, затем нажмите 'Загрузить данные'.
+
+
+
+
+
+
+
+
+
+
Детали источника
+
+
+
+
+
+
-
-
-
-
-
-
Сводная таблица усреднённых групп 0
-
@@ -235,17 +200,10 @@
{% endblock %}
diff --git a/dbapp/mainapp/utils.py b/dbapp/mainapp/utils.py
index 29ff99d..bf59c67 100644
--- a/dbapp/mainapp/utils.py
+++ b/dbapp/mainapp/utils.py
@@ -1413,27 +1413,28 @@ def kub_report(data_in: io.StringIO) -> pd.DataFrame:
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° долготы.
- Центральный меридиан зоны N: (6*N - 3)°
+ Зоны ГК (Пулково 1942) имеют EPSG коды 28404-28432 (зоны 4-32).
+ Каждая зона охватывает 6° долготы.
Args:
longitude: Долгота в градусах (от -180 до 180)
Returns:
- int: Номер зоны ГК (1-60)
+ int | None: Номер зоны ГК (4-32) или None если координаты вне зон ГК
"""
# Нормализуем долготу к диапазону 0-360
lon_normalized = longitude if longitude >= 0 else longitude + 360
# Вычисляем номер зоны (1-60)
zone = int((lon_normalized + 6) / 6)
- if zone > 60:
- zone = 60
- if zone < 1:
- zone = 1
+
+ # EPSG коды Пулково 1942 существуют только для зон 4-32
+ if zone < 4 or zone > 32:
+ return None
+
return zone
@@ -1441,14 +1442,8 @@ def get_gauss_kruger_epsg(zone: int) -> int:
"""
Возвращает EPSG код для зоны Гаусса-Крюгера (Pulkovo 1942 / Gauss-Kruger).
- EPSG коды для Pulkovo 1942 GK зон:
- - Зона 4: EPSG:28404
- - Зона 5: EPSG:28405
- - ...
- - Зона N: EPSG:28400 + N
-
Args:
- zone: Номер зоны ГК (1-60)
+ zone: Номер зоны ГК (4-32)
Returns:
int: EPSG код проекции
@@ -1456,13 +1451,50 @@ def get_gauss_kruger_epsg(zone: int) -> int:
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:
"""
- Преобразует координаты из WGS84 (EPSG:4326) в проекцию Гаусса-Крюгера.
+ Преобразует координаты из WGS84 в проекцию Гаусса-Крюгера.
Args:
coord: Координаты в формате (longitude, latitude) в WGS84
- zone: Номер зоны ГК (если None, определяется автоматически по долготе)
+ zone: Номер зоны ГК (если None, определяется автоматически)
Returns:
tuple: Координаты (x, y) в метрах в проекции ГК
@@ -1472,9 +1504,11 @@ def transform_wgs84_to_gk(coord: tuple, zone: int = None) -> tuple:
if zone is None:
zone = get_gauss_kruger_zone(lon)
+ if zone is None:
+ raise ValueError(f"Координаты ({lon}, {lat}) вне зон Гаусса-Крюгера (4-32)")
+
epsg_gk = get_gauss_kruger_epsg(zone)
- # Создаём трансформер WGS84 -> GK
transformer = Transformer.from_crs(
CRS.from_epsg(4326),
CRS.from_epsg(epsg_gk),
@@ -1487,7 +1521,7 @@ def transform_wgs84_to_gk(coord: tuple, zone: int = None) -> tuple:
def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
"""
- Преобразует координаты из проекции Гаусса-Крюгера в WGS84 (EPSG:4326).
+ Преобразует координаты из проекции Гаусса-Крюгера в WGS84.
Args:
coord: Координаты (x, y) в метрах в проекции ГК
@@ -1499,7 +1533,6 @@ def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
x, y = coord
epsg_gk = get_gauss_kruger_epsg(zone)
- # Создаём трансформер GK -> WGS84
transformer = Transformer.from_crs(
CRS.from_epsg(epsg_gk),
CRS.from_epsg(4326),
@@ -1510,37 +1543,126 @@ def transform_gk_to_wgs84(coord: tuple, zone: int) -> tuple:
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:
- coord1_gk: Первая точка (x, y) в метрах
- coord2_gk: Вторая точка (x, y) в метрах
+ coord: Координаты в формате (longitude, latitude) в WGS84
+ zone: Номер зоны UTM (если None, определяется автоматически)
+ is_northern: Северное полушарие (если None, определяется по широте)
Returns:
- float: Расстояние в километрах
+ tuple: Координаты (x, y) в метрах в проекции UTM
"""
- import math
- x1, y1 = coord1_gk
- x2, y2 = coord2_gk
- distance_m = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
- return distance_m / 1000
-
-
-def average_coords_in_gk(coords: list[tuple], zone: int = None) -> tuple:
- """
- Вычисляет среднее арифметическое координат в проекции Гаусса-Крюгера.
+ lon, lat = coord
- Алгоритм:
- 1. Определяет зону ГК по первой точке (если не указана)
- 2. Преобразует все координаты в проекцию ГК
- 3. Вычисляет среднее арифметическое X и Y
- 4. Преобразует результат обратно в WGS84
+ if zone is None:
+ zone = get_utm_zone(lon)
+
+ 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 transform_utm_to_wgs84(coord: tuple, zone: int, is_northern: bool = True) -> tuple:
+ """
+ Преобразует координаты из проекции UTM в WGS84.
+
+ Args:
+ coord: Координаты (x, y) в метрах в проекции UTM
+ zone: Номер зоны UTM
+ is_northern: Северное полушарие
+
+ 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:
coords: Список координат в формате [(lon1, lat1), (lon2, lat2), ...]
- zone: Номер зоны ГК (если None, определяется по первой точке)
Returns:
tuple: Средние координаты (longitude, latitude) в WGS84
@@ -1551,19 +1673,12 @@ def average_coords_in_gk(coords: list[tuple], zone: int = None) -> tuple:
if len(coords) == 1:
return coords[0]
- # Определяем зону по первой точке
- if zone is None:
- zone = get_gauss_kruger_zone(coords[0][0])
+ # Последовательно усредняем точки
+ result = coords[0]
+ for i in range(1, len(coords)):
+ result, _ = calculate_mean_coords(result, coords[i])
- # Преобразуем все координаты в ГК
- 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)
+ return result
def calculate_mean_coords(coord1: tuple, coord2: tuple) -> tuple[tuple, float]:
diff --git a/dbapp/mainapp/views/points_averaging.py b/dbapp/mainapp/views/points_averaging.py
index 9363251..f0510bb 100644
--- a/dbapp/mainapp/views/points_averaging.py
+++ b/dbapp/mainapp/views/points_averaging.py
@@ -317,7 +317,7 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
})
# 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
outliers = []
@@ -391,6 +391,7 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
'has_outliers': len(outliers) > 0,
'avg_coordinates': avg_coords_str,
'avg_coord_tuple': avg_coord,
+ 'avg_type': avg_type,
'avg_time': median_time_str,
'frequency': first_point.get('frequency', '-'),
'freq_range': first_point.get('freq_range', '-'),
@@ -414,13 +415,13 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
If only 1 point, return it as center.
Returns:
- tuple: (avg_coord, set of valid point indices)
+ tuple: (avg_coord, set of valid point indices, avg_type)
"""
if len(points_data) == 0:
- return (0, 0), set()
+ return (0, 0), set(), "ГК"
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
first_coord = points_data[0]['coord_tuple']
@@ -435,35 +436,32 @@ class PointsAveragingAPIView(LoginRequiredMixin, View):
valid_indices.add(i)
# 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):
"""
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:
- 1. Determine GK zone from the first point
- 2. Transform all coordinates to GK projection
- 3. Calculate arithmetic mean of X and Y
- 4. Transform result back to WGS84
+ Returns:
+ tuple: (avg_coord, avg_type) where avg_type is "ГК", "UTM" or "Геод"
"""
indices_list = sorted(indices)
if not indices_list:
- return (0, 0)
+ return (0, 0), "ГК"
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
coords = [points_data[idx]['coord_tuple'] for idx in indices_list]
- # Use Gauss-Kruger projection for averaging
- avg_coord = average_coords_in_gk(coords)
+ # Use Gauss-Kruger/UTM projection for averaging
+ avg_coord, avg_type = average_coords_in_gk(coords)
- return avg_coord
+ return avg_coord, avg_type
class RecalculateGroupAPIView(LoginRequiredMixin, View):
@@ -489,7 +487,7 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
# If include_all is False, use only non-outlier points and apply clustering
if include_all:
# 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)))
else:
# Filter out outliers first
@@ -499,7 +497,7 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
return JsonResponse({'error': 'No valid points after filtering'}, status=400)
# 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
for i, point in enumerate(points):
@@ -560,6 +558,7 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
'success': True,
'avg_coordinates': avg_coords_str,
'avg_coord_tuple': avg_coord,
+ 'avg_type': avg_type,
'total_points': len(points),
'valid_points_count': len(valid_points),
'outliers_count': len(outliers),
@@ -575,13 +574,13 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
1. Take the first point as reference
2. Find all points within 56 km of the first point
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:
- return (0, 0), set()
+ return (0, 0), set(), "ГК"
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
first_coord = tuple(points[0]['coord_tuple'])
@@ -595,27 +594,30 @@ class RecalculateGroupAPIView(LoginRequiredMixin, View):
if distance <= RANGE_DISTANCE:
valid_indices.add(i)
- # Step 3: Calculate average of all valid points using Gauss-Kruger projection
- avg_coord = self._calculate_average_from_indices(points, valid_indices)
+ # Step 3: Calculate average of all valid points
+ 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):
"""
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)
if not indices_list:
- return (0, 0)
+ return (0, 0), "ГК"
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
coords = [tuple(points[idx]['coord_tuple']) for idx in indices_list]
- # Use Gauss-Kruger projection for averaging
- avg_coord = average_coords_in_gk(coords)
+ # Use Gauss-Kruger/UTM projection for averaging
+ avg_coord, avg_type = average_coords_in_gk(coords)
- return avg_coord
+ return avg_coord, avg_type