Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weather forecast on dashboard #185

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ ocf_blosc2==0.0.4
zarr==2.14.2
elexonpy
fiona==1.9.6
herbie
133 changes: 133 additions & 0 deletions src/Weather_forecast_on_dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import streamlit as st
ADIMANV marked this conversation as resolved.
Show resolved Hide resolved
import pandas as pd
import plotly.express as px
from herbie import FastHerbie
from datetime import datetime, timedelta, time
import numpy as np
from concurrent.futures import ThreadPoolExecutor

@st.cache_data(show_spinner=False)
def fetch_data_for_init_time(init_time, forecast_date, lat, lon, parameter, model="ifs"):
ADIMANV marked this conversation as resolved.
Show resolved Hide resolved
# Adjust the initialization datetime based on the init_time offset in hours
init_datetime = forecast_date - timedelta(hours=init_time) # This will be a datetime object

peterdudfield marked this conversation as resolved.
Show resolved Hide resolved
FH = FastHerbie([init_datetime], model=model, fxx=range(0, 24, 1), fast=True)
ADIMANV marked this conversation as resolved.
Show resolved Hide resolved

try:
FH.download() # Ensure the file is downloaded
except Exception as e:
st.error(f"Failed to download data for initialization time {init_datetime}. Error: {e}")
return None, None

variable_subset = ":10[u|v]" if parameter == "u10:v10" else ":100[u|v]" if parameter == "u100:v100" else "2t"

try:
ds = FH.xarray(variable_subset, remove_grib=False)
if isinstance(ds, list):
ds = ds[0]
except EOFError as e:
st.error(f"Failed to read data file for {init_datetime}. Error: {e}")
return None, None
except Exception as e:
st.error(f"An unexpected error occurred while reading data: {e}")
return None, None

return ds, init_datetime

def process_initialization(init_time, forecast_date, lat, lon, parameter, model="ifs"):
ds, init_datetime = fetch_data_for_init_time(init_time, forecast_date, lat, lon, parameter, model)
if ds is None:
return []

interpolated_data = []

time_dim = ds['step'] if 'step' in ds.dims else ds['time'] if 'time' in ds.dims else None
if time_dim is None:
st.error("Neither 'step' nor 'time' dimension is present in the dataset. Unable to proceed with forecast.")
return []

for time_val in time_dim:
actual_forecast_time = init_datetime + pd.to_timedelta(time_val.values)
if actual_forecast_time.date() == forecast_date.date():
if parameter == "u10:v10":
u = ds['u10'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
v = ds['v10'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
value = np.sqrt(u**2 + v**2)
elif parameter == "u100:v100":
u = ds['u100'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
v = ds['v100'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
value = np.sqrt(u**2 + v**2)
else: # temp
value = ds['t2m'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val) - 273.15

interpolated_data.append({
'date_time': actual_forecast_time,
'latitude': lat,
'longitude': lon,
'value': value.compute().item(),
'init_time': f"{init_time} hours before"
})

return interpolated_data

def get_forecast(forecast_date, lat, lon, parameter, init_times, model="ifs"):
ADIMANV marked this conversation as resolved.
Show resolved Hide resolved
all_data = []
with ThreadPoolExecutor() as executor:
results = list(executor.map(lambda init_time: process_initialization(init_time, forecast_date, lat, lon, parameter, model), init_times))
for result in results:
all_data.extend(result)
return pd.DataFrame(all_data)

def weather_forecast_page():
# Streamlit app
st.title("Weather Forecasts from Different Initialization Times")

parameter_mapping = {
"Wind Speed (10m)": "u10:v10",
"Wind Speed (100m)": "u100:v100",
"Temperature": "2t"
}

parameter_option = st.sidebar.selectbox("Select Parameter", options=list(parameter_mapping.keys()))
parameter_value = parameter_mapping[parameter_option]
lat = st.sidebar.number_input("Enter Latitude", value=27.035, format="%.6f")
lon = st.sidebar.number_input("Enter Longitude", value=70.515, format="%.6f")

# Use datetime.combine to set the forecast_date to midnight
forecast_date = st.sidebar.date_input("Select Forecast Date", datetime.today().date())
forecast_date = datetime.combine(forecast_date, time(hour=0))

# Multiselect for initialization times in hours
init_time_options = st.sidebar.multiselect(
"Select Initialization Times (in hours before forecast date)",
peterdudfield marked this conversation as resolved.
Show resolved Hide resolved
options=[24, 18, 12, 6, 0],
default=[24, 12, 0] # Default selections: 24 hours before, 12 hours before, and current day
)

if st.button("Fetch Data"):
if not init_time_options:
st.warning("Please select at least one initialization time.")
else:
st.write("Note: Data loading may take up to 30 seconds. Please be patient...")
data = get_forecast(forecast_date, lat, lon, parameter_value, init_times=init_time_options )

if not data.empty:
fig = px.line(
data,
x='date_time',
y='value',
color='init_time',
labels={
'date_time': 'Date and Time',
'value': f'{parameter_option} ({"m/s" if "Wind Speed" in parameter_option else "°C"})',
'init_time': 'Initialization Time'
},
title=f"Forecast of {parameter_option} at ({lat}, {lon}) for {forecast_date.strftime('%Y-%m-%d')}"
)
st.plotly_chart(fig)

csv = data.to_csv(index=False)
file_name = f"{forecast_date.strftime('%Y-%m-%d')}_{parameter_option}_comparison_data.csv"
st.download_button(label="Download data as CSV", data=csv, file_name=file_name, mime='text/csv')
else:
st.warning("No data was collected.")
2 changes: 2 additions & 0 deletions src/main_india.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from satellite_page import satellite_page
from status import status_page
from users import user_page
from weather_forecast import weather_forecast_page

st.get_option("theme.primaryColor")
st.set_page_config(layout="wide", page_title="OCF Dashboard")
Expand All @@ -26,6 +27,7 @@
"API Users": user_page,
"NWP": nwp_page,
"Satellite": satellite_page,
"Weather Forecast": weather_forecast_page,
}

demo_name = st.sidebar.selectbox("Choose a page", page_names_to_funcs.keys(), 1)
Expand Down
222 changes: 222 additions & 0 deletions src/weather_forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import streamlit as st
import pandas as pd
import plotly.express as px
from herbie import FastHerbie
from datetime import datetime, timedelta, time
import numpy as np
from concurrent.futures import ThreadPoolExecutor
from typing import List, Optional, Tuple

@st.cache_data(show_spinner=False)
def fetch_data_for_init_time(
init_time: datetime,
forecast_date: datetime,
lat: float,
lon: float,
parameter: str,
model: str = "ifs"
) -> Tuple[Optional['xarray.Dataset'], Optional[datetime]]:
"""
Fetch the weather forecast data for a specific initialization time and forecast date.

Parameters:
- init_time (datetime): The initialization time (usually yesterday at a specific hour).
- forecast_date (datetime): The forecast date for which to get the forecast.
- lat (float): Latitude of the location for which the forecast is requested.
- lon (float): Longitude of the location for which the forecast is requested.
- parameter (str): The weather parameter to retrieve. Examples: "u10:v10", "u100:v100", or "2t".
- model (str): The weather model to use for fetching data (default: "ifs").

Returns:
- ds (xarray.Dataset or None): The forecast dataset for the specified parameters.
- init_time (datetime or None): The initialization time for the dataset.
"""
# Adjust the initialization datetime
FH = FastHerbie([init_time], model=model, fxx=range(init_time.hour, 24 + init_time.hour, 1), fast=True)
Copy link
Contributor

@peterdudfield peterdudfield Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldnt this be (forecast_date - init_time).hours or something like that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have already set up the init times to be forecast day-1. So to give init times of yesterday for the forecast of today.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea but if you say forecast is today, and init time is 06:00 yesterday. fxx = range(6,30) which willget times from 12:00 yesterday to 12:00 today, so not the whole of today


try:
FH.download() # Ensure the file is downloaded
except Exception as e:
st.error(f"Failed to download data for initialization time {init_time}. Error: {e}")
return None, None

# Determine the variable subset based on the selected parameter
variable_subset = ":10[u|v]" if parameter == "u10:v10" else ":100[u|v]" if parameter == "u100:v100" else "2t"

try:
ds = FH.xarray(variable_subset, remove_grib=False)
if isinstance(ds, list):
ds = ds[0]
except EOFError as e:
st.error(f"Failed to read data file for {init_time}. Error: {e}")
return None, None
except Exception as e:
st.error(f"An unexpected error occurred while reading data: {e}")
return None, None

return ds, init_time

def process_initialization(
init_time: datetime,
forecast_date: datetime,
lat: float,
lon: float,
parameter: str,
model: str = "ifs"
) -> List[dict]:
"""
Process the weather forecast for a specific initialization time,
interpolate data for the given latitude and longitude.

Parameters:
- init_time (datetime): The initialization time for the forecast.
- forecast_date (datetime): The forecast date for which the data is requested.
- lat (float): Latitude of the location for the forecast.
- lon (float): Longitude of the location for the forecast.
- parameter (str): The weather parameter to retrieve (e.g., "u10:v10", "u100:v100", or "2t").
- model (str): The weather model to use for fetching data (default: "ifs").

Returns:
- interpolated_data (list): A list of dictionaries containing forecast values and metadata.
"""
ds, init_time = fetch_data_for_init_time(init_time, forecast_date, lat, lon, parameter, model)
if ds is None:
return []

interpolated_data = []

# Determine the time dimension
time_dim = ds['step'] if 'step' in ds.dims else ds['time'] if 'time' in ds.dims else None
if time_dim is None:
st.error("Neither 'step' nor 'time' dimension is present in the dataset. Unable to proceed with forecast.")
return []

# Iterate over the forecast steps and interpolate the data
for time_val in time_dim:
actual_forecast_time = init_time + pd.to_timedelta(time_val.values)
if actual_forecast_time.date() == forecast_date.date():
if parameter == "u10:v10":
u = ds['u10'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
v = ds['v10'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
value = np.sqrt(u**2 + v**2)
elif parameter == "u100:v100":
u = ds['u100'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
v = ds['u100'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
ADIMANV marked this conversation as resolved.
Show resolved Hide resolved
value = np.sqrt(u**2 + v**2)
else: # Temperature (in Kelvin, converted to Celsius)
value = ds['t2m'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val) - 273.15

interpolated_data.append({
'date_time': actual_forecast_time,
'latitude': lat,
'longitude': lon,
'value': value.compute().item(),
'init_time': init_time.strftime('%Y-%m-%d %H:%M')
})

return interpolated_data

def get_forecast(
forecast_date: datetime,
lat: float,
lon: float,
parameter: str,
init_times: List[datetime],
model: str = "ifs"
) -> pd.DataFrame:
"""
Get the weather forecast data for multiple initialization times.

Parameters:
- forecast_date (datetime): The date for which the forecast is requested.
- lat (float): Latitude of the location for the forecast.
- lon (float): Longitude of the location for the forecast.
- parameter (str): The weather parameter to retrieve (e.g., "u10:v10", "u100:v100", or "2t").
- init_times (list): A list of datetime objects representing initialization times.
- model (str): The weather model to use for fetching data (default: "ifs").

Returns:
- pd.DataFrame: A Pandas DataFrame containing the forecast data for the selected parameters and times.
"""
all_data = []
with ThreadPoolExecutor() as executor:
results = list(executor.map(lambda init_time: process_initialization(init_time, forecast_date, lat, lon, parameter, model), init_times))
for result in results:
all_data.extend(result)
return pd.DataFrame(all_data)

def weather_forecast_page() -> None:
"""
The main page for displaying weather forecasts with user inputs.

- Allows users to input latitude and longitude.
- Select the forecast date.
- Choose initialization times based on yesterday's forecast generation times.
- Fetch and display forecast data for the chosen initialization times and location.
"""
# Streamlit app
st.title("Weather Forecasts from Different Initialization Times")

# Parameter selection
parameter_mapping = {
"Wind Speed (10m)": "u10:v10",
"Wind Speed (100m)": "u100:v100",
"Temperature": "2t"
}
parameter_option = st.sidebar.selectbox("Select Parameter", options=list(parameter_mapping.keys()))
parameter_value = parameter_mapping[parameter_option]

# Latitude and longitude inputs
lat = st.sidebar.number_input("Enter Latitude", value=27.035, format="%.6f")
lon = st.sidebar.number_input("Enter Longitude", value=70.515, format="%.6f")

# Forecast date selection
forecast_date = st.sidebar.date_input("Select Forecast Date", datetime.today().date())
forecast_date = datetime.combine(forecast_date, time(hour=0))

# Create initialization times for yesterday at 00:00, 06:00, 12:00, and 18:00
yesterday = forecast_date - timedelta(days=1)
init_times = [
yesterday.replace(hour=0),
yesterday.replace(hour=6),
yesterday.replace(hour=12),
yesterday.replace(hour=18)
]

# Multi-selection for initialization times
init_time_options = st.sidebar.multiselect(
"Select Initialization Times",
options=[time.strftime('%Y-%m-%d %H:%M') for time in init_times],
default=[time.strftime('%Y-%m-%d %H:%M') for time in init_times] # Default selects all times
)
selected_init_times = [datetime.strptime(time, '%Y-%m-%d %H:%M') for time in init_time_options]

# Fetch and display the data
if st.button("Fetch Data"):
if not selected_init_times:
st.warning("Please select at least one initialization time.")
else:
st.write("Note: Data loading may take up to 30 seconds. Please be patient...")
data = get_forecast(forecast_date, lat, lon, parameter_value, selected_init_times, model="ifs")

if not data.empty:
fig = px.line(
data,
x='date_time',
y='value',
color='init_time',
labels={
'date_time': 'Date and Time',
'value': f'{parameter_option} ({"m/s" if "Wind Speed" in parameter_option else "°C"})',
'init_time': 'Initialization Time'
},
title=f"Forecast of {parameter_option} at ({lat}, {lon}) for {forecast_date.strftime('%Y-%m-%d')}"
)
st.plotly_chart(fig)

# Downloadable CSV
csv = data.to_csv(index=False)
file_name = f"{forecast_date.strftime('%Y-%m-%d')}_{parameter_option}_comparison_data.csv"
st.download_button(label="Download data as CSV", data=csv, file_name=file_name, mime='text/csv')
else:
st.warning("No data was collected.")
Loading