Skip to content
Snippets Groups Projects
Commit 22b4ba7c authored by Florian Obersteiner's avatar Florian Obersteiner :octopus:
Browse files

Merge branch 'dev-0015' into 'main'

fail if no data, secondary y

See merge request !6
parents 4fedb02a df879c68
No related branches found
No related tags found
1 merge request!6fail if no data, secondary y
......@@ -15,8 +15,9 @@ Types of changes
## TODOs
- add sources for BB tagging and ACN background
- investigate if data caching is necessary (load-from-thredds)
- add a secondary y axis to the timeseries plot; plot as line if sufficient data is available
- scatter-plot: plot as line if sufficient data is available?
## Backlog
......@@ -29,6 +30,19 @@ Types of changes
## [Unreleased]
## v0.0.15 - 2025-03-26
### Added
- secondary variable to plot (optional)
- info about BB flagging
### Changed
- app startup immediately fails if no datasets are found
## v0.0.14 - 2025-03-26
### Changed
- skip files that have no ACN data
......
......@@ -12,6 +12,7 @@ import pandas as pd
import plotly.graph_objects as go
from dash import Dash
from dash.dependencies import Input, Output
from plotly.subplots import make_subplots
from src.config import DotDict, get_latest_semantic_version_tag
from src.data import load_from_disk, load_from_thredds
......@@ -43,7 +44,7 @@ data, ap_info = (
else load_from_disk(Path("./testdata").glob("*.nc"), appconfig, airportsconfig, True) # type: ignore
)
log.debug(f"Data loaded. Used THREDDS: {appconfig.USE_THREDDS}")
log.debug(f"Data size: {data.info()}")
# log.debug(f"Data size: {data.info()}")
app.layout = create_layout(data, appconfig)
server = app.server # for deployment via gunicorn / WSGI server
......@@ -55,13 +56,27 @@ server = app.server # for deployment via gunicorn / WSGI server
Output("fig-ts", "figure"),
Output("flight-dep-dest", "children"),
Output("var-info", "children"),
Output("second-var-info", "children"),
],
[
Input("flight-dropdown", "value"),
Input("variable-dropdown", "value"),
Input("second-variable-dropdown", "value"),
],
[Input("flight-dropdown", "value"), Input("variable-dropdown", "value")],
)
def update_plots(selected_flight: int, selected_variable: str):
def update_plots(selected_flight: int, primary_variable: str, secondary_variable: str):
filtered_df = data[data["flight_number"] == selected_flight]
valid_indices = filtered_df[selected_variable].notna()
unit = varconfig.loc[selected_variable, :].Unit
valid_indices = filtered_df[primary_variable].notna()
unit_primary = varconfig.loc[primary_variable, :].Unit
lname_primary = (varconfig.loc[primary_variable, :].Long_Name,)
unit_secondary = (
varconfig.loc[secondary_variable, :].Unit if secondary_variable in varconfig.index else "-"
)
lname_secondary = (
varconfig.loc[secondary_variable, :].Long_Name
if secondary_variable in varconfig.index
else ""
)
fig_map = go.Figure(
go.Scattermap(
......@@ -69,17 +84,17 @@ def update_plots(selected_flight: int, selected_variable: str):
lat=filtered_df.loc[valid_indices, "lat"],
mode="markers",
marker={
"color": filtered_df.loc[valid_indices, selected_variable],
"color": filtered_df.loc[valid_indices, primary_variable],
"colorscale": "Rainbow",
"colorbar": {
"x": 0.995,
"title": {"text": f"{selected_variable} {unit}", "side": "right"},
"title": {"text": f"{primary_variable} {unit_primary}", "side": "right"},
},
"showscale": True,
},
connectgaps=False,
hoverinfo="text",
hovertext=[f"{v:.1f}" for v in filtered_df[selected_variable]],
hovertext=[f"{v:.1f}" for v in filtered_df[primary_variable]],
name="Flight Path",
)
)
......@@ -117,11 +132,11 @@ def update_plots(selected_flight: int, selected_variable: str):
showlegend=False,
)
fig_ts = go.Figure()
fig_ts = make_subplots(specs=[[{"secondary_y": True}]])
fig_ts.add_trace(
go.Scatter(
x=filtered_df.index[valid_indices & ~filtered_df["BB_flag"]],
y=filtered_df.loc[valid_indices & ~filtered_df["BB_flag"], selected_variable],
y=filtered_df.loc[valid_indices & ~filtered_df["BB_flag"], primary_variable],
mode="markers",
marker={
"size": 3,
......@@ -134,7 +149,7 @@ def update_plots(selected_flight: int, selected_variable: str):
fig_ts.add_trace(
go.Scatter(
x=filtered_df.index[valid_indices & filtered_df["BB_flag"]],
y=filtered_df.loc[valid_indices & filtered_df["BB_flag"], selected_variable],
y=filtered_df.loc[valid_indices & filtered_df["BB_flag"], primary_variable],
mode="markers",
marker={
"size": 3,
......@@ -144,6 +159,21 @@ def update_plots(selected_flight: int, selected_variable: str):
name="flagged 'biomass burning'",
)
)
if secondary_variable != "None":
fig_ts.add_trace(
go.Scatter(
x=filtered_df.index,
y=filtered_df[secondary_variable],
mode="markers",
marker={
"size": 2,
"color": "black",
"showscale": False,
},
name=secondary_variable,
),
secondary_y=True,
)
fig_ts.update_layout(
paper_bgcolor="#e8e8e8",
......@@ -160,13 +190,15 @@ def update_plots(selected_flight: int, selected_variable: str):
)
fig_ts.update_xaxes(title="UTC")
fig_ts.update_yaxes(title=f"{selected_variable} {unit}")
fig_ts.update_yaxes(title=f"{primary_variable} {unit_primary}")
fig_ts.update_yaxes(title=f"{secondary_variable} {unit_secondary}", secondary_y=True)
return [
fig_map,
fig_ts,
ap_info[selected_flight],
varconfig.loc[selected_variable, :].Long_Name,
lname_primary,
lname_secondary,
]
......
......@@ -19,8 +19,14 @@ def load_from_thredds(
) -> (pd.DataFrame, dict[int, str]): # type: ignore
"""
Load multiple netCDF files from a THREDS TDS into a single DataFrame.
Raises:
ValueError: If 'ACN' not found in dataset.
ValueError: If no datasets are found.
"""
log.debug("begin load data from THREDDS...")
dataframes = []
airports = {}
......@@ -54,7 +60,11 @@ def load_from_thredds(
dataframes.append(df)
if len(dataframes) == 0:
raise ValueError("No datasets found!")
log.debug("Data loaded from THREDDS successfully")
return (pd.concat(dataframes), airports)
......@@ -63,6 +73,10 @@ def load_from_disk(
) -> (pd.DataFrame, dict[int, str]): # type: ignore
"""
Load multiple netCDF files into a single DataFrame.
Raises:
ValueError: If 'ACN' not found in dataset.
ValueError: If no datasets are found.
"""
dataframes = []
airports = {}
......@@ -99,5 +113,9 @@ def load_from_disk(
dataframes.append(df)
if len(dataframes) == 0:
raise ValueError("No datasets found!")
log.info("Data loaded from disk successfully")
return (pd.concat(dataframes), airports)
......@@ -34,6 +34,7 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
for c in df.columns
if c not in ["flight_number", "date", "BB_flag"]
]
var_sel_idx = 0
for idx, vo in enumerate(var_opts):
if vo["label"] == "ACN":
......@@ -90,7 +91,7 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
children=[
html.Label(
"Flight:",
style={"font-weight": "bold", "margin-right": "29px"},
style={"font-weight": "bold", "margin-right": "39px"},
),
dcc.Dropdown(
id="flight-dropdown",
......@@ -121,7 +122,7 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
children=[
html.Label(
"Variable:",
style={"font-weight": "bold", "margin-right": "10px"},
style={"font-weight": "bold", "margin-right": "20px"},
),
dcc.Dropdown(
id="variable-dropdown",
......@@ -147,6 +148,37 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
"align-items": "center",
},
),
html.Div(
id="second-variable-select",
children=[
html.Label(
"Secondary:",
style={"font-weight": "bold", "margin-right": "10px"},
),
dcc.Dropdown(
id="second-variable-dropdown",
options=([{"value": "None", "label": "(None)"}] + var_opts), # type: ignore
value="None", # type: ignore
clearable=False,
searchable=True,
style={
"display": "inline-block",
"width": "190px",
},
),
html.Label(
"",
id="second-var-info",
style={"margin-left": "29px", "font-size": "12px"},
),
],
style={
"display": "flex",
"flex-direction": "row",
"justify-content": "left",
"align-items": "center",
},
),
dcc.Graph(
id="fig-map",
style={
......@@ -165,6 +197,11 @@ def create_layout(df: pd.DataFrame, config: DotDict) -> list:
"margin-left": "10px",
},
),
html.Label(
"BB flagging: BB influence if ACN > (145 ppt + 3*ACN_prc)",
id="flagging-info",
style={"margin-left": "15px", "font-size": "12px"},
),
]
),
# --- ^^^ --- main content
......
......@@ -15,9 +15,6 @@ def get_datasets(cfg: DotDict) -> list[Dataset]:
)
datasets = []
if len(catalog.datasets) == 0:
raise ValueError("No datasets found in the catalog")
for i, ds in enumerate(catalog.datasets):
if not (ds.startswith(cfg.FNAME_PREFIX) and ds.endswith(cfg.FNAME_SUFFIX)):
continue
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment