├── NAMESPACE ├── DESCRIPTION ├── man ├── get_match.Rd └── get_points.Rd ├── R ├── get_trajectory.R ├── get_match.R └── get_points.R └── README.md /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(get_match) 4 | export(get_points) 5 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: courtvisionr 2 | Version: 0.1 3 | Date: 2020-01-21 4 | Title: Functions to extract court vision data for Grand Slam analysis 5 | Author: Steph Kovalchik [aut, cre] 6 | Maintainer: Steph Kovalchik 7 | Depends: data.table, jsonlite, glue 8 | Description: Functions to extract court vision data for Grand Slam analysis 9 | License: GPL (>= 2) 10 | RoxygenNote: 7.1.1 11 | -------------------------------------------------------------------------------- /man/get_match.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_match.R 3 | \name{get_match} 4 | \alias{get_match} 5 | \title{Scrape JSON of Match court vision data} 6 | \usage{ 7 | get_match(year, matchid, event = c("ao", "rg")) 8 | } 9 | \arguments{ 10 | \item{year}{Year of match (yyyy)} 11 | 12 | \item{matchid}{Character matchid (SM001)} 13 | 14 | \item{event}{One of "ao" (Aussie Open) or "rg" (Roland Garros)} 15 | } 16 | \description{ 17 | Scrape JSON of Match court vision data 18 | } 19 | \examples{ 20 | get_match(2021, "SM001", "rg") 21 | get_match(2021, "MS701", "ao") 22 | } 23 | -------------------------------------------------------------------------------- /R/get_trajectory.R: -------------------------------------------------------------------------------- 1 | get_trajectory <- function(point){ 2 | 3 | if(is.null(point$trajectoryData) | length(point$trajectoryData[[1]]) == 0) 4 | return(NULL) 5 | else{ 6 | shots <- unique(as.data.table(point$trajectoryData)) 7 | shots$shot <- cumsum(shots$position == "hit") 8 | 9 | shots <- shots[, ":=" ( 10 | court = point$court, 11 | set = point$set, 12 | game = point$game, 13 | point = point$point, 14 | serve = point$serve, 15 | rally = point$rallyLength, 16 | server = point$serverId, 17 | scorer = point$scorerId, 18 | receiver = point$receiverId, 19 | pointid = point$pointId, 20 | error = point$errorType 21 | )] 22 | 23 | return(shots) 24 | } 25 | } -------------------------------------------------------------------------------- /man/get_points.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get_points.R 3 | \name{get_points} 4 | \alias{get_points} 5 | \title{Create point-level data.table for court vision match} 6 | \usage{ 7 | get_points(x, matchid = NULL, year = NULL, event = NULL) 8 | } 9 | \arguments{ 10 | \item{x}{List from JSON input, return from \code{get_match}} 11 | 12 | \item{matchid}{character matchid as on grand slam results page (e.g. 'MS001')} 13 | 14 | \item{year}{Year of match (yyyy)} 15 | 16 | \item{event}{Character name of event} 17 | } 18 | \description{ 19 | Create point-level data.table for court vision match 20 | } 21 | \examples{ 22 | get_points(get_match(2021, "MS701", "ao"), "MS701", 2021) 23 | } 24 | -------------------------------------------------------------------------------- /R/get_match.R: -------------------------------------------------------------------------------- 1 | #' Scrape JSON of Match court vision data 2 | #' @param year Year of match (yyyy) 3 | #' @param matchid Character matchid (SM001) 4 | #' @param event One of "ao" (Aussie Open) or "rg" (Roland Garros) 5 | #' @export 6 | #' @examples 7 | #' get_match(2021, "SM001", "rg") 8 | #' get_match(2021, "MS701", "ao") 9 | get_match <- function(year, matchid, event = c("ao", "rg")){ 10 | 11 | if(event == "rg") 12 | url <- glue::glue("https://itp-rg-sls.infosys-platforms.com/prod/api/court-vision/year/{year}/eventId/520/matchId/{matchid}/pointId/0_0_0") 13 | else 14 | url <- glue::glue("https://itp-ao.infosys-platforms.com/api/court-vision/year/{year}/eventId/580/matchId/{matchid}/pointId/0_0_0") 15 | 16 | jsonlite::fromJSON(url)[[1]] 17 | } -------------------------------------------------------------------------------- /R/get_points.R: -------------------------------------------------------------------------------- 1 | #' Create point-level data.table for court vision match 2 | #' @param x List from JSON input, return from \code{get_match} 3 | #' @param matchid character matchid as on grand slam results page (e.g. 'MS001') 4 | #' @param year Year of match (yyyy) 5 | #' @param event Character name of event 6 | #' @export 7 | #' @examples 8 | #' get_points(get_match(2021, "MS701", "ao"), "MS701", 2021) 9 | get_points <- function(x, matchid = NULL, year = NULL, event = NULL){ 10 | 11 | points <- lapply(x$pointsData, get_trajectory) 12 | 13 | points <- rbindlist(points) 14 | 15 | players <- rbindlist(lapply(x$playersData, as.data.table)) 16 | 17 | points <- merge( 18 | points, 19 | players[, .(server = id, servername = name)], 20 | by = "server" 21 | ) 22 | 23 | points <- merge( 24 | points, 25 | players[, .(receiver = id, receivername = name)], 26 | by = "receiver" 27 | ) 28 | 29 | points <- points[, matchid := matchid] 30 | points <- points[, year := year] 31 | points <- points[, event := event] 32 | 33 | return(points) 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## courtvisionr 2 | 3 | This package provides a few functions for extracting summary tracking data included in the courtvision applet of the Australian Open and Roland Garros Grand Slams. The package does not include the data itself but rather provides the means for extracting what current and historical data is publicly available. 4 | 5 | ## Installation 6 | 7 | To get started with the package, you can install the latest version using `remotes`. 8 | 9 | ` 10 | remotes::install_github("skoval/courtvisionr") 11 | ` 12 | 13 | A quick way to get started is to look at the examples for the package's primary funciton `get_match`. 14 | 15 | ` 16 | library(courtvisionr) 17 | ` 18 | 19 | ` 20 | example(get_match) 21 | ` 22 | 23 | 24 | ## Coverage 25 | 26 | The _Court Vision_ data is part of the match results for matches where Hawkeye was in operation. This should cover all courts for the Australian Open since 2021 and all show courts for Roland Garros (and the Aussie Open prior to 2021). Beyond that, the coverage will depend on web design of Infosys. 27 | 28 | Because the event websites focus on the most recent year, some historical data may no longer be available once that year has passed. I believe match data is still accessible for 2020+ for the Australian Open and 2019+ for Roland Garros. 29 | 30 | There are other data elements that are also available (rally analysis, matchbeats, etc.). I haven't included this because those are less interesting, I think. But it would just be a matter of updating the URL pattern for those JSON sources to add those to the package. 31 | 32 | 33 | ## Match Codes 34 | 35 | Match does follow a specific formula and are the same from year to year (at least so far). They do differ at each event, however. Below is a general guide: 36 | 37 | 38 | |Event | Event Type | Formula | Example 39 | |:-----|:-----:|:-----:|:-----: 40 | | Roland Garros | Men's Singles | SM### | SM001 (Final match) 41 | | | Women's Singles | SD### | SD127 (First match) 42 | | | Men's Doubles | DM### | DM001 (Final) 43 | | | Women's Doubles | DD### | DD001 (Final) 44 | | | Mixed Doubles | MX### | MX001 (Final) 45 | | Australian Open | Men's Singles | MS### | MS701 (Final match) 46 | | | Women's Singles | WS### | WS001 (First match) 47 | | | Men's Doubles | MD### | MD601 (Final) 48 | | | Women's Doubles | WD### | WD001 (Final) 49 | | | Mixed Doubles | XD### | XD501 (Final) 50 | 51 | So all matchids have a 2-character event code and then 3 digits. For Roland Garros the digit is the match number starting from the biggest number and working toward 1 for the final. For the Australian Open, the first digit is the round (beginning with 1 and working up) and then a 2-digit match number starting at 1 and working up within each round. 52 | 53 | 54 | ## Terms of Use 55 | 56 | Scraping and use of data obtained through the package functions fall under the individual terms on each events webiste. For the Australian Open, the terms can be found at [this link](https://www.tennis.com.au/conditions-of-use). I can't find the comparable terms for Roland Garros. 57 | 58 | In general, good practice would dictate that any downloaded data is used for the informational/research, non-commerical purposes of individuals. Any uses outside of this or extensive scraping over a small period of time, would warrant pre-approval from the events. 59 | 60 | It is quite possible that, as more people attempt to access the data, that the site will be altered or made less accessible. If you notice any changes to accessibility, especially if it impacts the functionality of the package utilities, please post that on the repo `Issues` page. --------------------------------------------------------------------------------