File size: 19,342 Bytes
9a24494
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
import pandas as pd 
import numpy as np 

class CourtCoordinates:
    '''
    Stores court dimensions and calculates the (x,y,z) coordinates of the outside perimeter, 
    three point line, backboard, hoop, and free throw line.
    The default dimensions of a men's NCAA court.
    '''
    def __init__(self):
        self.court_length = 94                 # the court is 94 feet long
        self.court_width = 50                  # the court is 50 feet wide
        self.hoop_loc_x = 25                   # the hoop is at the center of the court length-wise
        self.hoop_loc_y = 4.25                 # the center of the hoop is 63 inches from the baseline
        self.hoop_loc_z = 10                   # the hoop is 10 feet off the ground
        self.hoop_radius = .75
        self.three_arc_distance = 23.75       # the NCAA men's three-point arc is 22ft and 1.75in from the hoop center
        self.three_straight_distance = 22      # the NCAA men's three-point straight section is 21ft 8in from the hoop center
        self.three_straight_length = 8.89      # the NCAA men's three-point straight section length is 8ft and 10.75in
        self.backboard_width = 6               # backboard is 6ft wide
        self.backboard_height = 4              # backboard is 4ft tall
        self.backboard_baseline_offset = 3     # backboard is 3ft from the baseline
        self.backboard_floor_offset = 9        # backboard is 9ft from the floor
        self.free_throw_line_distance = 15     # distance from the free throw line to the backboard

    @staticmethod
    def calculate_quadratic_values(a, b, c):
        '''
        Given values a, b, and c,
        the function returns the output of the quadratic formula
        '''
        x1 = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
        x2 = (-b - (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)

        return x1, x2

    def __get_court_perimeter_coordinates(self):
        '''
        Returns coordinates of full court perimeter lines. A court that is 50 feet wide and 94 feet long
        In shot chart data, each foot is represented by 10 units.
        '''
        width = self.court_width
        length = self.court_length
        court_perimeter_bounds = [
            [0, 0, 0], 
            [width, 0, 0], 
            [width, length, 0], 
            [0, length, 0], 
            [0, 0, 0]
        ]

        court_df = pd.DataFrame(court_perimeter_bounds, columns=['x','y','z'])
        court_df['line_group'] = 'outside_perimeter'
        court_df['color'] = 'court'
        
        return court_df
    
    def __get_half_court_coordinates(self):
        '''
        Returns coordinates for the half court line.
        '''
        width = self.court_width 
        half_length = self.court_length / 2
        circle_radius = 6
        circle_radius2 = 2
        circle_center = [width / 2, half_length, 0]
        circle_points = []
        circle_points2 = []
        num_points = 400  # Number of points to approximate the circle
        for i in range(num_points):
            angle = 2 * np.pi * i / num_points
            x = circle_center[0] + circle_radius * np.cos(angle)
            y = circle_center[1] + circle_radius * np.sin(angle)
            circle_points.append([x, y, circle_center[2]])
        for i in range(num_points):
            angle = 2 * np.pi * i / num_points
            x = circle_center[0] + circle_radius2 * np.cos(angle)
            y = circle_center[1] + circle_radius2 * np.sin(angle)
            circle_points2.append([x, y, circle_center[2]])

  # Example radius of the free throw circle, adjust as needed

        half_court_bounds = [[0, half_length, 0], [width, half_length, 0]]

        half_df = pd.DataFrame(half_court_bounds, columns=['x','y','z'])
        circle_df = pd.DataFrame(circle_points, columns=['x', 'y', 'z'])
        circle_df['line_group'] = f'free_throw_circle'
        circle_df['color'] = 'court'

        circle_df2 = pd.DataFrame(circle_points2, columns=['x', 'y', 'z'])
        circle_df2['line_group'] = f'free_throw_circle'
        circle_df2['color'] = 'court'

        half_df['line_group'] = 'half_court'
        half_df['color'] = 'court'

        return pd.concat([half_df, circle_df,circle_df2])

    def __get_backboard_coordinates(self, loc):
        '''
        Returns coordinates of the backboard on both ends of the court
        A backboard is 6 feet wide, 4 feet tall 
        Also adds a smaller rectangle inside the backboard starting at hoop height
        '''

        backboard_start = (self.court_width / 2) - (self.backboard_width / 2)
        backboard_end = (self.court_width / 2) + (self.backboard_width / 2)
        height = self.backboard_height
        floor_offset = self.backboard_floor_offset

        if loc == 'far':
            offset = self.backboard_baseline_offset
        elif loc == 'near':
            offset = self.court_length - self.backboard_baseline_offset

        # Backboard coordinates
        backboard_bounds = [
            [backboard_start, offset, floor_offset], 
            [backboard_start, offset, floor_offset + height], 
            [backboard_end, offset, floor_offset + height], 
            [backboard_end, offset, floor_offset], 
            [backboard_start, offset, floor_offset]
        ]

        # Coordinates of the smaller rectangle
        smaller_rect_width = 1.5  # Width of the smaller rectangle in feet
        smaller_rect_height = 1  # Height of the smaller rectangle in feet
        hoop_height = self.hoop_loc_z  # Height of the hoop

        # Smaller rectangle coordinates
        smaller_rect_start_x = backboard_start + (self.backboard_width / 2) - (smaller_rect_width / 2)
        smaller_rect_end_x = backboard_start + (self.backboard_width / 2) + (smaller_rect_width / 2)
        smaller_rect_y = offset  # Y coordinate same as the backboard

        smaller_rect_bounds = [
            [smaller_rect_start_x, offset, hoop_height], 
            [smaller_rect_start_x, offset, hoop_height + smaller_rect_height], 
            [smaller_rect_end_x, offset, hoop_height + smaller_rect_height], 
            [smaller_rect_end_x, offset, hoop_height], 
            [smaller_rect_start_x, offset, hoop_height]
        ]

        # Combine coordinates into DataFrames
        backboard_df = pd.DataFrame(backboard_bounds, columns=['x', 'y', 'z'])
        backboard_df['line_group'] = f'{loc}_backboard'
        backboard_df['color'] = 'backboard'

        smaller_rect_df = pd.DataFrame(smaller_rect_bounds, columns=['x', 'y', 'z'])
        smaller_rect_df['line_group'] = f'{loc}_smaller_rectangle'
        smaller_rect_df['color'] = 'backboard'  # Set color for smaller rectangle

        return pd.concat([backboard_df, smaller_rect_df])

    
    def __get_three_point_coordinates(self, loc):
        '''
        Returns coordinates of the three point line on both ends of the court
        Given that the ncaa men's three point line is 22ft and 1.5in from the center of the hoop
        '''
        
        # init values
        hoop_loc_x, hoop_loc_y = self.hoop_loc_x, self.hoop_loc_y
        strt_dst_start = (self.court_width/2) - self.three_straight_distance
        strt_dst_end = (self.court_width/2) + self.three_straight_distance
        strt_len = self.three_straight_length
        arc_dst = self.three_arc_distance

        start_straight = [
            [strt_dst_start,0,0],
            [strt_dst_start,strt_len,0]
        ]
        end_straight = [
            [strt_dst_end,strt_len,0], 
            [strt_dst_end,0,0]
        ]
        line_coordinates = []

        if loc == 'near': 
            crt_len = self.court_length
            hoop_loc_y = crt_len - hoop_loc_y
            start_straight = [[strt_dst_start,crt_len,0],[strt_dst_start,crt_len-strt_len,0]]
            end_straight = [[strt_dst_end,crt_len-strt_len,0], [strt_dst_end,crt_len,0]]

        # drawing the three point line
        line_coordinates.extend(start_straight)
        
        a = 1
        b = -2 * hoop_loc_y
        d = arc_dst 
        for x_coord in np.linspace(int(strt_dst_start), int(strt_dst_end), 100):
            c = hoop_loc_y ** 2 + (x_coord - 25) ** 2 - (d) ** 2

            y1, y2 = self.calculate_quadratic_values(a, b, c)
            if loc == 'far':
                y_coord = y1
            if loc == 'near':
                y_coord = y2
        
            line_coordinates.append([x_coord, y_coord, 0])

        line_coordinates.extend(end_straight)

        far_three_df = pd.DataFrame(line_coordinates, columns=['x', 'y', 'z'])
        far_three_df['line_group'] = f'{loc}_three'
        far_three_df['color'] = 'court'

        return far_three_df

    def __get_hoop_coordinates(self, loc):
        '''
        Returns the hoop coordinates of the far/near hoop
        '''
        hoop_coordinates_top_half = []
        hoop_coordinates_bottom_half = []

        hoop_loc_x, hoop_loc_y, hoop_loc_z = (self.hoop_loc_x, self.hoop_loc_y, self.hoop_loc_z)

        if loc == 'near': 
            hoop_loc_y = self.court_length - hoop_loc_y

        hoop_radius = self.hoop_radius
        hoop_min_x, hoop_max_x = (hoop_loc_x - hoop_radius, hoop_loc_x + hoop_radius)
        hoop_step = 0.1

        a = 1
        b = -2 * hoop_loc_y
        for hoop_coord_x in np.arange(hoop_min_x, hoop_max_x + hoop_step/2, hoop_step):
            c = hoop_loc_y ** 2 + (hoop_loc_x - round(hoop_coord_x,2)) ** 2 - hoop_radius ** 2
            hoop_coord_y1, hoop_coord_y2 = self.calculate_quadratic_values(a, b, c)

            hoop_coordinates_top_half.append([hoop_coord_x, hoop_coord_y1, hoop_loc_z])
            hoop_coordinates_bottom_half.append([hoop_coord_x, hoop_coord_y2, hoop_loc_z])

        hoop_coordinates_df = pd.DataFrame(hoop_coordinates_top_half + hoop_coordinates_bottom_half[::-1], columns=['x','y','z'])
        hoop_coordinates_df['line_group'] = f'{loc}_hoop'
        hoop_coordinates_df['color'] = 'hoop'
        
        return hoop_coordinates_df
    def __get_hoop_coordinates2(self, loc):
        num_net_lines = 10  # Number of vertical lines in the net
        net_length = 1.75  # Length of the net hanging down from the hoop (in feet)
        initial_radius = self.hoop_radius  # Radius at the top of the net

        hoop_net_coordinates = []
        hoop_loc_x, hoop_loc_y, hoop_loc_z = self.hoop_loc_x, self.hoop_loc_y, self.hoop_loc_z
        if loc == 'near': 
            hoop_loc_y = self.court_length - hoop_loc_y


        for i in range(num_net_lines):
            angle = (i * 2 * np.pi) / num_net_lines
            
            for j in np.linspace(0, net_length, num=10):
                # Decrease the radius from the initial radius to half of it at the bottom
                current_radius = initial_radius * (1 - (j / net_length) * 0.5)
                
                x = hoop_loc_x + current_radius * np.cos(angle)
                y = hoop_loc_y + current_radius * np.sin(angle)
                z = hoop_loc_z - j
                
                hoop_net_coordinates.append([x, y, z])
        
        # Add lines on the other side (negative angles)
        for i in range(num_net_lines):
            angle = (i * 2 * np.pi) / num_net_lines + np.pi  # Shift angles to cover the opposite side
            
            for j in np.linspace(0, net_length, num=10):
                current_radius = initial_radius * (1 - (j / net_length) * 0.5)
                
                x = hoop_loc_x + current_radius * np.cos(angle)
                y = hoop_loc_y + current_radius * np.sin(angle)
                z = hoop_loc_z - j
                
                hoop_net_coordinates.append([x, y, z])
        
        hoop_net_df = pd.DataFrame(hoop_net_coordinates, columns=['x', 'y', 'z'])
        hoop_net_df['line_group'] = f'{loc}hoop_net'
        hoop_net_df['color'] = 'net'  # Set color to Light Gray or any other color you prefer

        return hoop_net_df




    @staticmethod
    def calculate_quadratic_values(a, b, c):
        '''
        Given values a, b, and c,
        the function returns the output of the quadratic formula
        '''
        x1 = (-b + (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)
        x2 = (-b - (b ** 2 - 4 * a * c) ** 0.5) / (2 * a)

        return x1, x2

    def __get_free_throw_line_and_circle_coordinates(self, loc):
        '''
        Returns coordinates of the free throw line, circle at the center of the free throw line,
        and lines extending from the free throw line to the baseline. 
        Also adds two parallel lines, each 2 feet away from the existing lines,
        and a semicircle with a 4 ft radius starting at 3 ft or 91 ft from the baseline.
        The free throw line is 15 feet from the backboard and spans from sideline to sideline.
        The circle is centered on the free throw line and cuts the line in half.
        '''
        distance = 18
        width = self.court_width
        length = self.court_length
        circle_radius = 6  # Radius of the free throw circle
        semicircle_radius = 4  # Radius of the semicircle
        offset = 2  # Offset distance for the additional lines
        semicircle_start_distance_near = 3  # Starting distance from baseline for 'near'
        semicircle_start_distance_far = 91  # Starting distance from baseline for 'far'
        semicircle_start = semicircle_start_distance_near
        semicircle_start2 = semicircle_start_distance_far
        # Coordinates for the free throw line and the circle
        if loc == 'far':
            line_start = [17, length - distance, 0]
            line_end = [width - 17, length - distance, 0]
            circle_center = [width / 2, length - distance, 0]
            baseline_y = length  # Baseline is at the end of the court
            left_offset = -offset
            right_offset = offset
        else:
            line_start = [17, distance, 0]
            line_end = [width - 17, distance, 0]
            circle_center = [width / 2, distance, 0]
            baseline_y = 0  # Baseline is at the start of the court
            left_offset = -offset
            right_offset = offset

        # Generate circle coordinates
        circle_points = []
        num_points = 400  # Number of points to approximate the circle
        for i in range(num_points):
            angle = 2 * np.pi * i / num_points
            x = circle_center[0] + circle_radius * np.cos(angle)
            y = circle_center[1] + circle_radius * np.sin(angle)
            circle_points.append([x, y, circle_center[2]])
        

        # Generate semicircle coordinates
        semicircle_points = []
        num_points = 100  # Number of points to approximate the semicircle
        for i in range(num_points + 1):
            angle = np.pi * i / num_points
            x = circle_center[0] + 4 * np.cos(angle)
            y = semicircle_start + 5 * np.sin(angle)
            semicircle_points.append([x, y, circle_center[2]])
        semicircle_points2 = []
        for i in range(num_points + 1):
            angle = np.pi * i / num_points
            x = circle_center[0] + 4 * np.cos(angle)
            y = semicircle_start2 - 5 * np.sin(angle)
            semicircle_points2.append([x, y, circle_center[2]])

        # Coordinates for the straight lines extending to the baseline
        left_line_start = [19, length - distance, 0] if loc == 'far' else [19, distance, 0]
        left_line_end = [19, baseline_y, 0]
        right_line_start = [width - 19, length - distance, 0] if loc == 'far' else [width - 19, distance, 0]
        right_line_end = [width - 19, baseline_y, 0]

        # Additional lines
        left_offset_line_start = [19 + left_offset, length - distance, 0] if loc == 'far' else [19 + left_offset, distance, 0]
        left_offset_line_end = [19 + left_offset, baseline_y, 0]
        right_offset_line_start = [width - 19 + right_offset, length - distance, 0] if loc == 'far' else [width - 19 + right_offset, distance, 0]
        right_offset_line_end = [width - 19 + right_offset, baseline_y, 0]

        # Combine coordinates into DataFrames
        free_throw_line_bounds = [line_start, line_end]
        free_throw_line_df = pd.DataFrame(free_throw_line_bounds, columns=['x', 'y', 'z'])
        free_throw_line_df['line_group'] = f'{loc}_free_throw_line'
        free_throw_line_df['color'] = 'court'

        circle_df = pd.DataFrame(circle_points, columns=['x', 'y', 'z'])
        circle_df['line_group'] = f'{loc}_free_throw_circle'
        circle_df['color'] = 'court'

        semicircle_df = pd.DataFrame(semicircle_points, columns=['x', 'y', 'z'])
        semicircle_df['line_group'] = f'free_throw_semicircle'
        semicircle_df['color'] = 'court'

        semicircle_df2 = pd.DataFrame(semicircle_points2, columns=['x', 'y', 'z'])
        semicircle_df2['line_group'] = f'free_throw_semicircle2'
        semicircle_df2['color'] = 'court'

        left_line_df = pd.DataFrame([left_line_start, left_line_end], columns=['x', 'y', 'z'])
        left_line_df['line_group'] = f'{loc}_free_throw_left_line'
        left_line_df['color'] = 'court'

        right_line_df = pd.DataFrame([right_line_start, right_line_end], columns=['x', 'y', 'z'])
        right_line_df['line_group'] = f'{loc}_free_throw_right_line'
        right_line_df['color'] = 'court'

        # Additional offset lines
        left_offset_line_df = pd.DataFrame([left_offset_line_start, left_offset_line_end], columns=['x', 'y', 'z'])
        left_offset_line_df['line_group'] = f'{loc}_free_throw_left_offset_line'
        left_offset_line_df['color'] = 'court'

        right_offset_line_df = pd.DataFrame([right_offset_line_start, right_offset_line_end], columns=['x', 'y', 'z'])
        right_offset_line_df['line_group'] = f'{loc}_free_throw_right_offset_line'
        right_offset_line_df['color'] = 'court'

        # Return combined DataFrame
        return pd.concat([
            free_throw_line_df, 
            circle_df, 
            semicircle_df2,
            semicircle_df, 
            left_line_df, 
            right_line_df, 
            left_offset_line_df, 
            right_offset_line_df
        ])



    

    def get_court_lines(self):
        '''
        Returns a concatenated DataFrame of all the court coordinates including the new free throw line.
        '''
        court_df = self.__get_court_perimeter_coordinates()
        half_df = self.__get_half_court_coordinates()
        backboard_home = self.__get_backboard_coordinates('near')
        backboard_away = self.__get_backboard_coordinates('far')
        hoop_away = self.__get_hoop_coordinates('near')
        hoop_home = self.__get_hoop_coordinates('far')
        net_away = self.__get_hoop_coordinates2('near')
        net_home = self.__get_hoop_coordinates2('far')
        three_home = self.__get_three_point_coordinates('near')
        three_away = self.__get_three_point_coordinates('far')
        free_throw_line = self.__get_free_throw_line_and_circle_coordinates('near')
        free_throw_line2 = self.__get_free_throw_line_and_circle_coordinates('far')  # Get free throw line coordinates

        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])

        return court_lines_df