# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: py37_football
#     language: python
#     name: py37_football
# ---

# # **Tracking Data**
#
# ### Three matches from Swedish first division from 2019 season
#
# 1. 2019/07/22: Hammarby Vs IFElfsborg (5-2)
# > Khalili brace, Kacaniklic
# 2. 2019/10/20: Hammarby Vs MalmöFF (2-0)
# > Kacaniklic
# 3. 2019/09/30: Hammarby Vs Örebrö (5-1)
# > <NAME>
#
# Things to think about:
# * Set pieces will cause issues;
# * There's an entity resolution problem - I wonder whether you could computationall adjust  for this;
# * Sanity check: is a player faster than Usain Bolt?
# * Could maybe do some kind of replacement metric?
#
# **The assignment**
#
# The data analyst at you club has just received tracking data for three matches of her club (Hammarby, playing in Sweden). She wants to know what she can reliably infer from this data. She first asks you to calculate speed and direction of all players in the data using the techniques outlined in Laurie's videos. So, for any given frame you can plot the direction and speed of the players.  Give examples of 4 such frames from the matches. (3 points)
#
# Write a program which plots distance from goal, speed and acceleration of all Hammarby players as a short time series, i.e plot of these variables over time. The analyst is aware there are limitations and errors in the data. Provide her with a visualisation of the three variables for a few different examples of a 15 second sequence of play. At least one of these should show where the tracking data is relatively accurate and one should show a case whee there is an error. Write a short report using these figures on the possibilities and limitations of the data. (2 points)
#
# Now the analyst now asks you to analyse Hammarby's goals. She would like you to look at distance to nearest opposition player for each of the Hammarby players during the possession (you will have to find the start of possession time frame by hand) leading up to an open play goal.  Measure the distance to the nearest defending player and nearest teammate during  this buildup.  Use this to analyse one Hammarby player in particular. (3 points)
#
# Implement a further metric of you own choice that you think will help understand how Hammarby score goals. Use this to analyse your chosen Hammarby player.  (2 points)
#
# ---
#
# Output:
#
# 1. a non-technical description of how your method for measuring distance, speed, acceleration and relative positions of other players works.
# the examples of frames with speed and direction and time series of these variables for short sequences.
# an explanation of the strengths and weaknesses of the data as asked for by the data analyst.
# an analysis of the attacking runs of one of the Hammarby players in particular.
# 2. A runnable, commented code  as a (preferably) Python or R script that generates all the plots from the report and explains the method you have used. Important: this code should be a single file run immediately if placed with in the same directory as the Signality folder.

# ## **Data Preparation Approach**
#
# * Let's take advantage of the excellent work done already by <NAME>
# * Laurie has written functions to calculate various metrics on the Metrica dataset, which comes in a different format to the Signality data
# * Seems like a good idea to load the Signality data into a similar format to the Metrica dataset

# +
import pprint as pp
import pandas as pd
import numpy as np
import datetime

# imports required by data prep functions
import json
import math
import pickle
from copy import deepcopy

# Laurie's libraries
import scipy.signal as signal
import matplotlib.animation as animation

# removing annoying matplotlib warnings
import warnings
warnings.filterwarnings("ignore", category=UserWarning)

import re
import os
from collections import Counter
import pprint as pp

# plotting
import matplotlib.pyplot as plt

pd.options.display.max_rows = 500
pd.options.display.max_columns = 150

signalityRepo = r'2019/Tracking Data/'
movieRepo = r'Movies/'
# -

# ### **Loading the events data**

# +
df_events = pd.read_csv('df_subevents_Swedish_1920.csv')

df_events['eventIndex'] = df_events.sort_values(['matchId','timeStamp']).groupby('matchId').cumcount() + 1


# -

# # **1)** Data Preparation Functions

def initialise_dic_tracks(df_homePlayers, df_awayPlayers):
    """
    Initialises dictionaries for both home and away player locations
    """

    dic_home_tracks = {}
    dic_away_tracks = {}

    for homePlayer in df_homePlayers.playerIndex:
        for xy in ['x','y']:
            dic_home_tracks[f'Home_{homePlayer}_{xy}'] = []

    for awayPlayer in df_awayPlayers.playerIndex:
        for xy in ['x','y']:
            dic_away_tracks[f'Away_{awayPlayer}_{xy}'] = []
            
    return dic_home_tracks, dic_away_tracks


def populate_df_tracks(homeAway, homeAway_tracks, playersJerseyMapping, dic_tracks, df_players):
    """
    For a given team (home OR away), will transform the JSON track data to produce a dataframe just like Laurie's
    """
    
    lst_playerJerseys = df_players.jersey_number.values
    
    # iterating through frames for home/away team
    for n, frame in enumerate(homeAway_tracks):

        lst_playerJerseysPerFrame = []

        for player in frame:
            jersey_number = player.get('jersey_number')
            playerIndex = playersJerseyMapping[jersey_number]
            x,y = player.get('position', [np.nan, np.nan])

            # keeping track of jerseys that have a position for that frame
            lst_playerJerseysPerFrame.append(jersey_number)

            dic_tracks[f'{homeAway}_{playerIndex}_x'].append(x)
            # flipping the y axis to make the data sync with Laurie's plotting methods
            dic_tracks[f'{homeAway}_{playerIndex}_y'].append(-1*y)

        # list of jerseys that aren't in the frame
        lst_playerJerseysNotInFrame = list(set(lst_playerJerseys) - set(lst_playerJerseysPerFrame))

        # adding the jerseys that aren't in frame and providing an x,y position of nan, nan
        for jersey_number in lst_playerJerseysNotInFrame:
            playerIndex = playersJerseyMapping[jersey_number]
            x,y = [np.nan, np.nan]

            dic_tracks[f'{homeAway}_{playerIndex}_x'].append(x)
            dic_tracks[f'{homeAway}_{playerIndex}_y'].append(y)
    
    # transforming tracking dic to a tracking dataframe
    df_tracks = pd.DataFrame(dic_tracks)
    
    return df_tracks


def parse_raw_to_df(signalityRepo, rootFileName, interpolate=True):
    """
    Takes raw root of a match string e.g. 20190930.Hammarby-Örebrö and transforms it into 4 dataframes:
    1) home players
    2) away players
    3) home tracking
    4) away tracking
    """
    
    lst_df_home = []
    lst_df_away = []
    
    for half in ['.1','.2']:
        
        # producing filename prefix (just need to add either "-info_live.json" or "-tracks.json")
        fileNamePrefix = rootFileName + half

        # load info
        ## looks like the info JSON is duplicated between the two halves
        with open(os.path.join(signalityRepo, f'{fileNamePrefix}-info_live.json')) as f:
            info = json.load(f)

        # load tracks
        with open(os.path.join(signalityRepo, f'{fileNamePrefix}-tracks.json')) as f:
            tracks = json.load(f)

        # unpacking info
        ## looks like .1 and .2 files are duplicated, so just looking at the .1 (first half file)
        if half == '.1':
            matchId = info.get('id')
            venueId = info.get('venueId')
            timeStart = info.get('time_start')
            pitchLength, pitchWidth = info.get('calibration').get('pitch_size')
            homeTeam = info.get('team_home_name')
            awayTeam = info.get('team_away_name')

            # unpacking players
            homePlayers = info.get('team_home_players')
            awayPlayers = info.get('team_away_players')
            homeLineup = info.get('team_home_lineup')
            awayLineup = info.get('team_away_lineup')
            homeLineupSwitch = {homeLineup[i]:i for i in homeLineup}
            awayLineupSwitch = {awayLineup[i]:i for i in awayLineup}

            # putting player metadata in dataframe
            df_homePlayers = pd.DataFrame(homePlayers)
            df_awayPlayers = pd.DataFrame(awayPlayers)
            df_homePlayers['teamName'] = homeTeam
            df_awayPlayers['teamName'] = awayTeam

            # adding matchId to the player dataframes
            df_homePlayers['matchId'] = matchId
            df_awayPlayers['matchId'] = matchId
            df_homePlayers['matchName'] = rootFileName
            df_awayPlayers['matchName'] = rootFileName

            # adding 1-11 + sub player indices (will probably use these for the final column names like Laurie in the tracks df)
            df_homePlayers['playerIndex'] = [int(homeLineupSwitch[i]) if i in homeLineupSwitch else np.nan for i in df_homePlayers.jersey_number.values]
            df_awayPlayers['playerIndex'] = [int(awayLineupSwitch[i]) if i in awayLineupSwitch else np.nan for i in df_awayPlayers.jersey_number.values]
            df_homePlayers.loc[pd.isna(df_homePlayers['playerIndex']) == True, 'playerIndex'] = np.arange(int(np.nanmax(df_homePlayers.playerIndex))+1, len(df_homePlayers)+1)
            df_awayPlayers.loc[pd.isna(df_awayPlayers['playerIndex']) == True, 'playerIndex'] = np.arange(int(np.nanmax(df_awayPlayers.playerIndex))+1, len(df_awayPlayers)+1)
            df_homePlayers['playerIndex'] = df_homePlayers.playerIndex.apply(lambda x: int(x))
            df_awayPlayers['playerIndex'] = df_awayPlayers.playerIndex.apply(lambda x: int(x))

            # re-jigging cols and re-ordering rows
            df_homePlayers = df_homePlayers[['matchId','matchName','teamName','playerIndex','jersey_number','name']].sort_values('playerIndex')
            df_awayPlayers = df_awayPlayers[['matchId','matchName','teamName','playerIndex','jersey_number','name']].sort_values('playerIndex')

            homePlayersJerseyMapping = {i:j for i, j in zip(df_homePlayers.jersey_number, df_homePlayers.playerIndex)}
            awayPlayersJerseyMapping = {i:j for i, j in zip(df_awayPlayers.jersey_number, df_awayPlayers.playerIndex)}

        ## parsing the track data
        phase = int(half[-1])

        # extracting home and away tracks
        home_tracks = [i.get('home_team') for i in tracks]
        away_tracks = [i.get('away_team') for i in tracks]

        # ball tracks
        ball_tracks = [i.get('ball') for i in tracks]
        ball_tracks_position = [(i.get('position')[0],i.get('position')[1],i.get('position')[2]) if i.get('position') != None else (np.nan, np.nan, np.nan) for i in ball_tracks]
        ball_x = [i[0] for i in ball_tracks_position]
        # flipping the y-coordinate
        ball_y = [-1*i[1] for i in ball_tracks_position]
        ball_z = [i[2] for i in ball_tracks_position]
        ball_jerseyPossession = [i.get('player') for i in ball_tracks]
        ball_jerseyPossession = [int(i) if pd.isna(i) == False else np.nan for i in ball_jerseyPossession]

        # match timestamps
        match_time = [i.get('match_time') for i in tracks]
        period = [i.get('phase') for i in tracks]
        timeStamp = pd.to_datetime([datetime.datetime.utcfromtimestamp(i.get('utc_time')/1000) for i in tracks])

        # unpacking tracks
        ## 1) initialising dictionaries for home and away teams
        dic_home_tracks, dic_away_tracks = initialise_dic_tracks(df_homePlayers, df_awayPlayers)
        ## 2) producing home tracking dataframe
        df_home_tracks = populate_df_tracks('Home', home_tracks, homePlayersJerseyMapping, dic_home_tracks, df_homePlayers)
        ## 3) producing away tracking dataframe
        df_away_tracks = populate_df_tracks('Away', away_tracks, awayPlayersJerseyMapping, dic_away_tracks, df_awayPlayers)

        # putting things together
        ## 1) home
        df_home_tracks['ball_x'] = ball_x
        df_home_tracks['ball_y'] = ball_y
        df_home_tracks['ball_z'] = ball_z
        
        # at this point we just have player and ball positions in the dataframe, so providing option now to interpolate
        # linearly interpolating (inside only) when there are enclosed NaNs - this is the shortest path, and will thus be the slowest in a set amount of time, so won't overestimate speed / acceleration when we're missing data
        if interpolate:
            df_home_tracks = df_home_tracks.interpolate(method='linear', limit_area='inside')
        
        # and now adding things where we wouldn't want there to be any interpolation (like the ball_jerseyPossession)
        df_home_tracks['halfIndex'] = df_home_tracks.index
        df_home_tracks['matchId'] = matchId
        df_home_tracks['matchName'] = rootFileName
        df_home_tracks['Period'] = period
        df_home_tracks['Time [s]'] = np.array(match_time) / 1000
        df_home_tracks['TimeStamp'] = timeStamp
        df_home_tracks['ball_jerseyPossession'] = ball_jerseyPossession

        ## 2) away
        df_away_tracks['ball_x'] = ball_x
        df_away_tracks['ball_y'] = ball_y
        df_away_tracks['ball_z'] = ball_z
        
        # option to interpolate, like with the home team
        if interpolate:
            df_away_tracks = df_away_tracks.interpolate(method='linear', limit_area='inside')
        
        df_away_tracks['halfIndex'] = df_away_tracks.index
        df_away_tracks['matchId'] = matchId
        df_away_tracks['matchName'] = rootFileName
        df_away_tracks['Period'] = period
        df_away_tracks['Time [s]'] = np.array(match_time) / 1000
        df_away_tracks['TimeStamp'] = timeStamp
        df_away_tracks['ball_jerseyPossession'] = ball_jerseyPossession
        
        lst_df_home.append(df_home_tracks)
        lst_df_away.append(df_away_tracks)
        
    df_homeTracks = pd.concat(lst_df_home, ignore_index=True)
    df_awayTracks = pd.concat(lst_df_away, ignore_index=True)
    
    df_homeTracks['index'] = df_homeTracks.index
    df_awayTracks['index'] = df_awayTracks.index
    
    return df_homePlayers, df_awayPlayers, df_homeTracks, df_awayTracks, pitchLength, pitchWidth



# # **2)** Calculate first and second spacial derivatives

# +
def calc_player_velocities(team, smoothing=True, filter_='Savitzky-Golay', window=7, polyorder=1, maxspeed = 12):
    """ calc_player_velocities( tracking_data )
    
    Calculate player velocities in x & y direciton, and total player speed at each timestamp of the tracking data
    
    Parameters
    -----------
        team: the tracking DataFrame for home or away team
        smoothing: boolean variable that determines whether velocity measures are smoothed. Default is True.
        filter: type of filter to use when smoothing the velocities. Default is Savitzky-Golay, which fits a polynomial of order 'polyorder' to the data within each window
        window: smoothing window size in # of frames
        polyorder: order of the polynomial for the Savitzky-Golay filter. Default is 1 - a linear fit to the velocity, so gradient is the acceleration
        maxspeed: the maximum speed that a player can realisitically achieve (in meters/second). Speed measures that exceed maxspeed are tagged as outliers and set to NaN. 
        
    Returrns
    -----------
       team : the tracking DataFrame with columns for speed in the x & y direction and total speed added

    """
    # remove any velocity data already in the dataframe
    team = remove_player_velocities(team)
    
    # Get the player ids
    player_ids = np.unique( [ c[:-2] for c in team.columns if c[:4] in ['Home','Away'] ] )

    # Calculate the timestep from one frame to the next. Should always be 0.04 within the same half
    dt = team['Time [s]'].diff()
    
    # index of first frame in second half
    second_half_idx = team.Period.idxmax(2)
    
    # estimate velocities for players in team
    for player in player_ids: # cycle through players individually
        # difference player positions in timestep dt to get unsmoothed estimate of velicity
        vx = team[player+"_x"].diff() / dt
        vy = team[player+"_y"].diff() / dt

        if maxspeed>0:
            # remove unsmoothed data points that exceed the maximum speed (these are most likely position errors)
            raw_speed = np.sqrt( vx**2 + vy**2 )
            vx[ raw_speed>maxspeed ] = np.nan
            vy[ raw_speed>maxspeed ] = np.nan
            
        if smoothing:
            if filter_=='Savitzky-Golay':
                # calculate first half velocity
                vx.loc[:second_half_idx] = signal.savgol_filter(vx.loc[:second_half_idx],window_length=window,polyorder=polyorder)
                vy.loc[:second_half_idx] = signal.savgol_filter(vy.loc[:second_half_idx],window_length=window,polyorder=polyorder)        
                # calculate second half velocity
                vx.loc[second_half_idx:] = signal.savgol_filter(vx.loc[second_half_idx:],window_length=window,polyorder=polyorder)
                vy.loc[second_half_idx:] = signal.savgol_filter(vy.loc[second_half_idx:],window_length=window,polyorder=polyorder)
            elif filter_=='moving average':
                ma_window = np.ones( window ) / window 
                # calculate first half velocity
                vx.loc[:second_half_idx] = np.convolve( vx.loc[:second_half_idx] , ma_window, mode='same' ) 
                vy.loc[:second_half_idx] = np.convolve( vy.loc[:second_half_idx] , ma_window, mode='same' )      
                # calculate second half velocity
                vx.loc[second_half_idx:] = np.convolve( vx.loc[second_half_idx:] , ma_window, mode='same' ) 
                vy.loc[second_half_idx:] = np.convolve( vy.loc[second_half_idx:] , ma_window, mode='same' ) 
                
        
        # put player speed in x,y direction, and total speed back in the data frame
        team[player + "_vx"] = vx
        team[player + "_vy"] = vy
        team[player + "_speed"] = np.sqrt( vx**2 + vy**2 )

    return team


def remove_player_velocities(team):
    """
    Clean up function
    """
    # remove player velocoties and acceleeration measures that are already in the 'team' dataframe
    columns = [c for c in team.columns if c.split('_')[-1] in ['vx','vy','ax','ay','speed','acceleration']] # Get the player ids
    team = team.drop(columns=columns)
    return team


# +
def merge_tracking_data(home,away):
    '''
    merge home & away tracking data files into single data frame
    '''
    return home.drop(columns=['ball_x', 'ball_y']).merge( away, left_index=True, right_index=True )
    

def to_single_playing_direction(home,away,events):
    '''
    Flip coordinates in second half so that each team always shoots in the same direction through the match.
    '''
    for team in [home,away,events]:
        second_half_idx = team.Period.idxmax(2)
        columns = [c for c in team.columns if c[-1].lower() in ['x','y']]
        team.loc[second_half_idx:,columns] *= -1
    return home,away,events


# -

# # **3)** Functions to create plots and videos

def plot_pitch( field_dimen = (106.0,68.0), field_color ='green', linewidth=2, markersize=20):
    """ plot_pitch
    
    Plots a soccer pitch. All distance units converted to meters.
    
    Parameters
    -----------
        field_dimen: (length, width) of field in meters. Default is (106,68)
        field_color: color of field. options are {'green','white'}
        linewidth  : width of lines. default = 2
        markersize : size of markers (e.g. penalty spot, centre spot, posts). default = 20
        
    Returrns
    -----------
       fig,ax : figure and aixs objects (so that other data can be plotted onto the pitch)

    """
    fig,ax = plt.subplots(figsize=(12,8)) # create a figure 
    # decide what color we want the field to be. Default is green, but can also choose white
    if field_color=='green':
        ax.set_facecolor('mediumseagreen')
        lc = 'whitesmoke' # line color
        pc = 'w' # 'spot' colors
    elif field_color=='white':
        lc = 'k'
        pc = 'k'
    # ALL DIMENSIONS IN m
    border_dimen = (3,3) # include a border arround of the field of width 3m
    meters_per_yard = 0.9144 # unit conversion from yards to meters
    half_pitch_length = field_dimen[0]/2. # length of half pitch
    half_pitch_width = field_dimen[1]/2. # width of half pitch
    signs = [-1,1] 
    # Soccer field dimensions typically defined in yards, so we need to convert to meters
    goal_line_width = 8*meters_per_yard
    box_width = 20*meters_per_yard
    box_length = 6*meters_per_yard
    area_width = 44*meters_per_yard
    area_length = 18*meters_per_yard
    penalty_spot = 12*meters_per_yard
    corner_radius = 1*meters_per_yard
    D_length = 8*meters_per_yard
    D_radius = 10*meters_per_yard
    D_pos = 12*meters_per_yard
    centre_circle_radius = 10*meters_per_yard
    # plot half way line # center circle
    ax.plot([0,0],[-half_pitch_width,half_pitch_width],lc,linewidth=linewidth)
    ax.scatter(0.0,0.0,marker='o',facecolor=lc,linewidth=0,s=markersize)
    y = np.linspace(-1,1,50)*centre_circle_radius
    x = np.sqrt(centre_circle_radius**2-y**2)
    ax.plot(x,y,lc,linewidth=linewidth)
    ax.plot(-x,y,lc,linewidth=linewidth)
    for s in signs: # plots each line seperately
        # plot pitch boundary
        ax.plot([-half_pitch_length,half_pitch_length],[s*half_pitch_width,s*half_pitch_width],lc,linewidth=linewidth)
        ax.plot([s*half_pitch_length,s*half_pitch_length],[-half_pitch_width,half_pitch_width],lc,linewidth=linewidth)
        # goal posts & line
        ax.plot( [s*half_pitch_length,s*half_pitch_length],[-goal_line_width/2.,goal_line_width/2.],pc+'s',markersize=6*markersize/20.,linewidth=linewidth)
        # 6 yard box
        ax.plot([s*half_pitch_length,s*half_pitch_length-s*box_length],[box_width/2.,box_width/2.],lc,linewidth=linewidth)
        ax.plot([s*half_pitch_length,s*half_pitch_length-s*box_length],[-box_width/2.,-box_width/2.],lc,linewidth=linewidth)
        ax.plot([s*half_pitch_length-s*box_length,s*half_pitch_length-s*box_length],[-box_width/2.,box_width/2.],lc,linewidth=linewidth)
        # penalty area
        ax.plot([s*half_pitch_length,s*half_pitch_length-s*area_length],[area_width/2.,area_width/2.],lc,linewidth=linewidth)
        ax.plot([s*half_pitch_length,s*half_pitch_length-s*area_length],[-area_width/2.,-area_width/2.],lc,linewidth=linewidth)
        ax.plot([s*half_pitch_length-s*area_length,s*half_pitch_length-s*area_length],[-area_width/2.,area_width/2.],lc,linewidth=linewidth)
        # penalty spot
        ax.scatter(s*half_pitch_length-s*penalty_spot,0.0,marker='o',facecolor=lc,linewidth=0,s=markersize)
        # corner flags
        y = np.linspace(0,1,50)*corner_radius
        x = np.sqrt(corner_radius**2-y**2)
        ax.plot(s*half_pitch_length-s*x,-half_pitch_width+y,lc,linewidth=linewidth)
        ax.plot(s*half_pitch_length-s*x,half_pitch_width-y,lc,linewidth=linewidth)
        # draw the D
        y = np.linspace(-1,1,50)*D_length # D_length is the chord of the circle that defines the D
        x = np.sqrt(D_radius**2-y**2)+D_pos
        ax.plot(s*half_pitch_length-s*x,y,lc,linewidth=linewidth)
        
    # remove axis labels and ticks
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.set_xticks([])
    ax.set_yticks([])
    # set axis limits
    xmax = field_dimen[0]/2. + border_dimen[0]
    ymax = field_dimen[1]/2. + border_dimen[1]
    ax.set_xlim([-xmax,xmax])
    ax.set_ylim([-ymax,ymax])
    ax.set_axisbelow(True)
    return fig,ax


# +
def plot_frame(hometeam, awayteam, figax=None, team_colors=('r','b'), field_dimen = (106.0,68.0), include_player_velocities=False, PlayerMarkerSize=10, PlayerAlpha=0.7, annotate=False):
    """ plot_frame( hometeam, awayteam )
    
    Have re-written Laurie's plotting function to plot Signality data and to also deal with the numpy typing error that was occuring.
    
    Parameters
    -----------
        hometeam: row (i.e. instant) of the home team tracking data frame
        awayteam: row of the away team tracking data frame
        fig,ax: Can be used to pass in the (fig,ax) objects of a previously generated pitch. Set to (fig,ax) to use an existing figure, or None (the default) to generate a new pitch plot, 
        team_colors: Tuple containing the team colors of the home & away team. Default is 'r' (red, home team) and 'b' (blue away team)
        field_dimen: tuple containing the length and width of the pitch in meters. Default is (106,68)
        include_player_velocities: Boolean variable that determines whether player velocities are also plotted (as quivers). Default is False
        PlayerMarkerSize: size of the individual player marlers. Default is 10
        PlayerAlpha: alpha (transparency) of player markers. Defaault is 0.7
        annotate: Boolean variable that determines with player jersey numbers are added to the plot (default is False)
        
    Returrns
    -----------
       fig,ax : figure and aixs objects (so that other data can be plotted onto the pitch)

    """
    if figax is None: # create new pitch 
        fig,ax = plot_pitch( field_dimen = field_dimen )
    else: # overlay on a previously generated pitch
        fig,ax = figax # unpack tuple
        
    # plot home & away teams in order
    for team, color in zip( [hometeam, awayteam], team_colors) :

        x_columns = [c for c in team.keys() if c[-2:].lower()=='_x' and c!='ball_x'] # column header for player x positions
        y_columns = [c for c in team.keys() if c[-2:].lower()=='_y' and c!='ball_y'] # column header for player y positions

        x = np.array(team[x_columns], dtype=np.float64)
        y = np.array(team[y_columns], dtype=np.float64)

        ax.plot( x, y, color+'o', MarkerSize=PlayerMarkerSize, alpha=PlayerAlpha ) # plot player positions

        if include_player_velocities:
            vx_columns = np.array(['{}_vx'.format(c[:-2]) for c in x_columns]) # column header for player x positions
            vy_columns = np.array(['{}_vy'.format(c[:-2]) for c in y_columns]) # column header for player y positions

            vx = np.array(team[vx_columns], dtype=np.float64)
            vy = np.array(team[vy_columns], dtype=np.float64)

            ax.quiver( x, y, vx, vy, color=color, scale_units='inches', scale=10.,width=0.0015,headlength=5,headwidth=3,alpha=PlayerAlpha)

        if annotate:
            [ ax.text( team[x]+0.5, team[y]+0.5, x.split('_')[1], fontsize=10, color=color  ) for x,y in zip(x_columns,y_columns) if not ( np.isnan(team[x]) or np.isnan(team[y]) ) ] 

    # plot ball
    ax.plot( hometeam['ball_x'], hometeam['ball_y'], 'ko', MarkerSize=6, alpha=1.0, LineWidth=0)
    
    return fig,ax
    


# -

def save_match_clip(hometeam, awayteam, fpath, fname='clip_test', figax=None, frames_per_second=25, team_colors=('r','b'), field_dimen = (106.0,68.0), include_player_velocities=False, PlayerMarkerSize=10, PlayerAlpha=0.7):
    """ save_match_clip( hometeam, awayteam, fpath )
    
    Re-written Laurie's function to deal with bug caused by numpy typing of the (x,y) co-ords as it was fed into the quiver to plot the velocities
    Generates a movie from Signality tracking data, saving it in the 'fpath' directory with name 'fname'
    
    Parameters
    -----------
        hometeam: home team tracking data DataFrame. Movie will be created from all rows in the DataFrame
        awayteam: away team tracking data DataFrame. The indices *must* match those of the hometeam DataFrame
        fpath: directory to save the movie
        fname: movie filename. Default is 'clip_test.mp4'
        fig,ax: Can be used to pass in the (fig,ax) objects of a previously generated pitch. Set to (fig,ax) to use an existing figure, or None (the default) to generate a new pitch plot,
        frames_per_second: frames per second to assume when generating the movie. Default is 25.
        team_colors: Tuple containing the team colors of the home & away team. Default is 'r' (red, home team) and 'b' (blue away team)
        field_dimen: tuple containing the length and width of the pitch in meters. Default is (106,68)
        include_player_velocities: Boolean variable that determines whether player velocities are also plotted (as quivers). Default is False
        PlayerMarkerSize: size of the individual player marlers. Default is 10
        PlayerAlpha: alpha (transparency) of player markers. Default is 0.7
        
    Returrns
    -----------
       fig,ax : figure and aixs objects (so that other data can be plotted onto the pitch)

    """
    # check that indices match first
    assert np.all( hometeam.index==awayteam.index ), "Home and away team Dataframe indices must be the same"
    
    # in which case use home team index
    index = hometeam.index
    
    # Set figure and movie settings
    FFMpegWriter = animation.writers['ffmpeg']
    metadata = dict(title='Tracking Data', artist='Matplotlib', comment='Metrica tracking data clip')
    writer = FFMpegWriter(fps=frames_per_second, metadata=metadata)
    fname = fpath + '/' +  fname + '.mp4' # path and filename
    
    # create football pitch
    if figax is None:
        fig,ax = plot_pitch(field_dimen=field_dimen)
    else:
        fig,ax = figax
    fig.set_tight_layout(True)
    
    # Generate movie
    print("Generating movie...",end='')
    with writer.saving(fig, fname, 100):
        for i in index:
            figobjs = [] # this is used to collect up all the axis objects so that they can be deleted after each iteration
            for team,color in zip( [hometeam.loc[i],awayteam.loc[i]], team_colors) :
                x_columns = [c for c in team.keys() if c[-2:].lower()=='_x' and c!='ball_x'] # column header for player x positions
                y_columns = [c for c in team.keys() if c[-2:].lower()=='_y' and c!='ball_y'] # column header for player y positions
                
                x = np.array(team[x_columns], dtype=np.float64)
                y = np.array(team[y_columns], dtype=np.float64)
                
                objs, = ax.plot( x, y, color+'o', MarkerSize=PlayerMarkerSize, alpha=PlayerAlpha ) # plot player positions
                figobjs.append(objs)
                if include_player_velocities:
                    vx_columns = ['{}_vx'.format(c[:-2]) for c in x_columns] # column header for player x positions
                    vy_columns = ['{}_vy'.format(c[:-2]) for c in y_columns] # column header for player y positions
                    
                    vx = np.array(team[vx_columns], dtype=np.float64)
                    vy = np.array(team[vy_columns], dtype=np.float64)
                    
                    objs = ax.quiver( x, y, vx, vy, color=color, scale_units='inches', scale=10.,width=0.0015,headlength=5,headwidth=3,alpha=PlayerAlpha)
                    figobjs.append(objs)
                    
            # plot ball
            objs, = ax.plot( team['ball_x'], team['ball_y'], 'ko', MarkerSize=6, alpha=1.0, LineWidth=0)
            figobjs.append(objs)
            
            # include match time at the top
            frame_minute =  int( team['Time [s]']/60. )
            frame_second =  ( team['Time [s]']/60. - frame_minute ) * 60.
            timestring = "%d:%1.2f" % ( frame_minute, frame_second  )
            objs = ax.text(-2.5,field_dimen[1]/2.+1., timestring, fontsize=14 )
            figobjs.append(objs)
            writer.grab_frame()
            
            # Delete all axis objects (other than pitch lines) in preperation for next frame
            for figobj in figobjs:
                figobj.remove()
                
    print("\nMovie Completed.")
    plt.clf()
    plt.close(fig)    


# ---
#
# &nbsp;
#
# &nbsp;
#
# &nbsp;
#
# # Action starts here

rootFileNames = ['20190930.Hammarby-Örebrö'
                ,'20191020.Hammarby-MalmöFF'
                ,'20190722.Hammarby-IFElfsborg']

# ## **Selecting a match**

# +
# %%time

# load tracking data for a given match
df_homePlayers, df_awayPlayers, df_homeTracks, df_awayTracks, pitchLength, pitchWidth = parse_raw_to_df(signalityRepo, '20190722.Hammarby-IFElfsborg', interpolate=False)
# -

df_homePlayers

# ---
#
# &nbsp;
#
# &nbsp;
#
# &nbsp;
#
# ## **Applying Velocity and Acceleration Functions**

# +
# %%time

df_homeTracks = calc_player_velocities(df_homeTracks, smoothing=True, filter_='Savitzky-Golay', window=7, polyorder=1, maxspeed = 250)
df_awayTracks = calc_player_velocities(df_awayTracks, smoothing=True, filter_='Savitzky-Golay', window=7, polyorder=1, maxspeed = 250)
# -

# ### Taking a quick look at Khalili's speed

plt.figure(figsize=(50, 12))
plt.plot(df_homeTracks.index, df_homeTracks.Home_9_speed)
plt.show()

# ## Let's look at some obvious cases where velocity is crazy

df_homeTracks.loc[df_homeTracks['Home_9_speed'] > 20]









# ## **Looking at interesting sequences**

df_events.loc[(df_events['matchId'] == 1035923) & (df_events['eventSubType'].str.contains('Goal'))]

# ### Goal 1: 28:48
#
# > Data error: looks like the ball came off the defender to go to the player to make the assist, whereas actually it was very tight footwork by player #22

# +
timeFrom = '2019-07-22 17:18:40.564'
timeTo = '2019-07-22 17:19:09.564'

df_trackingEvent = df_homeTracks.loc[(df_homeTracks['TimeStamp'] > timeFrom) & (df_homeTracks['TimeStamp'] < timeTo)]

start, finish = df_trackingEvent.agg({'index':[np.min, np.max]}).reset_index(drop=True)['index'].values

save_match_clip(df_homeTracks.loc[start:finish], df_awayTracks.loc[start:finish], movieRepo, fname='VsElfsborg_firstGoalHammarby', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)
# -

# ### Goal 2 (OG from FK): 38:20
#
# Two issues with the data:
#
# 1. Ball disappearing (linear interpolation) - but don't get a feel for curve of the vall
# 2. Player from the pack darts out - you may think they'd dramatically left their defensive shape causing the exposure

# +
timeFrom = '2019-07-22 17:28:07.564'
timeTo = '2019-07-22 17:28:40.564'

df_trackingEvent = df_homeTracks.loc[(df_homeTracks['TimeStamp'] > timeFrom) & (df_homeTracks['TimeStamp'] < timeTo)]

start, finish = df_trackingEvent.agg({'index':[np.min, np.max]}).reset_index(drop=True)['index'].values

save_match_clip(df_homeTracks.loc[start:finish], df_awayTracks.loc[start:finish], movieRepo, fname='VsElfsborg_secondGoalHammarby', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)
# -

df_trackingEvent.head()

# ### Goal 3: 41:01
#
# > Reaction of Khalili is key
# > Maybe there's a metric for reaction time?

# +
timeFrom = '2019-07-22 17:30:50.564'
timeTo = '2019-07-22 17:31:21.564'

df_trackingEvent = df_homeTracks.loc[(df_homeTracks['TimeStamp'] > timeFrom) & (df_homeTracks['TimeStamp'] < timeTo)]

start, finish = df_trackingEvent.agg({'index':[np.min, np.max]}).reset_index(drop=True)['index'].values

save_match_clip(df_homeTracks.loc[start:finish], df_awayTracks.loc[start:finish], movieRepo, fname='VsElfsborg_thirdGoalHammarby', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)
# -

# ### Goal 4

# +
timeFrom = '2019-07-22 17:18:40.564'
timeTo = '2019-07-22 17:19:09.564'

df_trackingEvent = df_homeTracks.loc[(df_homeTracks['TimeStamp'] > timeFrom) & (df_homeTracks['TimeStamp'] < timeTo)]

start, finish = df_trackingEvent.agg({'index':[np.min, np.max]}).reset_index(drop=True)['index'].values

save_match_clip(df_homeTracks.loc[start:finish], df_awayTracks.loc[start:finish], movieRepo, fname='Elfsborg_first_goal', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)
# -

# ### Goal 5

# +
timeFrom = '2019-07-22 17:18:40.564'
timeTo = '2019-07-22 17:19:09.564'

df_trackingEvent = df_homeTracks.loc[(df_homeTracks['TimeStamp'] > timeFrom) & (df_homeTracks['TimeStamp'] < timeTo)]

start, finish = df_trackingEvent.agg({'index':[np.min, np.max]}).reset_index(drop=True)['index'].values

save_match_clip(df_homeTracks.loc[start:finish], df_awayTracks.loc[start:finish], movieRepo, fname='Elfsborg_first_goal', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)
# -

# ### Goal 6

# +
timeFrom = '2019-07-22 17:18:40.564'
timeTo = '2019-07-22 17:19:09.564'

df_trackingEvent = df_homeTracks.loc[(df_homeTracks['TimeStamp'] > timeFrom) & (df_homeTracks['TimeStamp'] < timeTo)]

start, finish = df_trackingEvent.agg({'index':[np.min, np.max]}).reset_index(drop=True)['index'].values

save_match_clip(df_homeTracks.loc[start:finish], df_awayTracks.loc[start:finish], movieRepo, fname='Elfsborg_first_goal', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)
# -

# ## Mapping Error

save_match_clip(df_homeTracks.loc[85380:86550], df_awayTracks.loc[85380:86550], movieRepo, fname='Mapping_Error_9_10', figax=None, frames_per_second=25, team_colors=('r','b')\
                ,field_dimen = (pitchLength, pitchWidth), include_player_velocities=True, PlayerMarkerSize=10, PlayerAlpha=0.7)

# ## Running off the ball example

frameIdx = 45020
plot_frame( df_homeTracks.loc[frameIdx], df_awayTracks.loc[frameIdx], include_player_velocities=True, annotate=True)

frameIdx = 45080
plot_frame( df_homeTracks.loc[frameIdx], df_awayTracks.loc[frameIdx], include_player_velocities=True, annotate=True)

frameIdx = 45105
plot_frame( df_homeTracks.loc[frameIdx], df_awayTracks.loc[frameIdx], include_player_velocities=True, annotate=True)

frameIdx = 45130
plot_frame( df_homeTracks.loc[frameIdx], df_awayTracks.loc[frameIdx], include_player_velocities=True, annotate=True)



# ## Look at fouls and plot the 30s after each foul 

df_events.loc[(df_events['matchId'] == 1035923) & (df_events['eventSubType'].str.contains('Goal'))]





df_events.eventSubType.value_counts()



# +
plt.figure(figsize=(16,12))

xx = df_homeTracks.ball_x + pitchLength/2
yy = df_homeTracks.ball_y + pitchWidth/2

plt.scatter(xx, yy)

plt.show()
# -





















# +
y_home = [len(i.get('home_team')) for i in tracks]
x_home = np.arange(len(y_home))

plt.figure(figsize=(16,8))
plt.plot(x_home,y_home)
plt.show()

# +
y_away = [len(i.get('away_team')) for i in tracks]
x_away = np.arange(len(y_away))

plt.figure(figsize=(16,8))
plt.plot(x_away,y_away)
plt.show()
# -























































# ### Loading in Events Data for the 3 Games

df_homeTracks



df_trackingEvent



StarcoderdataJupyterScriptsDedupFiltered