solarviewer 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. solar_radio_image_viewer/__init__.py +12 -0
  2. solar_radio_image_viewer/assets/add_tab_default.png +0 -0
  3. solar_radio_image_viewer/assets/add_tab_default_light.png +0 -0
  4. solar_radio_image_viewer/assets/add_tab_hover.png +0 -0
  5. solar_radio_image_viewer/assets/add_tab_hover_light.png +0 -0
  6. solar_radio_image_viewer/assets/browse.png +0 -0
  7. solar_radio_image_viewer/assets/browse_light.png +0 -0
  8. solar_radio_image_viewer/assets/close_tab_default.png +0 -0
  9. solar_radio_image_viewer/assets/close_tab_default_light.png +0 -0
  10. solar_radio_image_viewer/assets/close_tab_hover.png +0 -0
  11. solar_radio_image_viewer/assets/close_tab_hover_light.png +0 -0
  12. solar_radio_image_viewer/assets/ellipse_selection.png +0 -0
  13. solar_radio_image_viewer/assets/ellipse_selection_light.png +0 -0
  14. solar_radio_image_viewer/assets/icons8-ellipse-90.png +0 -0
  15. solar_radio_image_viewer/assets/icons8-ellipse-90_light.png +0 -0
  16. solar_radio_image_viewer/assets/icons8-info-90.png +0 -0
  17. solar_radio_image_viewer/assets/icons8-info-90_light.png +0 -0
  18. solar_radio_image_viewer/assets/profile.png +0 -0
  19. solar_radio_image_viewer/assets/profile_light.png +0 -0
  20. solar_radio_image_viewer/assets/rectangle_selection.png +0 -0
  21. solar_radio_image_viewer/assets/rectangle_selection_light.png +0 -0
  22. solar_radio_image_viewer/assets/reset.png +0 -0
  23. solar_radio_image_viewer/assets/reset_light.png +0 -0
  24. solar_radio_image_viewer/assets/ruler.png +0 -0
  25. solar_radio_image_viewer/assets/ruler_light.png +0 -0
  26. solar_radio_image_viewer/assets/search.png +0 -0
  27. solar_radio_image_viewer/assets/search_light.png +0 -0
  28. solar_radio_image_viewer/assets/settings.png +0 -0
  29. solar_radio_image_viewer/assets/settings_light.png +0 -0
  30. solar_radio_image_viewer/assets/splash.fits +0 -0
  31. solar_radio_image_viewer/assets/zoom_60arcmin.png +0 -0
  32. solar_radio_image_viewer/assets/zoom_60arcmin_light.png +0 -0
  33. solar_radio_image_viewer/assets/zoom_in.png +0 -0
  34. solar_radio_image_viewer/assets/zoom_in_light.png +0 -0
  35. solar_radio_image_viewer/assets/zoom_out.png +0 -0
  36. solar_radio_image_viewer/assets/zoom_out_light.png +0 -0
  37. solar_radio_image_viewer/create_video.py +1345 -0
  38. solar_radio_image_viewer/dialogs.py +2665 -0
  39. solar_radio_image_viewer/from_simpl/__init__.py +184 -0
  40. solar_radio_image_viewer/from_simpl/caltable_visualizer.py +1001 -0
  41. solar_radio_image_viewer/from_simpl/dynamic_spectra_dialog.py +332 -0
  42. solar_radio_image_viewer/from_simpl/make_dynamic_spectra.py +351 -0
  43. solar_radio_image_viewer/from_simpl/pipeline_logger_gui.py +1232 -0
  44. solar_radio_image_viewer/from_simpl/simpl_theme.py +352 -0
  45. solar_radio_image_viewer/from_simpl/utils.py +984 -0
  46. solar_radio_image_viewer/from_simpl/view_dynamic_spectra_GUI.py +1975 -0
  47. solar_radio_image_viewer/helioprojective.py +1916 -0
  48. solar_radio_image_viewer/helioprojective_viewer.py +817 -0
  49. solar_radio_image_viewer/helioviewer_browser.py +1514 -0
  50. solar_radio_image_viewer/main.py +148 -0
  51. solar_radio_image_viewer/move_phasecenter.py +1269 -0
  52. solar_radio_image_viewer/napari_viewer.py +368 -0
  53. solar_radio_image_viewer/noaa_events/__init__.py +32 -0
  54. solar_radio_image_viewer/noaa_events/noaa_events.py +430 -0
  55. solar_radio_image_viewer/noaa_events/noaa_events_gui.py +1922 -0
  56. solar_radio_image_viewer/norms.py +293 -0
  57. solar_radio_image_viewer/radio_data_downloader/__init__.py +25 -0
  58. solar_radio_image_viewer/radio_data_downloader/radio_data_downloader.py +756 -0
  59. solar_radio_image_viewer/radio_data_downloader/radio_data_downloader_gui.py +528 -0
  60. solar_radio_image_viewer/searchable_combobox.py +220 -0
  61. solar_radio_image_viewer/solar_context/__init__.py +41 -0
  62. solar_radio_image_viewer/solar_context/active_regions.py +371 -0
  63. solar_radio_image_viewer/solar_context/cme_alerts.py +234 -0
  64. solar_radio_image_viewer/solar_context/context_images.py +297 -0
  65. solar_radio_image_viewer/solar_context/realtime_data.py +528 -0
  66. solar_radio_image_viewer/solar_data_downloader/__init__.py +35 -0
  67. solar_radio_image_viewer/solar_data_downloader/solar_data_downloader.py +1667 -0
  68. solar_radio_image_viewer/solar_data_downloader/solar_data_downloader_cli.py +901 -0
  69. solar_radio_image_viewer/solar_data_downloader/solar_data_downloader_gui.py +1210 -0
  70. solar_radio_image_viewer/styles.py +643 -0
  71. solar_radio_image_viewer/utils/__init__.py +32 -0
  72. solar_radio_image_viewer/utils/rate_limiter.py +255 -0
  73. solar_radio_image_viewer/utils.py +952 -0
  74. solar_radio_image_viewer/video_dialog.py +2629 -0
  75. solar_radio_image_viewer/video_utils.py +656 -0
  76. solar_radio_image_viewer/viewer.py +11174 -0
  77. solarviewer-1.0.2.dist-info/METADATA +343 -0
  78. solarviewer-1.0.2.dist-info/RECORD +82 -0
  79. solarviewer-1.0.2.dist-info/WHEEL +5 -0
  80. solarviewer-1.0.2.dist-info/entry_points.txt +8 -0
  81. solarviewer-1.0.2.dist-info/licenses/LICENSE +21 -0
  82. solarviewer-1.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,528 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Solar Conditions Data Fetcher - Fetches solar conditions for a specific date.
4
+
5
+ Data sources:
6
+ - Historical Kp Index: services.swpc.noaa.gov/text/daily-geomagnetic-indices.txt (30-day history)
7
+ - Historical F10.7 Flux: services.swpc.noaa.gov/text/daily-solar-indices.txt (30-day history)
8
+ - Current Solar Wind: services.swpc.noaa.gov/products/solar-wind/plasma-7-day.json (7-day only)
9
+
10
+ Note: For dates older than 30 days, data may not be available from SWPC.
11
+ """
12
+
13
+ import re
14
+ import json
15
+ import urllib.request
16
+ import urllib.error
17
+ from dataclasses import dataclass
18
+ from datetime import datetime, date, timedelta
19
+ from typing import Optional, Dict, Any, List, Tuple
20
+
21
+
22
+ @dataclass
23
+ class SolarWindData:
24
+ """Current solar wind conditions."""
25
+ timestamp: datetime
26
+ speed: float # km/s
27
+ density: float # protons/cm³
28
+ temperature: float # Kelvin
29
+
30
+ @property
31
+ def speed_category(self) -> str:
32
+ """Categorize solar wind speed."""
33
+ if self.speed < 350:
34
+ return "Slow"
35
+ elif self.speed < 500:
36
+ return "Normal"
37
+ elif self.speed < 700:
38
+ return "Elevated"
39
+ else:
40
+ return "High"
41
+
42
+
43
+ @dataclass
44
+ class KpIndexData:
45
+ """Kp geomagnetic index data for a date."""
46
+ event_date: date
47
+ ap_value: int # Daily Ap index
48
+ kp_values: List[float] # 8 Kp values for the day (one per 3-hour interval)
49
+
50
+ @property
51
+ def kp_max(self) -> float:
52
+ """Get maximum Kp for the day."""
53
+ return max(self.kp_values) if self.kp_values else 0
54
+
55
+ @property
56
+ def kp_avg(self) -> float:
57
+ """Get average Kp for the day."""
58
+ return sum(self.kp_values) / len(self.kp_values) if self.kp_values else 0
59
+
60
+ @property
61
+ def storm_level(self) -> str:
62
+ """Get NOAA geomagnetic storm scale level based on max Kp."""
63
+ kp = self.kp_max
64
+ if kp >= 9:
65
+ return "G5 (Extreme)"
66
+ elif kp >= 8:
67
+ return "G4 (Severe)"
68
+ elif kp >= 7:
69
+ return "G3 (Strong)"
70
+ elif kp >= 6:
71
+ return "G2 (Moderate)"
72
+ elif kp >= 5:
73
+ return "G1 (Minor)"
74
+ else:
75
+ return "Quiet"
76
+
77
+ @property
78
+ def color_code(self) -> str:
79
+ """Get color code for Kp value."""
80
+ kp = self.kp_max
81
+ if kp >= 7:
82
+ return "#F44336" # Red
83
+ elif kp >= 5:
84
+ return "#FF9800" # Orange
85
+ elif kp >= 4:
86
+ return "#FFC107" # Amber
87
+ elif kp >= 2:
88
+ return "#4CAF50" # Green
89
+ else:
90
+ return "#2196F3" # Blue (very quiet)
91
+
92
+
93
+ @dataclass
94
+ class F107FluxData:
95
+ """F10.7 cm radio flux data for a date."""
96
+ event_date: date
97
+ flux_value: float # Solar Flux Units (sfu)
98
+ sunspot_number: int # SESC sunspot number
99
+ sunspot_area: int # 10E-6 Hemisphere
100
+ xray_background: str # Daily background X-ray flux (e.g., B1.5, *)
101
+
102
+ @property
103
+ def activity_level(self) -> str:
104
+ """Categorize solar activity based on F10.7."""
105
+ if self.flux_value < 70:
106
+ return "Very Low"
107
+ elif self.flux_value < 90:
108
+ return "Low"
109
+ elif self.flux_value < 120:
110
+ return "Moderate"
111
+ elif self.flux_value < 150:
112
+ return "Elevated"
113
+ elif self.flux_value < 200:
114
+ return "High"
115
+ else:
116
+ return "Very High"
117
+
118
+
119
+ @dataclass
120
+ class SolarConditions:
121
+ """Combined solar conditions for a specific date."""
122
+ event_date: date
123
+ kp_index: Optional[KpIndexData]
124
+ f107_flux: Optional[F107FluxData]
125
+ is_historical: bool # True if data is from archive, False if current
126
+ data_source: str # Description of data source
127
+ solar_wind: Optional[SolarWindData] = None # Only for current date
128
+
129
+ @property
130
+ def summary(self) -> str:
131
+ """Get a one-line summary of conditions."""
132
+ parts = []
133
+
134
+ if self.kp_index:
135
+ parts.append(f"Kp max: {self.kp_index.kp_max:.0f} ({self.kp_index.storm_level})")
136
+
137
+ if self.solar_wind:
138
+ parts.append(f"Wind: {self.solar_wind.speed:.0f} km/s")
139
+
140
+ if self.f107_flux:
141
+ parts.append(f"F10.7: {self.f107_flux.flux_value:.0f} sfu ({self.f107_flux.activity_level})")
142
+
143
+ return " | ".join(parts) if parts else "No data available"
144
+
145
+
146
+ def fetch_json(url: str, timeout: int = 30) -> Optional[Any]:
147
+ """Fetch and parse JSON from URL."""
148
+ try:
149
+ from ..utils import get_global_session
150
+ session = get_global_session()
151
+ response = session.get(url)
152
+ return json.loads(response.text)
153
+ except Exception as e:
154
+ print(f"Error fetching {url}: {e}")
155
+ return None
156
+
157
+
158
+ def fetch_solar_wind(target_date: Optional[date] = None) -> Optional[SolarWindData]:
159
+ """
160
+ Fetch solar wind conditions from SWPC 7-day history.
161
+
162
+ Args:
163
+ target_date: If provided, get the latest data for this specific date.
164
+ If None, get the absolute latest data available.
165
+ """
166
+ url = "https://services.swpc.noaa.gov/products/solar-wind/plasma-7-day.json"
167
+ data = fetch_json(url)
168
+
169
+ if not data or len(data) < 2:
170
+ return None
171
+
172
+ # Data format: [["time_tag", "density", "speed", "temperature"], [...], ...]
173
+ # Iterate backwards to find latest valid entry for the target date
174
+ for entry in reversed(data[1:]):
175
+ try:
176
+ timestamp_str = entry[0]
177
+ timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S.%f")
178
+
179
+ # If target_date is specified, verify match
180
+ if target_date and timestamp.date() != target_date:
181
+ # Optimized: Since data is chronological, if we see a date NEWER than target, skip.
182
+ # If we see a date OLDER, we missed our window (data not present).
183
+ if timestamp.date() > target_date:
184
+ continue
185
+ else:
186
+ # Found data older than target, so target date has no data in this file
187
+ return None
188
+
189
+ # Found potential match (or no target date), check values
190
+ density = float(entry[1]) if entry[1] else None
191
+ speed = float(entry[2]) if entry[2] else None
192
+ temperature = float(entry[3]) if entry[3] else None
193
+
194
+ if speed is None or density is None:
195
+ continue
196
+
197
+ return SolarWindData(
198
+ timestamp=timestamp,
199
+ speed=speed,
200
+ density=density,
201
+ temperature=temperature or 0,
202
+ )
203
+ except (ValueError, IndexError):
204
+ continue
205
+ return None
206
+
207
+
208
+ def fetch_text(url: str, timeout: int = 30) -> Optional[str]:
209
+ """Fetch text content from URL."""
210
+ try:
211
+ from ..utils import get_global_session
212
+ session = get_global_session()
213
+ response = session.get(url)
214
+ return response.text
215
+ except Exception as e:
216
+ print(f"Error fetching {url}: {e}")
217
+ return None
218
+
219
+
220
+ def parse_daily_geomagnetic_indices(text: str) -> Dict[date, KpIndexData]:
221
+ """
222
+ Parse SWPC daily-geomagnetic-indices.txt file.
223
+
224
+ Returns dict mapping dates to KpIndexData.
225
+ """
226
+ results = {}
227
+
228
+ for line in text.split("\n"):
229
+ line = line.strip()
230
+ if not line or line.startswith("#") or line.startswith(":"):
231
+ continue
232
+
233
+ # Format: YYYY MM DD A K K K K K K K K A K K K K K K K K A Kp Kp Kp Kp Kp Kp Kp Kp
234
+ # We want the planetary (Estimated) Kp values at the end
235
+ try:
236
+ parts = line.split()
237
+ if len(parts) < 30:
238
+ continue
239
+
240
+ year = int(parts[0])
241
+ month = int(parts[1])
242
+ day = int(parts[2])
243
+ event_date = date(year, month, day)
244
+
245
+ # Get planetary A value (index 21 approximately, may vary)
246
+ # Actually the format shows 3 sets of A + 8 K values
247
+ # Fredericksburg (10), College (10), Planetary (10)
248
+ # So planetary Kp starts at index 22
249
+
250
+ # Parse planetary Kp values (last 8 values)
251
+ kp_values = []
252
+ for i in range(-8, 0):
253
+ try:
254
+ kp = float(parts[i])
255
+ kp_values.append(kp)
256
+ except ValueError:
257
+ kp_values.append(0.0)
258
+
259
+ # Get planetary A index (before the 8 Kp values)
260
+ try:
261
+ ap_value = int(parts[-9])
262
+ except (ValueError, IndexError):
263
+ ap_value = 0
264
+
265
+ results[event_date] = KpIndexData(
266
+ event_date=event_date,
267
+ ap_value=ap_value,
268
+ kp_values=kp_values,
269
+ )
270
+ except (ValueError, IndexError) as e:
271
+ continue
272
+
273
+ return results
274
+
275
+
276
+ def parse_daily_solar_indices(text: str) -> Dict[date, F107FluxData]:
277
+ """
278
+ Parse SWPC daily-solar-indices.txt file.
279
+
280
+ Returns dict mapping dates to F107FluxData.
281
+ """
282
+ results = {}
283
+
284
+ for line in text.split("\n"):
285
+ line = line.strip()
286
+ if not line or line.startswith("#") or line.startswith(":"):
287
+ continue
288
+
289
+ # Format: YYYY MM DD F10.7 SSN Area NewReg Field Bkgd C M X S 1 2 3
290
+ try:
291
+ parts = line.split()
292
+ if len(parts) < 5:
293
+ continue
294
+
295
+ year = int(parts[0])
296
+ month = int(parts[1])
297
+ day = int(parts[2])
298
+ event_date = date(year, month, day)
299
+
300
+ flux = float(parts[3])
301
+ sunspot_number = int(parts[4])
302
+ sunspot_area = int(parts[5])
303
+ xray_background = parts[8]
304
+
305
+ results[event_date] = F107FluxData(
306
+ event_date=event_date,
307
+ flux_value=flux,
308
+ sunspot_number=sunspot_number,
309
+ sunspot_area=sunspot_area,
310
+ xray_background=xray_background,
311
+ )
312
+ except (ValueError, IndexError) as e:
313
+ continue
314
+
315
+ return results
316
+
317
+
318
+ def fetch_conditions_for_date(event_date: date) -> SolarConditions:
319
+ """
320
+ Fetch comprehensive solar conditions for a specific date.
321
+
322
+ Strategy:
323
+ 1. Geomagnetic & Solar Indices:
324
+ - < 35 days: Daily text files
325
+ - > 35 days: FTP Archive
326
+ 2. Solar Wind (Speed/Density):
327
+ - < 7 days: 7-day JSON history
328
+ - > 7 days: Not available (set to None)
329
+ """
330
+ kp_data = None
331
+ f107_data = None
332
+ solar_wind = None
333
+ data_source = "NOAA SWPC daily indices (30-day archive)"
334
+
335
+ today = date.today()
336
+ days_ago = (today - event_date).days
337
+
338
+ # 1. Fetch Solar Wind (if within 7 days)
339
+ if 0 <= days_ago <= 7:
340
+ try:
341
+ solar_wind = fetch_solar_wind(event_date)
342
+ except Exception as e:
343
+ print(f"Solar wind fetch failed: {e}")
344
+
345
+ # 2. Fetch Kp/F10.7 Indices
346
+ # Fetch from daily files (last 30 days)
347
+ if 0 <= days_ago <= 35:
348
+ # Fetch geomagnetic indices (Kp)
349
+ geo_text = fetch_text("https://services.swpc.noaa.gov/text/daily-geomagnetic-indices.txt")
350
+ if geo_text:
351
+ kp_dict = parse_daily_geomagnetic_indices(geo_text)
352
+ kp_data = kp_dict.get(event_date)
353
+
354
+ # Fetch solar indices (F10.7)
355
+ solar_text = fetch_text("https://services.swpc.noaa.gov/text/daily-solar-indices.txt")
356
+ if solar_text:
357
+ f107_dict = parse_daily_solar_indices(solar_text)
358
+ f107_data = f107_dict.get(event_date)
359
+
360
+ return SolarConditions(
361
+ event_date=event_date,
362
+ kp_index=kp_data,
363
+ f107_flux=f107_data,
364
+ is_historical=days_ago > 0,
365
+ data_source=data_source,
366
+ solar_wind=solar_wind,
367
+ )
368
+
369
+ # For older data, try FTP archives
370
+ if days_ago > 35:
371
+ try:
372
+ kp_data, f107_data = fetch_historical_from_ftp(event_date)
373
+ if kp_data or f107_data:
374
+ return SolarConditions(
375
+ event_date=event_date,
376
+ kp_index=kp_data,
377
+ f107_flux=f107_data,
378
+ is_historical=True,
379
+ data_source="NOAA SWPC FTP Archive",
380
+ )
381
+ except Exception as e:
382
+ print(f"FTP fetch error: {e}")
383
+
384
+ # Fallback / No data found
385
+ return SolarConditions(
386
+ event_date=event_date,
387
+ kp_index=None,
388
+ f107_flux=None,
389
+ is_historical=True,
390
+ data_source="Data not available",
391
+ )
392
+
393
+
394
+ # Cache for FTP data to avoid repeated connections
395
+ _ftp_cache = {}
396
+
397
+ def fetch_historical_from_ftp(event_date: date) -> Tuple[Optional[KpIndexData], Optional[F107FluxData]]:
398
+ """
399
+ Fetch historical data from SWPC FTP archives.
400
+
401
+ Files are typically:
402
+ - YYYYQx_DGD.txt (Geomagnetic)
403
+ - YYYYQx_DSD.txt (Solar/F10.7)
404
+
405
+ Legacy might be YYYY_DGD.txt
406
+ """
407
+ from ftplib import FTP
408
+ import io
409
+
410
+ year = event_date.year
411
+ quarter = (event_date.month - 1) // 3 + 1
412
+
413
+ # Try quarterly format first, then yearly
414
+ prefixes = [f"{year}Q{quarter}", f"{year}"]
415
+
416
+ kp_data = None
417
+ f107_data = None
418
+
419
+ # Check cache first
420
+ cache_key_kp = f"{year}_Q{quarter}_Kp"
421
+ cache_key_solar = f"{year}_Q{quarter}_Solar"
422
+
423
+ if cache_key_kp in _ftp_cache:
424
+ kp_dict = _ftp_cache[cache_key_kp]
425
+ kp_data = kp_dict.get(event_date)
426
+ else:
427
+ # Need to fetch Kp
428
+ pass # Will fetch below
429
+
430
+ if cache_key_solar in _ftp_cache:
431
+ f107_dict = _ftp_cache[cache_key_solar]
432
+ f107_data = f107_dict.get(event_date)
433
+
434
+ # If we have both from cache, return
435
+ if kp_data and f107_data:
436
+ return kp_data, f107_data
437
+
438
+ # If missing, fetch from FTP
439
+ try:
440
+ ftp = FTP('ftp.swpc.noaa.gov')
441
+ ftp.login()
442
+ ftp.cwd('pub/indices/old_indices')
443
+
444
+ # Fetch Geomagnetic Data (DGD) if needed
445
+ if cache_key_kp not in _ftp_cache:
446
+ dgd_content = None
447
+ for prefix in prefixes:
448
+ filename = f"{prefix}_DGD.txt"
449
+ try:
450
+ # Download file
451
+ bio = io.BytesIO()
452
+ ftp.retrbinary(f"RETR {filename}", bio.write)
453
+ dgd_content = bio.getvalue().decode('utf-8', errors='ignore')
454
+ break # Success
455
+ except Exception:
456
+ continue
457
+
458
+ if dgd_content:
459
+ kp_dict = parse_daily_geomagnetic_indices(dgd_content)
460
+ _ftp_cache[cache_key_kp] = kp_dict
461
+ kp_data = kp_dict.get(event_date)
462
+
463
+ # Fetch Solar Data (DSD) if needed
464
+ if cache_key_solar not in _ftp_cache:
465
+ dsd_content = None
466
+ for prefix in prefixes:
467
+ filename = f"{prefix}_DSD.txt"
468
+ try:
469
+ # Download file
470
+ bio = io.BytesIO()
471
+ ftp.retrbinary(f"RETR {filename}", bio.write)
472
+ dsd_content = bio.getvalue().decode('utf-8', errors='ignore')
473
+ break # Success
474
+ except Exception:
475
+ continue
476
+
477
+ if dsd_content:
478
+ f107_dict = parse_daily_solar_indices(dsd_content)
479
+ _ftp_cache[cache_key_solar] = f107_dict
480
+ f107_data = f107_dict.get(event_date)
481
+
482
+ ftp.quit()
483
+
484
+ except Exception as e:
485
+ print(f"FTP connection failed: {e}")
486
+ return None, None
487
+
488
+ return kp_data, f107_data
489
+
490
+
491
+ # Legacy function for backwards compatibility
492
+ def fetch_current_conditions() -> SolarConditions:
493
+ """
494
+ Fetch current solar conditions (today's data).
495
+
496
+ Returns:
497
+ SolarConditions object with today's data
498
+ """
499
+ return fetch_conditions_for_date(date.today())
500
+
501
+
502
+ if __name__ == "__main__":
503
+ from datetime import date, timedelta
504
+
505
+ # Test with a recent date
506
+ test_date = date.today() - timedelta(days=3)
507
+ print(f"Fetching solar conditions for {test_date}...")
508
+
509
+ conditions = fetch_conditions_for_date(test_date)
510
+
511
+ print(f"\nDate: {conditions.event_date}")
512
+ print(f"Data Source: {conditions.data_source}")
513
+ print(f"Summary: {conditions.summary}")
514
+
515
+ if conditions.kp_index:
516
+ kp = conditions.kp_index
517
+ print(f"\nKp Index:")
518
+ print(f" Ap: {kp.ap_value}")
519
+ print(f" Kp max: {kp.kp_max:.1f}")
520
+ print(f" Kp values: {kp.kp_values}")
521
+ print(f" Storm Level: {kp.storm_level}")
522
+
523
+ if conditions.f107_flux:
524
+ f107 = conditions.f107_flux
525
+ print(f"\nF10.7 Radio Flux:")
526
+ print(f" Value: {f107.flux_value:.1f} sfu")
527
+ print(f" Sunspot #: {f107.sunspot_number}")
528
+ print(f" Activity: {f107.activity_level}")
@@ -0,0 +1,35 @@
1
+ """
2
+ Solar Data Downloader Package
3
+
4
+ This package provides tools for downloading and processing solar data from various observatories:
5
+ - SDO/AIA (Atmospheric Imaging Assembly)
6
+ - SDO/HMI (Helioseismic and Magnetic Imager)
7
+ - IRIS (Interface Region Imaging Spectrograph)
8
+ - SOHO (Solar and Heliospheric Observatory)
9
+
10
+ The package includes:
11
+ - Core downloader module (solar_data_downloader.py)
12
+ - Command-line interface (solar_data_downloader_cli.py)
13
+ - Graphical user interface (solar_data_downloader_gui.py)
14
+ """
15
+
16
+ from .solar_data_downloader import (
17
+ download_aia,
18
+ download_aia_with_fido,
19
+ download_hmi,
20
+ download_hmi_with_fido,
21
+ download_iris,
22
+ download_soho,
23
+ )
24
+
25
+ from .solar_data_downloader_gui import launch_gui
26
+
27
+ __all__ = [
28
+ "download_aia",
29
+ "download_aia_with_fido",
30
+ "download_hmi",
31
+ "download_hmi_with_fido",
32
+ "download_iris",
33
+ "download_soho",
34
+ "launch_gui",
35
+ ]