@@ -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