Soccer-Data / Pitch3D.py
2nzi's picture
start
1f7470c verified
# 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)