diff --git a/dbapp/Dockerfile b/dbapp/Dockerfile
index 8b95d3f..a48659e 100644
--- a/dbapp/Dockerfile
+++ b/dbapp/Dockerfile
@@ -18,34 +18,26 @@ RUN apt-get update && apt-get install -y \
postgresql-client \
build-essential \
libpq-dev \
- gcc \
- g++ \
&& rm -rf /var/lib/apt/lists/*
-# Install Python dependencies for GDAL
-RUN pip install --upgrade pip && \
- pip install --no-cache-dir GDAL==$(gdal-config --version)
-
# Set work directory
WORKDIR /app
-# Copy project requirements
+# Copy project files
COPY pyproject.toml uv.lock ./
-# Install uv package manager
-RUN pip install --upgrade pip && pip install uv
+# Install uv and dependencies
+RUN pip install --no-cache-dir uv && \
+ uv sync --frozen --no-dev
-# Install dependencies using uv
-RUN uv pip install --system --no-cache-dir -r uv.lock
-
-# Copy project
+# Copy project code (после установки зависимостей для лучшего кэширования)
COPY . .
# Collect static files
-RUN python manage.py collectstatic --noinput
+RUN uv run manage.py collectstatic --noinput
# Expose port
EXPOSE 8000
# Run gunicorn server
-CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "dbapp.wsgi:application"]
\ No newline at end of file
+CMD [".venv/bin/gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "dbapp.wsgi:application"]
\ No newline at end of file
diff --git a/dbapp/dbapp/settings/base.py b/dbapp/dbapp/settings/base.py
index 5a51365..cb21ca1 100644
--- a/dbapp/dbapp/settings/base.py
+++ b/dbapp/dbapp/settings/base.py
@@ -72,6 +72,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил
+ 'django.middleware.locale.LocaleMiddleware', #Добавил
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', #Добавил
diff --git a/dbapp/gdal-3.10.2-cp313-cp313-win_amd64.whl b/dbapp/gdal-3.10.2-cp313-cp313-win_amd64.whl
new file mode 100644
index 0000000..07335c9
Binary files /dev/null and b/dbapp/gdal-3.10.2-cp313-cp313-win_amd64.whl differ
diff --git a/dbapp/mainapp/admin.py b/dbapp/mainapp/admin.py
index 496406d..3ffcb16 100644
--- a/dbapp/mainapp/admin.py
+++ b/dbapp/mainapp/admin.py
@@ -6,6 +6,7 @@ from .models import (
Standard,
SigmaParMark,
SigmaParameter,
+ SourceType,
Parameter,
Satellite,
Mirror,
@@ -128,6 +129,12 @@ class ModulationAdmin(admin.ModelAdmin):
search_fields = ("name",)
ordering = ("name",)
+@admin.register(SourceType)
+class ModulationAdmin(admin.ModelAdmin):
+ list_display = ("name",)
+ search_fields = ("name",)
+ ordering = ("name",)
+
@admin.register(Standard)
class StandardAdmin(admin.ModelAdmin):
@@ -209,23 +216,25 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
list_display = (
"id_satellite",
- "status",
+ # "status",
"frequency",
+ "transfer_frequency",
"freq_range",
- "power",
+ # "power",
+ "polarization",
"modulation",
"bod_velocity",
"snr",
- "standard",
+ # "standard",
"parameter",
- "packets",
+ # "packets",
"datetime_begin",
"datetime_end",
)
readonly_fields = (
"datetime_begin",
- "datetime_end",
-
+ "datetime_end",
+ "transfer_frequency"
)
list_display_links = ("id_satellite",)
list_filter = (
@@ -401,7 +410,7 @@ class ObjectAdmin(admin.ModelAdmin):
)
search_fields = (
"name",
- # "id_geo",
+ "id_geo__coords",
# "id_satellite__name",
# "id_vch_load__frequency",
)
@@ -413,6 +422,7 @@ class ObjectAdmin(admin.ModelAdmin):
"id_vch_load__modulation",
"id_vch_load__id_satellite",
"id_geo",
+ "id_source_type"
)
autocomplete_fields = ("id_geo",)
raw_id_fields = ("id_vch_load",)
@@ -422,11 +432,13 @@ class ObjectAdmin(admin.ModelAdmin):
def sat_name(self, obj):
return obj.id_vch_load.id_satellite
sat_name.short_description = "Спутник"
+ sat_name.admin_order_field = "id_vch_load__id_satellite__name"
def freq(self, obj):
par = obj.id_vch_load
return par.frequency
freq.short_description = "Частота, МГц"
+ freq.admin_order_field = "id_vch_load__frequency"
def distance_geo_kup(self, obj):
par = obj.id_geo.distance_coords_kup
@@ -458,6 +470,7 @@ class ObjectAdmin(admin.ModelAdmin):
par = obj.id_vch_load
return par.freq_range
freq_range.short_description = "Полоса, МГц"
+ freq_range.admin_order_field = "id_vch_load__freq_range"
def bod_velocity(self, obj):
par = obj.id_vch_load
@@ -482,6 +495,7 @@ class ObjectAdmin(admin.ModelAdmin):
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
return f"{lat} {lon}"
geo_coords.short_description = "Координаты геолокации"
+ geo_coords.admin_order_filed = "id_geo__coords"
def kupsat_coords(self, obj):
obj = obj.id_geo
diff --git a/dbapp/mainapp/forms.py b/dbapp/mainapp/forms.py
index fb89ea2..edc4943 100644
--- a/dbapp/mainapp/forms.py
+++ b/dbapp/mainapp/forms.py
@@ -1,5 +1,14 @@
from django import forms
-from .models import Satellite
+from .models import Satellite, Polarization
+
+class UploadFileForm(forms.Form):
+ file = forms.FileField(
+ label="Выберите файл",
+ widget=forms.FileInput(attrs={
+ 'class': 'form-file-input'
+ })
+ )
+
class LoadExcelData(forms.Form):
file = forms.FileField(
@@ -33,7 +42,7 @@ class LoadCsvData(forms.Form):
})
)
-class UploadFileForm(forms.Form):
+class UploadVchLoad(UploadFileForm):
sat_choice = forms.ModelChoiceField(
queryset=Satellite.objects.all(),
label="Выберите спутник",
@@ -41,12 +50,7 @@ class UploadFileForm(forms.Form):
'class': 'form-select'
})
)
- file = forms.FileField(
- label="Выберите текстовый файл",
- widget=forms.FileInput(attrs={
- 'class': 'form-file-input'
- })
- )
+
class VchLinkForm(forms.Form):
sat_choice = forms.ModelChoiceField(
@@ -75,4 +79,28 @@ class VchLinkForm(forms.Form):
'class': 'form-control',
'placeholder': 'Введите второе число'
})
+ )
+
+
+class NewEventForm(forms.Form):
+ # sat_choice = forms.ModelChoiceField(
+ # queryset=Satellite.objects.all(),
+ # label="Выберите спутник",
+ # widget=forms.Select(attrs={
+ # 'class': 'form-select'
+ # })
+ # )
+ # pol_choice = forms.ModelChoiceField(
+ # queryset=Polarization.objects.all(),
+ # label="Выберите поляризацию",
+ # widget=forms.Select(attrs={
+ # 'class': 'form-select'
+ # })
+ # )
+ file = forms.FileField(
+ label="Выберите файл",
+ widget=forms.FileInput(attrs={
+ 'class': 'form-control',
+ 'accept': '.xlsx,.xls'
+ })
)
\ No newline at end of file
diff --git a/dbapp/mainapp/migrations/0018_sigmaparameter_polarization_sigmaparameter_transfer_and_more.py b/dbapp/mainapp/migrations/0018_sigmaparameter_polarization_sigmaparameter_transfer_and_more.py
new file mode 100644
index 0000000..b3059f3
--- /dev/null
+++ b/dbapp/mainapp/migrations/0018_sigmaparameter_polarization_sigmaparameter_transfer_and_more.py
@@ -0,0 +1,31 @@
+# Generated by Django 5.2.7 on 2025-10-27 13:10
+
+import django.db.models.deletion
+import django.db.models.expressions
+import mainapp.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mainapp', '0017_alter_sigmaparameter_parameter'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='sigmaparameter',
+ name='polarization',
+ field=models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='polarizations_sigma', to='mainapp.polarization', verbose_name='Поляризация'),
+ ),
+ migrations.AddField(
+ model_name='sigmaparameter',
+ name='transfer',
+ field=models.FloatField(choices=[(-1.0, '-'), (9750.0, '9750 МГц'), (10750.0, '10750 МГц')], default=-1.0, verbose_name='Перенос по частоте'),
+ ),
+ migrations.AddField(
+ model_name='sigmaparameter',
+ name='transfer_frequency',
+ field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.expressions.CombinedExpression(models.F('frequency'), '+', models.F('transfer')), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Частота в Ku, МГц'),
+ ),
+ ]
diff --git a/dbapp/mainapp/migrations/0019_alter_satellite_name.py b/dbapp/mainapp/migrations/0019_alter_satellite_name.py
new file mode 100644
index 0000000..eeec1fb
--- /dev/null
+++ b/dbapp/mainapp/migrations/0019_alter_satellite_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-10-28 05:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mainapp', '0018_sigmaparameter_polarization_sigmaparameter_transfer_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='satellite',
+ name='name',
+ field=models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Имя спутника'),
+ ),
+ ]
diff --git a/dbapp/mainapp/migrations/0020_sourcetype_objitem_id_source_type.py b/dbapp/mainapp/migrations/0020_sourcetype_objitem_id_source_type.py
new file mode 100644
index 0000000..4359cb1
--- /dev/null
+++ b/dbapp/mainapp/migrations/0020_sourcetype_objitem_id_source_type.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.7 on 2025-10-29 14:00
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mainapp', '0019_alter_satellite_name'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SourceType',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50, unique=True, verbose_name='Тип источника')),
+ ],
+ options={
+ 'verbose_name': 'Тип источника',
+ 'verbose_name_plural': 'Типы источников',
+ },
+ ),
+ migrations.AddField(
+ model_name='objitem',
+ name='id_source_type',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='mainapp.sourcetype', verbose_name='Тип источника'),
+ ),
+ ]
diff --git a/dbapp/mainapp/models.py b/dbapp/mainapp/models.py
index 7ed0266..cd2fce8 100644
--- a/dbapp/mainapp/models.py
+++ b/dbapp/mainapp/models.py
@@ -2,6 +2,7 @@ from django.db import models
from django.contrib.auth.models import User
from django.contrib.gis.db import models as gis
from django.contrib.gis.db.models import functions
+from django.db.models import F, ExpressionWrapper
def get_default_polarization():
obj, created = Polarization.objects.get_or_create(
@@ -96,7 +97,7 @@ class Standard(models.Model):
class Satellite(models.Model):
- name = models.CharField(max_length=30, unique=True, verbose_name="Имя спутника", db_index=True)
+ name = models.CharField(max_length=100, unique=True, verbose_name="Имя спутника", db_index=True)
norad = models.IntegerField(blank=True, null=True, verbose_name="NORAD ID")
def __str__(self):
@@ -107,6 +108,40 @@ class Satellite(models.Model):
verbose_name_plural = "Спутники"
+class ObjItem(models.Model):
+ name = models.CharField(null=True, blank=True, max_length=100, verbose_name="Имя объекта", db_index=True)
+ # id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="objitems", verbose_name="Спутник")
+ # id_vch_load = models.ForeignKey(Parameter, on_delete=models.CASCADE, related_name="objitems", verbose_name="ВЧ загрузка")
+ # id_geo = models.ForeignKey(Geo, on_delete=models.CASCADE, related_name="objitems", verbose_name="Геоданные")
+ id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Пользователь", null=True, blank=True)
+ # id_source_type = models.ForeignKey(SourceType, on_delete=models.SET_NULL, related_name="objitems", verbose_name='Тип источника', null=True, blank=True)
+
+
+ def __str__(self):
+ return f"Объект {self.name}"
+
+ class Meta:
+ verbose_name = "Объект"
+ verbose_name_plural = "Объекты"
+ # constraints = [
+ # models.UniqueConstraint(
+ # fields=['id_vch_load', 'id_geo'],
+ # name='unique_objitem_combination'
+ # )
+ # ]
+
+class SourceType(models.Model):
+ name = models.CharField(max_length=50, unique=True, verbose_name="Тип источника")
+ objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ verbose_name = "Тип источника"
+ verbose_name_plural = 'Типы источников'
+
+
class Parameter(models.Model):
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="parameters", verbose_name="Спутник", null=True)
polarization = models.ForeignKey(
@@ -123,6 +158,7 @@ class Parameter(models.Model):
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
)
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
+ objitem = models.ForeignKey(ObjItem, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Источник",null=True, blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)
@@ -151,12 +187,35 @@ class Parameter(models.Model):
class SigmaParameter(models.Model):
+ TRANSFERS = [
+ (-1.0, "-"),
+ (9750.0, "9750 МГц"),
+ (10750.0, "10750 МГц")
+ ]
+
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="sigmapar_sat", verbose_name="Спутник")
+ transfer = models.FloatField(
+ choices=TRANSFERS,
+ default=-1.0,
+ verbose_name="Перенос по частоте"
+ )
status = models.CharField(max_length=20, blank=True, null=True, verbose_name="Статус")
frequency = models.FloatField(default=0, null=True, blank=True, verbose_name="Частота, МГц", db_index=True)
+ transfer_frequency = models.GeneratedField(
+ expression=ExpressionWrapper(
+ F('frequency') + F('transfer'),
+ output_field=models.FloatField()
+ ),
+ output_field=models.FloatField(),
+ db_persist=True,
+ null=True, blank=True, verbose_name="Частота в Ku, МГц"
+ )
freq_range = models.FloatField(default=0, null=True, blank=True, verbose_name="Полоса частот, МГц")
power = models.FloatField(default=0, null=True, blank=True, verbose_name="Мощность, дБм")
bod_velocity = models.FloatField(default=0, null=True, blank=True, verbose_name="Символьная скорость, БОД")
+ polarization = models.ForeignKey(
+ Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="polarizations_sigma", null=True, blank=True, verbose_name="Поляризация"
+ )
modulation = models.ForeignKey(
Modulation, default=get_default_modulation, on_delete=models.SET_DEFAULT, related_name="modulations_sigma", null=True, blank=True, verbose_name="Модуляция"
)
@@ -213,6 +272,7 @@ class Geo(models.Model):
db_persist=True,
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
)
+ objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True)
def __str__(self):
longitude = self.coords.coords[0]
@@ -234,23 +294,3 @@ class Geo(models.Model):
)
]
-
-class ObjItem(models.Model):
- name = models.CharField(null=True, blank=True, max_length=100, verbose_name="Имя объекта", db_index=True)
- # id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="objitems", verbose_name="Спутник")
- id_vch_load = models.ForeignKey(Parameter, on_delete=models.CASCADE, related_name="objitems", verbose_name="ВЧ загрузка")
- id_geo = models.ForeignKey(Geo, on_delete=models.CASCADE, related_name="objitems", verbose_name="Геоданные")
- id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Пользователь", null=True, blank=True)
-
- def __str__(self):
- return f"Объект {self.name}"
-
- class Meta:
- verbose_name = "Объект"
- verbose_name_plural = "Объекты"
- constraints = [
- models.UniqueConstraint(
- fields=['id_vch_load', 'id_geo'],
- name='unique_objitem_combination'
- )
- ]
\ No newline at end of file
diff --git a/dbapp/mainapp/templates/mainapp/home.html b/dbapp/mainapp/templates/mainapp/home.html
index 7def0d8..c38adf4 100644
--- a/dbapp/mainapp/templates/mainapp/home.html
+++ b/dbapp/mainapp/templates/mainapp/home.html
@@ -171,6 +171,26 @@
+
+
+
+
+
+
+
+
Формирование таблицы для Кубсатов
+
+
Добавьте новое событие с помощью выбора спутника и загрузки файла данных.
+
+ Добавить событие
+
+
+
+
{% endblock %}
\ No newline at end of file
diff --git a/dbapp/mainapp/templates/mainapp/link_vch.html b/dbapp/mainapp/templates/mainapp/link_vch.html
index 27b5925..65de198 100644
--- a/dbapp/mainapp/templates/mainapp/link_vch.html
+++ b/dbapp/mainapp/templates/mainapp/link_vch.html
@@ -31,13 +31,13 @@
{{ form.sat_choice.errors }}
{% endif %}
-
+ {% comment %}
Выберите перенос по частоте(МГц):
{{ form.ku_range }}
{% if form.ku_range.errors %}
{{ form.ku_range.errors }}
{% endif %}
-
+
{% endcomment %}
Разброс по частоте(в %)
{{ form.value1 }}
diff --git a/dbapp/mainapp/templates/mainapp/process_kubsat.html b/dbapp/mainapp/templates/mainapp/process_kubsat.html
new file mode 100644
index 0000000..1fef615
--- /dev/null
+++ b/dbapp/mainapp/templates/mainapp/process_kubsat.html
@@ -0,0 +1,52 @@
+{% extends 'mainapp/base.html' %}
+
+{% block title %}Новое событие{% endblock %}
+
+{% block content %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/dbapp/mainapp/templates/mainapp/transponders_upload.html b/dbapp/mainapp/templates/mainapp/transponders_upload.html
new file mode 100644
index 0000000..702b018
--- /dev/null
+++ b/dbapp/mainapp/templates/mainapp/transponders_upload.html
@@ -0,0 +1,53 @@
+{% extends 'mainapp/base.html' %}
+
+{% block title %}Загрузка данных транспондеров{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+
Загрузите xml-файл и выберите спутник для загрузки данных в базу.
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/dbapp/mainapp/urls.py b/dbapp/mainapp/urls.py
index 8b62f86..51d9cec 100644
--- a/dbapp/mainapp/urls.py
+++ b/dbapp/mainapp/urls.py
@@ -15,6 +15,7 @@ urlpatterns = [
path('cluster/', views.ClusterTestView.as_view(), name='cluster'),
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
+ path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
# path('upload/', views.upload_file, name='upload_file'),
]
\ No newline at end of file
diff --git a/dbapp/mainapp/utils.py b/dbapp/mainapp/utils.py
index bbcf831..1a12891 100644
--- a/dbapp/mainapp/utils.py
+++ b/dbapp/mainapp/utils.py
@@ -10,12 +10,18 @@ from .models import (
ObjItem,
CustomUser
)
+from mapsapp.models import Transponders
from datetime import datetime, time
import pandas as pd
import numpy as np
from django.contrib.gis.geos import Point
import json
import re
+import io
+from django.db.models import F, Count, Exists, OuterRef, Min, Max
+from geopy.geocoders import Nominatim
+import reverse_geocoder as rg
+import time
def get_all_constants():
sats = [sat.name for sat in Satellite.objects.all()]
@@ -74,7 +80,10 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
freq = remove_str(stroka[1]['Частота, МГц'])
freq_line = remove_str(stroka[1]['Полоса, МГц'])
v = remove_str(stroka[1]['Символьная скорость, БОД'])
- mod_obj, _ = Modulation.objects.get_or_create(name=stroka[1]['Модуляция'].strip())
+ 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]['Время']
@@ -192,17 +201,7 @@ def get_point_from_json(filepath: str):
def get_points_from_csv(file_content):
- import io
- if hasattr(file_content, 'read'):
- content = file_content.read()
- if isinstance(content, bytes):
- content = content.decode('utf-8')
- else:
- if isinstance(file_content, bytes):
- content = content.decode('utf-8')
- else:
- content = file_content
- df = pd.read_csv(io.StringIO(content), sep=";",
+ df = pd.read_csv(io.StringIO(file_content), sep=";",
names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
df['time'] = pd.to_datetime(df['time'], format='%d.%m.%Y %H:%M:%S')
@@ -266,73 +265,22 @@ def get_points_from_csv(file_content):
}
)
obj_item_obj.save()
- # df = pd.read_csv(filepath, sep=";",
- # names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
- # df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).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']
- # )
- # vch_load_obj, _ = Parameter.objects.get_or_create(
- # id_satellite=sat_obj,
- # polarization=pol_obj,
- # frequency=row['freq'],
- # freq_range=row['f_range'],
- # defaults={'id_user_add': 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': CustomUser.objects.get(id=1),
- # }
- # )
- # geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
-
- # obj_item_obj, _ = ObjItem.objects.get_or_create(
- # name=row['obj'],
- # # id_satellite=sat_obj,
- # id_vch_load=vch_load_obj,
- # id_geo=geo_obj,
- # defaults={
- # 'id_user_add': CustomUser.objects.get(id=1)
- # }
- # )
- # obj_item_obj.save()
-
def get_vch_load_from_html(file, sat: Satellite) -> None:
+ filename = file.name.split('_')
+ transfer = filename[3]
+ match filename[2]:
+ case 'H':
+ pol = 'Горизонтальная'
+ case 'V':
+ pol = 'Вертикальная'
+ case 'R':
+ pol = 'Правая'
+ case 'L':
+ pol = 'Левая'
+ case _:
+ pol = '-'
+
tables = pd.read_html(file, encoding='windows-1251')
df = tables[0]
df = df.drop(0).reset_index(drop=True)
@@ -362,6 +310,10 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
else:
pack = None
+ polarization, _ = Polarization.objects.get_or_create(
+ name=pol
+ )
+
mod, _ = Modulation.objects.get_or_create(
name=value['Модуляция']
)
@@ -372,7 +324,10 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
id_satellite=sat,
frequency=value['Частота, МГц'],
freq_range=value['Полоса, МГц'],
+ polarization=polarization,
defaults={
+ "transfer": float(transfer),
+ # "polarization": polarization,
"status": value['Статус'],
"power": value['Мощность, дБм'],
"bod_velocity": bod_velocity,
@@ -386,15 +341,6 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
)
sigma_load.save()
-def define_ku_transfer(min_freq: float, max_freq: float) -> int | None:
- fss = (10700, 11700)
- dss = (11700, 12750)
- if min_freq + 9750 >= fss[0] and max_freq + 9750 <= fss[1]:
- return 9750
- elif min_freq + 10750 >= dss[0] and max_freq + 10750 <= dss[1]:
- return 10750
- return None
-
def compare_and_link_vch_load(sat_id: Satellite, eps_freq: float, eps_frange: float, ku_range: float):
item_obj = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
vch_sigma = SigmaParameter.objects.filter(id_satellite=sat_id)
@@ -406,10 +352,62 @@ def compare_and_link_vch_load(sat_id: Satellite, eps_freq: float, eps_frange: fl
continue
# if unique_points = Point.objects.order_by('frequency').distinct('frequency')
for sigma in vch_sigma:
- if abs(sigma.frequency + ku_range - vch_load.frequency) <= vch_load.frequency*eps_freq/100 and abs(sigma.freq_range - vch_load.freq_range) <= vch_load.freq_range*eps_frange/100:
+ if (
+ abs(sigma.transfer_frequency - vch_load.frequency) <= vch_load.frequency*eps_freq/100 and
+ abs(sigma.freq_range - vch_load.freq_range) <= vch_load.freq_range*eps_frange/100 and
+ sigma.polarization == vch_load.polarization
+ ):
sigma.parameter = vch_load
sigma.save()
link_count += 1
return obj_count, link_count
-
\ No newline at end of file
+def kub_report(data_in: io.StringIO) -> pd.DataFrame:
+ df_in = pd.read_excel(data_in)
+ df = pd.DataFrame(columns=['Дата', 'Широта', 'Долгота',
+ 'Высота', 'Населённый пункт', 'ИСЗ',
+ 'Прямой канал, МГц', 'Обратный канал, МГц', 'Перенос, МГц', 'Полоса, МГц', 'Зеркала'])
+ for row in df_in.iterrows():
+ value = row[1]
+ date = datetime.date(datetime.now())
+ lat = value['Широта, град']
+ lon = value['Долгота, град']
+ isz = value['ИСЗ']
+ downlink = value['Обратный канал, МГц']
+ freq_range = value['Полоса, МГц']
+ norad = int(re.findall(r'\((\d+)\)', isz)[0])
+ sat_obj = Satellite.objects.get(norad=norad)
+ pol_obj = Polarization.objects.get(name=value['Поляризация'].strip())
+ transponder = Transponders.objects.filter(
+ sat_id=sat_obj,
+ polarization=pol_obj,
+ downlink__gte=downlink - F('frequency_range')/2,
+ downlink__lte=downlink + F('frequency_range')/2,
+ ).first()
+ # try:
+ # location = geolocator.reverse(f"{lat}, {lon}", language="ru").raw['address']
+ # loc_name = location.get('city', '') or location.get('town', '') or location.get('province', '') or location.get('country', '')
+ # except AttributeError:
+ # loc_name = ''
+ # time.sleep(1)
+ loc_name = ''
+ if transponder: #and not (len(transponder) > 1):
+ transfer = transponder.transfer
+ uplink = transfer + downlink
+ new_row = pd.DataFrame([{'Дата': date,
+ 'Широта': lat,
+ 'Долгота': lon,
+ 'Высота': 0.0,
+ 'Населённый пункт': loc_name,
+ 'ИСЗ': isz,
+ 'Прямой канал, МГц': uplink,
+ 'Обратный канал, МГц': downlink,
+ 'Перенос, МГц': transfer,
+ 'Полоса, МГц': freq_range,
+ 'Зеркала': ''
+ }])
+ df = pd.concat([df, new_row], ignore_index=True)
+ else:
+ print("Ничего не найдено в транспондерах")
+ return df
+
diff --git a/dbapp/mainapp/views.py b/dbapp/mainapp/views.py
index 564fc52..b5ec0d0 100644
--- a/dbapp/mainapp/views.py
+++ b/dbapp/mainapp/views.py
@@ -1,6 +1,6 @@
from django.shortcuts import render, redirect
from django.contrib import messages
-from django.http import JsonResponse
+from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_GET
from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator
@@ -13,13 +13,15 @@ from .utils import (
add_satellite_list,
get_points_from_csv,
get_vch_load_from_html,
- compare_and_link_vch_load
+ compare_and_link_vch_load,
+ kub_report
)
-from mapsapp.utils import parse_transponders_from_json
-from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm
+from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
+from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm, UploadVchLoad, NewEventForm
from .models import ObjItem
from .clusters import get_clusters
-from dbapp.settings import BASE_DIR
+from io import BytesIO
+
class AddSatellitesView(View):
@@ -27,13 +29,33 @@ class AddSatellitesView(View):
add_satellite_list()
return redirect('home')
-class AddTranspondersView(View):
- def get(self, request):
+# class AddTranspondersView(View):
+# def get(self, request):
+# try:
+# parse_transponders_from_json(BASE_DIR / "transponders.json")
+# except FileNotFoundError:
+# print("Файл не найден")
+# return redirect('home')
+
+class AddTranspondersView(FormView):
+ template_name = 'mainapp/transponders_upload.html'
+ form_class = UploadFileForm
+
+ def form_valid(self, form):
+ uploaded_file = self.request.FILES['file']
try:
- parse_transponders_from_json(BASE_DIR / "transponders.json")
- except FileNotFoundError:
- print("Файл не найден")
- return redirect('home')
+ content = uploaded_file.read()
+ parse_transponders_from_xml(BytesIO(content))
+ messages.success(self.request, "Файл успешно обработан")
+ except ValueError as e:
+ messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
+ except Exception as e:
+ messages.error(self.request, f"Неизвестная ошибка: {e}")
+ return redirect('add_trans')
+
+ def form_invalid(self, form):
+ messages.error(self.request, "Форма заполнена некорректно.")
+ return super().form_invalid(form)
class HomePageView(TemplateView):
template_name = 'mainapp/home.html'
@@ -118,22 +140,7 @@ class LoadCsvDataView(FormView):
messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form)
-# def upload_file(request):
-# if request.method == 'POST' and request.FILES:
-# form = UploadFileForm(request.POST, request.FILES)
-# if form.is_valid():
-# uploaded_file = request.FILES['file']
-# # Обработка текстового файла, например:
-# df = pd.read_csv(uploaded_file)
-# df = pd.read_csv(filepath, sep=";",
-# names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
-# df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
-# df['time'] = pd.to_datetime(df['time'], format='%d.%m.%Y %H:%M:%S')
-# get_points_from_csv(df)
-# return JsonResponse({'status': 'success'})
-# else:
-# return JsonResponse({'status': 'error', 'errors': form.errors}, status=400)
-# return render(request, 'mainapp/add_data_from_csv.html')
+
from collections import defaultdict
@method_decorator(staff_member_required, name='dispatch')
@@ -162,7 +169,6 @@ class ShowMapView(UserPassesTestMixin, View):
'frequency': p["freq"]
})
- # Преобразуем в список словарей для удобства в шаблоне
groups = [
{
"name": name,
@@ -190,7 +196,7 @@ class ClusterTestView(View):
class UploadVchLoadView(FormView):
template_name = 'mainapp/upload_html.html'
- form_class = UploadFileForm
+ form_class = UploadVchLoad
def form_valid(self, form):
selected_sat = form.cleaned_data['sat_choice']
@@ -224,4 +230,38 @@ class LinkVchSigmaView(FormView):
return redirect('link_vch_sigma')
def form_invalid(self, form):
- return self.render_to_response(self.get_context_data(form=form))
\ No newline at end of file
+ return self.render_to_response(self.get_context_data(form=form))
+
+
+class ProcessKubsatView(FormView):
+ template_name = 'mainapp/process_kubsat.html'
+ form_class = NewEventForm
+
+ def form_valid(self, form):
+ # selected_sat = form.cleaned_data['sat_choice']
+ # selected_pol = form.cleaned_data['pol_choice']
+ uploaded_file = self.request.FILES['file']
+ try:
+ content = uploaded_file.read()
+ df = kub_report(BytesIO(content))
+ output = BytesIO()
+ with pd.ExcelWriter(output, engine='openpyxl') as writer:
+ df.to_excel(writer, index=False, sheet_name='Результат')
+ output.seek(0)
+
+ response = HttpResponse(
+ output.getvalue(),
+ content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ )
+ response['Content-Disposition'] = f'attachment; filename="kubsat_report.xlsx"'
+
+ messages.success(self.request, "Событие успешно обработано!")
+ return response
+ except Exception as e:
+ messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
+ return redirect('kubsat_excel')
+ # return redirect('kubsat_excel')
+
+ def form_invalid(self, form):
+ messages.error(self.request, "Форма заполнена некорректно.")
+ return super().form_invalid(form)
\ No newline at end of file
diff --git a/dbapp/mapsapp/admin.py b/dbapp/mapsapp/admin.py
index c755340..d0b9bf2 100644
--- a/dbapp/mapsapp/admin.py
+++ b/dbapp/mapsapp/admin.py
@@ -5,20 +5,24 @@ from more_admin_filters import MultiSelectDropdownFilter, MultiSelectFilter, Mul
from import_export.admin import ImportExportActionModelAdmin
@admin.register(Transponders)
-class PolarizationAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
+class TranspondersAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
list_display = (
"sat_id",
"name",
"zone_name",
- "frequency",
+ "downlink",
+ "uplink",
"frequency_range",
+ "transfer",
"polarization",
)
list_filter = (
("polarization", MultiSelectRelatedDropdownFilter),
("sat_id", MultiSelectRelatedDropdownFilter),
- ("frequency", NumericRangeFilterBuilder()),
+ # ("frequency", NumericRangeFilterBuilder()),
"zone_name"
)
- search_fields = ("name",)
+ search_fields = ("name", "sat_id__name")
ordering = ("name",)
+ # def sat_name(self, obj):
+ # return
diff --git a/dbapp/mapsapp/migrations/0002_remove_transponders_frequency_transponders_downlink_and_more.py b/dbapp/mapsapp/migrations/0002_remove_transponders_frequency_transponders_downlink_and_more.py
new file mode 100644
index 0000000..4629613
--- /dev/null
+++ b/dbapp/mapsapp/migrations/0002_remove_transponders_frequency_transponders_downlink_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 5.2.7 on 2025-10-27 12:20
+
+import django.db.models.expressions
+import django.db.models.functions.math
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mapsapp', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='transponders',
+ name='frequency',
+ ),
+ migrations.AddField(
+ model_name='transponders',
+ name='downlink',
+ field=models.FloatField(blank=True, null=True, verbose_name='Downlink'),
+ ),
+ migrations.AddField(
+ model_name='transponders',
+ name='uplink',
+ field=models.FloatField(blank=True, null=True, verbose_name='Uplink'),
+ ),
+ migrations.AddField(
+ model_name='transponders',
+ name='transfer',
+ field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео, км'),
+ ),
+ ]
diff --git a/dbapp/mapsapp/migrations/0003_alter_transponders_transfer.py b/dbapp/mapsapp/migrations/0003_alter_transponders_transfer.py
new file mode 100644
index 0000000..50a890d
--- /dev/null
+++ b/dbapp/mapsapp/migrations/0003_alter_transponders_transfer.py
@@ -0,0 +1,20 @@
+# Generated by Django 5.2.7 on 2025-10-27 13:10
+
+import django.db.models.expressions
+import django.db.models.functions.math
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mapsapp', '0002_remove_transponders_frequency_transponders_downlink_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='transponders',
+ name='transfer',
+ field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Перенос'),
+ ),
+ ]
diff --git a/dbapp/mapsapp/migrations/0004_alter_transponders_zone_name.py b/dbapp/mapsapp/migrations/0004_alter_transponders_zone_name.py
new file mode 100644
index 0000000..ff585f7
--- /dev/null
+++ b/dbapp/mapsapp/migrations/0004_alter_transponders_zone_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-10-28 05:44
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mapsapp', '0003_alter_transponders_transfer'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='transponders',
+ name='zone_name',
+ field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Название зоны'),
+ ),
+ ]
diff --git a/dbapp/mapsapp/migrations/0005_alter_transponders_frequency_range.py b/dbapp/mapsapp/migrations/0005_alter_transponders_frequency_range.py
new file mode 100644
index 0000000..99f1ab7
--- /dev/null
+++ b/dbapp/mapsapp/migrations/0005_alter_transponders_frequency_range.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-10-29 14:00
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mapsapp', '0004_alter_transponders_zone_name'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='transponders',
+ name='frequency_range',
+ field=models.FloatField(blank=True, null=True, verbose_name='Полоса'),
+ ),
+ ]
diff --git a/dbapp/mapsapp/models.py b/dbapp/mapsapp/models.py
index edc5d79..6e7e8ed 100644
--- a/dbapp/mapsapp/models.py
+++ b/dbapp/mapsapp/models.py
@@ -1,15 +1,27 @@
from django.db import models
from mainapp.models import Satellite, Polarization, get_default_polarization
+from django.db.models import F, ExpressionWrapper
+from django.db.models.functions import Abs
class Transponders(models.Model):
name = models.CharField(max_length=30, null=True, blank=True, verbose_name="Название транспондера")
- frequency = models.FloatField(blank=True, null=True, verbose_name="Центральная частота")
- frequency_range = models.FloatField(blank=True, null=True, verbose_name="Полоса частот")
- zone_name = models.CharField(max_length=60, blank=True, null=True, verbose_name="Название зоны")
+ downlink = models.FloatField(blank=True, null=True, verbose_name="Downlink")
+ frequency_range = models.FloatField(blank=True, null=True, verbose_name="Полоса")
+ uplink = models.FloatField(blank=True, null=True, verbose_name="Uplink")
+ zone_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="Название зоны")
polarization = models.ForeignKey(
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="tran_polarizations", null=True, blank=True, verbose_name="Поляризация"
)
sat_id = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="tran_satellite", verbose_name="Спутник")
+ transfer =models.GeneratedField(
+ expression=ExpressionWrapper(
+ Abs(F('downlink') - F('uplink')),
+ output_field=models.FloatField()
+ ),
+ output_field=models.FloatField(),
+ db_persist=True,
+ null=True, blank=True, verbose_name="Перенос"
+ )
def __str__(self):
return self.name
diff --git a/dbapp/mapsapp/utils.py b/dbapp/mapsapp/utils.py
index e56a3e3..2c01317 100644
--- a/dbapp/mapsapp/utils.py
+++ b/dbapp/mapsapp/utils.py
@@ -3,6 +3,7 @@ import re
import json
from .models import Transponders
from mainapp.models import Polarization, Satellite
+from io import BytesIO
def search_satellite_on_page(data: dict, satellite_name: str):
for pos, value in data.get('page', {}).get('positions').items():
@@ -90,3 +91,68 @@ def parse_transponders_from_json(filepath: str):
)
tran_obj.save()
+
+from lxml import etree
+
+def parse_transponders_from_xml(data_in: BytesIO):
+
+ tree = etree.parse(data_in)
+ ns = {
+ 'i': 'http://www.w3.org/2001/XMLSchema-instance',
+ 'ns': 'http://schemas.datacontract.org/2004/07/Geolocation.Domain.Utils.Repository.SatellitesSerialization.Memos',
+ 'tr': 'http://schemas.datacontract.org/2004/07/Geolocation.Common.Extensions'
+ }
+ satellites = tree.xpath('//ns:SatelliteMemo', namespaces=ns)
+ for sat in satellites[:]:
+ name = sat.xpath('./ns:name/text()', namespaces=ns)[0]
+ if name == 'X' or 'DONT USE' in name:
+ continue
+ norad = sat.xpath('./ns:norad/text()', namespaces=ns)
+ beams = sat.xpath('.//ns:BeamMemo', namespaces=ns)
+ zones = {}
+ for zone in beams:
+ zone_name = zone.xpath('./ns:name/text()', namespaces=ns)[0] if zone.xpath('./ns:name/text()', namespaces=ns) else '-'
+ zones[zone.xpath('./ns:id/text()', namespaces=ns)[0]] = {
+ "name": zone_name,
+ "pol": zone.xpath('./ns:polarization/text()', namespaces=ns)[0],
+ }
+ transponders = sat.xpath('.//ns:TransponderMemo', namespaces=ns)
+ for transponder in transponders:
+ tr_id = transponder.xpath('./ns:downlinkBeamId/text()', namespaces=ns)[0]
+ downlink_start = float(transponder.xpath('./ns:downlinkFrequency/tr:start/text()', namespaces=ns)[0])
+ downlink_end = float(transponder.xpath('./ns:downlinkFrequency/tr:end/text()', namespaces=ns)[0])
+ uplink_start = float(transponder.xpath('./ns:uplinkFrequency/tr:start/text()', namespaces=ns)[0])
+ uplink_end = float(transponder.xpath('./ns:uplinkFrequency/tr:end/text()', namespaces=ns)[0])
+ tr_data = zones[tr_id]
+ # p = tr_data['pol'][0] if tr_data['pol'] else '-'
+ match tr_data['pol']:
+ case 'Horizontal':
+ pol = 'Горизонтальная'
+ case 'Vertical':
+ pol = 'Вертикальная'
+ case 'CircularRight':
+ pol = 'Правая'
+ case 'CircularLeft':
+ pol = 'Левая'
+ case _:
+ pol = '-'
+ tr_name = transponder.xpath('./ns:name/text()', namespaces=ns)[0]
+
+ pol_obj, _ = Polarization.objects.get_or_create(name=pol)
+ sat_obj, _ = Satellite.objects.get_or_create(
+ name=name,
+ defaults={
+ "norad": int(norad[0]) if norad else -1
+ })
+ trans_obj, _ = Transponders.objects.get_or_create(
+ polarization=pol_obj,
+ downlink=(downlink_start+downlink_end)/2/1000000,
+ uplink=(uplink_start+uplink_end)/2/1000000,
+ frequency_range=abs(downlink_end-downlink_start)/1000000,
+ name=tr_name,
+ defaults={
+ "zone_name": tr_data['name'],
+ "sat_id": sat_obj,
+ }
+ )
+ trans_obj.save()
diff --git a/dbapp/pyproject.toml b/dbapp/pyproject.toml
index 8097f46..cbeb3f6 100644
--- a/dbapp/pyproject.toml
+++ b/dbapp/pyproject.toml
@@ -19,7 +19,9 @@ dependencies = [
"django-leaflet>=0.32.0",
"django-map-widgets>=0.5.1",
"django-more-admin-filters>=1.13",
- "gdal",
+ "dotenv>=0.9.9",
+ "geopy>=2.4.1",
+ "gunicorn>=23.0.0",
"lxml>=6.0.2",
"matplotlib>=3.10.7",
"numpy>=2.3.3",
@@ -28,9 +30,11 @@ dependencies = [
"psycopg>=3.2.10",
"redis>=6.4.0",
"requests>=2.32.5",
+ "reverse-geocoder>=1.5.1",
"scikit-learn>=1.7.2",
"setuptools>=80.9.0",
]
-[tool.uv.sources]
-gdal = { path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" }
+
+[dependency-groups]
+dev = []
diff --git a/dbapp/uv.lock b/dbapp/uv.lock
index c04d333..f0c03a5 100644
--- a/dbapp/uv.lock
+++ b/dbapp/uv.lock
@@ -212,7 +212,9 @@ dependencies = [
{ name = "django-leaflet" },
{ name = "django-map-widgets" },
{ name = "django-more-admin-filters" },
- { name = "gdal" },
+ { name = "dotenv" },
+ { name = "geopy" },
+ { name = "gunicorn" },
{ name = "lxml" },
{ name = "matplotlib" },
{ name = "numpy" },
@@ -221,6 +223,7 @@ dependencies = [
{ name = "psycopg" },
{ name = "redis" },
{ name = "requests" },
+ { name = "reverse-geocoder" },
{ name = "scikit-learn" },
{ name = "setuptools" },
]
@@ -241,7 +244,9 @@ requires-dist = [
{ name = "django-leaflet", specifier = ">=0.32.0" },
{ name = "django-map-widgets", specifier = ">=0.5.1" },
{ name = "django-more-admin-filters", specifier = ">=1.13" },
- { name = "gdal", path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" },
+ { name = "dotenv", specifier = ">=0.9.9" },
+ { name = "geopy", specifier = ">=2.4.1" },
+ { name = "gunicorn", specifier = ">=23.0.0" },
{ name = "lxml", specifier = ">=6.0.2" },
{ name = "matplotlib", specifier = ">=3.10.7" },
{ name = "numpy", specifier = ">=2.3.3" },
@@ -250,10 +255,14 @@ requires-dist = [
{ name = "psycopg", specifier = ">=3.2.10" },
{ name = "redis", specifier = ">=6.4.0" },
{ name = "requests", specifier = ">=2.32.5" },
+ { name = "reverse-geocoder", specifier = ">=1.5.1" },
{ name = "scikit-learn", specifier = ">=1.7.2" },
{ name = "setuptools", specifier = ">=80.9.0" },
]
+[package.metadata.requires-dev]
+dev = []
+
[[package]]
name = "diff-match-patch"
version = "20241021"
@@ -410,6 +419,17 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/87/7c/4b261b96b357d94ef267f39856ef0bb72a33f078a38bd22ee96d168fe272/django_more_admin_filters-1.13-py3-none-any.whl", hash = "sha256:df4d46e4b589566b85f149ea5b7558c6cc4ae22b0d264973f8d4a2d478ef5120", size = 147360, upload-time = "2025-06-06T11:26:42.964Z" },
]
+[[package]]
+name = "dotenv"
+version = "0.9.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dotenv" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
+]
+
[[package]]
name = "et-xmlfile"
version = "2.0.0"
@@ -453,16 +473,37 @@ wheels = [
]
[[package]]
-name = "gdal"
-version = "3.10.2"
-source = { path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" }
+name = "geographiclib"
+version = "2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/78/4892343230a9d29faa1364564e525307a37e54ad776ea62c12129dbba704/geographiclib-2.1.tar.gz", hash = "sha256:6a6545e6262d0ed3522e13c515713718797e37ed8c672c31ad7b249f372ef108", size = 37004, upload-time = "2025-08-21T21:34:26Z" }
wheels = [
- { filename = "gdal-3.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:d6aae781b9847065f831f3457c6c01d0b9272818656031d723dc88c160a8ec26" },
+ { url = "https://files.pythonhosted.org/packages/31/b3/802576f2ea5dcb48501bb162e4c7b7b3ca5654a42b2c968ef98a797a4c79/geographiclib-2.1-py3-none-any.whl", hash = "sha256:e2a873b9b9e7fc38721ad73d5f4e6c9ed140d428a339970f505c07056997d40b", size = 40740, upload-time = "2025-08-21T21:34:24.955Z" },
]
-[package.metadata]
-requires-dist = [{ name = "numpy", marker = "extra == 'numpy'", specifier = ">1.0.0" }]
-provides-extras = ["numpy"]
+[[package]]
+name = "geopy"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "geographiclib" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/fd/ef6d53875ceab72c1fad22dbed5ec1ad04eb378c2251a6a8024bad890c3b/geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1", size = 117625, upload-time = "2023-11-23T21:49:32.734Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/15/cf2a69ade4b194aa524ac75112d5caac37414b20a3a03e6865dfe0bd1539/geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7", size = 125437, upload-time = "2023-11-23T21:49:30.421Z" },
+]
+
+[[package]]
+name = "gunicorn"
+version = "23.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
+]
[[package]]
name = "idna"
@@ -851,6 +892,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
+[[package]]
+name = "python-dotenv"
+version = "1.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
+]
+
[[package]]
name = "python-slugify"
version = "8.0.4"
@@ -896,6 +946,16 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
+[[package]]
+name = "reverse-geocoder"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+ { name = "scipy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0b/0f/b7d5d4b36553731f11983e19e1813a1059ad0732c5162c01b3220c927d31/reverse_geocoder-1.5.1.tar.gz", hash = "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c", size = 2246559, upload-time = "2016-09-15T16:46:46.277Z" }
+
[[package]]
name = "scikit-learn"
version = "1.7.2"