Добавил разделение по исчтоника и поправил функцию импорта из Excel csv
This commit is contained in:
@@ -24,6 +24,7 @@ from .models import (
|
||||
Polarization,
|
||||
Satellite,
|
||||
SigmaParameter,
|
||||
Source,
|
||||
Standard,
|
||||
)
|
||||
|
||||
@@ -78,139 +79,233 @@ def remove_str(s: str):
|
||||
|
||||
|
||||
def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
"""
|
||||
Импортирует данные из DataFrame с группировкой близких координат.
|
||||
|
||||
Алгоритм:
|
||||
1. Извлечь все координаты и данные строк из DataFrame
|
||||
2. Создать список необработанных записей (координата + данные строки)
|
||||
3. Пока список не пуст:
|
||||
a. Взять первую запись из списка
|
||||
b. Создать новый Source с coords_average = эта координата
|
||||
c. Создать ObjItem для этой записи и связать с Source
|
||||
d. Удалить запись из списка
|
||||
e. Для каждой оставшейся записи в списке:
|
||||
- Вычислить расстояние от её координаты до coords_average
|
||||
- Если расстояние <= 0.5 градуса:
|
||||
* Вычислить новое среднее ИНКРЕМЕНТАЛЬНО:
|
||||
new_avg = (coords_average + current_coord) / 2
|
||||
* Обновить coords_average в Source
|
||||
* Создать ObjItem для этой записи и связать с Source
|
||||
* Удалить запись из списка
|
||||
- Иначе: пропустить и проверить следующую запись
|
||||
4. Сохранить все изменения в БД
|
||||
|
||||
Важно: Среднее вычисляется инкрементально - каждая новая точка
|
||||
усредняется с текущим средним, а не со всеми точками кластера.
|
||||
|
||||
Args:
|
||||
df: DataFrame с данными
|
||||
sat: объект Satellite
|
||||
current_user: текущий пользователь (optional)
|
||||
|
||||
Returns:
|
||||
int: количество созданных Source
|
||||
"""
|
||||
try:
|
||||
df.rename(columns={"Модуляция ": "Модуляция"}, inplace=True)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
consts = get_all_constants()
|
||||
df.fillna(-1, inplace=True)
|
||||
for stroka in df.iterrows():
|
||||
geo_point = Point(coords_transform(stroka[1]["Координаты"]), srid=4326)
|
||||
valid_point = None
|
||||
kupsat_point = None
|
||||
|
||||
# Шаг 1: Извлечь все координаты и данные строк из DataFrame
|
||||
unprocessed_records = []
|
||||
|
||||
for idx, row in df.iterrows():
|
||||
try:
|
||||
if (
|
||||
stroka[1]["Координаты объекта"] != -1
|
||||
and stroka[1]["Координаты Кубсата"] != "+"
|
||||
):
|
||||
if (
|
||||
"ИРИ" not in stroka[1]["Координаты объекта"]
|
||||
and "БЛА" not in stroka[1]["Координаты объекта"]
|
||||
):
|
||||
valid_point = list(
|
||||
map(
|
||||
float,
|
||||
stroka[1]["Координаты объекта"]
|
||||
.replace(",", ".")
|
||||
.split(". "),
|
||||
)
|
||||
)
|
||||
valid_point = Point(valid_point[1], valid_point[0], srid=4326)
|
||||
if (
|
||||
stroka[1]["Координаты Кубсата"] != -1
|
||||
and stroka[1]["Координаты Кубсата"] != "+"
|
||||
):
|
||||
kupsat_point = list(
|
||||
map(
|
||||
float,
|
||||
stroka[1]["Координаты Кубсата"].replace(",", ".").split(". "),
|
||||
)
|
||||
# Извлекаем координату
|
||||
coord_tuple = coords_transform(row["Координаты"])
|
||||
|
||||
# Сохраняем запись с координатой и данными строки
|
||||
unprocessed_records.append({"coord": coord_tuple, "row": row, "index": idx})
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке строки {idx}: {e}")
|
||||
continue
|
||||
|
||||
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||
source_count = 0
|
||||
|
||||
# Шаг 3: Цикл обработки пока список не пуст
|
||||
while unprocessed_records:
|
||||
# Шаг 3a: Взять первую запись из списка
|
||||
first_record = unprocessed_records.pop(0)
|
||||
first_coord = first_record["coord"]
|
||||
|
||||
# Шаг 3b: Создать новый Source с coords_average = эта координата
|
||||
source = Source.objects.create(
|
||||
coords_average=Point(first_coord, srid=4326), created_by=user_to_use
|
||||
)
|
||||
source_count += 1
|
||||
|
||||
# Шаг 3c: Создать ObjItem для этой записи и связать с Source
|
||||
_create_objitem_from_row(first_record["row"], sat, source, user_to_use, consts)
|
||||
|
||||
# Шаг 3e: Для каждой оставшейся записи в списке
|
||||
records_to_remove = []
|
||||
|
||||
for i, record in enumerate(unprocessed_records):
|
||||
current_coord = record["coord"]
|
||||
|
||||
# Вычислить расстояние от координаты до 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
|
||||
)
|
||||
kupsat_point = Point(kupsat_point[1], kupsat_point[0], srid=4326)
|
||||
except KeyError:
|
||||
print("В таблице нет столбцов с координатами кубсата")
|
||||
try:
|
||||
polarization_obj, _ = Polarization.objects.get_or_create(
|
||||
name=stroka[1]["Поляризация"].strip()
|
||||
)
|
||||
except KeyError:
|
||||
polarization_obj, _ = Polarization.objects.get_or_create(name="-")
|
||||
freq = remove_str(stroka[1]["Частота, МГц"])
|
||||
freq_line = remove_str(stroka[1]["Полоса, МГц"])
|
||||
v = remove_str(stroka[1]["Символьная скорость, БОД"])
|
||||
try:
|
||||
mod_obj, _ = Modulation.objects.get_or_create(
|
||||
name=stroka[1]["Модуляция"].strip()
|
||||
)
|
||||
except AttributeError:
|
||||
mod_obj, _ = Modulation.objects.get_or_create(name="-")
|
||||
snr = remove_str(stroka[1]["ОСШ"])
|
||||
date = stroka[1]["Дата"].date()
|
||||
time_ = stroka[1]["Время"]
|
||||
if isinstance(time_, str):
|
||||
time_ = time_.strip()
|
||||
time_ = time(0, 0, 0)
|
||||
timestamp = datetime.combine(date, time_)
|
||||
current_mirrors = []
|
||||
mirror_1 = stroka[1]["Зеркало 1"].strip().split("\n")
|
||||
mirror_2 = stroka[1]["Зеркало 2"].strip().split("\n")
|
||||
if len(mirror_1) > 1:
|
||||
for mir in mirror_1:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mir.strip())
|
||||
current_mirrors.append(mir.strip())
|
||||
elif mirror_1[0] not in consts[3]:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mirror_1[0].strip())
|
||||
current_mirrors.append(mirror_1[0].strip())
|
||||
if len(mirror_2) > 1:
|
||||
for mir in mirror_2:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mir.strip())
|
||||
current_mirrors.append(mir.strip())
|
||||
elif mirror_2[0] not in consts[3]:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mirror_2[0].strip())
|
||||
current_mirrors.append(mirror_2[0].strip())
|
||||
location = stroka[1]["Местоопределение"].strip()
|
||||
comment = stroka[1]["Комментарий"]
|
||||
source = stroka[1]["Объект наблюдения"]
|
||||
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||
|
||||
geo, _ = Geo.objects.get_or_create(
|
||||
timestamp=timestamp,
|
||||
coords=geo_point,
|
||||
defaults={
|
||||
"coords_kupsat": kupsat_point,
|
||||
"coords_valid": valid_point,
|
||||
"location": location,
|
||||
"comment": comment,
|
||||
"is_average": (comment != -1.0),
|
||||
},
|
||||
)
|
||||
geo.save()
|
||||
geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors))
|
||||
# Обновить coords_average в Source
|
||||
source.coords_average = Point(new_avg, srid=4326)
|
||||
source.save()
|
||||
|
||||
# Check if ObjItem with same geo already exists
|
||||
existing_obj_item = ObjItem.objects.filter(geo_obj=geo).first()
|
||||
if existing_obj_item:
|
||||
# Check if parameter with same values exists for this object
|
||||
if (
|
||||
hasattr(existing_obj_item, 'parameter_obj') and
|
||||
existing_obj_item.parameter_obj and
|
||||
existing_obj_item.parameter_obj.id_satellite == sat and
|
||||
existing_obj_item.parameter_obj.polarization == polarization_obj and
|
||||
existing_obj_item.parameter_obj.frequency == freq and
|
||||
existing_obj_item.parameter_obj.freq_range == freq_line and
|
||||
existing_obj_item.parameter_obj.bod_velocity == v and
|
||||
existing_obj_item.parameter_obj.modulation == mod_obj and
|
||||
existing_obj_item.parameter_obj.snr == snr
|
||||
):
|
||||
# Skip creating duplicate
|
||||
continue
|
||||
|
||||
# Create new ObjItem and Parameter
|
||||
obj_item = ObjItem.objects.create(name=source, created_by=user_to_use)
|
||||
|
||||
vch_load_obj = Parameter.objects.create(
|
||||
id_satellite=sat,
|
||||
polarization=polarization_obj,
|
||||
frequency=freq,
|
||||
freq_range=freq_line,
|
||||
bod_velocity=v,
|
||||
modulation=mod_obj,
|
||||
snr=snr,
|
||||
objitem=obj_item
|
||||
# Создать ObjItem для этой записи и связать с Source
|
||||
_create_objitem_from_row(
|
||||
record["row"], sat, source, user_to_use, consts
|
||||
)
|
||||
|
||||
# Пометить запись для удаления
|
||||
records_to_remove.append(i)
|
||||
|
||||
# Удалить обработанные записи из списка (в обратном порядке, чтобы не сбить индексы)
|
||||
for i in reversed(records_to_remove):
|
||||
unprocessed_records.pop(i)
|
||||
|
||||
return source_count
|
||||
|
||||
|
||||
def _create_objitem_from_row(row, sat, source, user_to_use, consts):
|
||||
"""
|
||||
Вспомогательная функция для создания ObjItem из строки DataFrame.
|
||||
|
||||
Args:
|
||||
row: строка DataFrame
|
||||
sat: объект Satellite
|
||||
source: объект Source для связи
|
||||
user_to_use: пользователь для created_by
|
||||
consts: константы из get_all_constants()
|
||||
"""
|
||||
# Извлекаем координату
|
||||
geo_point = Point(coords_transform(row["Координаты"]), srid=4326)
|
||||
|
||||
# Обработка поляризации
|
||||
try:
|
||||
polarization_obj, _ = Polarization.objects.get_or_create(
|
||||
name=row["Поляризация"].strip()
|
||||
)
|
||||
|
||||
geo.objitem = obj_item
|
||||
geo.save()
|
||||
except KeyError:
|
||||
polarization_obj, _ = Polarization.objects.get_or_create(name="-")
|
||||
|
||||
# Обработка ВЧ параметров
|
||||
freq = remove_str(row["Частота, МГц"])
|
||||
freq_line = remove_str(row["Полоса, МГц"])
|
||||
v = remove_str(row["Символьная скорость, БОД"])
|
||||
|
||||
try:
|
||||
mod_obj, _ = Modulation.objects.get_or_create(name=row["Модуляция"].strip())
|
||||
except AttributeError:
|
||||
mod_obj, _ = Modulation.objects.get_or_create(name="-")
|
||||
|
||||
snr = remove_str(row["ОСШ"])
|
||||
|
||||
# Обработка времени
|
||||
date = row["Дата"].date()
|
||||
time_ = row["Время"]
|
||||
if isinstance(time_, str):
|
||||
time_ = time_.strip()
|
||||
time_ = time(0, 0, 0)
|
||||
timestamp = datetime.combine(date, time_)
|
||||
|
||||
# Обработка зеркал
|
||||
current_mirrors = []
|
||||
mirror_1 = row["Зеркало 1"].strip().split("\n")
|
||||
mirror_2 = row["Зеркало 2"].strip().split("\n")
|
||||
|
||||
if len(mirror_1) > 1:
|
||||
for mir in mirror_1:
|
||||
Mirror.objects.get_or_create(name=mir.strip())
|
||||
current_mirrors.append(mir.strip())
|
||||
elif mirror_1[0] not in consts[3]:
|
||||
Mirror.objects.get_or_create(name=mirror_1[0].strip())
|
||||
current_mirrors.append(mirror_1[0].strip())
|
||||
|
||||
if len(mirror_2) > 1:
|
||||
for mir in mirror_2:
|
||||
Mirror.objects.get_or_create(name=mir.strip())
|
||||
current_mirrors.append(mir.strip())
|
||||
elif mirror_2[0] not in consts[3]:
|
||||
Mirror.objects.get_or_create(name=mirror_2[0].strip())
|
||||
current_mirrors.append(mirror_2[0].strip())
|
||||
|
||||
location = row["Местоопределение"].strip()
|
||||
comment = row["Комментарий"]
|
||||
source_name = row["Объект наблюдения"]
|
||||
|
||||
# Создаем Geo объект (БЕЗ coords_kupsat и coords_valid)
|
||||
geo, _ = Geo.objects.get_or_create(
|
||||
timestamp=timestamp,
|
||||
coords=geo_point,
|
||||
defaults={
|
||||
"location": location,
|
||||
"comment": comment,
|
||||
"is_average": (comment != -1.0),
|
||||
},
|
||||
)
|
||||
geo.save()
|
||||
geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors))
|
||||
|
||||
# Проверяем, существует ли уже ObjItem с таким же geo
|
||||
existing_obj_item = ObjItem.objects.filter(geo_obj=geo).first()
|
||||
if existing_obj_item:
|
||||
# Проверяем, существует ли parameter с такими же значениями
|
||||
if (
|
||||
hasattr(existing_obj_item, "parameter_obj")
|
||||
and existing_obj_item.parameter_obj
|
||||
and existing_obj_item.parameter_obj.id_satellite == sat
|
||||
and existing_obj_item.parameter_obj.polarization == polarization_obj
|
||||
and existing_obj_item.parameter_obj.frequency == freq
|
||||
and existing_obj_item.parameter_obj.freq_range == freq_line
|
||||
and existing_obj_item.parameter_obj.bod_velocity == v
|
||||
and existing_obj_item.parameter_obj.modulation == mod_obj
|
||||
and existing_obj_item.parameter_obj.snr == snr
|
||||
):
|
||||
# Пропускаем создание дубликата
|
||||
return
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source
|
||||
obj_item = ObjItem.objects.create(
|
||||
name=source_name, source=source, created_by=user_to_use
|
||||
)
|
||||
|
||||
# Создаем Parameter
|
||||
Parameter.objects.create(
|
||||
id_satellite=sat,
|
||||
polarization=polarization_obj,
|
||||
frequency=freq,
|
||||
freq_range=freq_line,
|
||||
bod_velocity=v,
|
||||
modulation=mod_obj,
|
||||
snr=snr,
|
||||
objitem=obj_item,
|
||||
)
|
||||
|
||||
# Связываем geo с objitem
|
||||
geo.objitem = obj_item
|
||||
geo.save()
|
||||
|
||||
|
||||
def add_satellite_list():
|
||||
@@ -281,6 +376,33 @@ def get_point_from_json(filepath: str):
|
||||
|
||||
|
||||
def get_points_from_csv(file_content, current_user=None):
|
||||
"""
|
||||
Импортирует данные из CSV с группировкой близких координат.
|
||||
|
||||
Улучшенный алгоритм с учетом существующих Source:
|
||||
1. Извлечь все координаты и данные строк из DataFrame
|
||||
2. Создать список необработанных записей (координата + данные строки)
|
||||
3. Получить все существующие Source из БД
|
||||
4. Для каждой записи:
|
||||
a. Проверить, существует ли дубликат (координаты + частота)
|
||||
b. Если дубликат найден, пропустить запись
|
||||
c. Найти ближайший существующий Source (расстояние <= 0.5 градуса)
|
||||
d. Если найден:
|
||||
- Обновить coords_average этого Source (инкрементально)
|
||||
- Создать ObjItem и связать с этим Source
|
||||
e. Если не найден:
|
||||
- Создать новый Source
|
||||
- Создать ObjItem и связать с новым Source
|
||||
- Добавить новый Source в список существующих
|
||||
5. Сохранить все изменения в БД
|
||||
|
||||
Args:
|
||||
file_content: содержимое CSV файла
|
||||
current_user: текущий пользователь (optional)
|
||||
|
||||
Returns:
|
||||
int: количество созданных Source
|
||||
"""
|
||||
df = pd.read_csv(
|
||||
io.StringIO(file_content),
|
||||
sep=";",
|
||||
@@ -308,68 +430,196 @@ def get_points_from_csv(file_content, current_user=None):
|
||||
.astype(float)
|
||||
)
|
||||
df["time"] = pd.to_datetime(df["time"], format="%d.%m.%Y %H:%M:%S")
|
||||
for row in df.iterrows():
|
||||
row = row[1]
|
||||
match row["obj"].split(" ")[-1]:
|
||||
case "V":
|
||||
pol = "Вертикальная"
|
||||
case "H":
|
||||
pol = "Горизонтальная"
|
||||
case "R":
|
||||
pol = "Правая"
|
||||
case "L":
|
||||
pol = "Левая"
|
||||
case _:
|
||||
pol = "-"
|
||||
pol_obj, _ = Polarization.objects.get_or_create(name=pol)
|
||||
sat_obj, _ = Satellite.objects.get_or_create(
|
||||
name=row["sat"], defaults={"norad": row["norad_id"]}
|
||||
)
|
||||
mir_1_obj, _ = Mirror.objects.get_or_create(name=row["mir_1"])
|
||||
mir_2_obj, _ = Mirror.objects.get_or_create(name=row["mir_2"])
|
||||
mir_lst = [row["mir_1"], row["mir_2"]]
|
||||
if not pd.isna(row["mir_3"]):
|
||||
mir_3_obj, _ = Mirror.objects.get_or_create(name=row["mir_3"])
|
||||
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||
|
||||
geo_obj, _ = Geo.objects.get_or_create(
|
||||
timestamp=row["time"],
|
||||
coords=Point(row["lon"], row["lat"], srid=4326),
|
||||
defaults={
|
||||
"is_average": False,
|
||||
# 'id_user_add': user_to_use,
|
||||
},
|
||||
)
|
||||
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
||||
# Шаг 1: Извлечь все координаты и данные строк из DataFrame
|
||||
records = []
|
||||
|
||||
# Check if ObjItem with same geo already exists
|
||||
existing_obj_item = ObjItem.objects.filter(geo_obj=geo_obj).first()
|
||||
if existing_obj_item:
|
||||
# Check if parameter with same values exists for this object
|
||||
if (
|
||||
hasattr(existing_obj_item, 'parameter_obj') and
|
||||
existing_obj_item.parameter_obj and
|
||||
existing_obj_item.parameter_obj.id_satellite == sat_obj and
|
||||
existing_obj_item.parameter_obj.polarization == pol_obj and
|
||||
existing_obj_item.parameter_obj.frequency == row["freq"] and
|
||||
existing_obj_item.parameter_obj.freq_range == row["f_range"]
|
||||
):
|
||||
# Skip creating duplicate
|
||||
continue
|
||||
for idx, row in df.iterrows():
|
||||
try:
|
||||
# Извлекаем координату из колонок lat и lon
|
||||
coord_tuple = (row["lon"], row["lat"])
|
||||
|
||||
# Сохраняем запись с координатой и данными строки
|
||||
records.append({"coord": coord_tuple, "row": row, "index": idx})
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке строки {idx}: {e}")
|
||||
continue
|
||||
|
||||
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||
|
||||
# Шаг 3: Получить все существующие Source из БД
|
||||
existing_sources = list(Source.objects.filter(coords_average__isnull=False))
|
||||
new_sources_count = 0
|
||||
added_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
# Шаг 4: Обработка каждой записи
|
||||
for record in records:
|
||||
current_coord = record["coord"]
|
||||
row = record["row"]
|
||||
|
||||
# Create new ObjItem and Parameter
|
||||
obj_item = ObjItem.objects.create(name=row["obj"], created_by=user_to_use)
|
||||
# Шаг 4a: Проверить, существует ли дубликат (координаты + частота)
|
||||
if _is_duplicate_objitem(current_coord, row["freq"], row["f_range"]):
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
vch_load_obj = Parameter.objects.create(
|
||||
id_satellite=sat_obj,
|
||||
polarization=pol_obj,
|
||||
frequency=row["freq"],
|
||||
freq_range=row["f_range"],
|
||||
objitem=obj_item
|
||||
)
|
||||
# Шаг 4c: Найти ближайший существующий Source
|
||||
closest_source = None
|
||||
min_distance = float('inf')
|
||||
|
||||
geo_obj.objitem = obj_item
|
||||
geo_obj.save()
|
||||
for source in existing_sources:
|
||||
source_coord = (source.coords_average.x, source.coords_average.y)
|
||||
distance = calculate_distance_degrees(source_coord, current_coord)
|
||||
|
||||
if distance < min_distance:
|
||||
min_distance = distance
|
||||
closest_source = source
|
||||
|
||||
# Шаг 4d: Если найден близкий Source (расстояние <= 0.5 градуса)
|
||||
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)
|
||||
closest_source.coords_average = Point(new_avg, srid=4326)
|
||||
closest_source.save()
|
||||
|
||||
# Создать ObjItem и связать с существующим Source
|
||||
_create_objitem_from_csv_row(row, closest_source, user_to_use)
|
||||
added_count += 1
|
||||
else:
|
||||
# Шаг 4e: Создать новый Source
|
||||
new_source = Source.objects.create(
|
||||
coords_average=Point(current_coord, srid=4326),
|
||||
created_by=user_to_use
|
||||
)
|
||||
new_sources_count += 1
|
||||
|
||||
# Создать ObjItem и связать с новым Source
|
||||
_create_objitem_from_csv_row(row, new_source, user_to_use)
|
||||
added_count += 1
|
||||
|
||||
# Добавить новый Source в список существующих
|
||||
existing_sources.append(new_source)
|
||||
|
||||
print(f"Импорт завершен: создано {new_sources_count} новых источников, "
|
||||
f"добавлено {added_count} точек, пропущено {skipped_count} дубликатов")
|
||||
|
||||
return new_sources_count
|
||||
|
||||
|
||||
def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.001):
|
||||
"""
|
||||
Проверяет, существует ли уже ObjItem с такими же координатами и частотой.
|
||||
|
||||
Args:
|
||||
coord_tuple: кортеж (lon, lat) координат
|
||||
frequency: частота в МГц
|
||||
freq_range: полоса частот в МГц
|
||||
tolerance: допуск для сравнения координат в градусах (по умолчанию 0.001 ≈ 100м)
|
||||
|
||||
Returns:
|
||||
bool: True если дубликат найден, False иначе
|
||||
"""
|
||||
# Ищем ObjItems с близкими координатами через geo_obj
|
||||
nearby_objitems = ObjItem.objects.filter(
|
||||
geo_obj__coords__isnull=False
|
||||
).select_related('parameter_obj', 'geo_obj')
|
||||
|
||||
for objitem in nearby_objitems:
|
||||
if not objitem.geo_obj or not objitem.geo_obj.coords:
|
||||
continue
|
||||
|
||||
# Проверяем расстояние между координатами
|
||||
geo_coord = (objitem.geo_obj.coords.x, objitem.geo_obj.coords.y)
|
||||
distance = calculate_distance_degrees(coord_tuple, geo_coord)
|
||||
|
||||
if distance <= tolerance:
|
||||
# Координаты совпадают, проверяем частоту
|
||||
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
||||
param = objitem.parameter_obj
|
||||
# Проверяем совпадение частоты с небольшим допуском (0.1 МГц)
|
||||
if (abs(param.frequency - frequency) < 0.1 and
|
||||
abs(param.freq_range - freq_range) < 0.1):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _create_objitem_from_csv_row(row, source, user_to_use):
|
||||
"""
|
||||
Вспомогательная функция для создания ObjItem из строки CSV DataFrame.
|
||||
|
||||
Args:
|
||||
row: строка DataFrame
|
||||
source: объект Source для связи
|
||||
user_to_use: пользователь для created_by
|
||||
"""
|
||||
# Определяем поляризацию
|
||||
match row["obj"].split(" ")[-1]:
|
||||
case "V":
|
||||
pol = "Вертикальная"
|
||||
case "H":
|
||||
pol = "Горизонтальная"
|
||||
case "R":
|
||||
pol = "Правая"
|
||||
case "L":
|
||||
pol = "Левая"
|
||||
case _:
|
||||
pol = "-"
|
||||
|
||||
pol_obj, _ = Polarization.objects.get_or_create(name=pol)
|
||||
sat_obj, _ = Satellite.objects.get_or_create(
|
||||
name=row["sat"], defaults={"norad": row["norad_id"]}
|
||||
)
|
||||
mir_1_obj, _ = Mirror.objects.get_or_create(name=row["mir_1"])
|
||||
mir_2_obj, _ = Mirror.objects.get_or_create(name=row["mir_2"])
|
||||
mir_lst = [row["mir_1"], row["mir_2"]]
|
||||
if not pd.isna(row["mir_3"]):
|
||||
mir_3_obj, _ = Mirror.objects.get_or_create(name=row["mir_3"])
|
||||
mir_lst.append(row["mir_3"])
|
||||
|
||||
# Создаем Geo объект (БЕЗ coords_kupsat и coords_valid)
|
||||
geo_obj, _ = Geo.objects.get_or_create(
|
||||
timestamp=row["time"],
|
||||
coords=Point(row["lon"], row["lat"], srid=4326),
|
||||
defaults={
|
||||
"is_average": False,
|
||||
},
|
||||
)
|
||||
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
||||
|
||||
# Проверяем, существует ли уже ObjItem с таким же geo
|
||||
existing_obj_item = ObjItem.objects.filter(geo_obj=geo_obj).first()
|
||||
if existing_obj_item:
|
||||
# Проверяем, существует ли parameter с такими же значениями
|
||||
if (
|
||||
hasattr(existing_obj_item, "parameter_obj")
|
||||
and existing_obj_item.parameter_obj
|
||||
and existing_obj_item.parameter_obj.id_satellite == sat_obj
|
||||
and existing_obj_item.parameter_obj.polarization == pol_obj
|
||||
and existing_obj_item.parameter_obj.frequency == row["freq"]
|
||||
and existing_obj_item.parameter_obj.freq_range == row["f_range"]
|
||||
):
|
||||
# Пропускаем создание дубликата
|
||||
return
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source
|
||||
obj_item = ObjItem.objects.create(
|
||||
name=row["obj"], source=source, created_by=user_to_use
|
||||
)
|
||||
|
||||
# Создаем Parameter
|
||||
Parameter.objects.create(
|
||||
id_satellite=sat_obj,
|
||||
polarization=pol_obj,
|
||||
frequency=row["freq"],
|
||||
freq_range=row["f_range"],
|
||||
objitem=obj_item,
|
||||
)
|
||||
|
||||
# Связываем geo с objitem
|
||||
geo_obj.objitem = obj_item
|
||||
geo_obj.save()
|
||||
|
||||
|
||||
def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
@@ -450,13 +700,13 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
def get_frequency_tolerance_percent(freq_range_mhz: float) -> float:
|
||||
"""
|
||||
Определяет процент погрешности центральной частоты в зависимости от полосы частот.
|
||||
|
||||
|
||||
Args:
|
||||
freq_range_mhz (float): Полоса частот в МГц
|
||||
|
||||
|
||||
Returns:
|
||||
float: Процент погрешности для центральной частоты
|
||||
|
||||
|
||||
Диапазоны:
|
||||
- 0 - 0.5 МГц (0 - 500 кГц): 0.1%
|
||||
- 0.5 - 1.5 МГц (500 кГц - 1.5 МГц): 0.5%
|
||||
@@ -481,62 +731,66 @@ def compare_and_link_vch_load(
|
||||
):
|
||||
"""
|
||||
Привязывает SigmaParameter к Parameter на основе совпадения параметров.
|
||||
|
||||
|
||||
Погрешность центральной частоты определяется автоматически в зависимости от полосы частот:
|
||||
- 0-500 кГц: 0.1%
|
||||
- 500 кГц-1.5 МГц: 0.5%
|
||||
- 1.5-5 МГц: 1%
|
||||
- 5-10 МГц: 2%
|
||||
- >10 МГц: 5%
|
||||
|
||||
|
||||
Args:
|
||||
sat_id (Satellite): Спутник для фильтрации
|
||||
eps_freq (float): Не используется (оставлен для обратной совместимости)
|
||||
eps_frange (float): Погрешность полосы частот в процентах
|
||||
ku_range (float): Не используется (оставлен для обратной совместимости)
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (количество объектов, количество привязок)
|
||||
"""
|
||||
# Получаем все ObjItem с Parameter для данного спутника
|
||||
item_obj = ObjItem.objects.filter(
|
||||
parameter_obj__id_satellite=sat_id
|
||||
).select_related('parameter_obj', 'parameter_obj__polarization')
|
||||
|
||||
vch_sigma = SigmaParameter.objects.filter(
|
||||
id_satellite=sat_id
|
||||
).select_related('polarization')
|
||||
|
||||
).select_related("parameter_obj", "parameter_obj__polarization")
|
||||
|
||||
vch_sigma = SigmaParameter.objects.filter(id_satellite=sat_id).select_related(
|
||||
"polarization"
|
||||
)
|
||||
|
||||
link_count = 0
|
||||
obj_count = item_obj.count()
|
||||
|
||||
|
||||
for obj in item_obj:
|
||||
vch_load = obj.parameter_obj
|
||||
|
||||
|
||||
# Пропускаем объекты с некорректной частотой
|
||||
if not vch_load or vch_load.frequency == -1.0:
|
||||
continue
|
||||
|
||||
|
||||
# Определяем погрешность частоты на основе полосы
|
||||
freq_tolerance_percent = get_frequency_tolerance_percent(vch_load.freq_range)
|
||||
|
||||
|
||||
# Вычисляем допустимое отклонение частоты в МГц
|
||||
freq_tolerance_mhz = vch_load.frequency * freq_tolerance_percent / 100
|
||||
|
||||
|
||||
# Вычисляем допустимое отклонение полосы в МГц
|
||||
frange_tolerance_mhz = vch_load.freq_range * eps_frange / 100
|
||||
|
||||
|
||||
for sigma in vch_sigma:
|
||||
# Проверяем совпадение по всем параметрам
|
||||
freq_match = abs(sigma.transfer_frequency - vch_load.frequency) <= freq_tolerance_mhz
|
||||
frange_match = abs(sigma.freq_range - vch_load.freq_range) <= frange_tolerance_mhz
|
||||
freq_match = (
|
||||
abs(sigma.transfer_frequency - vch_load.frequency) <= freq_tolerance_mhz
|
||||
)
|
||||
frange_match = (
|
||||
abs(sigma.freq_range - vch_load.freq_range) <= frange_tolerance_mhz
|
||||
)
|
||||
pol_match = sigma.polarization == vch_load.polarization
|
||||
|
||||
|
||||
if freq_match and frange_match and pol_match:
|
||||
sigma.parameter = vch_load
|
||||
sigma.save()
|
||||
link_count += 1
|
||||
|
||||
|
||||
return obj_count, link_count
|
||||
|
||||
|
||||
@@ -614,6 +868,92 @@ def kub_report(data_in: io.StringIO) -> pd.DataFrame:
|
||||
return df
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Утилиты для работы с координатами
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def calculate_distance_degrees(coord1: tuple, coord2: 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
|
||||
"""
|
||||
lon1, lat1 = coord1
|
||||
lon2, lat2 = coord2
|
||||
|
||||
# Простая евклидова метрика для малых расстояний
|
||||
# Для более точных расчетов на больших расстояниях можно использовать формулу гаверсинуса
|
||||
delta_lon = lon2 - lon1
|
||||
delta_lat = lat2 - lat1
|
||||
|
||||
distance = (delta_lon**2 + delta_lat**2) ** 0.5
|
||||
|
||||
return distance
|
||||
|
||||
|
||||
def calculate_average_coords_incremental(
|
||||
current_average: tuple, new_coord: tuple
|
||||
) -> tuple:
|
||||
"""
|
||||
Вычисляет новое среднее между текущим средним и новой координатой.
|
||||
|
||||
Использует инкрементальное усреднение: каждая новая точка усредняется
|
||||
с текущим средним, а не со всеми точками кластера. Это упрощенный подход,
|
||||
где новое среднее = (текущее_среднее + новая_координата) / 2.
|
||||
|
||||
Важно: Это НЕ среднее арифметическое всех точек кластера, а инкрементальное
|
||||
усреднение между двумя точками (текущим средним и новой точкой).
|
||||
|
||||
Args:
|
||||
current_average (tuple): Текущее среднее в формате (longitude, latitude)
|
||||
new_coord (tuple): Новая координата в формате (longitude, latitude)
|
||||
|
||||
Returns:
|
||||
tuple: Новое среднее в формате (longitude, latitude)
|
||||
|
||||
Example:
|
||||
>>> avg1 = (37.62, 55.75) # Первая точка
|
||||
>>> avg2 = calculate_average_coords_incremental(avg1, (37.63, 55.76))
|
||||
>>> print(avg2)
|
||||
(37.625, 55.755)
|
||||
|
||||
>>> avg3 = calculate_average_coords_incremental(avg2, (37.64, 55.77))
|
||||
>>> print(avg3)
|
||||
(37.6325, 55.7625)
|
||||
|
||||
>>> # Проверка: среднее между одинаковыми точками
|
||||
>>> avg = calculate_average_coords_incremental((37.62, 55.75), (37.62, 55.75))
|
||||
>>> print(avg)
|
||||
(37.62, 55.75)
|
||||
"""
|
||||
current_lon, current_lat = current_average
|
||||
new_lon, new_lat = new_coord
|
||||
|
||||
# Инкрементальное усреднение: (current + new) / 2
|
||||
avg_lon = (current_lon + new_lon) / 2
|
||||
avg_lat = (current_lat + new_lat) / 2
|
||||
|
||||
return (avg_lon, avg_lat)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Утилиты для форматирования
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user