Skip to content

Commit f20d80a

Browse files
committed
Complete Web Interface with USCG AUX styling
UX Enhancements: - Tab-based interface (Calculator and Instructions) - US Coast Guard Auxiliary color scheme (#003087 blue, #E21B3C red) - Auto-switch to Calculator tab when solution is calculated - Full instructions tab for maritime navigator to understand how to use the app - Button-style tabs with hover effects Technical Details: - Session state management for tab switching - CSS for Coast Guard Aux branding - Solution persistance across tabs
1 parent 292335a commit f20d80a

File tree

1 file changed

+143
-28
lines changed

1 file changed

+143
-28
lines changed

app.py

Lines changed: 143 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,47 @@ def main():
2424
layout="wide"
2525
)
2626

27+
# Initialize session state for active tab
28+
if 'active_tab' not in st.session_state:
29+
st.session_state.active_tab = "Calculator"
30+
2731
st.title("⚓️ Collision Avoidance Radar Plotting App")
28-
st.markdown("Calculate collusion avoidance maneuvers using radar plotting techniques")
32+
st.markdown("Calculate collision avoidance maneuvers using radar plotting techniques")
2933

3034
# Disclaimer
3135
st.warning("⚠️ **Disclaimer**: This is an educational tool ONLY and should NOT be used for real collision avoidance situations. This is for training purposes ONLY.")
3236

37+
# Custom CSS for tab-like buttons (Coast Guard Auxiliary colors)
38+
st.markdown("""
39+
<style>
40+
/* Style for our custom tab buttons */
41+
div.stButton > button {
42+
height: 50px;
43+
border-radius: 8px;
44+
font-size: 16px;
45+
font-weight: 600;
46+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
47+
}
48+
</style>
49+
""", unsafe_allow_html=True)
50+
51+
# Create custom tab buttons using columns
52+
col1, col2 = st.columns(2)
53+
54+
with col1:
55+
if st.button("📊 Calculator", use_container_width=True,
56+
type="primary" if st.session_state.active_tab == "Calculator" else "secondary",
57+
key="calc_tab_btn"):
58+
st.session_state.active_tab = "Calculator"
59+
st.rerun()
60+
61+
with col2:
62+
if st.button("ℹ️ Instructions", use_container_width=True,
63+
type="primary" if st.session_state.active_tab == "Instructions" else "secondary",
64+
key="instr_tab_btn"):
65+
st.session_state.active_tab = "Instructions"
66+
st.rerun()
67+
3368
# Sidebar for inputs
3469
st.sidebar.header("📋 Input Parameters")
3570

@@ -118,6 +153,8 @@ def main():
118153
# Calculate Button
119154
if st.sidebar.button("🎯 Calculate Solution", type="primary"):
120155
try:
156+
# Switch to Calculator tab
157+
st.session_state.active_tab = "Calculator"
121158
# Create problem
122159
problem = RadarProblem(
123160
our_course = our_course,
@@ -131,54 +168,132 @@ def main():
131168
# Solve
132169
solution = solver_radar_problem(problem)
133170

134-
# Display results
171+
# Store solution in session state
172+
st.session_state.solution = solution
173+
st.session_state.problem = problem
174+
st.session_state.has_solution = True
175+
176+
except ValueError as e:
177+
st.session_state.error_message = f"❌ Invalid input: {str(e)}"
178+
st.session_state.has_solution = False
179+
except Exception as e:
180+
st.session_state.error_message = f"❌ Error calculating solution: {str(e)}"
181+
st.session_state.error_exception = e
182+
st.session_state.has_solution = False
183+
184+
# Rerun to update the tab button styling
185+
st.rerun()
186+
187+
# Display content based on active tab
188+
if st.session_state.active_tab == "Calculator":
189+
# Calculator Tab Content
190+
if 'has_solution' in st.session_state and st.session_state.has_solution:
135191
st.header("📊 Results")
136192

137193
col1, col2, col3 = st.columns(3)
138194

139195
with col1:
140-
st.metric("CPA Distance", f"{solution.cpa_range:.1f} NM")
141-
st.metric("CPA Bearing", f"{solution.cpa_bearing:06.2f}°")
142-
st.metric("Time to CPA", solution.cpa_time.strftime("%H:%M"))
196+
st.metric("CPA Distance", f"{st.session_state.solution.cpa_range:.1f} NM")
197+
st.metric("CPA Bearing", f"{st.session_state.solution.cpa_bearing:06.2f}°")
198+
st.metric("Time to CPA", st.session_state.solution.cpa_time.strftime("%H:%M"))
143199

144200
with col2:
145-
st.metric("SRM (Speed Relative Movement)", f"{solution.srm:.1f} kts")
146-
st.metric("DRM (Direction Relative Movement)", f"{solution.drm:06.2f}°")
147-
st.metric("STM (Speed True Movement)", f"{solution.stm:.1f} kts")
201+
st.metric("SRM (Speed Relative Movement)", f"{st.session_state.solution.srm:.1f} kts")
202+
st.metric("DRM (Direction Relative Movement)", f"{st.session_state.solution.drm:06.2f}°")
203+
st.metric("STM (Speed True Movement)", f"{st.session_state.solution.stm:.1f} kts")
148204

149205
with col3:
150-
st.metric("DTM (Direction True Movement)", f"{solution.dtm:06.2f}°")
151-
st.metric("New Course (N/C)", f"{solution.new_course:06.2f}°")
152-
st.metric("New Speed (N/S)", f"{solution.new_speed:.1f} kts")
206+
st.metric("DTM (Direction True Movement)", f"{st.session_state.solution.dtm:06.2f}°")
207+
st.metric("New Course (N/C)", f"{st.session_state.solution.new_course:06.2f}°")
208+
st.metric("New Speed (N/S)", f"{st.session_state.solution.new_speed:.1f} kts")
153209

154210
# Plot
155211
st.header("📈 Radar Plot")
156-
fig = plot_radar_solution(problem, solution, show=False)
212+
fig = plot_radar_solution(st.session_state.problem, st.session_state.solution, show=False)
157213
st.pyplot(fig)
158214

159215
# Success message
160216
st.success("✅ Solution calculated successfully!")
217+
elif 'error_message' in st.session_state:
218+
st.error(st.session_state.error_message)
219+
if 'error_exception' in st.session_state:
220+
st.exception(st.session_state.error_exception)
221+
else:
222+
st.info("👈 Enter your vessel information and target observations in the sidebar, then click 'Calculate Solution' to see results.")
161223

162-
except ValueError as e:
163-
st.error(f"❌ Invalid input: {str(e)}")
164-
except Exception as e:
165-
st.error(f"❌ Error calculating solution: {str(e)}")
166-
st.exception(e)
224+
else: # Instructions tab
225+
st.header("How to Use This App")
167226

168-
# Instructions
169-
with st.expander("ℹ️ How to Use"):
170227
st.markdown("""
171-
1. **Enter your vessel's information**: Course and speed
172-
2. **Set maneuver parameters**:
173-
- Maneuver Distance: How far ahead to plan the maneuver
174-
- Keep Out Distance: Desired closest point of approach after maneuver
175-
3. **Enter target vessel's First Appearance**: First radar observation (bearing, range, time)
176-
4. **Enter target vessel's Second Appearance**: Second radar observation (bearing, range, time)
177-
5. **Click Calculate** to see the solution and radar plot
228+
### 📋 Step-by-Step Guide
229+
230+
#### 1. Enter Your Vessel's Information (Sidebar)
231+
- **Course (°)**: Your current heading in degrees (0-359)
232+
- **Speed (kts)**: Your current speed in knots
233+
234+
#### 2. Set Maneuver Parameters
235+
- **Maneuver Distance (NM)**: How far ahead you want to plan the maneuver
236+
- **Keep Out Distance (NM)**: Your desired minimum safe distance (CPA) after maneuvering
237+
238+
#### 3. Enter Target Vessel - First Appearance (Point R)
239+
Record the first time you observe the target vessel on radar:
240+
- **Bearing (°)**: Direction to the target (0-359)
241+
- **Range (NM)**: Distance to the target in nautical miles
242+
- **Time (HH:MM)**: Time of observation in 24-hour format (e.g., 14:00)
243+
244+
#### 4. Enter Target Vessel - Second Appearance (Point M)
245+
Record the second observation (usually 6 minutes later):
246+
- **Bearing (°)**: New direction to the target
247+
- **Range (NM)**: New distance to the target
248+
- **Time (HH:MM)**: Time of this observation
249+
250+
#### 5. Calculate Solution
251+
Click the **"🎯 Calculate Solution"** button in the sidebar.
252+
253+
The app will display:
254+
- **CPA information**: Distance, bearing, and time of closest approach
255+
- **Relative Motion**: SRM (speed) and DRM (direction) of target relative to you
256+
- **True Motion**: STM (speed) and DTM (direction) of target's actual movement
257+
- **Recommended Maneuver**: New course and speed to maintain safe distance
258+
- **Radar Plot**: Visual representation of the situation
259+
260+
---
261+
262+
### 📚 Understanding the Results
263+
264+
**CPA (Closest Point of Approach)**
265+
- The minimum distance the target will pass if no action is taken
266+
267+
**SRM & DRM**
268+
- Speed and Direction of Relative Movement
269+
- How the target appears to move from your perspective
270+
271+
**STM & DTM**
272+
- Speed and Direction of True Movement
273+
- The target's actual course and speed
274+
275+
**N/C & N/S**
276+
- New Course and New Speed
277+
- Recommended changes to achieve your desired keep-out distance
278+
279+
---
280+
281+
### ⚠️ Important Notes
282+
283+
- Times must be in **HH:MM** format (e.g., 14:30)
284+
- All bearings are in **degrees** (0-359, where 0° is North)
285+
- All distances are in **nautical miles**
286+
- All speeds are in **knots**
287+
- This is an **educational tool ONLY** - not for real navigation!
288+
289+
---
178290
179-
The app will calculate the required course and speed changes to achieve the desired CPA.
291+
### 💡 Tips for Accurate Results
180292
181-
**Note**: Times must be in HH:MM format (e.g., 14:30)
293+
1. Take observations at least 6 minutes apart for better accuracy
294+
2. Ensure your vessel maintains steady course and speed between observations
295+
3. Use precise bearing and range measurements from your radar
296+
4. Double-check all input values before calculating
182297
""")
183298

184299

0 commit comments

Comments
 (0)