Chris773 commited on
Commit
d02da93
·
verified ·
1 Parent(s): 9958415

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -26
app.py CHANGED
@@ -1,9 +1,15 @@
 
 
 
 
 
1
  import re
2
  import xml.etree.ElementTree as ET
3
 
4
  import gradio as gr
5
  import numpy as np
6
  import matplotlib.pyplot as plt
 
7
 
8
  import flight_plotter_v32 as fp
9
 
@@ -23,6 +29,7 @@ def load_ping_rings_from_kml(path=KML_FILE):
23
  continue
24
  name = name_el.text.strip()
25
 
 
26
  if not name.lower().startswith("ping ring"):
27
  continue
28
 
@@ -45,6 +52,10 @@ def load_ping_rings_from_kml(path=KML_FILE):
45
  if pts:
46
  rings[name] = pts
47
 
 
 
 
 
48
  return rings
49
 
50
 
@@ -61,20 +72,20 @@ def latlon_to_xyz(lat_deg, lon_deg, r=1.0):
61
  return x, y, z
62
 
63
 
64
- # ---------------- legs ----------------
65
- def parse_legs(text):
66
  """
67
- Each line: heading_deg, speed_knots, minutes
 
68
  """
69
  legs = []
70
- for line in (text or "").splitlines():
71
- line = line.strip()
72
- if not line or line.startswith("#"):
 
 
73
  continue
74
- parts = [p.strip() for p in line.split(",")]
75
- if len(parts) != 3:
76
- raise ValueError(f"Bad leg line: {line} (need heading,speed,minutes)")
77
- legs.append((float(parts[0]), float(parts[1]), float(parts[2])))
78
 
79
  if not legs:
80
  legs = [(300, 500, 10), (270, 500, 10), (240, 500, 10)]
@@ -109,15 +120,22 @@ def plot_globe(path_pts, selected_rings):
109
  zs = np.sin(vv)
110
  ax.plot_surface(xs, ys, zs, linewidth=0, alpha=0.12)
111
 
112
- # Flight path
113
  lats = np.array([p[0] for p in path_pts])
114
  lons = np.array([p[1] for p in path_pts])
115
  xp, yp, zp = latlon_to_xyz(lats, lons)
116
- ax.plot(xp, yp, zp, linewidth=2)
117
- ax.scatter([xp[0]], [yp[0]], [zp[0]], s=30) # start
118
- ax.scatter([xp[-1]], [yp[-1]], [zp[-1]], s=30) # end
119
 
120
- # Ping rings from KML
 
 
 
 
 
 
 
 
 
 
121
  for ring_name in selected_rings or []:
122
  pts = PING_RINGS.get(ring_name)
123
  if not pts:
@@ -125,16 +143,26 @@ def plot_globe(path_pts, selected_rings):
125
  rlats = np.array([p[0] for p in pts])
126
  rlons = np.array([p[1] for p in pts])
127
  xr, yr, zr = latlon_to_xyz(rlats, rlons)
128
- ax.plot(xr, yr, zr, linewidth=1, alpha=0.95)
129
-
 
 
 
 
 
 
 
 
 
 
130
  ax.set_box_aspect([1, 1, 1])
131
  ax.set_axis_off()
132
  ax.view_init(elev=20, azim=-60)
133
  return fig
134
 
135
 
136
- def run_sim(start_lat, start_lon, legs_text, method, rings):
137
- legs = parse_legs(legs_text)
138
  path = simulate_path(start_lat, start_lon, legs, method)
139
  return plot_globe(path, rings)
140
 
@@ -148,22 +176,29 @@ with gr.Blocks() as demo:
148
  start_lon = gr.Slider(90, 110, value=96.3, label="Start Longitude")
149
  method = gr.Dropdown(["VINCENTY", "RHUMB"], value="VINCENTY", label="Method")
150
 
151
- legs_text = gr.Textbox(
152
- label="Multi-leg plan (one per line: heading_deg, speed_knots, minutes)",
153
- lines=6,
154
- value="300, 500, 10\n270, 500, 10\n240, 500, 10"
 
 
 
 
 
 
 
155
  )
156
 
157
  rings = gr.CheckboxGroup(
158
  choices=sorted(PING_RINGS.keys()),
159
- value=["Ping Ring 1"],
160
- label="Ping rings (from your KML)"
161
  )
162
 
163
  btn = gr.Button("Simulate on Globe")
164
  out = gr.Plot()
165
 
166
- btn.click(run_sim, inputs=[start_lat, start_lon, legs_text, method, rings], outputs=out)
167
 
168
  demo.launch()
169
 
 
1
+ # app.py
2
+ # MH370 Flight Simulator — Globe + Ping Rings (from KML)
3
+ # Improvements included: (3) legend + start/end labels, (5) thicker path + thinner rings,
4
+ # (6) multi-leg editor as a table (Dataframe)
5
+
6
  import re
7
  import xml.etree.ElementTree as ET
8
 
9
  import gradio as gr
10
  import numpy as np
11
  import matplotlib.pyplot as plt
12
+ from matplotlib.lines import Line2D
13
 
14
  import flight_plotter_v32 as fp
15
 
 
29
  continue
30
  name = name_el.text.strip()
31
 
32
+ # Only load "Ping Ring ..." LineStrings
33
  if not name.lower().startswith("ping ring"):
34
  continue
35
 
 
52
  if pts:
53
  rings[name] = pts
54
 
55
+ if not rings:
56
+ raise RuntimeError(
57
+ f"No ping rings found in KML. Check file name/path: {path} and Placemark names."
58
+ )
59
  return rings
60
 
61
 
 
72
  return x, y, z
73
 
74
 
75
+ # ---------------- legs (table) ----------------
76
+ def parse_legs_df(df):
77
  """
78
+ df is a list of rows, e.g. [[300,500,10],[270,500,10],...]
79
+ Each row: heading_deg, speed_knots, minutes
80
  """
81
  legs = []
82
+ for row in df or []:
83
+ if not row or len(row) < 3:
84
+ continue
85
+ h, s, m = row[0], row[1], row[2]
86
+ if h is None or s is None or m is None:
87
  continue
88
+ legs.append((float(h), float(s), float(m)))
 
 
 
89
 
90
  if not legs:
91
  legs = [(300, 500, 10), (270, 500, 10), (240, 500, 10)]
 
120
  zs = np.sin(vv)
121
  ax.plot_surface(xs, ys, zs, linewidth=0, alpha=0.12)
122
 
123
+ # Flight path (thicker)
124
  lats = np.array([p[0] for p in path_pts])
125
  lons = np.array([p[1] for p in path_pts])
126
  xp, yp, zp = latlon_to_xyz(lats, lons)
 
 
 
127
 
128
+ ax.plot(xp, yp, zp, linewidth=3, alpha=0.95)
129
+
130
+ # Start/End markers (bigger)
131
+ ax.scatter([xp[0]], [yp[0]], [zp[0]], s=60)
132
+ ax.scatter([xp[-1]], [yp[-1]], [zp[-1]], s=60)
133
+
134
+ # Labels near points
135
+ ax.text(xp[0], yp[0], zp[0], " Start", fontsize=10)
136
+ ax.text(xp[-1], yp[-1], zp[-1], " End", fontsize=10)
137
+
138
+ # Ping rings (thinner)
139
  for ring_name in selected_rings or []:
140
  pts = PING_RINGS.get(ring_name)
141
  if not pts:
 
143
  rlats = np.array([p[0] for p in pts])
144
  rlons = np.array([p[1] for p in pts])
145
  xr, yr, zr = latlon_to_xyz(rlats, rlons)
146
+ ax.plot(xr, yr, zr, linewidth=1, alpha=0.9)
147
+
148
+ # Legend (proxies are reliable in 3D)
149
+ legend_handles = [
150
+ Line2D([0], [0], linewidth=3, label="Flight path"),
151
+ Line2D([0], [0], marker="o", linestyle="None", markersize=8, label="Start"),
152
+ Line2D([0], [0], marker="o", linestyle="None", markersize=8, label="End"),
153
+ Line2D([0], [0], linewidth=1, label="Ping rings"),
154
+ ]
155
+ ax.legend(handles=legend_handles, loc="upper left")
156
+
157
+ # Globe look
158
  ax.set_box_aspect([1, 1, 1])
159
  ax.set_axis_off()
160
  ax.view_init(elev=20, azim=-60)
161
  return fig
162
 
163
 
164
+ def run_sim(start_lat, start_lon, legs_df, method, rings):
165
+ legs = parse_legs_df(legs_df)
166
  path = simulate_path(start_lat, start_lon, legs, method)
167
  return plot_globe(path, rings)
168
 
 
176
  start_lon = gr.Slider(90, 110, value=96.3, label="Start Longitude")
177
  method = gr.Dropdown(["VINCENTY", "RHUMB"], value="VINCENTY", label="Method")
178
 
179
+ legs_df = gr.Dataframe(
180
+ headers=["heading_deg", "speed_knots", "minutes"],
181
+ datatype=["number", "number", "number"],
182
+ row_count=(6, "dynamic"),
183
+ col_count=(3, "fixed"),
184
+ value=[
185
+ [300, 500, 10],
186
+ [270, 500, 10],
187
+ [240, 500, 10],
188
+ ],
189
+ label="Multi-leg plan",
190
  )
191
 
192
  rings = gr.CheckboxGroup(
193
  choices=sorted(PING_RINGS.keys()),
194
+ value=[sorted(PING_RINGS.keys())[0]],
195
+ label="Ping rings (from your KML)",
196
  )
197
 
198
  btn = gr.Button("Simulate on Globe")
199
  out = gr.Plot()
200
 
201
+ btn.click(run_sim, inputs=[start_lat, start_lon, legs_df, method, rings], outputs=out)
202
 
203
  demo.launch()
204