| |
| import pandas as pd |
| import dash |
| from dash import dcc, html |
| import webbrowser |
| from src.viz import Visualizer as viz |
| import plotly.graph_objects as go |
| import os |
| import datetime |
| import plotly.express as px |
| import json |
| import subprocess |
|
|
| start_time = 0 |
| end_time = 1700 |
|
|
| start_date = datetime.datetime(2024, 6, 7, 12, 0, 0) |
| end_date = start_date + datetime.timedelta(seconds=end_time) |
|
|
| |
| app = dash.Dash(__name__, suppress_callback_exceptions=True) |
|
|
| |
| data_store = {} |
|
|
| def parse_action_trace(path, exclude = []): |
| |
|
|
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
|
|
| df = pd.read_csv(path, engine = "python") |
| |
| |
| events = [] |
|
|
| for i in range(len(df['time'])): |
| if not any(excl in df['actionName'][i] for excl in exclude): |
| event = { |
| 'agent': df['agentName'][i], |
| 'name': df['actionName'][i], |
| 'start': start_date + datetime.timedelta(seconds=df['time'][i]), |
| 'end': start_date + datetime.timedelta(seconds=df['time'][i] + max(df['actionEndTime'][i], 2)), |
| 'duration': max(df['actionEndTime'][i], 2), |
| 'attributes': df['attributes'][i] |
| } |
| events.append(event) |
| |
| return events |
|
|
| def parse_all_data(path): |
|
|
| |
| subfolders = [f.name for f in os.scandir(path) if f.is_dir()] |
|
|
| |
| |
| data = {} |
| exclude = ['Detect_crash','Detect_conflict','Flight_Dynamics','Reroute_flight','Show_radar','Direct_to_waypoint','Change_heading','fly'] |
| for subfolder in subfolders: |
| data[subfolder] = { |
| 'wnd_action_trace': pd.read_csv(f'{path}/{subfolder}/actionTrace_Agent_PIC Vista.csv'), |
| 'all_action_trace': parse_action_trace(f'{path}/{subfolder}/actionTrace.csv', exclude), |
| } |
| data[subfolder]['aircraft_data'] = {} |
| for file in os.listdir(f'{path}/{subfolder}'): |
| if file.endswith('_acstate.csv'): |
| aircraft_id = file.split('_')[0] |
| data[subfolder]['aircraft_data'][aircraft_id] = pd.read_csv(f'{path}/{subfolder}/{file}') |
|
|
| subfolder_labels = [{'label': i, 'value': i} for i in subfolders] |
|
|
| return subfolder_labels, data |
|
|
| |
| app.layout = html.Div([ |
| html.Div([ |
| html.B("This is an interactive demonstration of case study results to showcase the ability of modeling and simulation to help envision distributed work systems"), |
| html.Br(), |
| dcc.Markdown("This interactive demo was performed under NASA contract number 80NSSC23CA121.", id="explanation") |
| ], style={'width': '100%', 'border': '1px solid gray'}), |
| html.Div([ |
| dcc.Markdown("It is envisioned that in future operatoins, each UAV is going to be controlled using a command and control (C2) link that allows communication with the remote pilot. This architecture can have potentially dangerous implications in the case of a failure of the command and control link. The UAV's are envisioned to be programmed with one or more contingency procedures that are to be executed in the case of a lost link. For example, to divert to and land at the nearest vertiport or continue along its preapproved route. This brings the question of how will the rest of the system need to react to the change in the aircraft trajectory. Is a system like this robust to an event like loss of link? ", id="explanation2") |
| ], style={'width': '100%', 'border': '1px solid gray'}), |
| html.Div([ |
| dcc.Markdown("If the dropdown menu is empty please wait a few seconds to allow the data to load. If the data doesnt load then click the submit button to force reload the dropdown. The files that contain data will take a few seconds to load so please be patient.", id="explanation"), |
| html.Button('Submit', id='submit-button', n_clicks=0) |
| ], style={'width': '100%', 'border': '1px solid gray'}), |
| html.Div([ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| html.Div([ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| html.Div("Select which condition to load in the interactive graphs:", id='text2'), |
| dcc.Dropdown( |
| id='subfolder-dropdown', |
| value=None |
| ), |
| dcc.Interval( |
| id='interval-component', |
| interval=1*1000, |
| n_intervals=0, |
| max_intervals=1 |
| ) |
| ], style={'width': '100%', 'border': '1px solid gray'}), |
| ], style={'display': 'flex'}), |
|
|
| html.Div([ |
| html.Div([ |
| dcc.Graph(id='map'), |
| dcc.Dropdown( |
| id='variable-dropdown', |
| |
| value=None |
| ), |
| dcc.Graph(id='altitude-series') |
| ], style={'width': '40%', 'display': 'inline-block', 'border': '1px solid gray'}), |
| html.Div([ |
| dcc.Graph(id='timeline'), |
| ], style={'width': '60%', 'display': 'inline-block', 'border': '1px solid gray'}) |
| ], style={'display': 'flex'}), |
|
|
| html.Div( |
| dcc.Slider( |
| id='time-slider', |
| min=start_time, |
| max=end_time, |
| value=start_time+100, |
| marks={str(time): str(time) for time in range(start_time, end_time+1, 50)}, |
| updatemode='drag' |
| ), |
| style={'width': '100%'} |
| ) |
|
|
| ]) |
|
|
| @app.callback( |
| dash.dependencies.Output('button', 'n_clicks'), |
| [dash.dependencies.Input('work-allocation', 'value')], |
| [dash.dependencies.Input('cont-time', 'value')], |
| [dash.dependencies.Input('ConAircraftResponse', 'value')], |
| [dash.dependencies.Input('OtherAircraftResponse', 'value')], |
| [dash.dependencies.Input('button', 'n_clicks')], |
| ) |
| def run_wmc(workAllocation, selected_time, ConAircraftResponse, OtherAircraftResponse, nc_clicks): |
| if nc_clicks > 0: |
| |
| print("Running WMC with input arguments",workAllocation,selected_time,ConAircraftResponse+OtherAircraftResponse) |
| |
| command = f"wmc/AAMv2 {workAllocation} {selected_time} {ConAircraftResponse + OtherAircraftResponse} 4000" |
|
|
| |
| with open("wmc/wrapper.sh", "w") as file: |
| file.write("#!/bin/bash\n") |
| file.write(command + "\n") |
|
|
| |
| subprocess.run(["chmod", "+x", "wmc/wrapper.sh"]) |
|
|
| |
| process = subprocess.Popen(["wmc/wrapper.sh"]) |
| |
| |
| try: |
| process.wait(timeout=50) |
| except subprocess.TimeoutExpired: |
| process.terminate() |
| |
| print("Completed running WMC...") |
| |
| return 0 |
| |
| |
| |
| |
| @app.callback( |
| dash.dependencies.Output('subfolder-dropdown', 'options'), |
| dash.dependencies.Output('variable-dropdown', 'options'), |
| dash.dependencies.Output('submit-button', 'n_clicks'), |
| [dash.dependencies.Input('interval-component', 'n_intervals'), |
| dash.dependencies.Input('submit-button', 'n_clicks')], |
| |
| ) |
| def load_data(n_intervals, n_clicks): |
| selected_path = "Results" |
| print("selected path is",selected_path) |
| global data_store |
|
|
| if n_intervals > 0 or n_clicks > 0: |
| if selected_path is None or not os.path.isdir(selected_path): |
| return [], [] |
| subfolder_labels, data = parse_all_data(selected_path) |
| print('subfolders',subfolder_labels) |
| if data is None: |
| data = {} |
| |
| data_store = data |
| labels = list(data[list(data.keys())[0]]['aircraft_data'][list(data[list(data.keys())[0]]['aircraft_data'].keys())[0]].columns.tolist()) |
| return subfolder_labels, labels, 0 |
| else: |
| return [], [], 0 |
|
|
| |
| @app.callback( |
| |
| dash.dependencies.Output('map', 'figure'), |
| dash.dependencies.Output('altitude-series', 'figure'), |
| dash.dependencies.Output('timeline', 'figure'), |
| |
| [dash.dependencies.Input('subfolder-dropdown', 'value'), |
| dash.dependencies.Input('time-slider', 'value'), |
| dash.dependencies.Input('variable-dropdown','value')] |
| ) |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| def update_graph(selected_subfolder, selected_time, selected_variable): |
| |
| if selected_subfolder not in data_store: |
| return go.Figure(), go.Figure(), go.Figure() |
|
|
| wnd_action_trace = data_store[selected_subfolder].get('wnd_action_trace', pd.DataFrame()) |
| all_action_trace = data_store[selected_subfolder].get('all_action_trace', []) |
| aircraft_data = data_store[selected_subfolder].get('aircraft_data', {}) |
|
|
| filtered_wnd_action_trace = wnd_action_trace[(wnd_action_trace['time'] >= start_time) & (wnd_action_trace['time'] <= selected_time)] |
| |
| filtered_all_action_trace = [] |
| for event in all_action_trace: |
| if (event['start'] - start_date).total_seconds() <= selected_time: |
| event_copy = event.copy() |
| if (event_copy['end'] - start_date).total_seconds() > selected_time: |
| event_copy['end'] = start_date + datetime.timedelta(seconds=selected_time) |
| filtered_all_action_trace.append(event_copy) |
|
|
| ac_data = {} |
| for aircraft_id, df in aircraft_data.items(): |
| filtered_df = df[(df['time'] >= start_time) & (df['time'] <= selected_time)] |
| ac_data[aircraft_id] = filtered_df |
|
|
| fig1 = go.Figure(layout=dict(autosize=True, width=None, height=500)) |
| fig1['layout']['uirevision'] = 'Hello world!' |
| fig1 = viz.plot_trajectory(fig1, ac_data) |
|
|
| |
| |
| |
|
|
| if selected_variable is not None: |
| fig3 = go.Figure(layout=dict(autosize=True, width=None, height=300)) |
| for aircraft_id, df in ac_data.items(): |
| fig3.add_trace(go.Scatter(x=df['time'], y=df[selected_variable], mode='lines', name=aircraft_id)) |
| fig3.update_layout(xaxis_title='Real Time', yaxis_title=selected_variable, showlegend=True, xaxis=dict(range=[start_time, end_time])) |
| fig3['layout']['uirevision'] = 'Hello world!' |
|
|
| else: |
| fig3 = go.Figure(layout=dict(autosize=True, width=None, height=300)) |
| |
| fig4 = go.Figure(layout=dict(autosize=True, width=None, height=2100)) |
| fig4 = viz.timeline(fig4, filtered_all_action_trace, selected_time, start_date, end_date) |
| fig4['layout']['uirevision'] = 'Hello world!' |
| fig4.update_layout(height=900) |
|
|
| |
| return fig1, fig3, fig4 |
| |
| |
| if __name__ == '__main__': |
|
|
| |
| |
| app.run_server(debug=False, host='0.0.0.0', port=7860) |
|
|