import numpy as np from scipy.optimize import curve_fit import matplotlib.pyplot as plt class TrajectoryFitter: def __init__(self, gravity=9.81): self.g = gravity def trajectory_model(self, t, v0, theta, h0): """ Physics model for projectile motion Parameters: t: time points v0: initial velocity theta: launch angle (in degrees) h0: initial height Returns: (x, y) coordinates at each time t """ # Convert angle to radians theta_rad = np.radians(theta) # Initial velocities in x and y directions v0x = v0 * np.cos(theta_rad) v0y = v0 * np.sin(theta_rad) # Position equations x = v0x * t y = h0 + v0y * t - 0.5 * self.g * t**2 return np.column_stack((x, y)) def fit_trajectory(self, points, times=None): """ Fit trajectory to user-selected points Parameters: points: array of (x, y) coordinates times: optional array of timestamps for each point Returns: (v0, theta, h0): initial velocity, launch angle, initial height trajectory: predicted points along entire path """ points = np.array(points) # If times not provided, estimate based on x positions if times is None: times = (points[:, 0] - points[0, 0]) / np.linalg.norm( points[1] - points[0] ) # Initial guesses initial_height = points[0, 1] # Estimate initial angle and velocity from first two points if len(points) >= 2: dx = points[1, 0] - points[0, 0] dy = points[1, 1] - points[0, 1] initial_theta = np.degrees(np.arctan2(dy, dx)) initial_v0 = np.sqrt(dx**2 + dy**2) / (times[1] - times[0]) else: initial_theta = 45 initial_v0 = 50 # Fit physics model to points try: params, covariance = curve_fit( lambda t, v0, theta: self.trajectory_model( t, v0, theta, initial_height ), times, points, p0=[initial_v0, initial_theta], bounds=([0, -90], [1000, 90]), # Reasonable bounds for golf ) v0_fit, theta_fit = params # Generate smooth trajectory for visualization t_smooth = np.linspace(min(times), max(times), 100) trajectory = self.trajectory_model( t_smooth, v0_fit, theta_fit, initial_height ) return { "initial_velocity": v0_fit, "launch_angle": theta_fit, "initial_height": initial_height, "trajectory": trajectory, "times": t_smooth, "covariance": covariance, } except RuntimeError as e: print(f"Fitting failed: {e}") return None def calculate_metrics(self, fit_results): """Calculate additional metrics from the fit""" v0 = fit_results["initial_velocity"] theta = fit_results["launch_angle"] h0 = fit_results["initial_height"] # Maximum height theta_rad = np.radians(theta) v0y = v0 * np.sin(theta_rad) max_height = h0 + (v0y**2) / (2 * self.g) # Total distance (range) t_total = (v0y + np.sqrt(v0y**2 + 2 * self.g * h0)) / self.g total_distance = v0 * np.cos(theta_rad) * t_total # Flight time flight_time = t_total return { "max_height": max_height, "total_distance": total_distance, "flight_time": flight_time, "initial_velocity_mph": v0 * 2.237, # Convert m/s to mph } def plot_trajectory(self, points, fit_results): """Visualize the fitted trajectory and original points""" plt.figure(figsize=(12, 6)) # Plot original points points = np.array(points) plt.scatter(points[:, 0], points[:, 1], color="red", label="Selected Points") # Plot fitted trajectory trajectory = fit_results["trajectory"] plt.plot(trajectory[:, 0], trajectory[:, 1], "b-", label="Fitted Trajectory") plt.grid(True) plt.xlabel("Distance (m)") plt.ylabel("Height (m)") plt.title("Golf Ball Trajectory Fit") plt.legend() plt.axis("equal") plt.show() # Example usage: if __name__ == "__main__": # Sample points (x, y) in meters points = [ (0, 0), # Starting point (50, 20), # Some point during flight (100, 0), # Landing point ] fitter = TrajectoryFitter() results = fitter.fit_trajectory(points) if results: metrics = fitter.calculate_metrics(results) print("\nFitted Parameters:") print( f"Initial Velocity: {results['initial_velocity']:.1f} m/s ({metrics['initial_velocity_mph']:.1f} mph)" ) print(f"Launch Angle: {results['launch_angle']:.1f} degrees") print(f"Max Height: {metrics['max_height']:.1f} m") print(f"Total Distance: {metrics['total_distance']:.1f} m") print(f"Flight Time: {metrics['flight_time']:.1f} s") fitter.plot_trajectory(points, results)