working logic
This commit is contained in:
commit
ccb53d9091
7 changed files with 534 additions and 0 deletions
10
.idea/HelioParser.iml
generated
Normal file
10
.idea/HelioParser.iml
generated
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.12 (HelioParser)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (HelioParser)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (HelioParser)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/HelioParser.iml" filepath="$PROJECT_DIR$/.idea/HelioParser.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
48
.idea/workspace.xml
generated
Normal file
48
.idea/workspace.xml
generated
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="01167edf-a4fe-4b9b-bd49-22d768ae9f8b" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"associatedIndex": 1
|
||||
}]]></component>
|
||||
<component name="ProjectId" id="3EwIJwKPDl7xmBDbQQN0GJ515Pu" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-PY-253.30387.173" />
|
||||
<option value="bundled-python-sdk-4762d8aabb82-6d6dccd035ac-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.30387.173" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="01167edf-a4fe-4b9b-bd49-22d768ae9f8b" name="Changes" comment="" />
|
||||
<created>1781083136733</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1781083136733</updated>
|
||||
<workItem from="1781083137783" duration="1000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
449
main.py
Normal file
449
main.py
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
import requests
|
||||
from datetime import datetime
|
||||
import os
|
||||
import json
|
||||
|
||||
|
||||
# --- Модель (Model) ---
|
||||
class HelioviewerModel:
|
||||
"""
|
||||
Модель отвечает за взаимодействие с Helioviewer API.
|
||||
Она не зависит от интерфейса и содержит только бизнес-логику.
|
||||
"""
|
||||
BASE_URL = "https://api.helioviewer.org/v1/"
|
||||
|
||||
def __init__(self):
|
||||
self.datasources = {} # Словарь для хранения доступных источников: {source_id: описание}
|
||||
# Используем предустановленный список популярных каналов вместо динамической загрузки
|
||||
self.init_default_sources()
|
||||
|
||||
def init_default_sources(self):
|
||||
"""Инициализирует список популярных источников данных."""
|
||||
self.datasources = {
|
||||
# SDO (Solar Dynamics Observatory)
|
||||
14: "🌞 SDO - AIA 335 (Fe XVI, корона, 2 млн K)",
|
||||
13: "🔥 SDO - AIA 304 (He II, хромосфера, протуберанцы)",
|
||||
12: "🌊 SDO - AIA 211 (Fe XIV, активные области)",
|
||||
11: "💥 SDO - AIA 193 (Fe XII, корональные выбросы массы)",
|
||||
10: "🌀 SDO - AIA 171 (Fe IX, спокойная корона, петли)",
|
||||
9: "⚡ SDO - AIA 131 (Fe VIII, вспышечная плазма)",
|
||||
8: "❄️ SDO - AIA 94 (Fe XVIII, горячие вспышки)",
|
||||
|
||||
# SOHO (Solar and Heliospheric Observatory)
|
||||
0: "👁️ SOHO - EIT 171 (Fe IX/X)",
|
||||
2: "🟡 SOHO - EIT 284 (Fe XV)",
|
||||
4: "🌑 SOHO - LASCO C2 (коронограф, видимый свет)",
|
||||
5: "🌘 SOHO - LASCO C3 (коронограф, широкое поле)",
|
||||
|
||||
# STEREO A и B
|
||||
16: "⭐ STEREO A - EUVI 195",
|
||||
17: "⭐ STEREO A - EUVI 171",
|
||||
18: "⭐ STEREO A - EUVI 304",
|
||||
19: "⭐ STEREO A - COR1",
|
||||
20: "⭐ STEREO A - COR2",
|
||||
|
||||
# Дополнительные каналы
|
||||
1: "📊 SOHO - MDI (магнитограмма)",
|
||||
3: "🌡️ SOHO - EIT 304 (He II)",
|
||||
}
|
||||
|
||||
def load_datasources_from_api(self):
|
||||
"""Пытается загрузить актуальный список источников из API (экспериментально)."""
|
||||
url = f"{self.BASE_URL}getDataSources/"
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
# API может вернуть JSON или строку
|
||||
if response.headers.get('content-type', '').startswith('application/json'):
|
||||
data = response.json()
|
||||
if isinstance(data, list):
|
||||
new_sources = {}
|
||||
for source in data:
|
||||
if isinstance(source, dict) and 'sourceId' in source:
|
||||
source_id = source.get('sourceId')
|
||||
# Формируем читаемое описание
|
||||
name = f"{source.get('observatory', '?')} - {source.get('instrument', '?')}"
|
||||
measurement = source.get('measurement', '')
|
||||
if measurement:
|
||||
name += f" - {measurement}"
|
||||
new_sources[source_id] = name
|
||||
|
||||
if new_sources:
|
||||
self.datasources = new_sources
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Не удалось загрузить источники из API: {e}")
|
||||
|
||||
return False
|
||||
|
||||
def get_jp2_image(self, source_id, date, jpip_link=False):
|
||||
"""
|
||||
Получает JP2 изображение или JPIP ссылку на него.
|
||||
|
||||
Args:
|
||||
source_id (int): ID источника данных.
|
||||
date (datetime): Желаемая дата и время снимка.
|
||||
jpip_link (bool): Если True, возвращает JPIP-ссылку (строку).
|
||||
Если False, возвращает бинарные данные изображения.
|
||||
|
||||
Returns:
|
||||
bytes or str: Данные изображения или JPIP-ссылка. None в случае ошибки.
|
||||
"""
|
||||
# Форматируем дату в ISO 8601 UTC, как требует API
|
||||
formatted_date = date.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
if jpip_link:
|
||||
# Для JPIP ссылки используем отдельный эндпоинт
|
||||
url = f"{self.BASE_URL}getJPIPClosest/"
|
||||
params = {
|
||||
'sourceId': source_id,
|
||||
'date': formatted_date
|
||||
}
|
||||
else:
|
||||
# Для скачивания изображения
|
||||
url = f"{self.BASE_URL}getJP2Image/"
|
||||
params = {
|
||||
'sourceId': source_id,
|
||||
'date': formatted_date
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, params=params, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
if jpip_link:
|
||||
# Возвращаем текст (JPIP ссылку)
|
||||
return response.text.strip()
|
||||
else:
|
||||
# Возвращаем бинарные данные изображения
|
||||
return response.content
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Ошибка при загрузке: {e}")
|
||||
return None
|
||||
|
||||
def get_available_sources(self):
|
||||
"""Возвращает словарь доступных источников данных."""
|
||||
return self.datasources
|
||||
|
||||
|
||||
# --- Представление (View) ---
|
||||
class HelioviewerView:
|
||||
"""
|
||||
Представление отвечает за графический интерфейс пользователя (GUI).
|
||||
"""
|
||||
|
||||
def __init__(self, root, controller):
|
||||
self.controller = controller
|
||||
self.root = root
|
||||
self.root.title("Helioviewer Солнечный загрузчик")
|
||||
self.root.geometry("900x700")
|
||||
self.root.resizable(True, True)
|
||||
|
||||
# Устанавливаем иконку (опционально)
|
||||
try:
|
||||
self.root.iconbitmap(default='icon.ico')
|
||||
except:
|
||||
pass
|
||||
|
||||
# Стили
|
||||
style = ttk.Style()
|
||||
style.theme_use('clam')
|
||||
|
||||
# Настройка цветов (темная тема для астрономического приложения)
|
||||
self.root.configure(bg='#2b2b2b')
|
||||
style.configure('TLabel', background='#2b2b2b', foreground='white')
|
||||
style.configure('TFrame', background='#2b2b2b')
|
||||
style.configure('TLabelframe', background='#2b2b2b', foreground='white')
|
||||
style.configure('TLabelframe.Label', background='#2b2b2b', foreground='white')
|
||||
style.configure('TButton', background='#3c3c3c', foreground='white')
|
||||
style.configure('TCombobox', fieldbackground='#3c3c3c', foreground='white')
|
||||
|
||||
# Основной фрейм с прокруткой
|
||||
self.canvas = tk.Canvas(root, bg='#2b2b2b', highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
|
||||
self.scrollable_frame = ttk.Frame(self.canvas)
|
||||
|
||||
self.scrollable_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
||||
)
|
||||
|
||||
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
||||
self.canvas.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
# Показываем прокрутку только если нужно
|
||||
self.canvas.pack(side="left", fill="both", expand=True)
|
||||
scrollbar.pack(side="right", fill="y")
|
||||
|
||||
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
|
||||
|
||||
main_frame = self.scrollable_frame
|
||||
|
||||
# Заголовок
|
||||
title_label = ttk.Label(main_frame, text="🌞 Helioviewer Солнечный загрузчик",
|
||||
font=('Arial', 16, 'bold'))
|
||||
title_label.pack(pady=10)
|
||||
|
||||
# 1. Фрейм для выбора источника данных
|
||||
source_frame = ttk.LabelFrame(main_frame, text="📡 1. Выбор спектрального канала", padding="10")
|
||||
source_frame.pack(fill=tk.X, pady=(0, 10), padx=10)
|
||||
|
||||
# Создаем фрейм с прокруткой для списка каналов
|
||||
source_list_frame = ttk.Frame(source_frame)
|
||||
source_list_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Текстовая метка
|
||||
ttk.Label(source_list_frame, text="Доступные инструменты и спектры:").pack(anchor=tk.W, pady=(0, 5))
|
||||
|
||||
# Список с прокруткой для выбора канала
|
||||
list_frame = ttk.Frame(source_list_frame)
|
||||
list_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
scrollbar_list = ttk.Scrollbar(list_frame)
|
||||
scrollbar_list.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
self.source_listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar_list.set,
|
||||
height=10, bg='#3c3c3c', fg='white',
|
||||
selectmode=tk.SINGLE, font=('Consolas', 9))
|
||||
self.source_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar_list.config(command=self.source_listbox.yview)
|
||||
|
||||
# Привязываем выбор
|
||||
self.source_listbox.bind('<<ListboxSelect>>', self.on_source_selected)
|
||||
|
||||
# Кнопка обновления
|
||||
refresh_button = ttk.Button(source_frame, text="🔄 Обновить список",
|
||||
command=self.controller.refresh_sources)
|
||||
refresh_button.pack(pady=(10, 0))
|
||||
|
||||
# 2. Фрейм для выбора даты
|
||||
date_frame = ttk.LabelFrame(main_frame, text="📅 2. Выбор даты и времени (UTC)", padding="10")
|
||||
date_frame.pack(fill=tk.X, pady=(0, 10), padx=10)
|
||||
|
||||
# Дата
|
||||
date_inner_frame = ttk.Frame(date_frame)
|
||||
date_inner_frame.pack(fill=tk.X, pady=5)
|
||||
ttk.Label(date_inner_frame, text="Дата (ГГГГ-ММ-ДД):").pack(side=tk.LEFT, padx=(0, 5))
|
||||
self.date_entry = ttk.Entry(date_inner_frame, width=15)
|
||||
self.date_entry.pack(side=tk.LEFT, padx=(0, 10))
|
||||
self.date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
|
||||
|
||||
# Кнопка "Сегодня"
|
||||
today_button = ttk.Button(date_inner_frame, text="Сегодня",
|
||||
command=self.set_today_date)
|
||||
today_button.pack(side=tk.LEFT)
|
||||
|
||||
# Время
|
||||
time_inner_frame = ttk.Frame(date_frame)
|
||||
time_inner_frame.pack(fill=tk.X, pady=5)
|
||||
ttk.Label(time_inner_frame, text="Время (ЧЧ:ММ:СС):").pack(side=tk.LEFT, padx=(0, 5))
|
||||
self.time_entry = ttk.Entry(time_inner_frame, width=15)
|
||||
self.time_entry.pack(side=tk.LEFT)
|
||||
self.time_entry.insert(0, "12:00:00")
|
||||
|
||||
# Кнопка "Сейчас"
|
||||
now_button = ttk.Button(time_inner_frame, text="Сейчас",
|
||||
command=self.set_now_time)
|
||||
now_button.pack(side=tk.LEFT, padx=(10, 0))
|
||||
|
||||
ttk.Label(time_inner_frame, text=" (Время в UTC)",
|
||||
font=('TkDefaultFont', 8, 'italic')).pack(side=tk.LEFT, padx=(5, 0))
|
||||
|
||||
# 3. Фрейм для кнопок действий
|
||||
action_frame = ttk.LabelFrame(main_frame, text="⚡ 3. Действия", padding="10")
|
||||
action_frame.pack(fill=tk.X, pady=(0, 10), padx=10)
|
||||
|
||||
button_frame = ttk.Frame(action_frame)
|
||||
button_frame.pack()
|
||||
|
||||
self.download_button = ttk.Button(button_frame, text="💾 Скачать изображение (JP2)",
|
||||
command=self.controller.download_image,
|
||||
width=25)
|
||||
self.download_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
self.get_jpip_button = ttk.Button(button_frame, text="🔗 Получить JPIP ссылку",
|
||||
command=self.controller.get_jpip_link,
|
||||
width=25)
|
||||
self.get_jpip_button.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 4. Текстовая область для вывода информации
|
||||
info_frame = ttk.LabelFrame(main_frame, text="📝 4. Информация / JPIP ссылка", padding="10")
|
||||
info_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10), padx=10)
|
||||
|
||||
self.info_text = tk.Text(info_frame, height=10, wrap=tk.WORD,
|
||||
bg='#1e1e1e', fg='#00ff00',
|
||||
selectbackground='#004400')
|
||||
scrollbar_info = ttk.Scrollbar(info_frame, orient=tk.VERTICAL, command=self.info_text.yview)
|
||||
self.info_text.configure(yscrollcommand=scrollbar_info.set)
|
||||
scrollbar_info.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
self.info_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Статус бар
|
||||
self.status_var = tk.StringVar()
|
||||
self.status_var.set("🌙 Готов. Выберите спектральный канал и дату.")
|
||||
status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN,
|
||||
anchor=tk.W, padding=(5, 3))
|
||||
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
# Выделенный ID источника
|
||||
self.selected_source_id = None
|
||||
|
||||
def _on_mousewheel(self, event):
|
||||
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
||||
|
||||
def on_source_selected(self, event):
|
||||
selection = self.source_listbox.curselection()
|
||||
if selection:
|
||||
item_text = self.source_listbox.get(selection[0])
|
||||
if ":" in item_text:
|
||||
try:
|
||||
self.selected_source_id = int(item_text.split(":", 1)[0])
|
||||
self.update_status(f"Выбран канал: {item_text}")
|
||||
except ValueError:
|
||||
self.selected_source_id = None
|
||||
|
||||
def set_sources(self, sources_dict):
|
||||
"""Обновляет список источников."""
|
||||
self.source_listbox.delete(0, tk.END)
|
||||
if not sources_dict:
|
||||
self.source_listbox.insert(tk.END, "Нет доступных источников")
|
||||
return
|
||||
|
||||
# Сортируем по ID
|
||||
for src_id in sorted(sources_dict.keys()):
|
||||
display_text = f"{src_id}: {sources_dict[src_id]}"
|
||||
self.source_listbox.insert(tk.END, display_text)
|
||||
|
||||
if self.source_listbox.size() > 0:
|
||||
self.source_listbox.selection_set(0)
|
||||
self.on_source_selected(None)
|
||||
|
||||
def set_today_date(self):
|
||||
self.date_entry.delete(0, tk.END)
|
||||
self.date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
|
||||
|
||||
def set_now_time(self):
|
||||
self.time_entry.delete(0, tk.END)
|
||||
self.time_entry.insert(0, datetime.now().strftime("%H:%M:%S"))
|
||||
|
||||
def get_selected_source_id(self):
|
||||
return self.selected_source_id
|
||||
|
||||
def get_selected_datetime(self):
|
||||
"""Возвращает datetime объект из введенных пользователем даты и времени."""
|
||||
date_str = self.date_entry.get()
|
||||
time_str = self.time_entry.get()
|
||||
datetime_str = f"{date_str} {time_str}"
|
||||
try:
|
||||
return datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
|
||||
except ValueError:
|
||||
messagebox.showerror("Ошибка ввода",
|
||||
"Неверный формат даты или времени.\nИспользуйте ГГГГ-ММ-ДД и ЧЧ:ММ:СС.")
|
||||
return None
|
||||
|
||||
def show_info(self, message, is_error=False):
|
||||
"""Показывает сообщение в текстовой области."""
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
if is_error:
|
||||
self.info_text.insert(tk.END, f"[{timestamp}] ❌ {message}\n", 'error')
|
||||
self.info_text.tag_config('error', foreground='red')
|
||||
else:
|
||||
self.info_text.insert(tk.END, f"[{timestamp}] ✅ {message}\n", 'success')
|
||||
self.info_text.tag_config('success', foreground='#00ff00')
|
||||
self.info_text.see(tk.END)
|
||||
|
||||
def clear_info(self):
|
||||
"""Очищает текстовую область."""
|
||||
self.info_text.delete(1.0, tk.END)
|
||||
|
||||
def update_status(self, message):
|
||||
"""Обновляет текст в статус-баре."""
|
||||
self.status_var.set(message)
|
||||
|
||||
def ask_save_filename(self, default_name="helioviewer_image.jp2"):
|
||||
"""Открывает диалог для выбора пути сохранения файла."""
|
||||
filetypes = [("JPEG2000 files", "*.jp2"), ("All files", "*.*")]
|
||||
return filedialog.asksaveasfilename(defaultextension=".jp2",
|
||||
filetypes=filetypes,
|
||||
initialfile=default_name)
|
||||
|
||||
|
||||
# --- Контроллер (Controller) ---
|
||||
class HelioviewerController:
|
||||
"""
|
||||
Контроллер связывает Model и View.
|
||||
"""
|
||||
|
||||
def __init__(self, root):
|
||||
self.model = HelioviewerModel()
|
||||
self.view = HelioviewerView(root, self)
|
||||
self.refresh_sources() # Инициализируем список источников
|
||||
|
||||
def refresh_sources(self):
|
||||
"""Обновляет список источников."""
|
||||
self.view.update_status("🔄 Загрузка списка источников...")
|
||||
sources = self.model.get_available_sources()
|
||||
self.view.set_sources(sources)
|
||||
self.view.update_status(f"✅ Загружено {len(sources)} спектральных каналов")
|
||||
self.view.show_info(f"Загружено {len(sources)} источников данных")
|
||||
|
||||
def _perform_action(self, get_jpip):
|
||||
"""Общая логика для скачивания или получения ссылки."""
|
||||
source_id = self.view.get_selected_source_id()
|
||||
if source_id is None:
|
||||
messagebox.showwarning("Нет источника", "Пожалуйста, выберите спектральный канал.")
|
||||
return
|
||||
|
||||
date_time = self.view.get_selected_datetime()
|
||||
if date_time is None:
|
||||
return
|
||||
|
||||
action_name = "JPIP ссылки" if get_jpip else "изображения"
|
||||
self.view.update_status(f"📡 Запрос {action_name} для канала {source_id}...")
|
||||
self.view.show_info(f"Запрашиваю {action_name} для {date_time} (канал {source_id})")
|
||||
|
||||
result = self.model.get_jp2_image(source_id, date_time, jpip_link=get_jpip)
|
||||
|
||||
if result is None:
|
||||
self.view.update_status(f"❌ Не удалось получить {action_name}.")
|
||||
self.view.show_info(f"Не удалось получить {action_name}", is_error=True)
|
||||
return
|
||||
|
||||
if get_jpip:
|
||||
# Показываем JPIP ссылку
|
||||
self.view.show_info(f"JPIP ссылка получена:\n{result}")
|
||||
self.view.update_status("✅ JPIP ссылка получена. Можно вставить в JHelioviewer")
|
||||
else:
|
||||
# Сохраняем изображение
|
||||
default_name = f"solar_{source_id}_{date_time.strftime('%Y%m%d_%H%M%S')}.jp2"
|
||||
filename = self.view.ask_save_filename(default_name)
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(result)
|
||||
file_size = len(result) / 1024 # размер в КБ
|
||||
self.view.show_info(f"Изображение сохранено: {filename}\nРазмер: {file_size:.1f} KB")
|
||||
self.view.update_status(f"✅ Изображение сохранено: {os.path.basename(filename)}")
|
||||
except IOError as e:
|
||||
self.view.show_info(f"Ошибка сохранения: {e}", is_error=True)
|
||||
self.view.update_status("❌ Ошибка сохранения")
|
||||
else:
|
||||
self.view.update_status("Сохранение отменено")
|
||||
|
||||
def download_image(self):
|
||||
"""Скачивает изображение (JP2)"""
|
||||
self._perform_action(get_jpip=False)
|
||||
|
||||
def get_jpip_link(self):
|
||||
"""Получает JPIP ссылку"""
|
||||
self._perform_action(get_jpip=True)
|
||||
|
||||
|
||||
# --- Точка входа в программу ---
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = HelioviewerController(root)
|
||||
root.mainloop()
|
||||
Loading…
Add table
Add a link
Reference in a new issue