Spaces:
Sleeping
Sleeping
| # Run: streamlit run app.py | |
| import streamlit as st | |
| import requests | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from datetime import datetime | |
| import warnings | |
| import base64 | |
| from io import StringIO | |
| import pytz | |
| from retrying import retry | |
| # Suppress SSL warnings (not recommended for production) | |
| warnings.filterwarnings('ignore', message='Unverified HTTPS request') | |
| # Cache API calls to improve performance | |
| def get_coordinates(city): | |
| url = f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=en&format=json" | |
| def fetch(): | |
| response = requests.get(url, verify=False, timeout=5) | |
| response.raise_for_status() | |
| return response.json() | |
| try: | |
| data = fetch() | |
| if 'results' in data and data['results']: | |
| return data['results'][0]['latitude'], data['results'][0]['longitude'], data['results'][0]['country'], data['results'][0].get('timezone', 'UTC') | |
| return None, None, None, None | |
| except requests.RequestException as e: | |
| st.error(f"Error fetching coordinates for {city}: {str(e)}") | |
| return None, None, None, None | |
| def get_weather(lat, lon): | |
| url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_mean,weathercode¤t_weather=true&temperature_unit=fahrenheit&timezone=auto" | |
| def fetch(): | |
| response = requests.get(url, verify=False, timeout=5) | |
| response.raise_for_status() | |
| return response.json() | |
| try: | |
| return fetch() | |
| except requests.RequestException as e: | |
| st.error(f"Error fetching weather data: {str(e)}") | |
| return None | |
| # Weather code to icon mapping | |
| weather_icons = { | |
| 0: "☀️", 1: "🌤️", 2: "⛅", 3: "☁️", 61: "🌧️", 71: "❄️" | |
| } | |
| # Streamlit configuration | |
| st.set_page_config(page_title="Weather Dashboard", layout="wide", page_icon="🌤️") | |
| # Custom CSS for professional styling | |
| st.markdown(""" | |
| <style> | |
| .main {background-color: #f4f6fa;} | |
| .stButton>button { | |
| background-color: #007bff; | |
| color: white; | |
| border-radius: 8px; | |
| padding: 10px 20px; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton>button:hover { | |
| background-color: #0056b3; | |
| transform: scale(1.05); | |
| } | |
| .stTextInput>div>input { | |
| border-radius: 8px; | |
| border: 1px solid #ced4da; | |
| } | |
| .footer { | |
| font-size: 12px; | |
| text-align: center; | |
| margin-top: 30px; | |
| padding: 15px; | |
| background-color: #e9ecef; | |
| border-radius: 8px; | |
| } | |
| .header { | |
| text-align: center; | |
| padding: 20px; | |
| background-color: #007bff; | |
| color: white; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .metric-card { | |
| background-color: #ffffff; | |
| padding: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
| text-align: center; | |
| } | |
| .expander-header { | |
| font-size: 1.5em; | |
| font-weight: bold; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Header with logo placeholder | |
| st.markdown(""" | |
| <div class='header'> | |
| <h1>🌍 Global Weather Dashboard</h1> | |
| <p>Powered by Open-Meteo API</p> | |
| <!-- Replace with your logo --> | |
| <img src="https://via.placeholder.com/100x50.png?text=Logo" style="margin-top: 10px;"> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Sidebar for controls | |
| with st.sidebar: | |
| st.header("Dashboard Settings") | |
| locations_input = st.text_input("Enter city names (comma-separated):", "New York, London, Tokyo", help="E.g., New York, London, Tokyo") | |
| predefined_cities = ["New York", "London", "Tokyo", "Sydney", "Paris", "Dubai", "Singapore"] | |
| selected_city = st.selectbox("Or select a city:", [""] + predefined_cities, help="Choose a city for quick access") | |
| chart_type = st.radio("Chart Type:", ["Separate Charts", "Combined Chart"], help="Choose how to display weather charts") | |
| if st.button("Fetch Weather", key="fetch_button"): | |
| st.session_state['fetch'] = True | |
| with st.expander("Security Info"): | |
| st.warning("⚠️ Using verify=False for SSL. This is insecure for production. Ensure valid SSL certificates for secure deployment.") | |
| # Main content | |
| if 'fetch' in st.session_state and st.session_state['fetch']: | |
| cities = [selected_city] if selected_city else [city.strip() for city in locations_input.split(',') if city.strip()] | |
| cities = list(dict.fromkeys(cities)) # Remove duplicates | |
| if not cities: | |
| st.warning("Please enter or select at least one city.") | |
| else: | |
| with st.spinner("Fetching weather data..."): | |
| # Collect coordinates for map | |
| coordinates = [] | |
| for city in cities: | |
| lat, lon, country, timezone = get_coordinates(city) | |
| if lat and lon: | |
| weather_data = get_weather(lat, lon) | |
| if weather_data and 'current_weather' in weather_data: | |
| temp = weather_data['current_weather'].get('temperature', 0) | |
| coordinates.append((city, lat, lon, country, timezone, temp)) | |
| # Render Plotly map | |
| st.markdown("### City Locations") | |
| if coordinates: | |
| df_map = pd.DataFrame(coordinates, columns=['City', 'Latitude', 'Longitude', 'Country', 'Timezone', 'Current Temp (°F)']) | |
| fig_map = px.scatter_geo( | |
| df_map, | |
| lat='Latitude', | |
| lon='Longitude', | |
| hover_name='City', | |
| hover_data=['Country', 'Current Temp (°F)'], | |
| title="City Locations (Colored by Current Temperature)", | |
| projection="natural earth", | |
| color='Current Temp (°F)', | |
| color_continuous_scale='RdBu_r', | |
| range_color=[df_map['Current Temp (°F)'].min(), df_map['Current Temp (°F)'].max()] | |
| ) | |
| fig_map.update_layout( | |
| showlegend=True, | |
| geo=dict( | |
| showland=True, | |
| landcolor="#e9ecef", | |
| showcountries=True, | |
| countrycolor="#cccccc", | |
| bgcolor="#f4f6fa" | |
| ), | |
| font=dict(size=12), | |
| margin=dict(l=20, r=20, t=50, b=20) | |
| ) | |
| fig_map.update_traces(marker=dict(size=12, line=dict(width=1, color='DarkSlateGrey'))) | |
| st.plotly_chart(fig_map, use_container_width=True) | |
| else: | |
| st.warning("No valid coordinates found for the provided cities.") | |
| # Weather data for each city | |
| for city in cities: | |
| with st.expander(f"🌆 Weather for {city}", expanded=True): | |
| lat, lon, country, timezone = get_coordinates(city) | |
| if lat and lon: | |
| st.write(f"📍 {city}, {country} (Lat: {lat:.2f}, Lon: {lon:.2f})") | |
| local_time = datetime.now(pytz.timezone(timezone)).strftime("%Y-%m-%d %H:%M:%S %Z") | |
| st.write(f"🕒 Local Time: {local_time}") | |
| weather_data = get_weather(lat, lon) | |
| if weather_data: | |
| # Current Weather | |
| current = weather_data.get('current_weather', {}) | |
| st.markdown("#### Current Weather", unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns([1, 1, 1]) | |
| with col1: | |
| st.markdown("<div class='metric-card'>", unsafe_allow_html=True) | |
| st.metric("Temperature", f"{current.get('temperature', 'N/A')} °F") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| with col2: | |
| st.markdown("<div class='metric-card'>", unsafe_allow_html=True) | |
| st.metric("Wind Speed", f"{current.get('windspeed', 'N/A')} km/h") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| with col3: | |
| st.markdown("<div class='metric-card'>", unsafe_allow_html=True) | |
| weather_code = current.get('weathercode', 0) | |
| st.metric("Condition", f"{weather_icons.get(weather_code, '🌫️')} {weather_code}") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Daily Forecast Table | |
| daily = weather_data.get('daily', {}) | |
| if daily: | |
| df = pd.DataFrame({ | |
| 'Date': pd.to_datetime(daily['time']), | |
| 'Max Temp (°F)': daily['temperature_2m_max'], | |
| 'Min Temp (°F)': daily['temperature_2m_min'], | |
| 'Precipitation Prob (%)': [prob * 100 if prob is not None else 0 for prob in daily['precipitation_probability_mean']], | |
| 'Condition': [weather_icons.get(code, '🌫️') for code in daily['weathercode']] | |
| }) | |
| st.markdown("#### 7-Day Forecast") | |
| st.dataframe(df.style.format({ | |
| 'Max Temp (°F)': '{:.1f}', | |
| 'Min Temp (°F)': '{:.1f}', | |
| 'Precipitation Prob (%)': '{:.0f}', | |
| 'Date': '{:%Y-%m-%d}' | |
| }).background_gradient(subset=['Max Temp (°F)'], cmap='Reds')) | |
| # Summary Statistics | |
| st.markdown("#### Summary Statistics") | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.markdown("<div class='metric-card'>", unsafe_allow_html=True) | |
| st.metric("Avg Max Temp", f"{df['Max Temp (°F)'].mean():.1f} °F") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| with col2: | |
| st.markdown("<div class='metric-card'>", unsafe_allow_html=True) | |
| st.metric("Avg Min Temp", f"{df['Min Temp (°F)'].mean():.1f} °F") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| with col3: | |
| st.markdown("<div class='metric-card'>", unsafe_allow_html=True) | |
| st.metric("Avg Precipitation Prob", f"{df['Precipitation Prob (%)'].mean():.0f}%") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Download CSV | |
| csv = df.to_csv(index=False) | |
| b64 = base64.b64encode(csv.encode()).decode() | |
| href = f'<a href="data:file/csv;base64,{b64}" download="{city}_forecast.csv">Download Forecast as CSV</a>' | |
| st.markdown(href, unsafe_allow_html=True) | |
| # Plotly Charts | |
| if chart_type == "Separate Charts": | |
| # Temperature Line Chart with Shaded Area | |
| st.markdown("#### Temperature Forecast") | |
| fig_temp = go.Figure() | |
| fig_temp.add_trace(go.Scatter( | |
| x=df['Date'], y=df['Max Temp (°F)'], | |
| name='Max Temp (°F)', line=dict(color='#ff4d4d'), | |
| hovertemplate='Max Temp: %{y:.1f}°F<br>%{x|%Y-%m-%d}<br>Condition: %{customdata}', | |
| customdata=df['Condition'] | |
| )) | |
| fig_temp.add_trace(go.Scatter( | |
| x=df['Date'], y=df['Min Temp (°F)'], | |
| name='Min Temp (°F)', line=dict(color='#4d79ff'), | |
| hovertemplate='Min Temp: %{y:.1f}°F<br>%{x|%Y-%m-%d}<br>Condition: %{customdata}', | |
| customdata=df['Condition'], | |
| fill='tonexty', fillcolor='rgba(77, 121, 255, 0.1)' | |
| )) | |
| max_temp_idx = df['Max Temp (°F)'].idxmax() | |
| fig_temp.add_annotation( | |
| x=df['Date'][max_temp_idx], y=df['Max Temp (°F)'][max_temp_idx], | |
| text=f"High: {df['Max Temp (°F)'][max_temp_idx]:.1f}°F", | |
| showarrow=True, arrowhead=2, ax=20, ay=-30 | |
| ) | |
| fig_temp.update_layout( | |
| showlegend=True, | |
| template='plotly_white', | |
| hovermode='x unified', | |
| xaxis_title="Date", | |
| yaxis_title="Temperature (°F)", | |
| font=dict(size=12), | |
| margin=dict(l=20, r=20, t=50, b=20) | |
| ) | |
| st.plotly_chart(fig_temp, use_container_width=True) | |
| # Precipitation Bar Chart | |
| st.markdown("#### Precipitation Probability") | |
| fig_precip = go.Figure(data=[ | |
| go.Bar( | |
| x=df['Date'], | |
| y=df['Precipitation Prob (%)'], | |
| marker_color='#1e90ff', | |
| hovertemplate='Precipitation: %{y:.0f}%<br>%{x|%Y-%m-%d}<br>Condition: %{customdata}', | |
| customdata=df['Condition'] | |
| ) | |
| ]) | |
| max_precip_idx = df['Precipitation Prob (%)'].idxmax() | |
| fig_precip.add_annotation( | |
| x=df['Date'][max_precip_idx], y=df['Precipitation Prob (%)'][max_precip_idx], | |
| text=f"Max: {df['Precipitation Prob (%)'][max_precip_idx]:.0f}%", | |
| showarrow=True, arrowhead=2, ax=20, ay=-30 | |
| ) | |
| fig_precip.update_layout( | |
| title=f"Precipitation Probability for {city}", | |
| xaxis_title="Date", | |
| yaxis_title="Probability (%)", | |
| template='plotly_white', | |
| font=dict(size=12), | |
| margin=dict(l=20, r=20, t=50, b=20) | |
| ) | |
| st.plotly_chart(fig_precip, use_container_width=True) | |
| else: | |
| # Combined Chart | |
| st.markdown("#### Combined Temperature and Precipitation Forecast") | |
| fig_combined = go.Figure() | |
| fig_combined.add_trace(go.Scatter( | |
| x=df['Date'], y=df['Max Temp (°F)'], | |
| name='Max Temp (°F)', line=dict(color='#ff4d4d'), | |
| hovertemplate='Max Temp: %{y:.1f}°F<br>%{x|%Y-%m-%d}<br>Condition: %{customdata}', | |
| customdata=df['Condition'] | |
| )) | |
| fig_combined.add_trace(go.Scatter( | |
| x=df['Date'], y=df['Min Temp (°F)'], | |
| name='Min Temp (°F)', line=dict(color='#4d79ff'), | |
| hovertemplate='Min Temp: %{y:.1f}°F<br>%{x|%Y-%m-%d}<br>Condition: %{customdata}', | |
| customdata=df['Condition'], | |
| fill='tonexty', fillcolor='rgba(77, 121, 255, 0.1)' | |
| )) | |
| fig_combined.add_trace(go.Bar( | |
| x=df['Date'], y=df['Precipitation Prob (%)'], | |
| name='Precipitation (%)', yaxis='y2', marker_color='#1e90ff', | |
| hovertemplate='Precipitation: %{y:.0f}%<br>%{x|%Y-%m-%d}<br>Condition: %{customdata}', | |
| customdata=df['Condition'], | |
| opacity=0.4 | |
| )) | |
| max_temp_idx = df['Max Temp (°F)'].idxmax() | |
| fig_combined.add_annotation( | |
| x=df['Date'][max_temp_idx], y=df['Max Temp (°F)'][max_temp_idx], | |
| text=f"High: {df['Max Temp (°F)'][max_temp_idx]:.1f}°F", | |
| showarrow=True, arrowhead=2, ax=20, ay=-30 | |
| ) | |
| fig_combined.update_layout( | |
| title=f"Combined Forecast for {city}", | |
| xaxis_title="Date", | |
| yaxis=dict(title="Temperature (°F)", titlefont=dict(color="#ff4d4d"), tickfont=dict(color="#ff4d4d")), | |
| yaxis2=dict(title="Precipitation (%)", titlefont=dict(color="#1e90ff"), tickfont=dict(color="#1e90ff"), | |
| overlaying='y', side='right'), | |
| template='plotly_white', | |
| hovermode='x unified', | |
| legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5), | |
| font=dict(size=12), | |
| margin=dict(l=20, r=20, t=50, b=20) | |
| ) | |
| st.plotly_chart(fig_combined, use_container_width=True) | |
| else: | |
| st.error(f"Failed to fetch weather data for {city}.") | |
| else: | |
| st.error(f"Could not find coordinates for {city}.") | |
| # Footer | |
| st.markdown(""" | |
| <div class='footer'> | |
| <p>Weather data by <a href="https://open-meteo.com" target="_blank">Open-Meteo.com</a> under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">CC BY 4.0</a> | | |
| For non-commercial use only | | |
| <a href="https://github.com/open-meteo/open-meteo" target="_blank">Source Code</a> | | |
| Contact: <a href="mailto:info@open-meteo.com">info@open-meteo.com</a></p> | |
| </div> | |
| """, unsafe_allow_html=True) |