Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Aurora Prediction Variables - CAMS Air Pollution</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background: #f5f5f5; | |
| } | |
| .container { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| margin-bottom: 20px; | |
| } | |
| h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; } | |
| h2 { color: #34495e; border-bottom: 2px solid #9b59b6; padding-bottom: 10px; } | |
| .method-section, .form-section { | |
| background: #f8f9fa; | |
| padding: 20px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| border-left: 4px solid #9b59b6; | |
| } | |
| .form-group { | |
| margin-bottom: 15px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 5px; | |
| font-weight: 600; | |
| color: #2c3e50; | |
| } | |
| select { | |
| width: 100%; | |
| padding: 10px; | |
| border: 2px solid #ddd; | |
| border-radius: 5px; | |
| font-size: 14px; | |
| } | |
| select:focus { | |
| border-color: #9b59b6; | |
| outline: none; | |
| } | |
| .btn { | |
| background: #9b59b6; | |
| color: white; | |
| padding: 12px 24px; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: 600; | |
| transition: all 0.3s; | |
| text-decoration: none; | |
| display: inline-block; | |
| margin-right: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .btn:hover { background: #8e44ad; } | |
| .btn:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| } | |
| .btn-secondary { | |
| background: #6c757d; | |
| } | |
| .btn-secondary:hover { | |
| background: #5a6268; | |
| } | |
| .btn-success { | |
| background: #28a745; | |
| } | |
| .btn-success:hover { | |
| background: #218838; | |
| } | |
| .step-selector { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .step-btn { | |
| padding: 8px 16px; | |
| border: 2px solid #ddd; | |
| background: white; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .step-btn:hover { | |
| border-color: #9b59b6; | |
| } | |
| .step-btn.active { | |
| background: #9b59b6; | |
| color: white; | |
| border-color: #9b59b6; | |
| } | |
| .info-box { | |
| background: #e8f4f8; | |
| border: 1px solid #bee5eb; | |
| border-radius: 5px; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| } | |
| .hidden-section { | |
| display: none; | |
| } | |
| .loading { | |
| text-align: center; | |
| color: #666; | |
| font-style: italic; | |
| } | |
| .back-link { | |
| color: #9b59b6; | |
| text-decoration: none; | |
| font-weight: 600; | |
| margin-bottom: 20px; | |
| display: inline-block; | |
| } | |
| .back-link:hover { | |
| text-decoration: underline; | |
| } | |
| .color-preview-section { | |
| margin-top: 15px; | |
| } | |
| .color-gradient { | |
| width: 100%; | |
| height: 20px; | |
| border-radius: 5px; | |
| border: 1px solid #ddd; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <a href="{{ url_for('prediction_runs') }}" class="back-link">← Back to Prediction Runs</a> | |
| <h1>🔮 Aurora Prediction Variables</h1> | |
| <div class="info-box"> | |
| <strong>📁 Run Directory:</strong> {{ run_dir }}<br> | |
| <strong>📊 Steps Available:</strong> {{ steps_data|length }} ({{ (steps_data|length * 12) }}h coverage) | |
| </div> | |
| <!-- Step 1: Step Selection --> | |
| <div class="method-section"> | |
| <h2>⏰ Step 1: Select Prediction Step</h2> | |
| <p>Choose which prediction time step to analyze:</p> | |
| <div class="step-selector"> | |
| {% for step_data in steps_data %} | |
| <div class="step-btn" onclick="selectStep({{ step_data.step }}, '{{ step_data.filename }}')"> | |
| Step {{ step_data.step }}<br> | |
| <small>T+{{ step_data.forecast_hours }}h</small> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| <!-- Step 2: Variable Selection (hidden until step selected) --> | |
| <div id="variableSection" class="hidden-section"> | |
| <div class="form-section"> | |
| <h2>🧪 Step 2: Select Variable</h2> | |
| <div id="variableLoading" class="loading">Loading variables...</div> | |
| <div id="variableContent" class="hidden-section"> | |
| <form method="POST" action="{{ url_for('aurora_plot') }}" id="plotForm"> | |
| <input type="hidden" name="run_dir" value="{{ run_dir }}"> | |
| <input type="hidden" name="step" id="selected_step" value=""> | |
| <div class="form-group"> | |
| <label for="variable">Choose Variable:</label> | |
| <select name="variable" id="variable" required onchange="handleVariableChange()"> | |
| <option value="">-- Select a variable --</option> | |
| </select> | |
| </div> | |
| <!-- Step 3: Pressure Level (shown for atmospheric variables) --> | |
| <div id="pressureSection" class="hidden-section"> | |
| <h2>📊 Step 3: Select Pressure Level</h2> | |
| <div class="form-group"> | |
| <label for="pressure_level">Pressure Level (hPa):</label> | |
| <select name="pressure_level" id="pressure_level"> | |
| </select> | |
| </div> | |
| </div> | |
| <!-- Step 4: Color Theme Selection --> | |
| <div id="plotOptionsSection" class="hidden-section"> | |
| <h2>🎨 Step 4: Select Color Theme</h2> | |
| <div class="form-group"> | |
| <label for="color_theme">Select Color Scheme:</label> | |
| <select name="color_theme" id="color_theme" onchange="updateColorPreview()"> | |
| {% for theme_id, theme_name in color_themes.items() %} | |
| <option value="{{ theme_id }}" | |
| {% if theme_id == 'viridis' %}selected{% endif %}> | |
| {{ theme_name }} | |
| </option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <div class="color-preview-section"> | |
| <p><strong>Preview:</strong> <span id="colorPreviewText">Viridis</span></p> | |
| <div class="color-gradient" id="colorPreview"></div> | |
| </div> | |
| </div> | |
| <!-- Step 5: Generate Plot --> | |
| <h2>📈 Step 5: Generate Plot</h2> | |
| <button type="submit" name="plot_type" value="static" class="btn" id="staticPlotBtn" disabled> | |
| 📊 Generate Static Plot | |
| </button> | |
| <button type="submit" name="plot_type" value="interactive" class="btn btn-success" id="interactivePlotBtn" disabled> | |
| 🌐 Generate Interactive Plot | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Download Section --> | |
| <div class="method-section"> | |
| <h2>💾 Download Data</h2> | |
| <a href="{{ url_for('download_prediction_netcdf', filename=run_dir) }}" class="btn btn-secondary"> | |
| 📥 Download All Files | |
| </a> | |
| </div> | |
| </div> | |
| <script> | |
| let currentStep = null; | |
| let currentVariables = null; | |
| function selectStep(step, filename) { | |
| // Update UI | |
| document.querySelectorAll('.step-btn').forEach(btn => btn.classList.remove('active')); | |
| event.target.closest('.step-btn').classList.add('active'); | |
| currentStep = step; | |
| document.getElementById('selected_step').value = step; | |
| // Show variable section and loading | |
| document.getElementById('variableSection').classList.remove('hidden-section'); | |
| document.getElementById('variableLoading').style.display = 'block'; | |
| document.getElementById('variableContent').classList.add('hidden-section'); | |
| // Load variables for this step | |
| fetch(`/api/aurora_step_variables/{{ run_dir }}/${step}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| alert('Error loading variables: ' + data.error); | |
| return; | |
| } | |
| currentVariables = data; | |
| populateVariables(data); | |
| // Hide loading, show content | |
| document.getElementById('variableLoading').style.display = 'none'; | |
| document.getElementById('variableContent').classList.remove('hidden-section'); | |
| }) | |
| .catch(error => { | |
| console.error('Error:', error); | |
| alert('Error loading variables'); | |
| document.getElementById('variableLoading').innerHTML = 'Error loading variables'; | |
| }); | |
| } | |
| function populateVariables(data) { | |
| const select = document.getElementById('variable'); | |
| select.innerHTML = '<option value="">-- Select a variable --</option>'; | |
| if (data.surface_vars && data.surface_vars.length > 0) { | |
| const surfaceGroup = document.createElement('optgroup'); | |
| surfaceGroup.label = 'Surface Variables'; | |
| data.surface_vars.forEach(varName => { | |
| const option = document.createElement('option'); | |
| option.value = varName; | |
| option.textContent = `${varName} (Surface)`; | |
| option.dataset.type = 'surface'; | |
| surfaceGroup.appendChild(option); | |
| }); | |
| select.appendChild(surfaceGroup); | |
| } | |
| if (data.atmos_vars && data.atmos_vars.length > 0) { | |
| const atmosGroup = document.createElement('optgroup'); | |
| atmosGroup.label = 'Atmospheric Variables'; | |
| data.atmos_vars.forEach(varName => { | |
| const option = document.createElement('option'); | |
| option.value = varName; | |
| option.textContent = `${varName} (Atmospheric)`; | |
| option.dataset.type = 'atmospheric'; | |
| atmosGroup.appendChild(option); | |
| }); | |
| select.appendChild(atmosGroup); | |
| } | |
| // Populate pressure levels | |
| const pressureSelect = document.getElementById('pressure_level'); | |
| pressureSelect.innerHTML = ''; | |
| if (data.pressure_levels && data.pressure_levels.length > 0) { | |
| data.pressure_levels.forEach(level => { | |
| const option = document.createElement('option'); | |
| option.value = level; | |
| option.textContent = `${level} hPa`; | |
| pressureSelect.appendChild(option); | |
| }); | |
| } | |
| } | |
| function handleVariableChange() { | |
| const select = document.getElementById('variable'); | |
| const selectedOption = select.options[select.selectedIndex]; | |
| if (selectedOption.value) { | |
| const isAtmospheric = selectedOption.dataset.type === 'atmospheric'; | |
| // Show/hide pressure section | |
| const pressureSection = document.getElementById('pressureSection'); | |
| if (isAtmospheric) { | |
| pressureSection.classList.remove('hidden-section'); | |
| } else { | |
| pressureSection.classList.add('hidden-section'); | |
| } | |
| // Show plot options | |
| document.getElementById('plotOptionsSection').classList.remove('hidden-section'); | |
| // Initialize color preview when plot options are shown | |
| updateColorPreview(); | |
| // Enable plot buttons | |
| document.getElementById('staticPlotBtn').disabled = false; | |
| document.getElementById('interactivePlotBtn').disabled = false; | |
| } else { | |
| // Hide sections if no variable selected | |
| document.getElementById('pressureSection').classList.add('hidden-section'); | |
| document.getElementById('plotOptionsSection').classList.add('hidden-section'); | |
| // Disable plot buttons | |
| document.getElementById('staticPlotBtn').disabled = true; | |
| document.getElementById('interactivePlotBtn').disabled = true; | |
| } | |
| } | |
| // Color theme preview | |
| const colorMaps = { | |
| 'viridis': 'linear-gradient(to right, #440154, #414487, #2a788e, #22a884, #7ad151, #fde725)', | |
| 'plasma': 'linear-gradient(to right, #0d0887, #6a00a8, #b12a90, #e16462, #fca636, #f0f921)', | |
| 'YlOrRd': 'linear-gradient(to right, #ffffcc, #ffeda0, #fed976, #feb24c, #fd8d3c, #e31a1c)', | |
| 'Blues': 'linear-gradient(to right, #f7fbff, #deebf7, #c6dbef, #9ecae1, #6baed6, #2171b5)', | |
| 'Reds': 'linear-gradient(to right, #fff5f0, #fee0d2, #fcbba1, #fc9272, #fb6a4a, #de2d26)', | |
| 'Greens': 'linear-gradient(to right, #f7fcf5, #e5f5e0, #c7e9c0, #a1d99b, #74c476, #238b45)', | |
| 'Oranges': 'linear-gradient(to right, #fff5eb, #fee6ce, #fdd0a2, #fdae6b, #fd8d3c, #d94701)', | |
| 'Purples': 'linear-gradient(to right, #fcfbfd, #efedf5, #dadaeb, #bcbddc, #9e9ac8, #756bb1)', | |
| 'inferno': 'linear-gradient(to right, #000004, #420a68, #932667, #dd513a, #fca50a, #fcffa4)', | |
| 'magma': 'linear-gradient(to right, #000004, #3b0f70, #8c2981, #de4968, #fe9f6d, #fcfdbf)', | |
| 'cividis': 'linear-gradient(to right, #00224e, #123570, #3b496c, #575d6d, #707173, #8a8678)', | |
| 'coolwarm': 'linear-gradient(to right, #3b4cc0, #688aef, #b7d4f1, #f7f7f7, #f4b2a6, #dc7176, #a50026)', | |
| 'RdYlBu': 'linear-gradient(to right, #a50026, #d73027, #f46d43, #fdae61, #fee090, #e0f3f8, #abd9e9, #74add1, #4575b4, #313695)', | |
| 'Spectral': 'linear-gradient(to right, #9e0142, #d53e4f, #f46d43, #fdae61, #fee08b, #e6f598, #abdda4, #66c2a5, #3288bd, #5e4fa2)' | |
| }; | |
| function updateColorPreview() { | |
| const theme = document.getElementById('color_theme').value; | |
| const preview = document.getElementById('colorPreview'); | |
| const previewText = document.getElementById('colorPreviewText'); | |
| previewText.textContent = document.getElementById('color_theme').selectedOptions[0].text; | |
| if (colorMaps[theme]) { | |
| preview.style.background = colorMaps[theme]; | |
| } else { | |
| preview.style.background = colorMaps['viridis']; | |
| } | |
| } | |
| // Initialize color preview when page loads | |
| document.addEventListener('DOMContentLoaded', function() { | |
| updateColorPreview(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |