File size: 10,046 Bytes
9f4ea7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import webbrowser
import dash as dcc
import dash as html
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
import networkx as nx
import datetime

class Visualizer:

    def wnd_visualization(figure_handle, dataframe, current_time, start_time = 0, end_time = 10^6):
            # Diagonal line y=x
        figure_handle.add_trace(go.Scatter(x=[start_time, end_time], y=[start_time, end_time], mode='lines', line=dict(color='black', dash='dash')))

        # Dotted lines from the point to axes
        figure_handle.add_trace(go.Scatter(x=[current_time, current_time, start_time], y=[start_time, current_time, current_time], mode='lines', line=dict(color='black', dash='dot')))

        for time, name in dataframe[['time', 'actionName']].values:
            figure_handle.add_trace(go.Scatter(x=[time], y=[time], mode='markers', marker=dict(color='black', size=10)))

        timestamps_and_ids_dict = {}

        for index, row in dataframe.iterrows():
            array_data = row['attributes']
            if isinstance(array_data, str):
                timestamp_id_triples = []
                for pair in array_data.split(';'):
                    triple = pair.split('|')
                    triple.append("")
                    timestamp_id_triples.append(triple)
                for triple in timestamp_id_triples:
                    if '(' in triple[1]:
                        triple[1], triple[2] = triple[1].split('(')
            else:
                timestamp_id_triples = []
            timestamp_id_triples.sort(key=lambda x: float(x[0][0]))

            timestamp_id_dict = {float(triple[0]): str(triple[1]) for triple in timestamp_id_triples}
            timestamps_and_ids_dict[row['time']] = timestamp_id_dict

        timestamps_before_simulation_time = {timestamp: data for timestamp, data in timestamps_and_ids_dict.items() if timestamp <= current_time}
        print(timestamps_before_simulation_time)
        mental_model_over_time = {}

        if timestamps_before_simulation_time:
            for mm_time, array_timestamps_and_ids in timestamps_before_simulation_time.items():
                for timestamp, value in array_timestamps_and_ids.items():
                    if value in mental_model_over_time:
                        # Value is already in the dictionary, append the timestamp to the existing list
                        mental_model_over_time[value].append((mm_time, timestamp))
                    else:
                        # Value is not in the dictionary, create a new list with the timestamp
                        mental_model_over_time[value] = [(mm_time, timestamp)]

            # Get last update on mental model
            previous_timestamp, array_timestamps_and_ids = max(timestamps_before_simulation_time.items(), key=lambda x: x[0])

            # Assuming mental model does not change in between, load this as current mental model into dictionary
            for timestamp, value in array_timestamps_and_ids.items():
                if value in mental_model_over_time:
                    # Value is already in the dictionary, append the timestamp to the existing list
                    mental_model_over_time[value].append((current_time, timestamp))
                else:
                    # Value is not in the dictionary, create a new list with the timestamp
                    mental_model_over_time[value] = [(current_time, timestamp)]

                # Add text box with name of event
                figure_handle.add_annotation(x=current_time, y=timestamp, text=value, showarrow=True, arrowhead=1, arrowcolor='black', arrowwidth=2)

        for label, coordinates in mental_model_over_time.items():
            for i in range(len(coordinates) - 1):
                x = [coordinates[i][0], coordinates[i + 1][0], coordinates[i + 1][0]]
                y = [coordinates[i][1], coordinates[i][1], coordinates[i + 1][1]]

                if all(xi <= yi for xi, yi in zip(x, y)):
                    figure_handle.add_trace(go.Scatter(x=x, y=y, mode='lines', line=dict(color='blue'), marker_symbol='triangle-up'))

                if all(xi >= yi for xi, yi in zip(x, y)):
                    figure_handle.add_trace(go.Scatter(x=x, y=y, mode='lines', line=dict(color='gray'), marker_symbol='circle'))

        figure_handle.update_layout(
            xaxis_title='Real Time',
            yaxis_title='Comprehension of Timeline of Events',
            showlegend=False,
            xaxis=dict(
                range=[start_time,end_time]
            ),
            yaxis=dict(
                range=[start_time,end_time],
                scaleanchor="x",
                scaleratio=1
            )
        )

        return figure_handle

    def plot_trajectory(figure_handle, parsed_data, uptime=float('inf'), background_image=None):
        fig = figure_handle

        if background_image:
            fig.add_layout_image(
                source=background_image,
                xref="x",
                yref="y",
                x=-96.88,
                y=33.16,
                sizex=0.23,
                sizey=0.44,
                sizing="fill",
                opacity=0.5,
                layer="below"
            )
        else:
            fig.update_layout(
                mapbox_style="open-street-map",
                mapbox_center_lon=-96.765,
                mapbox_center_lat=32.95,
                mapbox_zoom=10
            )

        # Create a graph for the flight plans
        G = nx.Graph()

        # Define waypoints and their positions with new names from the embedded code
        waypoints = {
            "NTI": (-96.7535, 32.928), "NTHW": (-96.749, 32.8573), "RUBL": (-96.7588, 32.78), 
            "HW342": (-96.8003, 32.7508), "TLWY": (-96.807, 32.769), "4DT": (-96.8508, 32.846),
            "T57": (-96.686, 32.888), "FSC": (-96.8213, 33.1413), "PLN": (-96.7275, 33.0297)
        }

        # Add edges between waypoints to the graph with updated names
        edges = [("NTI", "NTHW"), ("NTHW", "RUBL"), ("RUBL", "HW342"), ("HW342", "TLWY"), 
                 ("TLWY", "4DT"), ("T57", "NTI"), ("FSC", "PLN"),("PLN","NTI")]
        G.add_edges_from(edges)

        # Draw the graph with smaller waypoint symbols in gray and keep the node labels
        for edge in G.edges():
            x_values = [waypoints[edge[0]][0], waypoints[edge[1]][0]]
            y_values = [waypoints[edge[0]][1], waypoints[edge[1]][1]]
            fig.add_trace(go.Scattermapbox(lon=x_values, lat=y_values, mode='lines', line=dict(color='gray'), showlegend=False))#, dash='dash'

        for node in G.nodes():
            x_value = waypoints[node][0]
            y_value = waypoints[node][1]
            fig.add_trace(go.Scattermapbox(lon=[x_value], lat=[y_value], mode='markers', marker=dict(size=5, color='gray'), showlegend=False))#, symbol='pentagon'
        # for node, (x_value, y_value) in waypoints.items():
            # fig.add_annotation(lon=x_value, lat=y_value, text=node, showarrow=False, font=dict(size=6, color='gray'), xanchor='left', yanchor='bottom')
            
        # Add legend label for waypoints and corridors
        legend_labels = []
        legend_labels.append("Corridors")
        legend_labels.append("Waypoints")

        colors = ['blue', 'red', 'green', 'purple', 'orange', 'yellow', 'pink', 'cyan', 'magenta', 'brown']
        color_index = 0

        for ac_name, data in parsed_data.items():
            mask = (data['time'] >= 0) & (data['time'] <= uptime)
            filtered_xdata = data['longitude_deg'][mask]
            filtered_ydata = data['latitude_deg'][mask]
            filtered_heading = data['heading_deg'][mask]
            filtered_altitude = data['altitude_ft'][mask] # In case we ever want to do 3D visualizations

            fig.add_trace(go.Scattermapbox(#Scatter3d possibly for 3D but issue with Scattermapbox not being compatible
                mode="lines",
                lon=filtered_xdata,
                lat=filtered_ydata,
                line=dict(width=2, color=colors[color_index]),
                name=ac_name
            ))

            if len(filtered_xdata) > 0 and len(filtered_ydata) > 0:
                current_heading = filtered_heading.iloc[-1]
                fig.add_trace(go.Scattermapbox(
                    mode="markers",
                    lon=[filtered_xdata.iloc[-1]],
                    lat=[filtered_ydata.iloc[-1]],
                    marker=dict(size=10, color=colors[color_index]),#, symbol=['cross']), #To make custom markers, you need MapBox access token (to use any of Mapbox's tools, APIs, or SDK)
                    name=ac_name + " Current Location"
                ))

            color_index = (color_index + 1) % len(colors)

        fig.update_layout(
            mapbox=dict(
                center=dict(lon=-96.765, lat=32.95),
                zoom=9
            ),
            margin=dict(l=20, r=20, t=20, b=20)
        )

        return fig

    def timeline(figure_handle, events, current_time = 0, start_date = 0, end_date = 0):

        category_order = sorted(list(set([e['agent'] for e in events])))
        
        figure_handle = px.timeline(
            events, x_start="start", x_end="end", y="agent", 
            hover_data=['attributes', 'duration'], 
            color='name',# height=400, width=1600, 
            category_orders={'agent': category_order}
        )

        # Add a vertical dashed line at x = current_time
        current_datetime = start_date + datetime.timedelta(seconds=current_time)
        figure_handle.add_shape(
            type="line",
            x0=current_datetime, y0=0,
            x1=current_datetime, y1=1,
            yref="paper",
            line=dict(
                color="Black",
                width=2,
                dash="dash",
            )
        )

        figure_handle.update_layout(
            xaxis_range=[start_date, end_date]#,
            # width=600, 
            # height=500
        )

        return figure_handle