diff --git a/.DS_Store b/.DS_Store index a71212a94b76a836ef2d09f89252cc1bc2be750c..1a07c06c0500ebfdb6c47baa927d00978672bc1d 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8d8c5d4775dadce65379857878cb67a7f28daa15 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# GLACIERH2O diff --git a/analyze_glacier_data.py b/analyze_glacier_data.py index 0441583c4da56add0b48fde329f3bfb84bbb37e8..827c8a214988cf7602287e8f39559f7c001b49f9 100644 --- a/analyze_glacier_data.py +++ b/analyze_glacier_data.py @@ -1,55 +1,88 @@ import pandas as pd -import os - -# Define the file paths -gtn_report_path = 'GTN Report.xlsx' -glacier_data_dir = './glacier_data' - -# Load the GTN report -gtn_data = pd.read_excel(gtn_report_path) - -# Prepare a dictionary for region-to-country mapping based on GTN report -region_to_country = { - 'alaska': 'US - UNITED STATES', - 'scandinavia': 'SE - SWEDEN', - 'new_zealand': 'NZ - NEW ZEALAND', - 'antarctic_and_subantarctic': 'AQ - ANTARCTICA', - 'russian_arctic': 'RU - RUSSIAN FEDERATION', - 'greenland_periphery': 'GL - GREENLAND', - 'iceland': 'IS - ICELAND', - 'svalbard': 'NO - NORWAY', - 'western_canada_us': 'CA - CANADA', - 'arctic_canada_north': 'CA - CANADA', - 'arctic_canada_south': 'CA - CANADA', - 'south_asia_east': 'IN - INDIA', - 'south_asia_west': 'PK - PAKISTAN', - 'central_asia': 'KZ - KAZAKHSTAN', - 'caucasus_middle_east': 'GE - GEORGIA', - 'central_europe': 'DE - GERMANY', - 'north_asia': 'CN - CHINA', - 'low_latitudes': 'BR - BRAZIL', - 'southern_andes': 'CL - CHILE' +import json +# Generate visualizations and display them directly to the user using ace_tools +import ace_tools as tools +import matplotlib.pyplot as plt + + +# Load the glacier data +with open('/Users/hamza/glacier/data/glacier_data.json', 'r') as file: + glacier_data = json.load(file) + +# Load the GTN report data +with open('/Users/hamza/glacier/data/gtn_report.json', 'r') as file: + gtn_report = json.load(file) + +# Convert to DataFrames for easier manipulation +glacier_df = pd.DataFrame(glacier_data) +gtn_df = pd.DataFrame(gtn_report) + +# Extract unique regions from glacier data +unique_regions = glacier_df['region'].unique() + +# Mapping countries from GTN report to corresponding glacier regions manually +region_country_mapping = { + "1_alaska": ["US - UNITED STATES"], + "2_western_canada_us": ["CA - CANADA", "US - UNITED STATES"], + "3_arctic_canada": ["CA - CANADA"], + "4_greenland": ["GL - GREENLAND"], + "5_iceland": ["IS - ICELAND"], + "6_svalbard_jan_mayen": ["SJ - SVALBARD AND JAN MAYEN"], + "7_scandinavia": ["NO - NORWAY", "SE - SWEDEN", "FI - FINLAND"], + "8_russian_arctic": ["RU - RUSSIAN FEDERATION"], + "9_siberia": ["RU - RUSSIAN FEDERATION"], + "10_central_asia": ["KZ - KAZAKHSTAN", "TJ - TAJIKISTAN", "UZ - UZBEKISTAN", "KG - KYRGYZSTAN"], + "11_himalaya": ["NP - NEPAL", "IN - INDIA", "CN - CHINA", "BT - BHUTAN", "PK - PAKISTAN"], + "12_caucasus_middle_east": ["GE - GEORGIA", "TR - TURKEY", "IR - IRAN"], + "13_southern_andes": ["CL - CHILE", "AR - ARGENTINA"], + "14_new_zealand": ["NZ - NEW ZEALAND"], + "15_africa": ["MA - MOROCCO"], + "16_antarctic": ["AQ - ANTARCTICA"], } -# Initialize an empty list for storing region-country mapping -region_country_mapping = [] +# Assign regions to the GTN report based on country mapping +def assign_region(country_code): + for region, countries in region_country_mapping.items(): + if country_code in countries: + return region + return "Unknown" + +gtn_df['region'] = gtn_df['GRDCCOUNTRY'].apply(assign_region) + +# Merge glacier data with GTN report based on 'region' +merged_df = pd.merge(glacier_df, gtn_df, on='region', how='inner') + +import ace_tools as tools; tools.display_dataframe_to_user(name="Merged Glacier and GTN Data", dataframe=merged_df) + +# Display the first few rows of the merged dataframe +merged_df.head() + -# Loop through each glacier CSV file in the directory -for file_name in os.listdir(glacier_data_dir): - if file_name.endswith('.csv'): - region_name = file_name.split('.')[0] - # Check if region exists in the predefined mapping - if region_name in region_to_country: - country = region_to_country[region_name] - region_country_mapping.append({'region': region_name, 'country': country}) -# Create a DataFrame for region-country mapping -region_country_df = pd.DataFrame(region_country_mapping) +# Plot 1: Glacier Mass Change vs Station Elevation +fig1, ax1 = plt.subplots(figsize=(12, 8)) +ax1.scatter(merged_df['glacier_mass_change'], merged_df['station_elevation'], alpha=0.7, edgecolors='k', s=80) +ax1.set_title('Glacier Mass Change vs Station Elevation', fontsize=14) +ax1.set_xlabel('Glacier Mass Change (Gt)', fontsize=12) +ax1.set_ylabel('Station Elevation (m)', fontsize=12) +ax1.grid(True) -# Save the mapping to a CSV file -region_country_mapping_file = './data/region_to_country_mapping.csv' -region_country_df.to_csv(region_country_mapping_file, index=False) +# Plot 2: Average Glacier Mass Change by Region +fig2, ax2 = plt.subplots(figsize=(14, 8)) +region_mass_change = merged_df.groupby('region')['glacier_mass_change'].mean().sort_values() +region_mass_change.plot(kind='barh', ax=ax2, color='skyblue', edgecolor='black') +ax2.set_title('Average Glacier Mass Change by Region', fontsize=14) +ax2.set_xlabel('Average Mass Change (Gt)', fontsize=12) +ax2.set_ylabel('Region', fontsize=12) +ax2.grid(axis='x', linestyle='--', alpha=0.7) -print(f"Region to Country mapping saved to {region_country_mapping_file}") +# Plot 3: Station Elevation vs Glacier Area +fig3, ax3 = plt.subplots(figsize=(12, 8)) +ax3.scatter(merged_df['glacier_area'], merged_df['station_elevation'], alpha=0.7, color='green', edgecolors='k', s=80) +ax3.set_title('Station Elevation vs Glacier Area', fontsize=14) +ax3.set_xlabel('Glacier Area (km²)', fontsize=12) +ax3.set_ylabel('Station Elevation (m)', fontsize=12) +ax3.grid(True) -# Now you can load this CSV into your project for future use +# Display the plots to the user +tools.display_dataframe_to_user(name="Merged Glacier and GTN Data", dataframe=merged_df) diff --git a/css/styles.css b/css/styles.css index 9aa874d5fb76a82ca1f98f4276bd27d7ef5490f8..b0daeb6e7cac794979fc62bf07b66df5b72e2ba7 100644 --- a/css/styles.css +++ b/css/styles.css @@ -45,6 +45,20 @@ h1 { box-shadow: var(--box-shadow); } +.dark-mode #controls { + background-color: #34495e !important; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4) !important; +} + +.dark-mode #controls button, +.dark-mode #controls select, +.dark-mode #controls input, +.dark-mode #controls label { + color: #ecf0f1 !important; + background-color: rgba(255, 255, 255, 0.1) !important; + border: 1px solid rgba(255, 255, 255, 0.2) !important; +} + .year-control { display: flex; align-items: center; @@ -86,6 +100,7 @@ button:hover, select:hover, input:hover { flex: 1; max-width: 70%; height: 80vh; + position: relative; border-radius: var(--border-radius); box-shadow: var(--box-shadow); } @@ -94,47 +109,56 @@ button:hover, select:hover, input:hover { width: 100%; height: 100%; border-radius: var(--border-radius); + position: relative; + z-index: 1; } #chart-container { flex: 1; max-width: 28%; + height: 80vh; display: flex; flex-direction: column; gap: 20px; - height: 80vh; } -#region-chart-1, #region-chart-2 { +#region-chart { width: 100%; - height: 220px; + height: 100%; border-radius: var(--border-radius); box-shadow: var(--box-shadow); } -/* Legend */ +/* Legend Styles */ #legend { position: absolute; - bottom: 15px; + top: 10px; right: 10px; - background-color: rgba(255, 255, 255, 0.8); - padding: 15px; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px; border-radius: 8px; box-shadow: var(--box-shadow); font-weight: bold; - width: 220px; + width: 200px; + z-index: 1002; } -#legend .color-gradient { - width: 100%; - height: 20px; - background: linear-gradient(to right, #003366, #33cc33, #ff3300); - border-radius: 8px; - margin-bottom: 10px; +#legend .legend-title { + text-align: center; + font-size: 14px; + margin-bottom: 8px; } #legend .legend-item { display: flex; - justify-content: space-between; - font-size: 12px; + align-items: center; + margin-bottom: 5px; } + +#legend .color-box { + width: 20px; + height: 20px; + margin-right: 8px; + border-radius: 4px; + border: 1px solid #ccc; +} \ No newline at end of file diff --git a/index.html b/index.html index 9204e8c301ae112ba0ad96a8ccad42bda0075334..fa4252480636315fbb72668c8043921d4ab55108 100644 --- a/index.html +++ b/index.html @@ -2,17 +2,15 @@ <html lang="en"> <head> <meta charset="UTF-8"> - <title>🌍 GlacierH2O - </title> + <title>🌍 GlacierH2O</title> <link rel="stylesheet" href="css/styles.css"> <script src="https://cdn.jsdelivr.net/npm/d3@7"></script> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> - <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> + <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> </head> <body> - <h1>🌍 GlacierH2O - </h1> + <h1>🌍 GlacierH2O</h1> <div id="controls"> <div class="year-control"> @@ -23,23 +21,19 @@ <select id="basemap-select"> <option value="OpenStreetMap">OpenStreetMap</option> <option value="Satellite">Satellite</option> - </select> <button id="dark-mode-toggle">🌙 Dark Mode</button> - <button id="download-btn">⬇️ Download Chart</button> </div> - <div id="global-stats"></div> - <div id="container"> - <div id="map-container"><div id="map"></div></div> + <div id="map-container"> + <div id="map"></div> + </div> <div id="chart-container"> - <canvas id="region-chart-1"></canvas> - <canvas id="region-chart-2"></canvas> - <canvas id="region-chart-3"></canvas> + <canvas id="region-chart"></canvas> </div> </div> <script type="module" src="js/main.js"></script> </body> -</html> +</html> \ No newline at end of file diff --git a/js/chart.js b/js/chart.js index 9130f2fa7d69431e19ae8ee015b8b4a5f8876a81..8ec6c716a009c9dae7a4e86ee8bb4738c0e31680 100644 --- a/js/chart.js +++ b/js/chart.js @@ -2,66 +2,90 @@ let chartInstance = null; export function updateChart(region, darkMode = false) { d3.json("data/glacier_data.json").then((data) => { - const regionData = data.filter(d => d.region === region).sort((a, b) => new Date(a.end_date) - new Date(b.end_date)); - const globalData = data.sort((a, b) => new Date(a.end_date) - new Date(b.end_date)); + // Filter data for the selected region + const regionData = data + .filter(d => d.region === region) + .sort((a, b) => new Date(a.end_date) - new Date(b.end_date)); + + const ctx = document.getElementById("region-chart").getContext("2d"); - const ctx1 = document.getElementById("region-chart-1").getContext("2d"); - const ctx2 = document.getElementById("region-chart-2").getContext("2d"); - - // Destroy existing charts - if (chartInstance) chartInstance.forEach(chart => chart.destroy()); + // Destroy existing chart if it exists + if (chartInstance) { + chartInstance.destroy(); + } + // Determine text and grid colors based on dark mode const textColor = darkMode ? "#ecf0f1" : "#34495e"; const gridColor = darkMode ? "rgba(255, 255, 255, 0.2)" : "rgba(0, 0, 0, 0.1)"; - const commonOptions = { + // Chart options + const options = { responsive: true, + maintainAspectRatio: false, plugins: { - legend: { labels: { color: textColor } }, + legend: { + labels: { + color: textColor + } + }, + title: { + display: true, + text: region === 'global' ? 'Global Glacier Mass Change' : `${region.replace(/_/g, ' ')} Glacier Mass Change`, + font: { + size: 14 + }, + color: textColor + } }, scales: { - x: { title: { display: true, text: "Year", color: textColor }, ticks: { color: textColor }, grid: { color: gridColor } }, - y: { title: { display: true, text: "Mass Change (Gt)", color: textColor }, ticks: { color: textColor }, grid: { color: gridColor } }, - }, + x: { + title: { + display: true, + text: "Year", + color: textColor + }, + ticks: { + color: textColor + }, + grid: { + color: gridColor + } + }, + y: { + title: { + display: true, + text: "Mass Change (Gt)", + color: textColor + }, + ticks: { + color: textColor + }, + grid: { + color: gridColor + } + } + } }; - // Chart 1: Glacier Mass Change Over Time - const chart1 = new Chart(ctx1, { + // Create the chart + chartInstance = new Chart(ctx, { type: "line", data: { labels: regionData.map(d => d.end_date), datasets: [{ - label: "Glacier Mass Change (Gt)", + label: region === 'global' ? "Global Glacier Mass Change" : `${region.replace(/_/g, ' ')} Glacier Mass Change`, data: regionData.map(d => d.glacier_mass_change), borderColor: "#3498db", backgroundColor: "rgba(52, 152, 219, 0.2)", fill: true, tension: 0.4, - }], + pointRadius: 3, + pointBackgroundColor: "#3498db" + }] }, - options: commonOptions, + options: options }); - - // Chart 2: Global Area vs. Mass Change - const chart2 = new Chart(ctx2, { - type: "scatter", - data: { - datasets: [{ - label: "Global Area vs. Mass Change", - data: globalData.map(d => ({ x: d.glacier_area, y: d.glacier_mass_change })), - backgroundColor: "rgba(46, 204, 113, 0.5)", - pointRadius: 6, - }], - }, - options: { - ...commonOptions, - scales: { - x: { title: { display: true, text: "Glacier Area (km²)", color: textColor }, ticks: { color: textColor }, grid: { color: gridColor } }, - y: { title: { display: true, text: "Mass Change (Gt)", color: textColor }, ticks: { color: textColor }, grid: { color: gridColor } }, - }, - }, - }); - - chartInstance = [chart1, chart2]; }); } + +export { chartInstance }; \ No newline at end of file diff --git a/js/main.js b/js/main.js index aebf415bdae83bd5a157a1d8e11cb8298f913c93..9473d0caced633647252242602f6d020c331bf24 100644 --- a/js/main.js +++ b/js/main.js @@ -1,4 +1,4 @@ -import { updateChart } from "./chart.js"; +import { updateChart, chartInstance } from "./chart.js"; document.addEventListener("DOMContentLoaded", () => { const yearSlider = document.getElementById("year-slider"); @@ -7,61 +7,148 @@ document.addEventListener("DOMContentLoaded", () => { const darkModeToggle = document.getElementById("dark-mode-toggle"); let glacierData = []; + let currentRegion = 'global'; // Default to global view - const isDarkMode = () => document.body.classList.contains("dark-mode"); - - // Dark mode toggle - darkModeToggle.addEventListener("click", () => { - document.body.classList.toggle("dark-mode"); - darkModeToggle.textContent = isDarkMode() ? "☀️ Light Mode" : "🌙 Dark Mode"; - updateChart(currentRegion, isDarkMode()); - }); - + // Initialize the map const map = L.map("map").setView([20, 0], 2); + + // Define base map layers const baseMaps = { "OpenStreetMap": L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"), - "Satellite": L.tileLayer("https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png"), - "Terrain": L.tileLayer("https://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.jpg"), + "Satellite": L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}") }; - baseMaps["OpenStreetMap"].addTo(map); + // Add initial base layer + let currentBaseLayer = baseMaps["OpenStreetMap"]; + currentBaseLayer.addTo(map); + + // Handle base map selection baseMapSelector.addEventListener("change", (e) => { - Object.values(baseMaps).forEach(layer => map.removeLayer(layer)); - baseMaps[e.target.value].addTo(map); + map.removeLayer(currentBaseLayer); + currentBaseLayer = baseMaps[e.target.value]; + currentBaseLayer.addTo(map); }); + // Create legend for map + const createLegend = () => { + const legend = L.control({ position: "topright" }); + + legend.onAdd = () => { + const div = L.DomUtil.create("div", "info legend"); + div.setAttribute("id", "legend"); + div.innerHTML = ` + <div class="legend-title">Glacier Mass Change</div> + <div class="legend-item"> + <div class="color-box" style="background-color: #003366;"></div> + <span>Freeze (≥ 10 Gt)</span> + </div> + <div class="legend-item"> + <div class="color-box" style="background-color: #33cc33;"></div> + <span>Stable (-10 to 10 Gt)</span> + </div> + <div class="legend-item"> + <div class="color-box" style="background-color: #ff3300;"></div> + <span>Melting (< -10 Gt)</span> + </div> + `; + + // Prevent map interactions when clicking on legend + L.DomEvent.disableClickPropagation(div); + return div; + }; + + legend.addTo(map); + }; + + // Update map markers based on selected year const updateMapMarkers = (year) => { + // Clear existing markers map.eachLayer(layer => { - if (layer instanceof L.CircleMarker) map.removeLayer(layer); + if (layer instanceof L.CircleMarker) { + map.removeLayer(layer); + } }); - glacierData.filter(d => Math.round(d.end_date) === +year).forEach((data) => { - L.circleMarker([data.latitude, data.longitude], { - radius: Math.max(data.glacier_area / 2000, 6), - fillColor: data.glacier_mass_change > -10 && data.glacier_mass_change < 10 ? "#33cc33" : d3.scaleSequential(d3.interpolateRdYlBu).domain([-100, 100])(data.glacier_mass_change), - color: "#222", - weight: 1, - fillOpacity: 0.8, - }) - .bindPopup(`<strong>Region:</strong> ${data.region.replace(/_/g, " ")}<br><strong>Mass Change:</strong> ${data.glacier_mass_change} Gt`) - .on("click", () => updateChart(data.region, isDarkMode())) - .addTo(map); - }); + // Add new markers for the selected year + glacierData + .filter(d => Math.round(d.end_date) === +year) + .forEach((data) => { + // Determine color based on mass change + let fillColor; + if (data.glacier_mass_change >= 10) { + fillColor = "#003366"; // Freeze + } else if (data.glacier_mass_change <= -10) { + fillColor = "#ff3300"; // Melting + } else { + fillColor = "#33cc33"; // Stable + } + + // Create circle marker + const marker = L.circleMarker([data.latitude, data.longitude], { + radius: Math.max(data.glacier_area / 2000, 6), + fillColor, + color: "#222", + weight: 1, + fillOpacity: 0.8 + }); + + // Add popup with region information + marker.bindPopup(` + <strong>Region:</strong> ${data.region.replace(/_/g, " ")}<br> + <strong>Mass Change:</strong> ${data.glacier_mass_change} Gt + `); + + // Add click event to update charts + marker.on('click', function() { + currentRegion = data.region; + updateChart(data.region, document.body.classList.contains('dark-mode')); + }); + + // Add marker to map + marker.addTo(map); + }); }; + // Handle year slider input yearSlider.addEventListener("input", (e) => { yearInput.value = e.target.value; updateMapMarkers(e.target.value); }); + // Handle year input changes yearInput.addEventListener("input", (e) => { const year = Math.max(2000, Math.min(2025, e.target.value)); yearSlider.value = year; updateMapMarkers(year); }); - d3.json("data/glacier_data.json").then((data) => { + // Toggle dark mode + darkModeToggle.addEventListener("click", () => { + document.body.classList.toggle("dark-mode"); + darkModeToggle.textContent = document.body.classList.contains("dark-mode") + ? "☀️ Light Mode" + : "🌙 Dark Mode"; + + // Update charts with new theme + updateChart(currentRegion, document.body.classList.contains('dark-mode')); + }); + + // Add window resize handler + window.addEventListener('resize', () => { + if (chartInstance) { + chartInstance.resize(); + } + }); + + // Initialize the application + Promise.all([ + d3.json("data/glacier_data.json") + ]).then(([data]) => { glacierData = data; updateMapMarkers(yearSlider.value); + createLegend(); + updateChart('global', document.body.classList.contains('dark-mode')); + }).catch(error => { + console.error("Error loading data:", error); }); -}); +}); \ No newline at end of file