Rewrite to make it more modular
added veiws for area and single waveforms added settings dialog added MenuBar and populated with "View" "Settings" and "Help" Help menu contains "About" currently ** ** WILL NOT RUN due to rewrite being incomplete ** **
This commit is contained in:
BIN
assets/icons/app_icon.png
Normal file
BIN
assets/icons/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
@@ -64,3 +64,11 @@ QWidget#waveformChartContainer {
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
QLabel#chartTitleLabel {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #CBD5E0;
|
||||
padding-bottom: 2px;
|
||||
padding-left: 5px;
|
||||
}
|
2
config/__init__.py
Normal file
2
config/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .areas_config import AREAS_OF_INTEREST
|
||||
from .stations_config import ALL_STATIONS_CONFIG, SENSOR_TYPE_CHANNELS, STATION_LOOKUP
|
BIN
config/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
config/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
config/__pycache__/areas_config.cpython-313.pyc
Normal file
BIN
config/__pycache__/areas_config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
config/__pycache__/stations_config.cpython-313.pyc
Normal file
BIN
config/__pycache__/stations_config.cpython-313.pyc
Normal file
Binary file not shown.
173
config/areas_config.py
Normal file
173
config/areas_config.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# DICTIONARY mapping area names to a list of station codes belonging to that area.
|
||||
#
|
||||
# Each station config in the list is a dictionary:
|
||||
# {
|
||||
# code: StationCode
|
||||
# preferred_channel: channelCode
|
||||
# preferred_location: locationCode
|
||||
# }
|
||||
AREAS_OF_INTEREST = {
|
||||
"Northland & Auckland": [
|
||||
|
||||
],
|
||||
"Waikato": [
|
||||
|
||||
],
|
||||
"Hikaurangi Subduction Zone": [
|
||||
{
|
||||
"code": "RIZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "GLKZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
|
||||
},
|
||||
{
|
||||
"code": "MXZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "WMGZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "PUZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "CNGZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "RIGZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "PRGZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "KNZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "WHHZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "ARHZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "CKHZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "KAHZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "PXZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "PRHZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "BFZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "CPWZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "TMWZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "TRWZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "WRRZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "CMWZ",
|
||||
"preferred_channel": "EHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "BSWZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
{
|
||||
"code": "CTZ",
|
||||
"preferred_channel": "HHE",
|
||||
"preferred_location": "10"
|
||||
},
|
||||
],
|
||||
"Rotorua & East Cape": [
|
||||
|
||||
],
|
||||
"White Island": [
|
||||
|
||||
],
|
||||
"Taupo & Greater Area": [
|
||||
|
||||
],
|
||||
"Hawkes Bay": [
|
||||
|
||||
],
|
||||
"Mount Taranaki": [
|
||||
|
||||
],
|
||||
"Palmerston North & Whanganui": [
|
||||
|
||||
],
|
||||
"Mount Ruapehu": [
|
||||
|
||||
],
|
||||
"Wellington (Ashurst & South)": [
|
||||
|
||||
],
|
||||
"Christchurch & Kaikoura incl. Banks Peninsula": [
|
||||
|
||||
],
|
||||
"Southland (Otago, Invicargil Milford Sound etc)": [
|
||||
|
||||
],
|
||||
"Stewart Island, Snares Island & Antartica": [
|
||||
|
||||
],
|
||||
"Southern Alps / Alpine Fault": [
|
||||
|
||||
],
|
||||
"Queenstown & Wanaka": [
|
||||
|
||||
],
|
||||
}
|
251
config/stations_config.py
Normal file
251
config/stations_config.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""
|
||||
THIS IS A DICTIONARY that maps a sensot type to a list of available channels for that type
|
||||
"""
|
||||
SENSOR_TYPE_CHANNELS = {
|
||||
"Broadband": ["BHZ", "BHN", "BHE", "HHZ", "HHN", "HHE", "LHZ", "LHN", "LHE"],
|
||||
"Short Period": ["EHZ", "EHN", "EHE", "SHZ", "SHN", "SHE"],
|
||||
"Strong Motion": ["HNZ", "HNN", "HNE"],
|
||||
"All Channels": []
|
||||
}
|
||||
|
||||
#List of all known stations with their details
|
||||
# Each Station is a DICTIONARY
|
||||
ALL_STATIONS_CONFIG = [
|
||||
{
|
||||
"code": "RIZ",
|
||||
"name": "Bells Beach, Raoul Is.",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Raoul Island", "Bells Beach" "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "GLKZ",
|
||||
"name": "Green Lake, Raoul Is.",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Raoul Island", "Green Lake", "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "MXZ",
|
||||
"name": "Te Araroa",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["East Coast", "Te Araroa", "North Island", "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "WMGZ",
|
||||
"name": "Ruatoria",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["East Coast", "Ruatoria", "North Island", "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "PUZ",
|
||||
"name": "Tokomaru Bay",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["East Coast", "Tokomaru Bay", "North Island", "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "CNGZ",
|
||||
"name": "Tologa Bay",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Shot Period"],
|
||||
"area_tags": ["East Coast", "Tologa Bay", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "RIGZ",
|
||||
"name": "Gisborne",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Gisborne", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "PRGZ",
|
||||
"name": "Tikiwhata",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["East Coast", "Tikiwhata", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "KNZ",
|
||||
"name": "Wairoa",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Wairoa", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "WHHZ",
|
||||
"name": "Waihua",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Waihua", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "ARHZ",
|
||||
"name": "Aropaonui",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["East Coast", "Aropaonui", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "CKHZ",
|
||||
"name": "Cape Kidnappers",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Cape Kidnappers", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "KAHZ",
|
||||
"name": "Tukituki",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Tukituki", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "PXZ",
|
||||
"name": "Pawanui",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Pawanui", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "PRHZ",
|
||||
"name": "Porangahau",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Porangahau", "East Coast", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "BFZ",
|
||||
"name": "Birch Farm",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Birch Farm", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "CPWZ",
|
||||
"name": "Castlepoint",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Castlepoint", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "TMWZ",
|
||||
"name": "Traveller",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Traveller", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "WRRZ",
|
||||
"name": "White Rock",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["White Rock", "Hikaurangi Trench", "North Island"]
|
||||
},
|
||||
{
|
||||
"code": "CMWZ",
|
||||
"name": "Cape Campbell",
|
||||
"location_codes": {
|
||||
"EH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Short Period"],
|
||||
"area_tags": ["Cape Campbell", "North Island" "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "BSWZ",
|
||||
"name": "Black Birch Station",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Black Birch Station", "South Island" "Hikaurangi Trench"]
|
||||
},
|
||||
{
|
||||
"code": "CTZ",
|
||||
"name": "Chatham Islands",
|
||||
"location_codes": {
|
||||
"HH": "10",
|
||||
"LH": "10",
|
||||
"default": "10"
|
||||
},
|
||||
"types": ["Broadband"],
|
||||
"area_tags": ["Chatham Islands", "Hikaurangi Trench"]
|
||||
},
|
||||
]
|
||||
|
||||
# Post processing for convenience
|
||||
ALL_UNIQUE_CHANNELS = sorted(list(set(sum(SENSOR_TYPE_CHANNELS.values(), []))))
|
||||
SENSOR_TYPE_CHANNELS["All Channels"] = ALL_UNIQUE_CHANNELS
|
||||
|
||||
# Pre built dictionary for quick lookup by station code
|
||||
STATION_LOOKUP = {station['code']: station for station in ALL_STATIONS_CONFIG}
|
Binary file not shown.
37
src/data/persistence.py
Normal file
37
src/data/persistence.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
APP_SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__))), 'config', 'app_settings.json')
|
||||
DEFAULT_SETTINGS = {
|
||||
'theme': 'dark_blue.xml',
|
||||
'default_duration_minutes': 30,
|
||||
'last_area_selected': ' --- Select Area ---',
|
||||
'last_sensor_type': 'Broadband',
|
||||
'last_staion_selected': 'WEL',
|
||||
'last_station_selected': 'HHZ',
|
||||
'last_location_selected': '10',
|
||||
# Add More Settings here
|
||||
}
|
||||
|
||||
def load_app_settings():
|
||||
"""Loads application setings from app_settings.json"""
|
||||
settings = DEFAULT_SETTINGS.copy()
|
||||
if os.path.exists(APP_SETTINGS_FILE):
|
||||
try:
|
||||
with open(APP_SETTINGS_FILE, 'r') as f:
|
||||
user_settings = json.load(f)
|
||||
settings.update(user_settings)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Warning: Could not decode app_settings.json. Using default settings. Error: {e}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Error loading app_settings.json. Using default settings. Error: {e}")
|
||||
return settings
|
||||
|
||||
def save_app_settings(settings):
|
||||
"""Saves application settings to app_settings.json."""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(APP_SETTINGS_FILE), exist_ok=True)
|
||||
with open(APP_SETTINGS_FILE, 'w') as f:
|
||||
json.dump(seettings, f, indent=4)
|
||||
except Exception as e:
|
||||
print(f"ERROR: could not save app_settings.json. Error: {e}")
|
18
src/main.py
18
src/main.py
@@ -4,6 +4,24 @@ from PyQt6.QtWidgets import QApplication
|
||||
from src.ui.main_window import MainWindow
|
||||
from qt_material import apply_stylesheet
|
||||
|
||||
# --- Debug Imports ---
|
||||
print("\n--- sys.path DEBUG ---")
|
||||
print("Current Working Directory:", os.getcwd())
|
||||
print("Script Directory:", os.path.dirname(os.path.abspath(__file__)))
|
||||
print("Project Root (calculated):", os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
||||
print("Python sys.path:")
|
||||
for p in sys.path:
|
||||
print(f" - {p}")
|
||||
print("--- END sys.path DEBUG ---\n")
|
||||
|
||||
#test direct import
|
||||
try:
|
||||
import config.areas_config
|
||||
print(f"DEBUG: AREAS_OF_INTEREST keys: {config.areas_config.AREAS_OF_INTEREST.keys()}")
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Failed to directly import config.areas_config: {e}")
|
||||
# --- END DEBUGGING INPORTS ---
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
@@ -1,138 +1,159 @@
|
||||
from PyQt6.QtWidgets import QMainWindow, QLabel, QVBoxLayout, QWidget, QPushButton, QComboBox, QHBoxLayout
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from src.ui.waveform_chart import WaveformChart
|
||||
from src.data.geonet_api import fetch_waveform_data
|
||||
from PyQt6.QtWidgets import ( QMainWindow, QLabel, QVBoxLayout, QWidget, QStackedWidget, QMenuBar, QMenu, QStatusBar, QApplication )
|
||||
from PyQt6.QtCore import Qt, QTimer, Qsize
|
||||
from src.ui.views.single_waveform_view import SingleWaveformView
|
||||
from src.ui.views.area_waveforms_view import AreaWavefromsView
|
||||
from src.ui.dialogs.settings_dialog import SettingsDialog
|
||||
|
||||
import config
|
||||
import sys
|
||||
|
||||
# Theme Modes
|
||||
THEME_MODE_DARK = "dark_blue.xml"
|
||||
THEME_MODE_LIGHT = "light_blue.xml"
|
||||
THEME_MODE_SYSTEM = "system" # Not directly supported but Qt-Material
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("NZ EarthQuake Viewer (POC)")
|
||||
self.setGeometry(100, 100, 1200, 800) # x, y, width, height
|
||||
self.setMinimumSize(800, 600)
|
||||
|
||||
self.setWindowTitle("NZ Earthquake Viewer V0.2 (POC)")
|
||||
self.setGeometry(100, 100, 1200, 800)
|
||||
self.seMinimumSize(800, 600)
|
||||
|
||||
# Load Pesistent Settings
|
||||
self.app_settings = load_app_settings()
|
||||
self._apply_theme(self.app_settings.get('theme', THEME_MODE_DARK))
|
||||
|
||||
self.resizeEvent = self._on_resize_event
|
||||
|
||||
# --- Central Widget & Layout ---
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
self.main_layout = QVBoxLayout(central_widget)
|
||||
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
self.main_layout.setSpacing(8)
|
||||
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
main_layout.setContentsMargins(20, 20, 20, 20)
|
||||
main_layout.setSpacing(15)
|
||||
|
||||
controls_layout = QHBoxLayout()
|
||||
|
||||
# App Title Label
|
||||
self.app_title_label = QLabel("NZ Seismic Viewer", self)
|
||||
self.app_title_label.setObjectName("headerLabel")
|
||||
main_layout.addWidget(self.app_title_label)
|
||||
self.main_layout.addWidget(self.app_title_label)
|
||||
|
||||
#Station & channel selection
|
||||
station_label = QLabel("Station:", self)
|
||||
station_label.setProperty("controlLabel", True)
|
||||
self.station_selector = QComboBox(self)
|
||||
self.station_selector.addItems(["WEL", "MAZ", "LTZ", "WRRZ"])
|
||||
# Manage different views
|
||||
self.stacked_widget = QStackedWidget(self)
|
||||
self.main_layout.addWidget(self.stackedWidget)
|
||||
|
||||
channel_label = QLabel("Channel:", self)
|
||||
channel_label.setProperty("controlLabel", True)
|
||||
self.channel_selector = QComboBox(self)
|
||||
self.channel_selector.addItems([
|
||||
"BHZ", "BHN", "BHE", # Common Broadband
|
||||
"HHZ", "HHN", "HHE", # High-Gain Broadband
|
||||
"LHZ", "LHN", "LHE", # Low-Gain Broadband, Long Period
|
||||
# Instantiate views
|
||||
self.area_waveforms_view = AreaWaveformsView(self)
|
||||
self.single_waveform_view = SingleWaveformView(self)
|
||||
|
||||
"EHZ", "EHN", "EHE", # Short-Period (Popular)
|
||||
self.stacked_widget.addWidget(self.area_waveforms_view) # Index 0
|
||||
self.stacked_widget.addWidget(self.single_waveform_view) # Index 1
|
||||
|
||||
"HNZ", "HNN", "HNE", # High-Gain, Strong Motion
|
||||
# Menu Bar
|
||||
self._create_menu_bar()
|
||||
|
||||
# OTHER COMMON Channels that could be encountered in the future
|
||||
# ONLY UNCOMMENT THE LINE WHEN NEEDED!
|
||||
# "BMZ", "BNN", "BNE", # Broadband Variants
|
||||
# "ENZ", "ENN", "ENE", # Short-Period Variants
|
||||
# "SHZ", "SHN", "SHE", # other Short-Period variants (Less Common in NZ)
|
||||
])
|
||||
self.channel_selector.setCurrentText("HHE") # Default
|
||||
|
||||
#Duration Selector
|
||||
duration_label = QLabel("Duration (min):", self)
|
||||
duration_label.setProperty("controlLabel", True)
|
||||
self.duration_selector = QComboBox(self)
|
||||
self.duration_selector.addItems(["5", "15", "20", "30", "60", "90", "120"]) # 60 =1hr | 90 = 1.5hrs | 120 = 2hrs
|
||||
self.duration_selector.setCurrentText("30") # Default to 30 mins
|
||||
|
||||
#Location Code Selector (GeoNet requires this to not be '--')
|
||||
location_label = QLabel("Location:", self)
|
||||
location_label.setProperty("controlLabel", True)
|
||||
self.location_selector = QComboBox(self)
|
||||
self.location_selector.addItems(["--", "00", "01", "02", "03", "10", "20", "22", "30"]) # DOUBLE CHECK AS GET INTO THE MULTIPLE GRAPHS
|
||||
self.location_selector.setCurrentText("10") # most responsive I've found on swarm - BC
|
||||
|
||||
# Fetch Button
|
||||
self.fetch_button = QPushButton("Fetch waveform", self)
|
||||
self.fetch_button.clicked.connect(self.fetch_and_plot_waveform)
|
||||
|
||||
# LAYOUT
|
||||
controls_layout.addWidget(station_label)
|
||||
controls_layout.addWidget(self.station_selector)
|
||||
controls_layout.addSpacing(15)
|
||||
controls_layout.addWidget(channel_label)
|
||||
controls_layout.addWidget(self.channel_selector)
|
||||
controls_layout.addSpacing(15)
|
||||
controls_layout.addWidget(location_label)
|
||||
controls_layout.addWidget(self.location_selector)
|
||||
controls_layout.addSpacing(15)
|
||||
controls_layout.addWidget(duration_label)
|
||||
controls_layout.addWidget(self.duration_selector)
|
||||
controls_layout.addStretch(1)
|
||||
controls_layout.addWidget(self.fetch_button)
|
||||
|
||||
main_layout.addLayout(controls_layout)
|
||||
|
||||
self.status_label = QLabel("Ready.", self)
|
||||
# Status Bar
|
||||
self.status_bar = QStatusBar(self)
|
||||
self.setStatusBar(self.status_bar)
|
||||
self.status_label = QLabel("Ready.")
|
||||
self.status_label.setObjectName("statusLabel")
|
||||
self.status_abr.addWidget(self.status_label, 1)
|
||||
|
||||
main_layout.addWidget(self.status_label)
|
||||
|
||||
self.waveform_chart = WaveformChart(self)
|
||||
self.waveform_chart.setObjectName("waveformChartContainer")
|
||||
|
||||
main_layout.addWidget(self.waveform_chart)
|
||||
# Intiial View and Data Load
|
||||
self.show_area_waveforms_view()
|
||||
|
||||
# Polling Timer
|
||||
self.polling_timer = QTimer(self)
|
||||
self.polling_timer.setInterval(15 * 1000) # 15 seconds
|
||||
self.polling_timer.timeout.connect(self.fetch_and_plot_waveform)
|
||||
self.polling_timer.setInterval(15 * 1000)
|
||||
self.polling_timer.timeout.connect(self._refresh_current_view)
|
||||
self.polling_timer.start()
|
||||
|
||||
self.fetch_and_plot_waveform()
|
||||
# Menu bar creation
|
||||
def _create_menu_bar(self):
|
||||
menu_bar = self.menuBar()
|
||||
|
||||
def set_status(self, message, is_error=False):
|
||||
self.status_label.setText(message)
|
||||
# View Menu
|
||||
view_menu = menu_bar.addMenu("&View")
|
||||
area_view_action = view_menu.addAction("Area Waveforms")
|
||||
single_view_action = view_menu.addAction("Single Waveform")
|
||||
|
||||
if is_error:
|
||||
self.status_label.setStyleSheet("color: #EF4444; font-weight: bold;")
|
||||
else:
|
||||
self.status_label.setStyleSheet("color: #6B7280; font-weight: normal;")
|
||||
area_view_action.triggered.connect(self.show_area_waveforms_view)
|
||||
single_view_action.triggered.connect(self.show_single_waveform_view)
|
||||
|
||||
def fetch_and_plot_waveform(self):
|
||||
station = self.station_selector.currentText()
|
||||
channel = self.channel_selector.currentText()
|
||||
location_code = self.location_selector.currentText()
|
||||
self.set_status(f"Fetching data for {station}-{channel}...", is_error=False)
|
||||
self.fetch_button.setEnabled(False)
|
||||
# Options Menu
|
||||
options_menu = menu_bar.addMenu("&Options")
|
||||
settings_action = options_menu.addAction("Settings")
|
||||
settings_action.triggered.connect(self.show_settings_dialog)
|
||||
|
||||
try:
|
||||
traces = fetch_waveform_data(station, channel, location=location_code, duration_seconds=1800)
|
||||
# Help Menu
|
||||
help_menu = menu_bar.addMenu("&Help")
|
||||
about_action = help_menu.addAction("About")
|
||||
about_action.triggered.connect(self.show_about_dialog)
|
||||
|
||||
if traces:
|
||||
selected_trace = next((t for t in traces if t['channel'] == channel), None)
|
||||
if selected_trace and selected_trace['points']:
|
||||
self.waveform_chart.plot_waveform(selected_trace['points'], f"Live Waveform: {station}-{channel}")
|
||||
self.set_status(f"Data for {station}-{channel} updated. Last sample at {selected_trace['points'][-1]['x'].strftime('%H:%M:%S')}", is_error=False)
|
||||
else:
|
||||
self.set_status(f"No waveform points found for {station}-{channel} in this window", is_error=True)
|
||||
self.waveform_chart.plot_waveform([], f"No data: {station}-{channel}")
|
||||
# view Management Methods
|
||||
def show_area_waveforms_view(self):
|
||||
self.stacked_widget.setCurrentWidget(self.area_waveforms_view)
|
||||
self.area_waveforms_view.load_selected_area_waveforms()
|
||||
self.area_waveforms_view.start_polling() # may need self._refresh_current_view() if start_polling() not work
|
||||
self.set_status("Showing Area Waveforms.")
|
||||
self.current_view_mode = "area"
|
||||
|
||||
def show_single_waveform_view(self):
|
||||
self.stacked_widget.setCurrentWidget(self.single_waveform_view)
|
||||
self.single_waveform_view.start_polling()
|
||||
self.set_status("Showing Single Waveform. Select Parameters and click 'fetch' button")
|
||||
self.current_view_mode = "single"
|
||||
|
||||
def show_settigns_dialog(self):
|
||||
settings_dialog = sSettignsDialog(self.app_settings, self)
|
||||
settings_dialog.settings_changed.connect(self._handle_settings_changed)
|
||||
settings_dialog.exec()
|
||||
|
||||
def _handle_settings_changed(self, new_settings):
|
||||
self.app_settigns.update(new_settings)
|
||||
save_app_settings(self.app_settigns)
|
||||
|
||||
# Apply new theme if it changed
|
||||
if 'theme' in new_settings:
|
||||
self._apply_theme(new_settings['theme'])
|
||||
|
||||
def _apply_theme(self, theme_choice):
|
||||
from qt_material import apply_stylesheet
|
||||
|
||||
current_app = QApplication.instance()
|
||||
if not current_app: return
|
||||
|
||||
if theme_choic == THEME_MODE_SYSTEM:
|
||||
# qt-material doesn't have a 'system' theme
|
||||
# Try to detect the system light / dark mode and set the theme acordingly.
|
||||
# default to dark mode if system is not detected.
|
||||
print("System theme detection not fully implemented, defaulting to Dark Theme.")
|
||||
apply_stylesheet(current_app, theme=THEME_MODE_DARK)
|
||||
elif theme_choice == THEME_MODE_LIGHT:
|
||||
apply_stylesheet(current_app, theme=THEME_MODE_LIGHT)
|
||||
else: # Defaults to dark mode if no specific match
|
||||
apply_stylesheet(current_app, theme=THEME_MODE_DARK)
|
||||
print(f"Applied theme: {theme_choice}")
|
||||
|
||||
def _refresh_current_view(self)
|
||||
""" Refreshes the currently active view's data."""
|
||||
if self.current_view_mode == "area":
|
||||
self.area_waveforms_view.load_area_data()
|
||||
elif self.current_view_mode == "single":
|
||||
self.single_waveform_view.load_single_data()
|
||||
# other views added here following the above format
|
||||
|
||||
def _on_resize_event(self, event):
|
||||
"""Overrides the resize event to trigger re layout in active view"""
|
||||
if self.current_view_mode == "area":
|
||||
self.area_waveforms_view.hanndle_resize()
|
||||
elif self.current_view_mode == "single":
|
||||
self.single_waveform_view.handle_resize()
|
||||
QMainWindow.resizeEvent(self, event)
|
||||
|
||||
# Status forwarding for "sub" views
|
||||
def update_main_status(self, message, is_error=False):
|
||||
self.status_label.setText(message)
|
||||
if is_error:
|
||||
self.status_label.setStlyeSheet("color: #EF4444; font-weight: bold;")
|
||||
else:
|
||||
self.set_status(f"No Data returned from GeoNet for {station}-{channel}.", is_error=True)
|
||||
self.waveform_chart.plot_waveform([], f"No data: {station}-{channel}")
|
||||
except Exception as e:
|
||||
self.set_status(f"Error fetching waveform: {e}", is_error=True)
|
||||
print(f"Detailed error in main_window.py: {e}", file=sys.stderr)
|
||||
finally:
|
||||
self.fetch_button.setEnabled(True)
|
||||
self.status_label.setStyleSheet("color: #A0AEC0; font-weight: normal;")
|
||||
|
39
src/ui/views/area_waveforms_view.py
Normal file
39
src/ui/views/area_waveforms_view.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from PyQt6.QtWidgets import ( QWidget, QLabel, QVBoxLayout, QPushButon, QComboBox, QHBoxLayout, QGridLayout )
|
||||
from PyQt6.QtCore import Qt
|
||||
from src.ui.waveform_chart import WaveformChart
|
||||
from src.ui.geonet_api import fetch_waveform_data
|
||||
import config
|
||||
import sys
|
||||
import math
|
||||
|
||||
class AreaWaveformsView(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.parent_window = parent
|
||||
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.setSpacing(8)
|
||||
|
||||
# Area Selection Controls Row 1
|
||||
area_controls_layout = QHBoxLayout()
|
||||
area_controls_layout.setSpacing(5)
|
||||
|
||||
area_label = QLabel("Area:", self)
|
||||
area_label.setProperty("controlLabel", True)
|
||||
area_controls_layout.addWidget(area_label)
|
||||
|
||||
self.area_selector = QComboBox(self)
|
||||
self.area_selector.addItems(["-- Select Area --"] + list(config.AREAS_OF_INTEREST.keys()))
|
||||
controls_controls_layout.addWidget(self.area_selector)
|
||||
|
||||
self.load_area_button = QPushButton("Fetch Waveforms", self)
|
||||
self.load_area_button.setObjectName("loadAreaButton")
|
||||
self.load_area_button.clicked.connect(self.load_area_data)
|
||||
|
||||
area_controls_layout.addWidget(self.load_area_button)
|
||||
area_controls_layout.addStretch(1)
|
||||
self.main_layout.addLayout(area_controls_layout)
|
||||
|
||||
# sensor Type Selector - Row 2
|
||||
|
0
src/ui/views/dialogs/__init__.py
Normal file
0
src/ui/views/dialogs/__init__.py
Normal file
0
src/ui/views/dialogs/settigns_dialog.py
Normal file
0
src/ui/views/dialogs/settigns_dialog.py
Normal file
0
src/ui/views/single_waveform_view.py
Normal file
0
src/ui/views/single_waveform_view.py
Normal file
@@ -6,7 +6,7 @@ import matplotlib.dates as mdates
|
||||
from src.utils.mpl_styles import apply_mpl_styles
|
||||
|
||||
class WaveformChart(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None, figsize=(10, 4)):
|
||||
super().__init__(parent)
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.layout.setContentsMargins(0,0,0,0)
|
||||
@@ -16,11 +16,12 @@ class WaveformChart(QWidget):
|
||||
apply_mpl_styles()
|
||||
|
||||
self.title_label = QLabel("Waveform Chart", self)
|
||||
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.title_label.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
|
||||
self.title_label.setObjectName("chartTitleLabel")
|
||||
self.layout.addWidget(self.title_label)
|
||||
|
||||
# Matplot
|
||||
self.fig = Figure(figsize=(10,4), dpi=100)
|
||||
self.fig = Figure(figsize=figsize, dpi=100)
|
||||
self.canvas = FigureCanvas(self.fig)
|
||||
self.layout.addWidget(self.canvas)
|
||||
|
||||
@@ -36,6 +37,12 @@ class WaveformChart(QWidget):
|
||||
def plot_waveform(self, points, title=""):
|
||||
self.title_label.setText(title)
|
||||
self.ax.clear()
|
||||
self.ax.set_xlabel("Time")
|
||||
self.ax.set_ylabel("Amplitude")
|
||||
self.ax.tick_params(axis='x')
|
||||
self.ax.tick_params(axis='y')
|
||||
self.ax.xaxis.set_major_formatter(self.formatter)
|
||||
self.ax.tick_params(axis='x', rotation=45)
|
||||
|
||||
if points:
|
||||
x_data = [p['x'] for p in points]
|
||||
|
0
src/utils/datetime_helpers.py
Normal file
0
src/utils/datetime_helpers.py
Normal file
0
src/utils/error_handling.py
Normal file
0
src/utils/error_handling.py
Normal file
Reference in New Issue
Block a user