rj7002 commited on
Commit
9a24494
1 Parent(s): 000cc20

Create courtCoordinates.py

Browse files
Files changed (1) hide show
  1. courtCoordinates.py +438 -0
courtCoordinates.py ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ class CourtCoordinates:
5
+ '''
6
+ Stores court dimensions and calculates the (x,y,z) coordinates of the outside perimeter,
7
+ three point line, backboard, hoop, and free throw line.
8
+ The default dimensions of a men's NCAA court.
9
+ '''
10
+ def __init__(self):
11
+ self.court_length = 94 # the court is 94 feet long
12
+ self.court_width = 50 # the court is 50 feet wide
13
+ self.hoop_loc_x = 25 # the hoop is at the center of the court length-wise
14
+ self.hoop_loc_y = 4.25 # the center of the hoop is 63 inches from the baseline
15
+ self.hoop_loc_z = 10 # the hoop is 10 feet off the ground
16
+ self.hoop_radius = .75
17
+ self.three_arc_distance = 23.75 # the NCAA men's three-point arc is 22ft and 1.75in from the hoop center
18
+ self.three_straight_distance = 22 # the NCAA men's three-point straight section is 21ft 8in from the hoop center
19
+ self.three_straight_length = 8.89 # the NCAA men's three-point straight section length is 8ft and 10.75in
20
+ self.backboard_width = 6 # backboard is 6ft wide
21
+ self.backboard_height = 4 # backboard is 4ft tall
22
+ self.backboard_baseline_offset = 3 # backboard is 3ft from the baseline
23
+ self.backboard_floor_offset = 9 # backboard is 9ft from the floor
24
+ self.free_throw_line_distance = 15 # distance from the free throw line to the backboard
25
+
26
+ @staticmethod
27
+ def calculate_quadratic_values(a, b, c):
28
+ '''
29
+ Given values a, b, and c,
30
+ the function returns the output of the quadratic formula
31
+ '''
32
+ x1 = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
33
+ x2 = (-b - (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
34
+
35
+ return x1, x2
36
+
37
+ def __get_court_perimeter_coordinates(self):
38
+ '''
39
+ Returns coordinates of full court perimeter lines. A court that is 50 feet wide and 94 feet long
40
+ In shot chart data, each foot is represented by 10 units.
41
+ '''
42
+ width = self.court_width
43
+ length = self.court_length
44
+ court_perimeter_bounds = [
45
+ [0, 0, 0],
46
+ [width, 0, 0],
47
+ [width, length, 0],
48
+ [0, length, 0],
49
+ [0, 0, 0]
50
+ ]
51
+
52
+ court_df = pd.DataFrame(court_perimeter_bounds, columns=['x','y','z'])
53
+ court_df['line_group'] = 'outside_perimeter'
54
+ court_df['color'] = 'court'
55
+
56
+ return court_df
57
+
58
+ def __get_half_court_coordinates(self):
59
+ '''
60
+ Returns coordinates for the half court line.
61
+ '''
62
+ width = self.court_width
63
+ half_length = self.court_length / 2
64
+ circle_radius = 6
65
+ circle_radius2 = 2
66
+ circle_center = [width / 2, half_length, 0]
67
+ circle_points = []
68
+ circle_points2 = []
69
+ num_points = 400 # Number of points to approximate the circle
70
+ for i in range(num_points):
71
+ angle = 2 * np.pi * i / num_points
72
+ x = circle_center[0] + circle_radius * np.cos(angle)
73
+ y = circle_center[1] + circle_radius * np.sin(angle)
74
+ circle_points.append([x, y, circle_center[2]])
75
+ for i in range(num_points):
76
+ angle = 2 * np.pi * i / num_points
77
+ x = circle_center[0] + circle_radius2 * np.cos(angle)
78
+ y = circle_center[1] + circle_radius2 * np.sin(angle)
79
+ circle_points2.append([x, y, circle_center[2]])
80
+
81
+ # Example radius of the free throw circle, adjust as needed
82
+
83
+ half_court_bounds = [[0, half_length, 0], [width, half_length, 0]]
84
+
85
+ half_df = pd.DataFrame(half_court_bounds, columns=['x','y','z'])
86
+ circle_df = pd.DataFrame(circle_points, columns=['x', 'y', 'z'])
87
+ circle_df['line_group'] = f'free_throw_circle'
88
+ circle_df['color'] = 'court'
89
+
90
+ circle_df2 = pd.DataFrame(circle_points2, columns=['x', 'y', 'z'])
91
+ circle_df2['line_group'] = f'free_throw_circle'
92
+ circle_df2['color'] = 'court'
93
+
94
+ half_df['line_group'] = 'half_court'
95
+ half_df['color'] = 'court'
96
+
97
+ return pd.concat([half_df, circle_df,circle_df2])
98
+
99
+ def __get_backboard_coordinates(self, loc):
100
+ '''
101
+ Returns coordinates of the backboard on both ends of the court
102
+ A backboard is 6 feet wide, 4 feet tall
103
+ Also adds a smaller rectangle inside the backboard starting at hoop height
104
+ '''
105
+
106
+ backboard_start = (self.court_width / 2) - (self.backboard_width / 2)
107
+ backboard_end = (self.court_width / 2) + (self.backboard_width / 2)
108
+ height = self.backboard_height
109
+ floor_offset = self.backboard_floor_offset
110
+
111
+ if loc == 'far':
112
+ offset = self.backboard_baseline_offset
113
+ elif loc == 'near':
114
+ offset = self.court_length - self.backboard_baseline_offset
115
+
116
+ # Backboard coordinates
117
+ backboard_bounds = [
118
+ [backboard_start, offset, floor_offset],
119
+ [backboard_start, offset, floor_offset + height],
120
+ [backboard_end, offset, floor_offset + height],
121
+ [backboard_end, offset, floor_offset],
122
+ [backboard_start, offset, floor_offset]
123
+ ]
124
+
125
+ # Coordinates of the smaller rectangle
126
+ smaller_rect_width = 1.5 # Width of the smaller rectangle in feet
127
+ smaller_rect_height = 1 # Height of the smaller rectangle in feet
128
+ hoop_height = self.hoop_loc_z # Height of the hoop
129
+
130
+ # Smaller rectangle coordinates
131
+ smaller_rect_start_x = backboard_start + (self.backboard_width / 2) - (smaller_rect_width / 2)
132
+ smaller_rect_end_x = backboard_start + (self.backboard_width / 2) + (smaller_rect_width / 2)
133
+ smaller_rect_y = offset # Y coordinate same as the backboard
134
+
135
+ smaller_rect_bounds = [
136
+ [smaller_rect_start_x, offset, hoop_height],
137
+ [smaller_rect_start_x, offset, hoop_height + smaller_rect_height],
138
+ [smaller_rect_end_x, offset, hoop_height + smaller_rect_height],
139
+ [smaller_rect_end_x, offset, hoop_height],
140
+ [smaller_rect_start_x, offset, hoop_height]
141
+ ]
142
+
143
+ # Combine coordinates into DataFrames
144
+ backboard_df = pd.DataFrame(backboard_bounds, columns=['x', 'y', 'z'])
145
+ backboard_df['line_group'] = f'{loc}_backboard'
146
+ backboard_df['color'] = 'backboard'
147
+
148
+ smaller_rect_df = pd.DataFrame(smaller_rect_bounds, columns=['x', 'y', 'z'])
149
+ smaller_rect_df['line_group'] = f'{loc}_smaller_rectangle'
150
+ smaller_rect_df['color'] = 'backboard' # Set color for smaller rectangle
151
+
152
+ return pd.concat([backboard_df, smaller_rect_df])
153
+
154
+
155
+ def __get_three_point_coordinates(self, loc):
156
+ '''
157
+ Returns coordinates of the three point line on both ends of the court
158
+ Given that the ncaa men's three point line is 22ft and 1.5in from the center of the hoop
159
+ '''
160
+
161
+ # init values
162
+ hoop_loc_x, hoop_loc_y = self.hoop_loc_x, self.hoop_loc_y
163
+ strt_dst_start = (self.court_width/2) - self.three_straight_distance
164
+ strt_dst_end = (self.court_width/2) + self.three_straight_distance
165
+ strt_len = self.three_straight_length
166
+ arc_dst = self.three_arc_distance
167
+
168
+ start_straight = [
169
+ [strt_dst_start,0,0],
170
+ [strt_dst_start,strt_len,0]
171
+ ]
172
+ end_straight = [
173
+ [strt_dst_end,strt_len,0],
174
+ [strt_dst_end,0,0]
175
+ ]
176
+ line_coordinates = []
177
+
178
+ if loc == 'near':
179
+ crt_len = self.court_length
180
+ hoop_loc_y = crt_len - hoop_loc_y
181
+ start_straight = [[strt_dst_start,crt_len,0],[strt_dst_start,crt_len-strt_len,0]]
182
+ end_straight = [[strt_dst_end,crt_len-strt_len,0], [strt_dst_end,crt_len,0]]
183
+
184
+ # drawing the three point line
185
+ line_coordinates.extend(start_straight)
186
+
187
+ a = 1
188
+ b = -2 * hoop_loc_y
189
+ d = arc_dst
190
+ for x_coord in np.linspace(int(strt_dst_start), int(strt_dst_end), 100):
191
+ c = hoop_loc_y ** 2 + (x_coord - 25) ** 2 - (d) ** 2
192
+
193
+ y1, y2 = self.calculate_quadratic_values(a, b, c)
194
+ if loc == 'far':
195
+ y_coord = y1
196
+ if loc == 'near':
197
+ y_coord = y2
198
+
199
+ line_coordinates.append([x_coord, y_coord, 0])
200
+
201
+ line_coordinates.extend(end_straight)
202
+
203
+ far_three_df = pd.DataFrame(line_coordinates, columns=['x', 'y', 'z'])
204
+ far_three_df['line_group'] = f'{loc}_three'
205
+ far_three_df['color'] = 'court'
206
+
207
+ return far_three_df
208
+
209
+ def __get_hoop_coordinates(self, loc):
210
+ '''
211
+ Returns the hoop coordinates of the far/near hoop
212
+ '''
213
+ hoop_coordinates_top_half = []
214
+ hoop_coordinates_bottom_half = []
215
+
216
+ hoop_loc_x, hoop_loc_y, hoop_loc_z = (self.hoop_loc_x, self.hoop_loc_y, self.hoop_loc_z)
217
+
218
+ if loc == 'near':
219
+ hoop_loc_y = self.court_length - hoop_loc_y
220
+
221
+ hoop_radius = self.hoop_radius
222
+ hoop_min_x, hoop_max_x = (hoop_loc_x - hoop_radius, hoop_loc_x + hoop_radius)
223
+ hoop_step = 0.1
224
+
225
+ a = 1
226
+ b = -2 * hoop_loc_y
227
+ for hoop_coord_x in np.arange(hoop_min_x, hoop_max_x + hoop_step/2, hoop_step):
228
+ c = hoop_loc_y ** 2 + (hoop_loc_x - round(hoop_coord_x,2)) ** 2 - hoop_radius ** 2
229
+ hoop_coord_y1, hoop_coord_y2 = self.calculate_quadratic_values(a, b, c)
230
+
231
+ hoop_coordinates_top_half.append([hoop_coord_x, hoop_coord_y1, hoop_loc_z])
232
+ hoop_coordinates_bottom_half.append([hoop_coord_x, hoop_coord_y2, hoop_loc_z])
233
+
234
+ hoop_coordinates_df = pd.DataFrame(hoop_coordinates_top_half + hoop_coordinates_bottom_half[::-1], columns=['x','y','z'])
235
+ hoop_coordinates_df['line_group'] = f'{loc}_hoop'
236
+ hoop_coordinates_df['color'] = 'hoop'
237
+
238
+ return hoop_coordinates_df
239
+ def __get_hoop_coordinates2(self, loc):
240
+ num_net_lines = 10 # Number of vertical lines in the net
241
+ net_length = 1.75 # Length of the net hanging down from the hoop (in feet)
242
+ initial_radius = self.hoop_radius # Radius at the top of the net
243
+
244
+ hoop_net_coordinates = []
245
+ hoop_loc_x, hoop_loc_y, hoop_loc_z = self.hoop_loc_x, self.hoop_loc_y, self.hoop_loc_z
246
+ if loc == 'near':
247
+ hoop_loc_y = self.court_length - hoop_loc_y
248
+
249
+
250
+ for i in range(num_net_lines):
251
+ angle = (i * 2 * np.pi) / num_net_lines
252
+
253
+ for j in np.linspace(0, net_length, num=10):
254
+ # Decrease the radius from the initial radius to half of it at the bottom
255
+ current_radius = initial_radius * (1 - (j / net_length) * 0.5)
256
+
257
+ x = hoop_loc_x + current_radius * np.cos(angle)
258
+ y = hoop_loc_y + current_radius * np.sin(angle)
259
+ z = hoop_loc_z - j
260
+
261
+ hoop_net_coordinates.append([x, y, z])
262
+
263
+ # Add lines on the other side (negative angles)
264
+ for i in range(num_net_lines):
265
+ angle = (i * 2 * np.pi) / num_net_lines + np.pi # Shift angles to cover the opposite side
266
+
267
+ for j in np.linspace(0, net_length, num=10):
268
+ current_radius = initial_radius * (1 - (j / net_length) * 0.5)
269
+
270
+ x = hoop_loc_x + current_radius * np.cos(angle)
271
+ y = hoop_loc_y + current_radius * np.sin(angle)
272
+ z = hoop_loc_z - j
273
+
274
+ hoop_net_coordinates.append([x, y, z])
275
+
276
+ hoop_net_df = pd.DataFrame(hoop_net_coordinates, columns=['x', 'y', 'z'])
277
+ hoop_net_df['line_group'] = f'{loc}hoop_net'
278
+ hoop_net_df['color'] = 'net' # Set color to Light Gray or any other color you prefer
279
+
280
+ return hoop_net_df
281
+
282
+
283
+
284
+
285
+ @staticmethod
286
+ def calculate_quadratic_values(a, b, c):
287
+ '''
288
+ Given values a, b, and c,
289
+ the function returns the output of the quadratic formula
290
+ '''
291
+ x1 = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
292
+ x2 = (-b - (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
293
+
294
+ return x1, x2
295
+
296
+ def __get_free_throw_line_and_circle_coordinates(self, loc):
297
+ '''
298
+ Returns coordinates of the free throw line, circle at the center of the free throw line,
299
+ and lines extending from the free throw line to the baseline.
300
+ Also adds two parallel lines, each 2 feet away from the existing lines,
301
+ and a semicircle with a 4 ft radius starting at 3 ft or 91 ft from the baseline.
302
+ The free throw line is 15 feet from the backboard and spans from sideline to sideline.
303
+ The circle is centered on the free throw line and cuts the line in half.
304
+ '''
305
+ distance = 18
306
+ width = self.court_width
307
+ length = self.court_length
308
+ circle_radius = 6 # Radius of the free throw circle
309
+ semicircle_radius = 4 # Radius of the semicircle
310
+ offset = 2 # Offset distance for the additional lines
311
+ semicircle_start_distance_near = 3 # Starting distance from baseline for 'near'
312
+ semicircle_start_distance_far = 91 # Starting distance from baseline for 'far'
313
+ semicircle_start = semicircle_start_distance_near
314
+ semicircle_start2 = semicircle_start_distance_far
315
+ # Coordinates for the free throw line and the circle
316
+ if loc == 'far':
317
+ line_start = [17, length - distance, 0]
318
+ line_end = [width - 17, length - distance, 0]
319
+ circle_center = [width / 2, length - distance, 0]
320
+ baseline_y = length # Baseline is at the end of the court
321
+ left_offset = -offset
322
+ right_offset = offset
323
+ else:
324
+ line_start = [17, distance, 0]
325
+ line_end = [width - 17, distance, 0]
326
+ circle_center = [width / 2, distance, 0]
327
+ baseline_y = 0 # Baseline is at the start of the court
328
+ left_offset = -offset
329
+ right_offset = offset
330
+
331
+ # Generate circle coordinates
332
+ circle_points = []
333
+ num_points = 400 # Number of points to approximate the circle
334
+ for i in range(num_points):
335
+ angle = 2 * np.pi * i / num_points
336
+ x = circle_center[0] + circle_radius * np.cos(angle)
337
+ y = circle_center[1] + circle_radius * np.sin(angle)
338
+ circle_points.append([x, y, circle_center[2]])
339
+
340
+
341
+ # Generate semicircle coordinates
342
+ semicircle_points = []
343
+ num_points = 100 # Number of points to approximate the semicircle
344
+ for i in range(num_points + 1):
345
+ angle = np.pi * i / num_points
346
+ x = circle_center[0] + 4 * np.cos(angle)
347
+ y = semicircle_start + 5 * np.sin(angle)
348
+ semicircle_points.append([x, y, circle_center[2]])
349
+ semicircle_points2 = []
350
+ for i in range(num_points + 1):
351
+ angle = np.pi * i / num_points
352
+ x = circle_center[0] + 4 * np.cos(angle)
353
+ y = semicircle_start2 - 5 * np.sin(angle)
354
+ semicircle_points2.append([x, y, circle_center[2]])
355
+
356
+ # Coordinates for the straight lines extending to the baseline
357
+ left_line_start = [19, length - distance, 0] if loc == 'far' else [19, distance, 0]
358
+ left_line_end = [19, baseline_y, 0]
359
+ right_line_start = [width - 19, length - distance, 0] if loc == 'far' else [width - 19, distance, 0]
360
+ right_line_end = [width - 19, baseline_y, 0]
361
+
362
+ # Additional lines
363
+ left_offset_line_start = [19 + left_offset, length - distance, 0] if loc == 'far' else [19 + left_offset, distance, 0]
364
+ left_offset_line_end = [19 + left_offset, baseline_y, 0]
365
+ right_offset_line_start = [width - 19 + right_offset, length - distance, 0] if loc == 'far' else [width - 19 + right_offset, distance, 0]
366
+ right_offset_line_end = [width - 19 + right_offset, baseline_y, 0]
367
+
368
+ # Combine coordinates into DataFrames
369
+ free_throw_line_bounds = [line_start, line_end]
370
+ free_throw_line_df = pd.DataFrame(free_throw_line_bounds, columns=['x', 'y', 'z'])
371
+ free_throw_line_df['line_group'] = f'{loc}_free_throw_line'
372
+ free_throw_line_df['color'] = 'court'
373
+
374
+ circle_df = pd.DataFrame(circle_points, columns=['x', 'y', 'z'])
375
+ circle_df['line_group'] = f'{loc}_free_throw_circle'
376
+ circle_df['color'] = 'court'
377
+
378
+ semicircle_df = pd.DataFrame(semicircle_points, columns=['x', 'y', 'z'])
379
+ semicircle_df['line_group'] = f'free_throw_semicircle'
380
+ semicircle_df['color'] = 'court'
381
+
382
+ semicircle_df2 = pd.DataFrame(semicircle_points2, columns=['x', 'y', 'z'])
383
+ semicircle_df2['line_group'] = f'free_throw_semicircle2'
384
+ semicircle_df2['color'] = 'court'
385
+
386
+ left_line_df = pd.DataFrame([left_line_start, left_line_end], columns=['x', 'y', 'z'])
387
+ left_line_df['line_group'] = f'{loc}_free_throw_left_line'
388
+ left_line_df['color'] = 'court'
389
+
390
+ right_line_df = pd.DataFrame([right_line_start, right_line_end], columns=['x', 'y', 'z'])
391
+ right_line_df['line_group'] = f'{loc}_free_throw_right_line'
392
+ right_line_df['color'] = 'court'
393
+
394
+ # Additional offset lines
395
+ left_offset_line_df = pd.DataFrame([left_offset_line_start, left_offset_line_end], columns=['x', 'y', 'z'])
396
+ left_offset_line_df['line_group'] = f'{loc}_free_throw_left_offset_line'
397
+ left_offset_line_df['color'] = 'court'
398
+
399
+ right_offset_line_df = pd.DataFrame([right_offset_line_start, right_offset_line_end], columns=['x', 'y', 'z'])
400
+ right_offset_line_df['line_group'] = f'{loc}_free_throw_right_offset_line'
401
+ right_offset_line_df['color'] = 'court'
402
+
403
+ # Return combined DataFrame
404
+ return pd.concat([
405
+ free_throw_line_df,
406
+ circle_df,
407
+ semicircle_df2,
408
+ semicircle_df,
409
+ left_line_df,
410
+ right_line_df,
411
+ left_offset_line_df,
412
+ right_offset_line_df
413
+ ])
414
+
415
+
416
+
417
+
418
+
419
+ def get_court_lines(self):
420
+ '''
421
+ Returns a concatenated DataFrame of all the court coordinates including the new free throw line.
422
+ '''
423
+ court_df = self.__get_court_perimeter_coordinates()
424
+ half_df = self.__get_half_court_coordinates()
425
+ backboard_home = self.__get_backboard_coordinates('near')
426
+ backboard_away = self.__get_backboard_coordinates('far')
427
+ hoop_away = self.__get_hoop_coordinates('near')
428
+ hoop_home = self.__get_hoop_coordinates('far')
429
+ net_away = self.__get_hoop_coordinates2('near')
430
+ net_home = self.__get_hoop_coordinates2('far')
431
+ three_home = self.__get_three_point_coordinates('near')
432
+ three_away = self.__get_three_point_coordinates('far')
433
+ free_throw_line = self.__get_free_throw_line_and_circle_coordinates('near')
434
+ free_throw_line2 = self.__get_free_throw_line_and_circle_coordinates('far') # Get free throw line coordinates
435
+
436
+ court_lines_df = pd.concat([court_df, half_df, backboard_home, backboard_away, hoop_away, hoop_home, three_home, three_away, free_throw_line,free_throw_line2,net_away,net_home])
437
+
438
+ return court_lines_df