# import numpy as np # import streamlit as st # import plotly.express as px # import plotly.graph_objects as go # import pandas as pd # # Soccer field dimensions (in meters) # WIDTH = 80 # Width of the field # LENGTH = 120 # Length of the field # GOAL_HEIGHT = 2.44 # Standard goal height # PENALTY_AREA_WIDTH = 40.3 # PENALTY_AREA_DEPTH = 16.5 # GOAL_AREA_WIDTH = 18.32 # GOAL_AREA_DEPTH = 5.5 # GOAL_WIDTH = 7.32 # Standard width of a soccer goal # def create_field_df(): # """Create dataframes for different parts of the soccer field.""" # field_perimeter_bounds = [[0, 0, 0], [WIDTH, 0, 0], [WIDTH, LENGTH, 0], [0, LENGTH, 0], [0, 0, 0]] # field_df = pd.DataFrame(field_perimeter_bounds, columns=['x', 'y', 'z']) # field_df['line_group'] = 'field_perimeter' # field_df['color'] = 'field' # half_field_bounds = [[0, LENGTH / 2, 0], [WIDTH, LENGTH / 2, 0]] # half_df = pd.DataFrame(half_field_bounds, columns=['x', 'y', 'z']) # half_df['line_group'] = 'half_field' # half_df['color'] = 'field' # left_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, 0, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'left_penalty_area') # right_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, LENGTH - PENALTY_AREA_DEPTH, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'right_penalty_area') # left_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, 0, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'left_goal_area') # right_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, LENGTH - GOAL_AREA_DEPTH, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'right_goal_area') # return pd.concat([field_df, half_df, left_penalty_df, right_penalty_df, left_goal_df, right_goal_df]) # def create_rectangle_df(start_x, start_y, width, height, line_group): # """Create a dataframe representing a rectangle on the field.""" # rectangle_bounds = [ # [start_x, start_y, 0], # [start_x + width, start_y, 0], # [start_x + width, start_y + height, 0], # [start_x, start_y + height, 0], # [start_x, start_y, 0] # ] # df = pd.DataFrame(rectangle_bounds, columns=['x', 'y', 'z']) # df['line_group'] = line_group # df['color'] = 'field' # return df # def create_center_circle(): # """Create a 3D line trace for the center circle.""" # theta = np.linspace(0, 2 * np.pi, 100) # x = [(WIDTH / 2) + (9.15 * np.cos(t)) for t in theta] # y = [(LENGTH / 2) + (9.15 * np.sin(t)) for t in theta] # z = [0] * 100 # return go.Scatter3d(x=x, y=y, z=z, mode='lines', line=dict(color='white', width=2)) # def create_goalposts(): # """Create goalpost lines for both ends of the field.""" # goalposts = [] # goalposts.extend([ # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), # go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)) # ]) # goalposts.extend([ # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), # go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), # go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)) # ]) # return goalposts # def generate_trajectory(start_point, end_point, peak_height=10, num_coords=100, trajectory_type='parabolic'): # """Generate a trajectory (parabolic or linear) between start and end points.""" # shot_start_x, shot_start_y, start_z = start_point # hoop_x, hoop_y, end_z = end_point # if trajectory_type == 'parabolic': # distance_x = hoop_x - shot_start_x # a = -4 * peak_height / (distance_x ** 2) # shot_path_coords = [] # for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)): # z = a * (x - (shot_start_x + hoop_x) / 2) ** 2 + peak_height # y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords) # shot_path_coords.append([x, y, z]) # shot_path_coords[0][2] = start_z # Ensure start z is as specified # shot_path_coords[-1][2] = end_z # Ensure end z is as specified # elif trajectory_type == 'linear': # shot_path_coords = [] # for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)): # y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords) # z = start_z + (end_z - start_z) * (index / num_coords) # shot_path_coords.append([x, y, z]) # return pd.DataFrame(shot_path_coords, columns=['x', 'y', 'z']) # def plot_trajectories(fig, start_points, end_points, trajectory_type='parabolic', peak_height=10, num_coords=100): # """Plot multiple trajectories on the field.""" # for start_point, end_point in zip(start_points, end_points): # trajectory_df = generate_trajectory(start_point, end_point, peak_height, num_coords, trajectory_type) # fig.add_trace(go.Scatter3d( # y=trajectory_df['x'], # x=trajectory_df['y'], # z=trajectory_df['z'], # mode='lines', # line=dict(color='red', width=4) # )) # fig.add_trace(go.Scatter3d( # y=[trajectory_df['x'].iloc[0], trajectory_df['x'].iloc[-1]], # x=[trajectory_df['y'].iloc[0], trajectory_df['y'].iloc[-1]], # z=[trajectory_df['z'].iloc[0], trajectory_df['z'].iloc[-1]], # mode='markers', # marker=dict(size=3, color='red') # )) # def create_soccer_field_plot(): # """Create a 3D soccer field plot with trajectories.""" # field_df = create_field_df() # fig = px.line_3d( # data_frame=field_df, x='x', y='y', z='z', line_group='line_group', color='color', # color_discrete_map={'field': '#FFFFFF'} # ) # fig.add_trace(go.Mesh3d( # x=[0, WIDTH, WIDTH, 0], # y=[0, 0, LENGTH, LENGTH], # z=[0, 0, 0, 0], # color='rgb(0, 128, 0)', # opacity=0.5 # )) # fig.add_trace(create_center_circle()) # for goalpost in create_goalposts(): # fig.add_trace(goalpost) # max_dimension = max(WIDTH, LENGTH, GOAL_HEIGHT) # fig.update_layout( # scene=dict( # aspectmode="manual", # aspectratio=dict(x=1, y=1, z=0.125), # xaxis=dict( # range=[-10, max_dimension + 10], # visible=False # ), # yaxis=dict( # range=[-10, max_dimension + 10], # visible=False # ), # zaxis=dict( # range=[0, 15], # visible=False # ), # camera=dict( # eye=dict(x=0.34, y=0, z=0.45) # ), # ), # paper_bgcolor='rgba(0,0,0,0)', # plot_bgcolor='rgba(0,0,0,0)', # showlegend=False, # ) # return fig # def main_3D_pitch(start_points, end_points, trajectory_type='linear'): # st.title("3D Soccer Field Trajectory Visualization") # fig = create_soccer_field_plot() # plot_trajectories(fig, start_points, end_points, trajectory_type=trajectory_type, peak_height=5, num_coords=100) # st.plotly_chart(fig) import numpy as np import streamlit as st import plotly.express as px import plotly.graph_objects as go import pandas as pd # Soccer field dimensions (in meters) WIDTH = 80 # Width of the field LENGTH = 120 # Length of the field GOAL_HEIGHT = 2.44 # Standard goal height PENALTY_AREA_WIDTH = 40.3 PENALTY_AREA_DEPTH = 16.5 GOAL_AREA_WIDTH = 18.32 GOAL_AREA_DEPTH = 5.5 GOAL_WIDTH = 7.32 # Standard width of a soccer goal def create_field_df(): """Create dataframes for different parts of the soccer field.""" field_perimeter_bounds = [[0, 0, 0], [WIDTH, 0, 0], [WIDTH, LENGTH, 0], [0, LENGTH, 0], [0, 0, 0]] field_df = pd.DataFrame(field_perimeter_bounds, columns=['x', 'y', 'z']) field_df['line_group'] = 'field_perimeter' field_df['color'] = 'field' half_field_bounds = [[0, LENGTH / 2, 0], [WIDTH, LENGTH / 2, 0]] half_df = pd.DataFrame(half_field_bounds, columns=['x', 'y', 'z']) half_df['line_group'] = 'half_field' half_df['color'] = 'field' left_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, 0, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'left_penalty_area') right_penalty_df = create_rectangle_df((WIDTH - PENALTY_AREA_WIDTH) / 2, LENGTH - PENALTY_AREA_DEPTH, PENALTY_AREA_WIDTH, PENALTY_AREA_DEPTH, 'right_penalty_area') left_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, 0, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'left_goal_area') right_goal_df = create_rectangle_df((WIDTH - GOAL_AREA_WIDTH) / 2, LENGTH - GOAL_AREA_DEPTH, GOAL_AREA_WIDTH, GOAL_AREA_DEPTH, 'right_goal_area') return pd.concat([field_df, half_df, left_penalty_df, right_penalty_df, left_goal_df, right_goal_df]) def create_rectangle_df(start_x, start_y, width, height, line_group): """Create a dataframe representing a rectangle on the field.""" rectangle_bounds = [ [start_x, start_y, 0], [start_x + width, start_y, 0], [start_x + width, start_y + height, 0], [start_x, start_y + height, 0], [start_x, start_y, 0] ] df = pd.DataFrame(rectangle_bounds, columns=['x', 'y', 'z']) df['line_group'] = line_group df['color'] = 'field' return df def create_center_circle(): """Create a 3D line trace for the center circle.""" theta = np.linspace(0, 2 * np.pi, 100) x = [(WIDTH / 2) + (9.15 * np.cos(t)) for t in theta] y = [(LENGTH / 2) + (9.15 * np.sin(t)) for t in theta] z = [0] * 100 return go.Scatter3d(x=x, y=y, z=z, mode='lines', line=dict(color='white', width=2)) def create_goalposts(): """Create goalpost lines for both ends of the field.""" goalposts = [] goalposts.extend([ go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[LENGTH, LENGTH], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)) ]) goalposts.extend([ go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) - (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), go.Scatter3d(x=[(WIDTH / 2) + (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[0, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)), go.Scatter3d(x=[(WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2)], y=[0, 0], z=[GOAL_HEIGHT, GOAL_HEIGHT], mode='lines', line=dict(color='black', width=4)) ]) return goalposts def create_goal_net(start_x, end_x, start_y, end_y, height, width): """Create a 3D mesh for the goal net.""" x = [start_x, end_x, end_x, start_x, start_x] y = [start_y, start_y, end_y, end_y, start_y] z = [height, height, height, height, height] return go.Mesh3d(x=x, y=y, z=z, opacity=0.1, color='blue') def generate_trajectory(start_point, end_point, peak_height=10, num_coords=100, trajectory_type='parabolic'): """Generate a trajectory (parabolic or linear) between start and end points.""" shot_start_x, shot_start_y, start_z = start_point hoop_x, hoop_y, end_z = end_point if trajectory_type == 'parabolic': distance_x = hoop_x - shot_start_x a = -4 * peak_height / (distance_x ** 2) shot_path_coords = [] for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)): z = a * (x - (shot_start_x + hoop_x) / 2) ** 2 + peak_height y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords) shot_path_coords.append([x, y, z]) shot_path_coords[0][2] = start_z # Ensure start z is as specified shot_path_coords[-1][2] = end_z # Ensure end z is as specified elif trajectory_type == 'linear': shot_path_coords = [] for index, x in enumerate(np.linspace(shot_start_x, hoop_x, num_coords + 1)): y = shot_start_y + (hoop_y - shot_start_y) * (index / num_coords) z = start_z + (end_z - start_z) * (index / num_coords) shot_path_coords.append([x, y, z]) return pd.DataFrame(shot_path_coords, columns=['x', 'y', 'z']) def plot_trajectories(fig, start_points, end_points, trajectory_type='parabolic', peak_height=10, num_coords=100): """Plot multiple trajectories on the field.""" for start_point, end_point in zip(start_points, end_points): trajectory_df = generate_trajectory(start_point, end_point, peak_height, num_coords, trajectory_type) fig.add_trace(go.Scatter3d( y=trajectory_df['x'], x=trajectory_df['y'], z=trajectory_df['z'], mode='lines', line=dict(color='red', width=4) )) fig.add_trace(go.Scatter3d( y=[trajectory_df['x'].iloc[0], trajectory_df['x'].iloc[-1]], x=[trajectory_df['y'].iloc[0], trajectory_df['y'].iloc[-1]], z=[trajectory_df['z'].iloc[0], trajectory_df['z'].iloc[-1]], mode='markers', marker=dict(size=3, color='red') )) def create_soccer_field_plot(): """Create a 3D soccer field plot with trajectories.""" field_df = create_field_df() fig = px.line_3d( data_frame=field_df, x='x', y='y', z='z', line_group='line_group', color='color', color_discrete_map={'field': '#FFFFFF'} ) fig.add_trace(go.Mesh3d( x=[0, WIDTH, WIDTH, 0], y=[0, 0, LENGTH, LENGTH], z=[0, 0, 0, 0], color='rgb(0, 128, 0)', opacity=0.5 )) fig.add_trace(create_center_circle()) for goalpost in create_goalposts(): fig.add_trace(goalpost) # # Add goal nets # fig.add_trace(create_goal_net((WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2), 0, -GOAL_HEIGHT, GOAL_HEIGHT, GOAL_WIDTH)) # fig.add_trace(create_goal_net((WIDTH / 2) - (GOAL_WIDTH / 2), (WIDTH / 2) + (GOAL_WIDTH / 2), LENGTH, LENGTH + GOAL_HEIGHT, GOAL_HEIGHT, GOAL_WIDTH)) max_dimension = max(WIDTH, LENGTH, GOAL_HEIGHT) fig.update_layout( scene=dict( aspectmode="manual", aspectratio=dict(x=1, y=1, z=0.125), xaxis=dict( range=[-10, max_dimension + 10], visible=False ), yaxis=dict( range=[-10, max_dimension + 10], visible=False ), zaxis=dict( range=[0, 15], visible=False ), camera=dict( eye=dict(x=0.34, y=0, z=0.45) ), ), paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)', showlegend=False, ) return fig def main_3D_pitch(start_points, end_points, trajectory_type='linear'): st.title("3D Soccer Field Trajectory Visualization") fig = create_soccer_field_plot() plot_trajectories(fig, start_points, end_points, trajectory_type=trajectory_type, peak_height=5, num_coords=100) st.plotly_chart(fig)