Доделал таблицу с кубсатом

This commit is contained in:
2025-12-10 12:29:43 +03:00
parent 8393734dc3
commit d889dc7b2a
10 changed files with 1458 additions and 218 deletions

View File

@@ -27,9 +27,14 @@ class KubsatView(LoginRequiredMixin, FormView):
context['full_width_page'] = True
# Добавляем данные для вкладки заявок
from mainapp.models import SourceRequest
from mainapp.models import SourceRequest, Satellite
# Список спутников для формы создания заявки
context['satellites'] = Satellite.objects.all().order_by('name')
requests_qs = SourceRequest.objects.select_related(
'source', 'source__info', 'source__ownership',
'satellite',
'created_by__user', 'updated_by__user'
).prefetch_related(
'source__source_objitems__parameter_obj__modulation'
@@ -72,6 +77,36 @@ class KubsatView(LoginRequiredMixin, FormView):
requests_list.append(req)
context['requests'] = requests_list
# Сериализуем заявки в JSON для Tabulator
import json
requests_json_data = []
for req in requests_list:
requests_json_data.append({
'id': req.id,
'source_id': req.source_id,
'satellite_name': req.satellite.name if req.satellite else '-',
'status': req.status,
'status_display': req.get_status_display(),
'priority': req.priority,
'priority_display': req.get_priority_display(),
'request_date': req.request_date.strftime('%d.%m.%Y') if req.request_date else '-',
'card_date': req.card_date.strftime('%d.%m.%Y') if req.card_date else '-',
'planned_at': req.planned_at.strftime('%d.%m.%Y %H:%M') if req.planned_at else '-',
'downlink': float(req.downlink) if req.downlink else None,
'uplink': float(req.uplink) if req.uplink else None,
'transfer': float(req.transfer) if req.transfer else None,
'coords_lat': float(req.coords.y) if req.coords else None,
'coords_lon': float(req.coords.x) if req.coords else None,
'region': req.region or '',
'gso_success': req.gso_success,
'kubsat_success': req.kubsat_success,
'coords_source_lat': float(req.coords_source.y) if req.coords_source else None,
'coords_source_lon': float(req.coords_source.x) if req.coords_source else None,
'comment': req.comment or '',
})
context['requests_json'] = json.dumps(requests_json_data, ensure_ascii=False)
context['status_choices'] = SourceRequest.STATUS_CHOICES
context['priority_choices'] = SourceRequest.PRIORITY_CHOICES
context['current_status'] = status or ''

View File

@@ -3,14 +3,20 @@
"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from django.views.generic import ListView, CreateView, UpdateView
from django.urls import reverse_lazy
from django.db.models import Q
from mainapp.models import SourceRequest, SourceRequestStatusHistory, Source
from mainapp.models import SourceRequest, SourceRequestStatusHistory, Source, Satellite
from mainapp.forms import SourceRequestForm
import re
import pandas as pd
from datetime import datetime
from django.contrib.gis.geos import Point
class SourceRequestListView(LoginRequiredMixin, ListView):
"""Список заявок на источники."""
@@ -22,6 +28,7 @@ class SourceRequestListView(LoginRequiredMixin, ListView):
def get_queryset(self):
queryset = SourceRequest.objects.select_related(
'source', 'source__info', 'source__ownership',
'satellite',
'created_by__user', 'updated_by__user'
).order_by('-created_at')
@@ -174,6 +181,217 @@ class SourceRequestDeleteView(LoginRequiredMixin, View):
}, status=404)
class SourceRequestBulkDeleteView(LoginRequiredMixin, View):
"""Массовое удаление заявок."""
def post(self, request):
import json
try:
data = json.loads(request.body)
ids = data.get('ids', [])
if not ids:
return JsonResponse({
'success': False,
'error': 'Не выбраны заявки для удаления'
}, status=400)
deleted_count, _ = SourceRequest.objects.filter(pk__in=ids).delete()
return JsonResponse({
'success': True,
'message': f'Удалено заявок: {deleted_count}',
'deleted_count': deleted_count
})
except json.JSONDecodeError:
return JsonResponse({
'success': False,
'error': 'Неверный формат данных'
}, status=400)
class SourceRequestExportView(LoginRequiredMixin, View):
"""Экспорт заявок в Excel."""
def get(self, request):
from django.http import HttpResponse
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
from io import BytesIO
# Получаем заявки с фильтрами
queryset = SourceRequest.objects.select_related(
'satellite'
).order_by('-created_at')
# Применяем фильтры
status = request.GET.get('status')
if status:
queryset = queryset.filter(status=status)
priority = request.GET.get('priority')
if priority:
queryset = queryset.filter(priority=priority)
gso_success = request.GET.get('gso_success')
if gso_success == 'true':
queryset = queryset.filter(gso_success=True)
elif gso_success == 'false':
queryset = queryset.filter(gso_success=False)
kubsat_success = request.GET.get('kubsat_success')
if kubsat_success == 'true':
queryset = queryset.filter(kubsat_success=True)
elif kubsat_success == 'false':
queryset = queryset.filter(kubsat_success=False)
# Создаём Excel файл
wb = Workbook()
ws = wb.active
ws.title = "Заявки"
# Заголовки (как в импорте, но без источника + приоритет + статус + комментарий)
headers = [
'Дата постановки задачи',
'Дата формирования карточки',
'Дата проведения',
'Спутник',
'Частота Downlink',
'Частота Uplink',
'Перенос',
'Координаты ГСО',
'Район',
'Приоритет',
'Статус',
'Результат ГСО',
'Результат кубсата',
'Координаты источника',
'Комментарий',
]
# Стили
header_font = Font(bold=True)
header_fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
green_fill = PatternFill(start_color="90EE90", end_color="90EE90", fill_type="solid")
red_fill = PatternFill(start_color="FF6B6B", end_color="FF6B6B", fill_type="solid")
gray_fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
# Записываем заголовки
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_num, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center', vertical='center')
# Записываем данные
for row_num, req in enumerate(queryset, 2):
# Дата постановки задачи
ws.cell(row=row_num, column=1, value=req.request_date.strftime('%d.%m.%Y') if req.request_date else '')
# Дата формирования карточки
ws.cell(row=row_num, column=2, value=req.card_date.strftime('%d.%m.%Y') if req.card_date else '')
# Дата проведения
ws.cell(row=row_num, column=3, value=req.planned_at.strftime('%d.%m.%y %H:%M') if req.planned_at else '')
# Спутник
satellite_str = ''
if req.satellite:
satellite_str = req.satellite.name
if req.satellite.norad:
satellite_str += f' ({req.satellite.norad})'
ws.cell(row=row_num, column=4, value=satellite_str)
# Частота Downlink
ws.cell(row=row_num, column=5, value=req.downlink if req.downlink else '')
# Частота Uplink
ws.cell(row=row_num, column=6, value=req.uplink if req.uplink else '')
# Перенос
ws.cell(row=row_num, column=7, value=req.transfer if req.transfer else '')
# Координаты ГСО
coords_gso = ''
if req.coords:
coords_gso = f'{req.coords.y:.6f} {req.coords.x:.6f}'
ws.cell(row=row_num, column=8, value=coords_gso)
# Район
ws.cell(row=row_num, column=9, value=req.region or '')
# Приоритет
ws.cell(row=row_num, column=10, value=req.get_priority_display())
# Статус (с цветом)
status_cell = ws.cell(row=row_num, column=11, value=req.get_status_display())
if req.status in ['successful', 'result_received']:
status_cell.fill = green_fill
elif req.status == 'unsuccessful':
status_cell.fill = red_fill
else:
status_cell.fill = gray_fill
# Результат ГСО (с цветом)
gso_cell = ws.cell(row=row_num, column=12)
if req.gso_success is True:
gso_cell.value = 'Да'
gso_cell.fill = green_fill
elif req.gso_success is False:
gso_cell.value = 'Нет'
gso_cell.fill = red_fill
else:
gso_cell.value = ''
# Результат кубсата (с цветом)
kubsat_cell = ws.cell(row=row_num, column=13)
if req.kubsat_success is True:
kubsat_cell.value = 'Да'
kubsat_cell.fill = green_fill
elif req.kubsat_success is False:
kubsat_cell.value = 'Нет'
kubsat_cell.fill = red_fill
else:
kubsat_cell.value = ''
# Координаты источника
coords_source = ''
if req.coords_source:
coords_source = f'{req.coords_source.y:.6f} {req.coords_source.x:.6f}'
ws.cell(row=row_num, column=14, value=coords_source)
# Комментарий
ws.cell(row=row_num, column=15, value=req.comment or '')
# Автоширина колонок
for column in ws.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = min(max_length + 2, 40)
ws.column_dimensions[column_letter].width = adjusted_width
# Сохраняем в BytesIO
output = BytesIO()
wb.save(output)
output.seek(0)
# Возвращаем файл
response = HttpResponse(
output.read(),
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
filename = f'source_requests_{datetime.now().strftime("%Y%m%d_%H%M")}.xlsx'
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
class SourceRequestAPIView(LoginRequiredMixin, View):
"""API для получения данных о заявках источника."""
@@ -230,6 +448,7 @@ class SourceRequestDetailAPIView(LoginRequiredMixin, View):
try:
req = SourceRequest.objects.select_related(
'source', 'source__info', 'source__ownership',
'satellite',
'created_by__user', 'updated_by__user'
).prefetch_related(
'status_history__changed_by__user',
@@ -250,39 +469,59 @@ class SourceRequestDetailAPIView(LoginRequiredMixin, View):
})
# Получаем данные из первой точки источника (имя, модуляция, символьная скорость)
source_data = _get_source_extra_data(req.source)
source_data = _get_source_extra_data(req.source) if req.source else {
'objitem_name': '-', 'modulation': '-', 'symbol_rate': '-'
}
# Координаты из заявки или из источника
# Координаты ГСО из заявки или из источника
coords_lat = None
coords_lon = None
if req.coords:
coords_lat = req.coords.y
coords_lon = req.coords.x
elif req.source.coords_average:
elif req.source and req.source.coords_average:
coords_lat = req.source.coords_average.y
coords_lon = req.source.coords_average.x
# Координаты источника
coords_source_lat = None
coords_source_lon = None
if req.coords_source:
coords_source_lat = req.coords_source.y
coords_source_lon = req.coords_source.x
data = {
'id': req.id,
'source_id': req.source_id,
'satellite_id': req.satellite_id,
'satellite_name': req.satellite.name if req.satellite else '-',
'status': req.status,
'status_display': req.get_status_display(),
'priority': req.priority,
'priority_display': req.get_priority_display(),
'planned_at': req.planned_at.isoformat() if req.planned_at else None,
'planned_at': req.planned_at.strftime('%Y-%m-%dT%H:%M') if req.planned_at else '',
'planned_at_display': req.planned_at.strftime('%d.%m.%Y %H:%M') if req.planned_at else '-',
'request_date': req.request_date.isoformat() if req.request_date else None,
'request_date_display': req.request_date.strftime('%d.%m.%Y') if req.request_date else '-',
'card_date': req.card_date.isoformat() if req.card_date else None,
'card_date_display': req.card_date.strftime('%d.%m.%Y') if req.card_date else '-',
'status_updated_at': req.status_updated_at.strftime('%d.%m.%Y %H:%M') if req.status_updated_at else '-',
'downlink': req.downlink,
'uplink': req.uplink,
'transfer': req.transfer,
'region': req.region or '',
'gso_success': req.gso_success,
'kubsat_success': req.kubsat_success,
'comment': req.comment or '',
'created_at': req.created_at.strftime('%d.%m.%Y %H:%M') if req.created_at else '-',
'created_by': str(req.created_by) if req.created_by else '-',
'history': history,
# Дополнительные данные
# Координаты ГСО
'coords_lat': coords_lat,
'coords_lon': coords_lon,
# Координаты источника
'coords_source_lat': coords_source_lat,
'coords_source_lon': coords_source_lon,
'points_count': req.points_count,
'objitem_name': source_data['objitem_name'],
'modulation': source_data['modulation'],
@@ -321,15 +560,16 @@ def _get_source_extra_data(source):
class SourceDataAPIView(LoginRequiredMixin, View):
"""API для получения данных источника (координаты, имя точки, модуляция, символьная скорость)."""
"""API для получения данных источника (координаты, имя точки, модуляция, символьная скорость, транспондер)."""
def get(self, request, source_id):
from mainapp.utils import calculate_mean_coords
from datetime import datetime
try:
source = Source.objects.select_related('info', 'ownership').prefetch_related(
'source_objitems__parameter_obj__modulation',
'source_objitems__parameter_obj__id_satellite',
'source_objitems__transponder__sat_id',
'source_objitems__geo_obj'
).get(pk=source_id)
except Source.DoesNotExist:
@@ -339,10 +579,18 @@ class SourceDataAPIView(LoginRequiredMixin, View):
source_data = _get_source_extra_data(source)
# Рассчитываем усреднённые координаты из всех точек (сортируем по дате ГЛ)
objitems = source.source_objitems.select_related('geo_obj').order_by('geo_obj__timestamp')
objitems = source.source_objitems.select_related('geo_obj', 'transponder', 'transponder__sat_id').order_by('geo_obj__timestamp')
avg_coords = None
points_count = 0
# Данные из транспондера
downlink = None
uplink = None
transfer = None
satellite_id = None
satellite_name = None
for objitem in objitems:
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.coords:
coord = (float(objitem.geo_obj.coords.x), float(objitem.geo_obj.coords.y))
@@ -351,6 +599,24 @@ class SourceDataAPIView(LoginRequiredMixin, View):
avg_coords = coord
else:
avg_coords, _ = calculate_mean_coords(avg_coords, coord)
# Берём данные из первого транспондера
if downlink is None and objitem.transponder:
transponder = objitem.transponder
downlink = transponder.downlink
uplink = transponder.uplink
transfer = transponder.transfer
if transponder.sat_id:
satellite_id = transponder.sat_id.pk
satellite_name = transponder.sat_id.name
# Если нет данных из транспондера, пробуем из параметров
if satellite_id is None:
for objitem in objitems:
if objitem.parameter_obj and objitem.parameter_obj.id_satellite:
satellite_id = objitem.parameter_obj.id_satellite.pk
satellite_name = objitem.parameter_obj.id_satellite.name
break
# Если нет координат из точек, берём из источника
coords_lat = None
@@ -373,6 +639,359 @@ class SourceDataAPIView(LoginRequiredMixin, View):
'symbol_rate': source_data['symbol_rate'],
'info': source.info.name if source.info else '-',
'ownership': source.ownership.name if source.ownership else '-',
# Данные из транспондера
'downlink': downlink,
'uplink': uplink,
'transfer': transfer,
'satellite_id': satellite_id,
'satellite_name': satellite_name,
}
return JsonResponse(data)
class SourceRequestImportView(LoginRequiredMixin, View):
"""Импорт заявок из Excel файла."""
def get(self, request):
"""Отображает форму загрузки файла."""
return render(request, 'mainapp/source_request_import.html')
def post(self, request):
"""Обрабатывает загруженный Excel файл."""
from openpyxl import load_workbook
from openpyxl.styles import PatternFill
if 'file' not in request.FILES:
return JsonResponse({'success': False, 'error': 'Файл не загружен'}, status=400)
file = request.FILES['file']
try:
# Читаем Excel файл с openpyxl для доступа к цветам
wb = load_workbook(file, data_only=True)
ws = wb.worksheets[0]
# Получаем заголовки (очищаем от пробелов)
headers = []
for cell in ws[1]:
val = cell.value
if val:
headers.append(str(val).strip())
else:
headers.append(None)
# Находим индекс столбца "Результат кубсата"
kubsat_col_idx = None
for i, h in enumerate(headers):
if h and 'кубсат' in h.lower():
kubsat_col_idx = i
break
results = {
'created': 0,
'errors': [],
'skipped': 0,
'headers': [h for h in headers if h], # Для отладки
}
custom_user = getattr(request.user, 'customuser', None)
# Обрабатываем строки начиная со второй (первая - заголовки)
for row_idx, row in enumerate(ws.iter_rows(min_row=2, values_only=False), start=2):
try:
# Получаем значения
row_values = {headers[i]: cell.value for i, cell in enumerate(row) if i < len(headers)}
# Получаем цвет ячейки "Результат кубсата"
kubsat_is_red = False
kubsat_value = None
if kubsat_col_idx is not None and kubsat_col_idx < len(row):
kubsat_cell = row[kubsat_col_idx]
kubsat_value = kubsat_cell.value
# Проверяем цвет заливки
if kubsat_cell.fill and kubsat_cell.fill.fgColor:
color = kubsat_cell.fill.fgColor
if color.type == 'rgb' and color.rgb:
# Красный цвет: FF0000 или близкие оттенки
rgb = color.rgb
if isinstance(rgb, str) and len(rgb) >= 6:
# Убираем альфа-канал если есть
rgb = rgb[-6:]
r = int(rgb[0:2], 16)
g = int(rgb[2:4], 16)
b = int(rgb[4:6], 16)
# Считаем красным если R > 200 и G < 100 и B < 100
if r > 180 and g < 120 and b < 120:
kubsat_is_red = True
self._process_row(row_values, row_idx, results, custom_user, kubsat_is_red, kubsat_value)
except Exception as e:
results['errors'].append(f"Строка {row_idx}: {str(e)}")
return JsonResponse({
'success': True,
'created': results['created'],
'skipped': results['skipped'],
'errors': results['errors'][:20],
'total_errors': len(results['errors']),
'headers': results.get('headers', [])[:15], # Для отладки
})
except Exception as e:
return JsonResponse({'success': False, 'error': f'Ошибка чтения файла: {str(e)}'}, status=400)
def _process_row(self, row, row_idx, results, custom_user, kubsat_is_red=False, kubsat_value=None):
"""Обрабатывает одну строку из Excel."""
# Пропускаем полностью пустые строки (все значения None или пустые)
has_any_data = any(v for v in row.values() if v is not None and str(v).strip())
if not has_any_data:
results['skipped'] += 1
return
# Парсим дату заявки (Дата постановки задачи)
request_date = self._parse_date(row.get('Дата постановки задачи'))
# Парсим дату формирования карточки
card_date = self._parse_date(row.get('Дата формирования карточки'))
# Парсим дату и время планирования (Дата проведения)
planned_at = self._parse_datetime(row.get('Дата проведения'))
# Ищем спутник по NORAD
satellite = self._find_satellite(row.get('Спутник'))
# Парсим частоты
downlink = self._parse_float(row.get('Частота Downlink'))
uplink = self._parse_float(row.get('Частота Uplink'))
transfer = self._parse_float(row.get('Перенос'))
# Парсим координаты ГСО
coords = self._parse_coords(row.get('Координаты ГСО'))
# Район
region = str(row.get('Район', '')).strip() if row.get('Район') else None
# Результат ГСО
gso_result = row.get('Результат ГСО')
gso_success = None
comment_parts = []
if gso_result:
gso_str = str(gso_result).strip().lower()
if gso_str in ('успешно', 'да', 'true', '1'):
gso_success = True
else:
gso_success = False
comment_parts.append(f"Результат ГСО: {str(gso_result).strip()}")
# Результат кубсата - по цвету ячейки
kubsat_success = None
if kubsat_is_red:
kubsat_success = False
elif kubsat_value:
kubsat_success = True
# Добавляем значение кубсата в комментарий
if kubsat_value:
comment_parts.append(f"Результат кубсата: {str(kubsat_value).strip()}")
# Координаты источника
coords_source = self._parse_coords(row.get('Координаты источника'))
# Определяем статус по логике:
# - если есть координата источника -> result_received
# - если нет координаты источника, но ГСО успешно -> successful
# - если нет координаты источника и ГСО не успешно -> unsuccessful
status = 'planned'
if coords_source:
status = 'result_received'
elif gso_success is True:
status = 'successful'
elif gso_success is False:
status = 'unsuccessful'
# Собираем комментарий
comment = '; '.join(comment_parts) if comment_parts else None
# Создаём заявку
source_request = SourceRequest(
source=None,
satellite=satellite,
status=status,
priority='medium',
request_date=request_date,
card_date=card_date,
planned_at=planned_at,
downlink=downlink,
uplink=uplink,
transfer=transfer,
region=region,
gso_success=gso_success,
kubsat_success=kubsat_success,
comment=comment,
created_by=custom_user,
updated_by=custom_user,
)
# Устанавливаем координаты
if coords:
source_request.coords = Point(coords[1], coords[0], srid=4326)
if coords_source:
source_request.coords_source = Point(coords_source[1], coords_source[0], srid=4326)
source_request.save()
# Создаём начальную запись в истории
SourceRequestStatusHistory.objects.create(
source_request=source_request,
old_status='',
new_status=source_request.status,
changed_by=custom_user,
)
results['created'] += 1
def _parse_date(self, value):
"""Парсит дату из различных форматов."""
if pd.isna(value):
return None
if isinstance(value, datetime):
return value.date()
value_str = str(value).strip()
# Пробуем разные форматы
formats = ['%d.%m.%Y', '%d.%m.%y', '%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y']
for fmt in formats:
try:
return datetime.strptime(value_str, fmt).date()
except ValueError:
continue
return None
def _parse_datetime(self, value):
"""Парсит дату и время из различных форматов."""
if pd.isna(value):
return None
if isinstance(value, datetime):
return value
value_str = str(value).strip()
# Пробуем разные форматы
formats = [
'%d.%m.%y %H:%M', '%d.%m.%Y %H:%M', '%d.%m.%y %H:%M:%S', '%d.%m.%Y %H:%M:%S',
'%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S', '%d/%m/%Y %H:%M', '%d/%m/%y %H:%M'
]
for fmt in formats:
try:
return datetime.strptime(value_str, fmt)
except ValueError:
continue
return None
def _find_satellite(self, value):
"""Ищет спутник по названию с NORAD в скобках."""
if pd.isna(value):
return None
value_str = str(value).strip()
# Ищем NORAD в скобках: "NSS 12 (36032)"
match = re.search(r'\((\d+)\)', value_str)
if match:
norad = int(match.group(1))
try:
return Satellite.objects.get(norad=norad)
except Satellite.DoesNotExist:
pass
# Пробуем найти по имени
name = re.sub(r'\s*\(\d+\)\s*', '', value_str).strip()
if name:
satellite = Satellite.objects.filter(name__icontains=name).first()
if satellite:
return satellite
return None
def _parse_float(self, value):
"""Парсит число с плавающей точкой."""
if pd.isna(value):
return None
try:
# Заменяем запятую на точку
value_str = str(value).replace(',', '.').strip()
return float(value_str)
except (ValueError, TypeError):
return None
def _parse_coords(self, value):
"""Парсит координаты из строки. Возвращает (lat, lon) или None.
Поддерживаемые форматы:
- "24.920695 46.733201" (точка как десятичный разделитель, пробел между координатами)
- "24,920695 46,733201" (запятая как десятичный разделитель, пробел между координатами)
- "24.920695, 46.733201" (точка как десятичный разделитель, запятая+пробел между координатами)
- "21.763585. 39.158290" (точка с пробелом между координатами)
"""
if pd.isna(value):
return None
value_str = str(value).strip()
if not value_str:
return None
# Формат "21.763585. 39.158290" - точка с пробелом как разделитель координат
if re.search(r'\.\s+', value_str):
parts = re.split(r'\.\s+', value_str)
if len(parts) >= 2:
try:
lat = float(parts[0].replace(',', '.'))
lon = float(parts[1].replace(',', '.'))
return (lat, lon)
except (ValueError, TypeError):
pass
# Формат "24.920695, 46.733201" - запятая с пробелом как разделитель координат
if ', ' in value_str:
parts = value_str.split(', ')
if len(parts) >= 2:
try:
lat = float(parts[0].replace(',', '.'))
lon = float(parts[1].replace(',', '.'))
return (lat, lon)
except (ValueError, TypeError):
pass
# Формат "24,920695 46,733201" или "24.920695 46.733201" - пробел как разделитель координат
# Сначала разбиваем по пробелам
parts = value_str.split()
if len(parts) >= 2:
try:
# Заменяем запятую на точку в каждой части отдельно
lat = float(parts[0].replace(',', '.'))
lon = float(parts[1].replace(',', '.'))
return (lat, lon)
except (ValueError, TypeError):
pass
# Формат "24.920695;46.733201" - точка с запятой как разделитель
if ';' in value_str:
parts = value_str.split(';')
if len(parts) >= 2:
try:
lat = float(parts[0].strip().replace(',', '.'))
lon = float(parts[1].strip().replace(',', '.'))
return (lat, lon)
except (ValueError, TypeError):
pass
return None