# 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 @st.cache_data(ttl=3600) def get_coordinates(city): url = f"https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1&language=en&format=json" @retry(stop_max_attempt_number=3, wait_fixed=2000) 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 @st.cache_data(ttl=3600) 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" @retry(stop_max_attempt_number=3, wait_fixed=2000) 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(""" """, unsafe_allow_html=True) # Header with logo placeholder st.markdown("""

🌍 Global Weather Dashboard

Powered by Open-Meteo API

""", 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("
", unsafe_allow_html=True) st.metric("Temperature", f"{current.get('temperature', 'N/A')} °F") st.markdown("
", unsafe_allow_html=True) with col2: st.markdown("
", unsafe_allow_html=True) st.metric("Wind Speed", f"{current.get('windspeed', 'N/A')} km/h") st.markdown("
", unsafe_allow_html=True) with col3: st.markdown("
", unsafe_allow_html=True) weather_code = current.get('weathercode', 0) st.metric("Condition", f"{weather_icons.get(weather_code, '🌫️')} {weather_code}") st.markdown("
", 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("
", unsafe_allow_html=True) st.metric("Avg Max Temp", f"{df['Max Temp (°F)'].mean():.1f} °F") st.markdown("
", unsafe_allow_html=True) with col2: st.markdown("
", unsafe_allow_html=True) st.metric("Avg Min Temp", f"{df['Min Temp (°F)'].mean():.1f} °F") st.markdown("
", unsafe_allow_html=True) with col3: st.markdown("
", unsafe_allow_html=True) st.metric("Avg Precipitation Prob", f"{df['Precipitation Prob (%)'].mean():.0f}%") st.markdown("
", unsafe_allow_html=True) # Download CSV csv = df.to_csv(index=False) b64 = base64.b64encode(csv.encode()).decode() href = f'Download Forecast as CSV' 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
%{x|%Y-%m-%d}
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
%{x|%Y-%m-%d}
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}%
%{x|%Y-%m-%d}
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
%{x|%Y-%m-%d}
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
%{x|%Y-%m-%d}
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}%
%{x|%Y-%m-%d}
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(""" """, unsafe_allow_html=True)