Поправил привязку вч загрузки, сделал модальное окно.
This commit is contained in:
@@ -70,17 +70,20 @@ class VchLinkForm(forms.Form):
|
|||||||
# label='Выбор диапазона'
|
# label='Выбор диапазона'
|
||||||
# )
|
# )
|
||||||
value1 = forms.FloatField(
|
value1 = forms.FloatField(
|
||||||
label="Первое число",
|
label="Разброс по частоте (не используется)",
|
||||||
|
required=False,
|
||||||
|
initial=0.0,
|
||||||
widget=forms.NumberInput(attrs={
|
widget=forms.NumberInput(attrs={
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'placeholder': 'Введите первое число'
|
'placeholder': 'Не используется - погрешность определяется автоматически'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
value2 = forms.FloatField(
|
value2 = forms.FloatField(
|
||||||
label="Второе число",
|
label="Разброс по полосе (в %)",
|
||||||
widget=forms.NumberInput(attrs={
|
widget=forms.NumberInput(attrs={
|
||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'placeholder': 'Введите второе число'
|
'placeholder': 'Введите погрешность полосы в процентах',
|
||||||
|
'step': '0.1'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<h3 class="card-title mb-0">Добавление списка спутников</h3>
|
<h3 class="card-title mb-0">Добавление списка спутников</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="card-text">Добавьте новый список спутников в базу данных для последующего использования в загрузке данных.</p>
|
<p class="card-text">Добавьте новый список спутников в базу данных для последующего использования в загрузке данных.</p>
|
||||||
<a href="{% url 'mainapp:add_sats' %}" class="btn btn-info">
|
<a href="{% url 'mainapp:add_sats' %}" class="btn btn-info disabled">
|
||||||
Добавить список спутников
|
Добавить список спутников
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
<h3 class="card-title mb-0">Добавление транспондеров</h3>
|
<h3 class="card-title mb-0">Добавление транспондеров</h3>
|
||||||
</div>
|
</div>
|
||||||
<p class="card-text">Добавьте список транспондеров из JSON-файла в базу данных. Требуется наличие файла transponders.json.</p>
|
<p class="card-text">Добавьте список транспондеров из JSON-файла в базу данных. Требуется наличие файла transponders.json.</p>
|
||||||
<a href="{% url 'mainapp:add_trans' %}" class="btn btn-warning">
|
<a href="{% url 'mainapp:add_trans' %}" class="btn btn-warning disabled">
|
||||||
Добавить транспондеры
|
Добавить транспондеры
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
<!-- SigmaParameter Data Modal -->
|
||||||
|
<div class="modal fade" id="sigmaParameterModal" tabindex="-1" aria-labelledby="sigmaParameterModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-info text-white">
|
||||||
|
<h5 class="modal-title" id="sigmaParameterModalLabel">
|
||||||
|
<i class="bi bi-graph-up"></i> Данные SigmaParameter
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="sigmaParameterModalBody">
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-info" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showSigmaParameterModal(parameterId) {
|
||||||
|
// Показываем модальное окно
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('sigmaParameterModal'));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
// Показываем индикатор загрузки
|
||||||
|
const modalBody = document.getElementById('sigmaParameterModalBody');
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-info" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Загружаем данные
|
||||||
|
fetch(`/api/sigma-parameter/${parameterId}/`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка загрузки данных');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.sigma_parameters.length === 0) {
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<i class="bi bi-info-circle"></i> Нет связанных SigmaParameter
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем HTML с данными
|
||||||
|
let html = '<div class="container-fluid">';
|
||||||
|
|
||||||
|
data.sigma_parameters.forEach((sigma, index) => {
|
||||||
|
html += `
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<strong><i class="bi bi-broadcast"></i> SigmaParameter #${index + 1}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<!-- Основная информация -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<strong><i class="bi bi-info-circle"></i> Основная информация</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-sm table-borderless mb-0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="width: 40%;">Спутник:</td>
|
||||||
|
<td><strong>${sigma.satellite}</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Частота:</td>
|
||||||
|
<td><strong>${sigma.frequency} МГц</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Частота в Ku:</td>
|
||||||
|
<td><strong>${sigma.transfer_frequency} МГц</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Полоса частот:</td>
|
||||||
|
<td><strong>${sigma.freq_range} МГц</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Поляризация:</td>
|
||||||
|
<td><span class="badge bg-info">${sigma.polarization}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Технические параметры -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<strong><i class="bi bi-gear"></i> Технические параметры</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-sm table-borderless mb-0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="width: 40%;">Модуляция:</td>
|
||||||
|
<td><span class="badge bg-secondary">${sigma.modulation}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Стандарт:</td>
|
||||||
|
<td><span class="badge bg-secondary">${sigma.standard}</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Сим. скорость:</td>
|
||||||
|
<td><strong>${sigma.bod_velocity} БОД</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">ОСШ:</td>
|
||||||
|
<td><strong>${sigma.snr} дБ</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Мощность:</td>
|
||||||
|
<td><strong>${sigma.power} дБм</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Статус:</td>
|
||||||
|
<td>${sigma.status}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Пакетность:</td>
|
||||||
|
<td>${sigma.packets}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Временные параметры -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<strong><i class="bi bi-clock-history"></i> Временные параметры</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-sm table-borderless mb-0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="width: 40%;">Начало измерения:</td>
|
||||||
|
<td><strong>${sigma.datetime_begin}</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">Окончание измерения:</td>
|
||||||
|
<td><strong>${sigma.datetime_end}</strong></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Отметки -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<strong><i class="bi bi-check2-square"></i> Отметки (${sigma.marks.length})</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (sigma.marks.length > 0) {
|
||||||
|
html += `
|
||||||
|
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
|
||||||
|
<table class="table table-sm table-striped mb-0">
|
||||||
|
<thead class="table-light sticky-top">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Отметка</th>
|
||||||
|
<th>Дата</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
sigma.marks.forEach(mark => {
|
||||||
|
const markClass = mark.mark === '+' ? 'text-success' : 'text-danger';
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td class="${markClass}"><strong>${mark.mark}</strong></td>
|
||||||
|
<td>${mark.date}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
html += `
|
||||||
|
<p class="text-muted mb-0">Нет отметок</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
modalBody.innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i> ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -20,7 +20,17 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p class="card-text">Введите допустимый разброс для частоты и полосы</p>
|
<div class="alert alert-info" role="alert">
|
||||||
|
<strong>Информация о привязке:</strong>
|
||||||
|
<p class="mb-2">Погрешность центральной частоты определяется автоматически в зависимости от полосы:</p>
|
||||||
|
<ul class="mb-0 small">
|
||||||
|
<li>0 - 500 кГц: <strong>0.1%</strong></li>
|
||||||
|
<li>500 кГц - 1.5 МГц: <strong>0.5%</strong></li>
|
||||||
|
<li>1.5 - 5 МГц: <strong>1%</strong></li>
|
||||||
|
<li>5 - 10 МГц: <strong>2%</strong></li>
|
||||||
|
<li>Более 10 МГц: <strong>5%</strong></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -38,17 +48,19 @@
|
|||||||
<div class="text-danger mt-1">{{ form.ku_range.errors }}</div>
|
<div class="text-danger mt-1">{{ form.ku_range.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div> {% endcomment %}
|
</div> {% endcomment %}
|
||||||
<div class="mb-3">
|
{% comment %} <div class="mb-3">
|
||||||
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в МГц)</label>
|
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в МГц)</label>
|
||||||
{{ form.value1 }}
|
{{ form.value1 }}
|
||||||
|
<small class="form-text text-muted">Не используется - погрешность определяется автоматически</small>
|
||||||
{% if form.value1.errors %}
|
{% if form.value1.errors %}
|
||||||
<div class="text-danger mt-1">{{ form.value1.errors }}</div>
|
<div class="text-danger mt-1">{{ form.value1.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div> {% endcomment %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="{{ form.value2.id_for_label }}" class="form-label">Разброс по полосе(в %)</label>
|
<label for="{{ form.value2.id_for_label }}" class="form-label">Разброс по полосе (в %)</label>
|
||||||
{{ form.value2 }}
|
{{ form.value2 }}
|
||||||
|
<small class="form-text text-muted">Допустимое отклонение полосы частот в процентах</small>
|
||||||
{% if form.value2.errors %}
|
{% if form.value2.errors %}
|
||||||
<div class="text-danger mt-1">{{ form.value2.errors }}</div>
|
<div class="text-danger mt-1">{{ form.value2.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -257,6 +257,12 @@
|
|||||||
onchange="toggleColumn(this)"> Тип источника
|
onchange="toggleColumn(this)"> Тип источника
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="25" checked
|
||||||
|
onchange="toggleColumn(this)"> Sigma
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -470,6 +476,7 @@
|
|||||||
{% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %}
|
{% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %}
|
{% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %}
|
||||||
{% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %}
|
{% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %}
|
||||||
|
{% include 'mainapp/components/_table_header.html' with label="Sigma" field="" sortable=False %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -511,10 +518,19 @@
|
|||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if item.has_sigma %}
|
||||||
|
<a href="#" class="text-info text-decoration-none" onclick="showSigmaParameterModal({{ item.obj.parameter_obj.id }}); return false;" title="{{ item.sigma_info }}">
|
||||||
|
<i class="bi bi-graph-up"></i> {{ item.sigma_info }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="25" class="text-center py-4">
|
<td colspan="26" class="text-center py-4">
|
||||||
{% if selected_satellite_id %}
|
{% if selected_satellite_id %}
|
||||||
Нет данных для выбранных фильтров
|
Нет данных для выбранных фильтров
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -1182,6 +1198,9 @@
|
|||||||
<!-- Include the selected items offcanvas component -->
|
<!-- Include the selected items offcanvas component -->
|
||||||
{% include 'mainapp/components/_selected_items_offcanvas.html' %}
|
{% include 'mainapp/components/_selected_items_offcanvas.html' %}
|
||||||
|
|
||||||
|
<!-- Include the sigma parameter modal component -->
|
||||||
|
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
|
||||||
|
|
||||||
<!-- LyngSat Data Modal -->
|
<!-- LyngSat Data Modal -->
|
||||||
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
|
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ urlpatterns = [
|
|||||||
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
|
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
|
||||||
path('link-lyngsat/', views.LinkLyngsatSourcesView.as_view(), name='link_lyngsat'),
|
path('link-lyngsat/', views.LinkLyngsatSourcesView.as_view(), name='link_lyngsat'),
|
||||||
path('api/lyngsat/<int:lyngsat_id>/', views.LyngsatDataAPIView.as_view(), name='lyngsat_data_api'),
|
path('api/lyngsat/<int:lyngsat_id>/', views.LyngsatDataAPIView.as_view(), name='lyngsat_data_api'),
|
||||||
|
path('api/sigma-parameter/<int:parameter_id>/', views.SigmaParameterDataAPIView.as_view(), name='sigma_parameter_data_api'),
|
||||||
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
|
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
|
||||||
path('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'),
|
path('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'),
|
||||||
path('object/<int:pk>/edit/', views.ObjItemUpdateView.as_view(), name='objitem_update'),
|
path('object/<int:pk>/edit/', views.ObjItemUpdateView.as_view(), name='objitem_update'),
|
||||||
|
|||||||
@@ -447,27 +447,96 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
|
|||||||
sigma_load.save()
|
sigma_load.save()
|
||||||
|
|
||||||
|
|
||||||
|
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%
|
||||||
|
- 1.5 - 5 МГц: 1%
|
||||||
|
- 5 - 10 МГц: 2%
|
||||||
|
- > 10 МГц: 5%
|
||||||
|
"""
|
||||||
|
if freq_range_mhz < 0.5:
|
||||||
|
return 0.005
|
||||||
|
elif freq_range_mhz < 1.5:
|
||||||
|
return 0.01
|
||||||
|
elif freq_range_mhz < 5.0:
|
||||||
|
return 0.02
|
||||||
|
elif freq_range_mhz < 10.0:
|
||||||
|
return 0.05
|
||||||
|
else:
|
||||||
|
return 0.1
|
||||||
|
|
||||||
|
|
||||||
def compare_and_link_vch_load(
|
def compare_and_link_vch_load(
|
||||||
sat_id: Satellite, eps_freq: float, eps_frange: float, ku_range: float
|
sat_id: Satellite, eps_freq: float, eps_frange: float, ku_range: float
|
||||||
):
|
):
|
||||||
item_obj = ObjItem.objects.filter(parameters_obj__id_satellite=sat_id)
|
"""
|
||||||
vch_sigma = SigmaParameter.objects.filter(id_satellite=sat_id)
|
Привязывает 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')
|
||||||
|
|
||||||
link_count = 0
|
link_count = 0
|
||||||
obj_count = len(item_obj)
|
obj_count = item_obj.count()
|
||||||
for idx, obj in enumerate(item_obj):
|
|
||||||
vch_load = obj.parameters_obj.get()
|
for obj in item_obj:
|
||||||
if vch_load.frequency == -1.0:
|
vch_load = obj.parameter_obj
|
||||||
|
|
||||||
|
# Пропускаем объекты с некорректной частотой
|
||||||
|
if not vch_load or vch_load.frequency == -1.0:
|
||||||
continue
|
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:
|
for sigma in vch_sigma:
|
||||||
if (
|
# Проверяем совпадение по всем параметрам
|
||||||
abs(sigma.transfer_frequency - vch_load.frequency) <= eps_freq
|
freq_match = abs(sigma.transfer_frequency - vch_load.frequency) <= freq_tolerance_mhz
|
||||||
and abs(sigma.freq_range - vch_load.freq_range)
|
frange_match = abs(sigma.freq_range - vch_load.freq_range) <= frange_tolerance_mhz
|
||||||
<= vch_load.freq_range * eps_frange / 100
|
pol_match = sigma.polarization == vch_load.polarization
|
||||||
and sigma.polarization == vch_load.polarization
|
|
||||||
):
|
if freq_match and frange_match and pol_match:
|
||||||
sigma.parameter = vch_load
|
sigma.parameter = vch_load
|
||||||
sigma.save()
|
sigma.save()
|
||||||
link_count += 1
|
link_count += 1
|
||||||
|
|
||||||
return obj_count, link_count
|
return obj_count, link_count
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -362,12 +362,13 @@ class LinkVchSigmaView(LoginRequiredMixin, FormView):
|
|||||||
form_class = VchLinkForm
|
form_class = VchLinkForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
freq = form.cleaned_data["value1"]
|
# value1 больше не используется - погрешность частоты определяется автоматически
|
||||||
freq_range = form.cleaned_data["value2"]
|
freq_range = form.cleaned_data["value2"]
|
||||||
# ku_range = float(form.cleaned_data['ku_range'])
|
|
||||||
sat_id = form.cleaned_data["sat_choice"]
|
sat_id = form.cleaned_data["sat_choice"]
|
||||||
# print(freq, freq_range, ku_range, sat_id.pk)
|
|
||||||
count_all, link_count = compare_and_link_vch_load(sat_id, freq, freq_range, 1)
|
# Передаём 0 для eps_freq и ku_range, так как они не используются
|
||||||
|
count_all, link_count = compare_and_link_vch_load(sat_id, 0, freq_range, 0)
|
||||||
|
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request, f"Привязано {link_count} из {count_all} объектов"
|
self.request, f"Привязано {link_count} из {count_all} объектов"
|
||||||
)
|
)
|
||||||
@@ -479,6 +480,84 @@ class LyngsatDataAPIView(LoginRequiredMixin, View):
|
|||||||
return JsonResponse({'error': str(e)}, status=500)
|
return JsonResponse({'error': str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
class SigmaParameterDataAPIView(LoginRequiredMixin, View):
|
||||||
|
"""API для получения данных SigmaParameter"""
|
||||||
|
|
||||||
|
def get(self, request, parameter_id):
|
||||||
|
from .models import Parameter
|
||||||
|
|
||||||
|
try:
|
||||||
|
parameter = Parameter.objects.select_related(
|
||||||
|
'id_satellite',
|
||||||
|
'polarization',
|
||||||
|
'modulation',
|
||||||
|
'standard'
|
||||||
|
).prefetch_related(
|
||||||
|
'sigma_parameter__mark',
|
||||||
|
'sigma_parameter__id_satellite',
|
||||||
|
'sigma_parameter__polarization',
|
||||||
|
'sigma_parameter__modulation',
|
||||||
|
'sigma_parameter__standard'
|
||||||
|
).get(id=parameter_id)
|
||||||
|
|
||||||
|
# Получаем все связанные SigmaParameter
|
||||||
|
sigma_params = parameter.sigma_parameter.all()
|
||||||
|
|
||||||
|
sigma_data = []
|
||||||
|
for sigma in sigma_params:
|
||||||
|
# Получаем отметки
|
||||||
|
marks = []
|
||||||
|
for mark in sigma.mark.all().order_by('-timestamp'):
|
||||||
|
mark_str = '+' if mark.mark else '-'
|
||||||
|
date_str = '-'
|
||||||
|
if mark.timestamp:
|
||||||
|
local_time = timezone.localtime(mark.timestamp)
|
||||||
|
date_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
marks.append({
|
||||||
|
'mark': mark_str,
|
||||||
|
'date': date_str
|
||||||
|
})
|
||||||
|
|
||||||
|
# Форматируем даты начала и окончания
|
||||||
|
datetime_begin_str = '-'
|
||||||
|
if sigma.datetime_begin:
|
||||||
|
local_time = timezone.localtime(sigma.datetime_begin)
|
||||||
|
datetime_begin_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
datetime_end_str = '-'
|
||||||
|
if sigma.datetime_end:
|
||||||
|
local_time = timezone.localtime(sigma.datetime_end)
|
||||||
|
datetime_end_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
sigma_data.append({
|
||||||
|
'id': sigma.id,
|
||||||
|
'satellite': sigma.id_satellite.name if sigma.id_satellite else '-',
|
||||||
|
'frequency': f"{sigma.frequency:.3f}" if sigma.frequency else '-',
|
||||||
|
'transfer_frequency': f"{sigma.transfer_frequency:.3f}" if sigma.transfer_frequency else '-',
|
||||||
|
'freq_range': f"{sigma.freq_range:.3f}" if sigma.freq_range else '-',
|
||||||
|
'polarization': sigma.polarization.name if sigma.polarization else '-',
|
||||||
|
'modulation': sigma.modulation.name if sigma.modulation else '-',
|
||||||
|
'standard': sigma.standard.name if sigma.standard else '-',
|
||||||
|
'bod_velocity': f"{sigma.bod_velocity:.0f}" if sigma.bod_velocity else '-',
|
||||||
|
'snr': f"{sigma.snr:.1f}" if sigma.snr is not None else '-',
|
||||||
|
'power': f"{sigma.power:.1f}" if sigma.power is not None else '-',
|
||||||
|
'status': sigma.status or '-',
|
||||||
|
'packets': 'Да' if sigma.packets else 'Нет' if sigma.packets is not None else '-',
|
||||||
|
'datetime_begin': datetime_begin_str,
|
||||||
|
'datetime_end': datetime_end_str,
|
||||||
|
'marks': marks
|
||||||
|
})
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'parameter_id': parameter.id,
|
||||||
|
'sigma_parameters': sigma_data
|
||||||
|
})
|
||||||
|
except Parameter.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'Parameter не найден'}, status=404)
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'error': str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
class ProcessKubsatView(LoginRequiredMixin, FormMessageMixin, FormView):
|
class ProcessKubsatView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||||
template_name = "mainapp/process_kubsat.html"
|
template_name = "mainapp/process_kubsat.html"
|
||||||
form_class = NewEventForm
|
form_class = NewEventForm
|
||||||
@@ -585,6 +664,10 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
"parameter_obj__modulation",
|
"parameter_obj__modulation",
|
||||||
"parameter_obj__standard",
|
"parameter_obj__standard",
|
||||||
)
|
)
|
||||||
|
.prefetch_related(
|
||||||
|
"parameter_obj__sigma_parameter",
|
||||||
|
"parameter_obj__sigma_parameter__polarization",
|
||||||
|
)
|
||||||
.filter(parameter_obj__id_satellite_id__in=selected_satellites)
|
.filter(parameter_obj__id_satellite_id__in=selected_satellites)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -598,6 +681,9 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
"parameter_obj__polarization",
|
"parameter_obj__polarization",
|
||||||
"parameter_obj__modulation",
|
"parameter_obj__modulation",
|
||||||
"parameter_obj__standard",
|
"parameter_obj__standard",
|
||||||
|
).prefetch_related(
|
||||||
|
"parameter_obj__sigma_parameter",
|
||||||
|
"parameter_obj__sigma_parameter__polarization",
|
||||||
)
|
)
|
||||||
|
|
||||||
if freq_min is not None and freq_min.strip() != "":
|
if freq_min is not None and freq_min.strip() != "":
|
||||||
@@ -872,6 +958,23 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
# Check if LyngSat source is linked
|
# Check if LyngSat source is linked
|
||||||
source_type = "ТВ" if obj.lyngsat_source else "-"
|
source_type = "ТВ" if obj.lyngsat_source else "-"
|
||||||
|
|
||||||
|
# Check if SigmaParameter is linked
|
||||||
|
has_sigma = False
|
||||||
|
sigma_info = "-"
|
||||||
|
if param:
|
||||||
|
sigma_count = param.sigma_parameter.count()
|
||||||
|
if sigma_count > 0:
|
||||||
|
has_sigma = True
|
||||||
|
# Get first sigma parameter for preview
|
||||||
|
first_sigma = param.sigma_parameter.first()
|
||||||
|
if first_sigma:
|
||||||
|
sigma_freq = f"{first_sigma.frequency:.3f}" if first_sigma.frequency else "-"
|
||||||
|
sigma_range = f"{first_sigma.freq_range:.3f}" if first_sigma.freq_range else "-"
|
||||||
|
sigma_pol = first_sigma.polarization.name if first_sigma.polarization else "-"
|
||||||
|
# Сокращаем поляризацию
|
||||||
|
sigma_pol_short = sigma_pol[0] if sigma_pol and sigma_pol != "-" else "-"
|
||||||
|
sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}"
|
||||||
|
|
||||||
processed_objects.append(
|
processed_objects.append(
|
||||||
{
|
{
|
||||||
"id": obj.id,
|
"id": obj.id,
|
||||||
@@ -896,6 +999,8 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
"is_average": is_average,
|
"is_average": is_average,
|
||||||
"source_type": source_type,
|
"source_type": source_type,
|
||||||
"standard": standard_name,
|
"standard": standard_name,
|
||||||
|
"has_sigma": has_sigma,
|
||||||
|
"sigma_info": sigma_info,
|
||||||
"obj": obj,
|
"obj": obj,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,26 +25,33 @@ class CesiumMapView(LoginRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
Отображает спутники и их зоны покрытия на интерактивной 3D карте.
|
Отображает спутники и их зоны покрытия на интерактивной 3D карте.
|
||||||
"""
|
"""
|
||||||
template_name = 'mapsapp/map3d.html'
|
|
||||||
|
template_name = "mapsapp/map3d.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
# Оптимизированный запрос - загружаем только необходимые поля
|
# Оптимизированный запрос - загружаем только необходимые поля
|
||||||
context['sats'] = Satellite.objects.filter(
|
# Фильтруем спутники, у которых есть параметры с привязанными объектами
|
||||||
parameters__objitems__isnull=False
|
context["sats"] = (
|
||||||
).distinct().only('id', 'name').order_by('name')
|
Satellite.objects.filter(parameters__objitem__isnull=False)
|
||||||
|
.distinct()
|
||||||
|
.only("id", "name")
|
||||||
|
.order_by("name")
|
||||||
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class GetFootprintsView(LoginRequiredMixin, View):
|
class GetFootprintsView(LoginRequiredMixin, View):
|
||||||
"""
|
"""
|
||||||
API для получения зон покрытия (footprints) спутника.
|
API для получения зон покрытия (footprints) спутника.
|
||||||
|
|
||||||
Возвращает список названий зон покрытия для указанного спутника.
|
Возвращает список названий зон покрытия для указанного спутника.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, sat_id):
|
def get(self, request, sat_id):
|
||||||
try:
|
try:
|
||||||
# Оптимизированный запрос - загружаем только поле name
|
# Оптимизированный запрос - загружаем только поле name
|
||||||
sat_name = Satellite.objects.only('name').get(id=sat_id).name
|
sat_name = Satellite.objects.only("name").get(id=sat_id).name
|
||||||
footprint_names = get_band_names(sat_name)
|
footprint_names = get_band_names(sat_name)
|
||||||
|
|
||||||
return JsonResponse(footprint_names, safe=False)
|
return JsonResponse(footprint_names, safe=False)
|
||||||
@@ -60,6 +67,7 @@ class TileProxyView(View):
|
|||||||
|
|
||||||
Кэширует тайлы на 7 дней для улучшения производительности.
|
Кэширует тайлы на 7 дней для улучшения производительности.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Константы
|
# Константы
|
||||||
TILE_BASE_URL = "https://static.satbeams.com/tiles"
|
TILE_BASE_URL = "https://static.satbeams.com/tiles"
|
||||||
CACHE_DURATION = 60 * 60 * 24 * 7 # 7 дней
|
CACHE_DURATION = 60 * 60 * 24 * 7 # 7 дней
|
||||||
@@ -72,7 +80,7 @@ class TileProxyView(View):
|
|||||||
|
|
||||||
def get(self, request, footprint_name, z, x, y):
|
def get(self, request, footprint_name, z, x, y):
|
||||||
# Валидация имени footprint
|
# Валидация имени footprint
|
||||||
if not footprint_name.replace('-', '').replace('_', '').isalnum():
|
if not footprint_name.replace("-", "").replace("_", "").isalnum():
|
||||||
return HttpResponse("Invalid footprint name", status=400)
|
return HttpResponse("Invalid footprint name", status=400)
|
||||||
|
|
||||||
url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png"
|
url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png"
|
||||||
@@ -80,7 +88,7 @@ class TileProxyView(View):
|
|||||||
try:
|
try:
|
||||||
resp = requests.get(url, timeout=self.REQUEST_TIMEOUT)
|
resp = requests.get(url, timeout=self.REQUEST_TIMEOUT)
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
response = HttpResponse(resp.content, content_type='image/png')
|
response = HttpResponse(resp.content, content_type="image/png")
|
||||||
response["Access-Control-Allow-Origin"] = "*"
|
response["Access-Control-Allow-Origin"] = "*"
|
||||||
response["Cache-Control"] = f"public, max-age={self.CACHE_DURATION}"
|
response["Cache-Control"] = f"public, max-age={self.CACHE_DURATION}"
|
||||||
return response
|
return response
|
||||||
@@ -91,26 +99,37 @@ class TileProxyView(View):
|
|||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
return HttpResponse(f"Proxy error: {e}", status=500)
|
return HttpResponse(f"Proxy error: {e}", status=500)
|
||||||
|
|
||||||
|
|
||||||
class LeafletMapView(LoginRequiredMixin, TemplateView):
|
class LeafletMapView(LoginRequiredMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
Представление для отображения 2D карты с использованием Leaflet.
|
Представление для отображения 2D карты с использованием Leaflet.
|
||||||
|
|
||||||
Отображает спутники и транспондеры на интерактивной 2D карте.
|
Отображает спутники и транспондеры на интерактивной 2D карте.
|
||||||
"""
|
"""
|
||||||
template_name = 'mapsapp/map2d.html'
|
|
||||||
|
template_name = "mapsapp/map2d.html"
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
# Оптимизированные запросы - загружаем только необходимые поля
|
# Оптимизированные запросы - загружаем только необходимые поля
|
||||||
context['sats'] = Satellite.objects.filter(
|
# Фильтруем спутники, у которых есть параметры с привязанными объектами
|
||||||
parameters__objitems__isnull=False
|
context["sats"] = (
|
||||||
).distinct().only('id', 'name').order_by('name')
|
Satellite.objects.filter(parameters__objitem__isnull=False)
|
||||||
|
.distinct()
|
||||||
|
.only("id", "name")
|
||||||
|
.order_by("name")
|
||||||
|
)
|
||||||
|
|
||||||
context['trans'] = Transponders.objects.select_related(
|
context["trans"] = Transponders.objects.select_related(
|
||||||
'sat_id', 'polarization'
|
"sat_id", "polarization"
|
||||||
).only(
|
).only(
|
||||||
'id', 'name', 'sat_id__name', 'polarization__name',
|
"id",
|
||||||
'downlink', 'frequency_range', 'zone_name'
|
"name",
|
||||||
|
"sat_id__name",
|
||||||
|
"polarization__name",
|
||||||
|
"downlink",
|
||||||
|
"frequency_range",
|
||||||
|
"zone_name",
|
||||||
)
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -121,17 +140,19 @@ class GetTransponderOnSatIdView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
Возвращает список транспондеров для указанного спутника с оптимизированными запросами.
|
Возвращает список транспондеров для указанного спутника с оптимизированными запросами.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, sat_id):
|
def get(self, request, sat_id):
|
||||||
# Оптимизированный запрос с select_related и only
|
# Оптимизированный запрос с select_related и only
|
||||||
trans = Transponders.objects.filter(
|
trans = (
|
||||||
sat_id=sat_id
|
Transponders.objects.filter(sat_id=sat_id)
|
||||||
).select_related('polarization').only(
|
.select_related("polarization")
|
||||||
'name', 'downlink', 'frequency_range',
|
.only(
|
||||||
'zone_name', 'polarization__name'
|
"name", "downlink", "frequency_range", "zone_name", "polarization__name"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not trans.exists():
|
if not trans.exists():
|
||||||
return JsonResponse({'error': 'Объектов не найдено'}, status=404)
|
return JsonResponse({"error": "Объектов не найдено"}, status=404)
|
||||||
|
|
||||||
# Используем list comprehension для лучшей производительности
|
# Используем list comprehension для лучшей производительности
|
||||||
output = [
|
output = [
|
||||||
@@ -140,7 +161,7 @@ class GetTransponderOnSatIdView(LoginRequiredMixin, View):
|
|||||||
"frequency": tran.downlink,
|
"frequency": tran.downlink,
|
||||||
"frequency_range": tran.frequency_range,
|
"frequency_range": tran.frequency_range,
|
||||||
"zone_name": tran.zone_name,
|
"zone_name": tran.zone_name,
|
||||||
"polarization": tran.polarization.name if tran.polarization else "-"
|
"polarization": tran.polarization.name if tran.polarization else "-",
|
||||||
}
|
}
|
||||||
for tran in trans
|
for tran in trans
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user