# imports
import os
import streamlit as st
import plotly.express as px
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def calc_metrics(df: pd.DataFrame, runway: pd.DataFrame):
"""Calculates metrics to be used for plots"""
tech_aircraft = df['tech_aircraft'].unique().tolist() # Populate all combinations
# Calculate metrics for plots
# Determine mission feasibility
df.loc[df['departure_airport_code'].isin(runway['AIRPORT_IATA']), 'Dep RWY Length [m]']= runway.set_index('AIRPORT_IATA').loc[df['departure_airport_code'],'RUNWAY_TAKE_OFF_LENGTH_m'].values
df.loc[df['arrival_airport_code'].isin(runway['AIRPORT_IATA']), 'Arr RWY Length [m]']= runway.set_index('AIRPORT_IATA').loc[df['arrival_airport_code'],'RUNWAY_TAKE_OFF_LENGTH_m'].values
df['TOFL feasible for mission']= (df['Dep RWY Length [m]'] > df['TechAircraft.TOFL.TOFL'])&(df['Arr RWY Length [m]'] > df['TechAircraft.TOFL.TOFL'])
# Column headers
headers = ['Max. MTOW [kg]', 'Max. MLW [kg]', 'Avg. nPAX', 'Sum. of COC RUNWAY', 'Avg. of TOFL [m]', 'Avg. of Vapp [kts]',
'Sum. of DOC_BlockFuel', 'Avg. of COC', 'Sum. of COC Fuel', 'Sum. of COC MAINTENANCE'
, 'Sum. of COC NAVIGATION', 'Sum. of COC RAMP', 'Avg. of Equivalent_Thrust [lbf]', 'TechAircraft.Results.Missions.DOC.DOC_Range',
'TechAircraft.TOFL.TOFL', 'TechAircraft.Results.Missions.DOC.DOC_BlockFuel', 'TOFL feasible for mission']
for spec in tech_aircraft:
df.loc[df['tech_aircraft']== spec, 'Max. MTOW [kg]'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.Mission Data.MTOW'].max()
df.loc[df['tech_aircraft']== spec, 'Max. MLW [kg]'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.Mission Data.MLW'].max()
df.loc[df['tech_aircraft']== spec, 'Avg. nPAX'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.Mass Derivation inputs.nPAX'].mean()
df.loc[df['tech_aircraft']== spec, 'Sum. of COC RUNWAY'] = df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.RUNWAY'].sum()
# df.loc[df['tech_aircraft']== spec, 'Sum. of TOFL infeasible for mission'] = df['TechAircraft.TOFL.TOFL infeasible for this mission'].sum()["INFEASIBLE"]
df.loc[df['tech_aircraft']== spec, 'Avg. of TOFL [m]']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.TOFL.TOFL'].mean()
df.loc[df['tech_aircraft']== spec, 'Avg. of Vapp [kts]']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.TOFL.Vapp'].mean()
df.loc[df['tech_aircraft']== spec, 'Sum. of DOC_BlockFuel']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.Results.Missions.DOC.DOC_BlockFuel'].sum()
df.loc[df['tech_aircraft']== spec, 'Avg. of COC']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.COMPLETE_COC'].mean()
df.loc[df['tech_aircraft']== spec, 'Sum. of COC Fuel']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.FUEL'].sum()
df.loc[df['tech_aircraft']== spec, 'Sum. of COC MAINTENANCE']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.MAINTENANCE'].sum()
df.loc[df['tech_aircraft']== spec, 'Sum. of COC NAVIGATION']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.NAVIGATION'].sum()
df.loc[df['tech_aircraft']== spec, 'Sum. of COC RAMP']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.COC outputs.RAMP_HANDLING'].sum()
df.loc[df['tech_aircraft']== spec, 'Avg. of Equivalent_Thrust [lbf]']= df.loc[df['tech_aircraft']== spec, 'TechAircraft.TOFL.Airbus_equivalent_thrust'].mean()
final_columns = ['tech_aircraft'] + ['Fuel', 'TOFL', 'Engine', 'Range', 'Year'] + headers
df = df[final_columns].drop_duplicates()
df['Range'] = df['Range'].str.extract('(\d+)')
df = df.sort_values(["Year"], ascending=[True])
# df = df.sort_values(["Year", "Range"], ascending=[True, True])
return df
# Remove unnecessary columns
def plot_SliderScatter(df: pd.DataFrame, xlabel: str, ylabel: str):
fig = px.scatter(df, x=xlabel, y=ylabel,
animation_frame="Year",
animation_group="tech_aircraft",
color="TOFL",
symbol = "Fuel",
hover_name="tech_aircraft",
# text = "Engine",
title=(xlabel + " vs " + ylabel + " overtime")
)
fig.update_traces(textposition='top center')
fig["layout"].pop("updatemenus") # optional, drop animation buttons
return fig
def plot_3D(df: pd.DataFrame, xlabel: str, ylabel: str, zlabel: str):
fig = go.Figure()
colors = px.colors.qualitative.Plotly # Default qualitative color scheme
symbols = ['circle', 'square', 'diamond', 'cross', 'x'] # Default symbols
fig = px.scatter_3d(df, x=xlabel, y=ylabel, z=zlabel,
animation_frame="Year",
animation_group="tech_aircraft",
symbol = "Fuel",
color="TOFL",
hover_name="tech_aircraft",
# text = "Engine",
title=(xlabel + " vs " + ylabel + " overtime")
)
fig.update_traces(textposition='top center')
fig.update_layout(scene_camera=dict(eye=dict(x=2, y=2, z=0.5)))
# fig.update_layout(scene=dict(
# yaxis=dict(range=[df[ylabel].min(), df[ylabel].max()]),
# zaxis=dict(range=[df[zlabel].min(), df[zlabel].max()])
# ))
# fig["layout"].pop("updatemenus") # optional, drop animation buttons
return fig
# File reading
# Get script directory
# script_dir = os.path.dirname(os.path.abspath(__file__))
# os.environ["PATH"] += os.pathsep + 'C:/Program Files (x86)/Graphviz-12.1.0-win64/bin/' # Solve graphviz path problem
st.set_page_config(page_title="Hackathon Results Visualisation", layout="centered")
def parameters_study_page():
upload_file = st.file_uploader(label="Design point output files in .csv format.", type="csv", accept_multiple_files=True)
file_flag = [0,0]
if len(upload_file) != 0:
dfs =[pd.read_csv(f) for f in upload_file]
df = pd.concat(dfs, ignore_index=True)
df[['Fuel', 'TOFL', 'Engine', 'Range', 'Year']] = df['tech_aircraft'].str.extract(r'([^_]+)_([^_]+)_([^_]+)_([^_]+)_([^_]+)')
file_flag[0] = 1
runway_file = st.file_uploader(label="Runway data csv", type="csv")
if runway_file is not None:
df_runway = pd.read_csv(runway_file)
file_flag[1] = 1
if file_flag[0]==1 & file_flag[1]==1:
st.write("Files uploaded.")
df = calc_metrics(df, df_runway)
with st.form(key="Select parameters"):
heads = list(df.columns.values)
params = st.multiselect("Select parameters to compare against: (Default: 'Range', 'TechAircraft.Results.Missions.DOC.DOC_Range', 'TechAircraft.TOFL.TOFL')", heads, ['Range', 'TechAircraft.Results.Missions.DOC.DOC_Range', 'TechAircraft.TOFL.TOFL'], max_selections=3)
plot_button = st.form_submit_button("Generate 3D plot")
if st.button("Plot"):
st.plotly_chart(plot_SliderScatter(df, 'Avg. of TOFL [m]', 'Avg. of Equivalent_Thrust [lbf]'), use_container_width=True)
st.plotly_chart(plot_SliderScatter(df, 'Avg. of TOFL [m]', 'Max. MTOW [kg]'), use_container_width=True)
st.plotly_chart(plot_3D(df, params[0], params[1], params[2]), use_container_width=True)
st.toast("Plots completed!")
else:
st.write("Input files incomplete.")
def Fuel_Fleet_study_page():
colour_group = px.colors.qualitative.Plotly # Change this if we have more fuel/TOFL/engine options
def update (change):
# Example of dynamically downselection using sliders.
if change == 'Kero':
st.session_state.LH2 = st.session_state.Kero / 2
st.session_state.SAF = st.session_state.Kero / 2
else:
st.session_state.Kero = st.session_state.LH2 + st.session_state.SAF
def calcStep(df, Col:str) -> int:
df_ans = (df[Col].max()-df[Col].min())/(df[Col].unique().size-1)
return df_ans.astype('int64')
upload_file = st.file_uploader(label="CCC Fuel price files in .csv format.", type="csv", accept_multiple_files=True)
if len(upload_file) != 0:
dfs =[pd.read_csv(f) for f in upload_file]
for df in dfs:
df['Year'] = df.columns.values[0]
df.drop(df.columns[0], axis=1, inplace=True)
df = pd.concat(dfs, ignore_index=True)
with st.form(key="Fleet mixing"):
KeroInput = st.slider('Price of Kerosene [$/USG]', df['Kerosene price [$/USG]'].min(), df['Kerosene price [$/USG]'].max(), value=2, step=calcStep(df, 'Kerosene price [$/USG]'), key='Kero')
LH2Input = st.slider('Price of LH2 [$/USG]', df['LH2 price [$/USG]'].min(), df['LH2 price [$/USG]'].max(), value=2, step=calcStep(df, 'LH2 price [$/USG]'), key='LH2')
SAFInput = st.slider('Price of SAF [$/USG]', df['SAF price [$/USG]'].min(), df['SAF price [$/USG]'].max(), value=2, step=calcStep(df, 'SAF price [$/USG]'), key='SAF')
NH3Input = st.slider('Price of NH3 [$/USG]', df['NH3 price [$/USG]'].min(), df['NH3 price [$/USG]'].max(), value=2, step=calcStep(df, 'NH3 price [$/USG]'), key='NH3')
MethanolInput = st.slider('Price of Methanol [$/USG]', df['CH3OH price [$/USG]'].min(), df['CH3OH price [$/USG]'].max(), value=2, step=calcStep(df, 'CH3OH price [$/USG]'), key='CH3OH')
df_ans = df[(df['Kerosene price [$/USG]'] == KeroInput) & (df['LH2 price [$/USG]']==LH2Input) & (df['SAF price [$/USG]'] == SAFInput) & (df['NH3 price [$/USG]'] == NH3Input) & (df['CH3OH price [$/USG]'] == MethanolInput)]
df_ans.reset_index(drop=True, inplace=True)
st.form_submit_button("Find current scenario")
if df_ans.empty:
st.warning("Current scenario does not exist, please try again 🙁")
else:
st.success("Congrats for finding a valid scenario, current chance: " + str(round(len(df[df['Year'] == df['Year'].unique()[0]])/(df['Kerosene price [$/USG]'].unique().size**5), 2)), icon="🍀")
if not df_ans.empty:
st.write(df_ans)
fig = make_subplots(
rows=2, cols=3,
column_widths=[0.6, 0.2, 0.2],
row_heights=[1, 1],
specs=[[{"type": "bar"}, {"type": "pie"}, {"type": "pie"}],
[{"type": "bar"}, {"type": "pie"}, {"type": "pie"}]],
subplot_titles=("Scheduled no. of Flights", "Fleet % by Fuel", "", "Fuel Consumption [kg]", "Fleet % by TOFL", "Fleet % by Engine"))
columns_BarPlot = ["Scheduled number of Kerosene flights", "Scheduled number of CH3OH flights", "Scheduled number of LH2 flights","Scheduled number of NH3 flights","Scheduled number of SAF flights"]
columns_2nd_BarPlot = ["Kerosene fuel consumption across all bases [kg]","CH3OH fuel consumption across all bases [kg]","LH2 fuel consumption across all bases [kg]","NH3 fuel consumption across all bases [kg]","SAF fuel consumption across all bases [kg]"]
columns_fleet_PieChart = ["Fleet size (Kerosene) after scheduling","Fleet size (CH3OH) after scheduling","Fleet size (LH2) after scheduling","Fleet size (NH3) before after scheduling","Fleet size (SAF) after scheduling"]
TOFL_PieChart = ["Fleet size (TOFL1) after scheduling","Fleet size (TOFL2) after scheduling","Fleet size (TOFL3) after scheduling"]
Engine_PieChart = ["Fleet size (Engine1) after scheduling","Fleet size (Engine2) after scheduling"]
fuelTypes = ["Kerosene", "CH3OH", "LH2", "NH3", "SAF"]
years = df_ans["Year"].unique().tolist()
j = 0
for i, year in enumerate(years):
for i in range(len(fuelTypes)):
fig.add_trace(
go.Bar(x = [fuelTypes[i]], y=[df_ans.loc[j, columns_BarPlot[i]]], name=fuelTypes[i] + " at " + year, marker=dict(color=colour_group[i]) ),
row=1, col=1,
)
for i in range(len(fuelTypes)):
fig.add_trace(
go.Bar(x = [fuelTypes[i]], y=[df_ans.loc[j, columns_2nd_BarPlot[i]]], name=fuelTypes[i] + " at " + year, marker=dict(color=colour_group[i]), showlegend=False),
row=2, col=1,
)
fig.add_trace(
go.Pie( labels=fuelTypes, values=df_ans.loc[j, columns_fleet_PieChart], marker=dict(colors=colour_group), showlegend=False),
row=1, col=2,
)
fig.add_trace(
go.Pie( labels=TOFL_PieChart, values=df_ans.loc[j, TOFL_PieChart], marker=dict(colors=colour_group[5:8])), # Change this if we have more fuel/TOFL/engine options
row=2, col=2,
)
fig.add_trace(
go.Pie( labels=Engine_PieChart, values=df_ans.loc[j, Engine_PieChart], marker=dict(colors=colour_group[8:])), # Change this if we have more fuel/TOFL/engine options
row=2, col=3,
)
j = j + 1
steps = []
for i in range(0,len(fig.data),len(fuelTypes)*2+3):
step = dict(
method="update",
args=[{"visible": [False] * len(fig.data)},
{"title": "Current Year: " + years[int(i/(len(fuelTypes)*2+3))]},
{"label": years[int(i/(len(fuelTypes)*2+3))]}], # layout attribute
label= years[int(i/(len(fuelTypes)*2+3))]
)
for k in range(i, i+len(fuelTypes)*2+3):
step["args"][0]["visible"][k] = True # Toggle i to i+k'th traces to "visible"
steps.append(step)
sliders = [dict(
active=0,
currentvalue={"prefix": "Current Year: " },
pad={"t": 50},
steps=steps,
)]
fig.update_layout(
sliders=sliders
)
st.plotly_chart(fig)
pages = {
"Page 1": parameters_study_page,
"Fleet Mixing study": Fuel_Fleet_study_page,
}
selection = st.sidebar.radio("GoTo", list(pages.keys()))
# Page rendering
page = pages[selection]
page()