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

Clipping generation data #198

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
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
4 changes: 2 additions & 2 deletions src/pvsite_forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def pvsite_forecast_page():

for forecast in forecasts:
x = [i.start_utc for i in forecast]
y = [i.forecast_power_kw for i in forecast]
y = [max(i.forecast_power_kw, 0) for i in forecast]

# get generation values for selected sites and plot
with connection.get_session() as session:
Expand All @@ -93,7 +93,7 @@ def pvsite_forecast_page():
)
capacity = get_site_capacity(session = session, site_uuidss = site_selection)

yy = [generation.generation_power_kw for generation in generations if generation is not None]
yy = [max(generation.generation_power_kw, 0) for generation in generations if generation is not None]
xx = [generation.start_utc for generation in generations if generation is not None]

df_forecast = pd.DataFrame({'forecast_datetime': x, 'forecast_power_kw': y})
Expand Down
237 changes: 237 additions & 0 deletions src/weather_forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
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 fxx range based on initialization time
if init_time.hour == 6:
forecast_hours = range(18, 42 + 1) # Fetch up to 42 hours from initialization time
elif init_time.hour == 12:
forecast_hours = range(12, 36 + 1) # Fetch up to 36 hours from initialization time
elif init_time.hour == 18:
forecast_hours = range(6, 30 + 1) # Fetch up to 30 hours from initialization time
else:
forecast_hours = range(24, 48 + 1) # Default 24 hours for other initialization times

FH = FastHerbie([init_time], model=model, fxx=forecast_hours, fast=True)

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)

# Debugging: Show the actual forecast time to check if the full day is captured
st.write(f"Forecast time: {actual_forecast_time}")

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['100'].interp(latitude=lat, longitude=lon, method="nearest", step=time_val)
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 5 minutes. 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