# ballCoordinates

### What does the include do?

The `ballCoordinates` include allows users to track ball location on the pitch in real-time, essential for strategic analysis and fan engagement. Previously accessible via the `metadata` include, the standalone `ballCoordinates` include now provides faster, targeted access to ball-tracking data.

### Why use ball coordinates?

This feature benefits:

* **Betting insights**: Helps assess whether a team is likely to score or gain a corner based on ball position.
* **Offensive analysis**: Quickly indicates which team is in an attacking position.
* **Tactical analysis**: Track possession zones, attacking patterns, and defensive positioning.
* **Fan engagement**: Build interactive heat maps and ball movement visualizations.
* **Performance analytics**: Analyse ball circulation, territory control, and attacking efficiency.

For deeper analysis, combine `ballCoordinates` with the [Pressure Index](https://claude.ai/football/tutorials-and-guides/tutorials/includes/pressure-index) to understand offensive and defensive pressures in real-time.

### Requesting ball coordinates

To retrieve ball coordinates, use the following include in a fixture request:

```http
https://api.sportmonks.com/v3/football/fixtures/{ID}?api_token=YOUR_TOKEN&include=ballCoordinates
```

**Example:** Track ball movements in a past fixture, such as the Champions League 2025 round 5 (ID: 19568502)

```http
https://api.sportmonks.com/v3/football/fixtures/19568502
?api_token=YOUR_TOKEN&include=ballCoordinates
```

### Response Structure

When you include `ballCoordinates` in your request, you'll receive an array of ball coordinate objects within your fixture response:

```json
{
  "data": {
    "id": 19568502,
    "sport_id": 1,
    "league_id": 2,
    "season_id": 25580,
    "stage_id": 77478049,
    "group_id": null,
    "aggregate_id": null,
    "round_id": 388953,
    "state_id": 5,
    "venue_id": 321614,
    "name": "Chelsea vs FC Barcelona",
    "starting_at": "2025-11-25 20:00:00",
    "result_info": "Chelsea won after full-time.",
    "leg": "1/1",
    "details": null,
    "length": 90,
    "placeholder": false,
    "has_odds": true,
    "has_premium_odds": true,
    "starting_at_timestamp": 1764100800,
    "ballcoordinates": [
      {
        "id": 258909457,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "93:32",
        "x": "0.91",
        "y": "0.49"
      },
      {
        "id": 258909452,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:49",
        "x": "0.32",
        "y": "0.5"
      },
      {
        "id": 258909446,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:43",
        "x": "0.32",
        "y": "0.5"
      },
      {
        "id": 258909440,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:39",
        "x": "0.32",
        "y": "0.5"
      },
      {
        "id": 258909435,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:33",
        "x": "0.32",
        "y": "0.5"
      },
      {
        "id": 258909427,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:29",
        "x": "0.52",
        "y": "0.65"
      },
      {
        "id": 258909424,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:23",
        "x": "0.52",
        "y": "0.65"
      },
      {
        "id": 258909414,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:19",
        "x": "0.29",
        "y": "0.5"
      },
      {
        "id": 258909405,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:14",
        "x": "0.29",
        "y": "0.5"
      },
      {
        "id": 258909397,
        "fixture_id": 19568502,
        "period_id": 6379940,
        "timer": "92:09",
        "x": "0.35",
        "y": "0.46"
      }
    ]
  }
}
```

### Field descriptions

| Field        | Type    | Description                                                                 |
| ------------ | ------- | --------------------------------------------------------------------------- |
| `id`         | integer | Unique identifier for this ball coordinate entry                            |
| `fixture_id` | integer | ID of the fixture this coordinate belongs to                                |
| `period_id`  | integer | ID of the period (half) when this position was recorded                     |
| `timer`      | string  | Match time in MM:SS format when the ball position was captured              |
| `x`          | string  | Normalized X-coordinate representing the length of the field (0.01 to 1.01) |
| `y`          | string  | Normalized Y-coordinate representing the width of the field (-0.02 to 1.02) |

### Interpreting X and Y Values

#### Coordinate system

* **X-axis (length of the field)**: Range from `0.01` to `1.01`
  * `0.01` = One goal line (attacking team's perspective)
  * `0.51` = Center of the field
  * `1.01` = Opposite goal line
* **Y-axis (width of the field)**: Range from `-0.02` to `1.02`
  * `-0.02` to `0.00` = Slightly outside left sideline
  * `0.00` = Left sideline
  * `0.51` = Center of the field (width)
  * `1.02` = Right sideline
  * `1.02` to `1.04` = Slightly outside right sideline

#### Data frequency

Ball tracking provides an average of **568 data entries per match**, which means you'll know where the ball is located approximately **6.3 times per minute**. High-action matches can have over 1,000 entries, providing up to **12 data points per minute**.

#### Data delay

Ball tracking has a slight delay of **\~15 seconds** but remains faster than live television data, making it suitable for near-real-time applications.

### Building a field display

To build a field display based on the `x` and `y` values provided by the Sportmonks ballCoordinates data, you'll map the values to a coordinate system representing the pitch.

#### Define the field dimensions

The normalized `x` values range from `0.01` to `1.01`, representing the length of the field (goal line to goal line).

The `y` values range from `-0.02` to `1.02`, covering the width (sideline to sideline).

#### Map coordinates

Scale each `x` and `y` value to your field dimensions in pixels or meters.

For example, if the field is displayed as 1000 pixels wide, `x = 0.51` would place the ball at 50% of the field length (500 pixels from one goal).

#### Set boundaries

Use the provided ranges to map boundaries and draw sideline and goal areas at `y = -0.02` to `1.02` and `x = 0.01` to `1.01`.

### Code examples

#### Python Example

```python
import requests

# API configuration
API_TOKEN = "YOUR_TOKEN"
FIXTURE_ID = 19568502

# Request ball coordinates
url = f"https://api.sportmonks.com/v3/football/fixtures/{FIXTURE_ID}"
params = {
    "api_token": API_TOKEN,
    "include": "ballCoordinates"
}

response = requests.get(url, params=params)
data = response.json()

# Extract ball coordinates
ball_coords = data['data'].get('ballcoordinates', [])

print(f"Total ball coordinate entries: {len(ball_coords)}")

# Process ball coordinates
for coord in ball_coords[:5]:  # Show first 5 entries
    x = float(coord['x'])
    y = float(coord['y'])
    timer = coord['timer']
    
    # Convert to field position description
    if x < 0.33:
        zone = "Defensive third"
    elif x < 0.67:
        zone = "Middle third"
    else:
        zone = "Attacking third"
    
    print(f"Time: {timer} - Position: ({x:.2f}, {y:.2f}) - Zone: {zone}")

# Calculate ball possession by zones
def calculate_zone_time(coordinates):
    """Calculate time spent in each third of the field"""
    zones = {"defensive": 0, "middle": 0, "attacking": 0}
    
    for coord in coordinates:
        x = float(coord['x'])
        if x < 0.33:
            zones["defensive"] += 1
        elif x < 0.67:
            zones["middle"] += 1
        else:
            zones["attacking"] += 1
    
    total = sum(zones.values())
    percentages = {k: (v / total * 100) for k, v in zones.items()}
    
    return percentages

zone_percentages = calculate_zone_time(ball_coords)
print(f"\nZone Distribution:")
print(f"Defensive third: {zone_percentages['defensive']:.1f}%")
print(f"Middle third: {zone_percentages['middle']:.1f}%")
print(f"Attacking third: {zone_percentages['attacking']:.1f}%")
```

#### JavaScript example

```javascript
// API configuration
const API_TOKEN = "YOUR_TOKEN";
const FIXTURE_ID = 19568502;

// Request ball coordinates
async function getBallCoordinates() {
    const url = `https://api.sportmonks.com/v3/football/fixtures/${FIXTURE_ID}`;
    const params = new URLSearchParams({
        api_token: API_TOKEN,
        include: "ballCoordinates"
    });

    try {
        const response = await fetch(`${url}?${params}`);
        const data = await response.json();
        
        const ballCoords = data.data.ballcoordinates || [];
        console.log(`Total ball coordinate entries: ${ballCoords.length}`);
        
        // Process ball coordinates
        ballCoords.slice(0, 5).forEach(coord => {
            const x = parseFloat(coord.x);
            const y = parseFloat(coord.y);
            const timer = coord.timer;
            
            // Convert to field position description
            let zone;
            if (x < 0.33) {
                zone = "Defensive third";
            } else if (x < 0.67) {
                zone = "Middle third";
            } else {
                zone = "Attacking third";
            }
            
            console.log(`Time: ${timer} - Position: (${x.toFixed(2)}, ${y.toFixed(2)}) - Zone: ${zone}`);
        });
        
        // Calculate zone distribution
        const zoneDistribution = calculateZoneDistribution(ballCoords);
        console.log("\nZone Distribution:");
        console.log(`Defensive third: ${zoneDistribution.defensive.toFixed(1)}%`);
        console.log(`Middle third: ${zoneDistribution.middle.toFixed(1)}%`);
        console.log(`Attacking third: ${zoneDistribution.attacking.toFixed(1)}%`);
        
    } catch (error) {
        console.error("Error fetching ball coordinates:", error);
    }
}

function calculateZoneDistribution(coordinates) {
    const zones = { defensive: 0, middle: 0, attacking: 0 };
    
    coordinates.forEach(coord => {
        const x = parseFloat(coord.x);
        if (x < 0.33) {
            zones.defensive++;
        } else if (x < 0.67) {
            zones.middle++;
        } else {
            zones.attacking++;
        }
    });
    
    const total = Object.values(zones).reduce((a, b) => a + b, 0);
    
    return {
        defensive: (zones.defensive / total) * 100,
        middle: (zones.middle / total) * 100,
        attacking: (zones.attacking / total) * 100
    };
}

// Example: Visualize on a canvas
function drawBallMovement(coordinates, canvasId) {
    const canvas = document.getElementById(canvasId);
    const ctx = canvas.getContext('2d');
    
    // Define field dimensions (in pixels)
    const fieldWidth = 1000;
    const fieldHeight = 680;
    
    // Draw field background
    ctx.fillStyle = '#228B22';
    ctx.fillRect(0, 0, fieldWidth, fieldHeight);
    
    // Draw field lines
    ctx.strokeStyle = '#FFFFFF';
    ctx.lineWidth = 2;
    ctx.strokeRect(0, 0, fieldWidth, fieldHeight); // Outer boundary
    ctx.beginPath();
    ctx.moveTo(fieldWidth / 2, 0);
    ctx.lineTo(fieldWidth / 2, fieldHeight); // Center line
    ctx.stroke();
    
    // Draw ball positions
    coordinates.forEach((coord, index) => {
        const x = parseFloat(coord.x) * fieldWidth;
        const y = parseFloat(coord.y) * fieldHeight;
        
        // Fade older positions
        const opacity = Math.max(0.1, index / coordinates.length);
        ctx.fillStyle = `rgba(255, 0, 0, ${opacity})`;
        ctx.beginPath();
        ctx.arc(x, y, 3, 0, 2 * Math.PI);
        ctx.fill();
    });
}

// Run the example
getBallCoordinates();
```

### Data availability

Ball coordinates are available for select leagues and fixtures. The data is typically available for:

* **Top-tier European leagues**: Premier League, La Liga, Serie A, Bundesliga, Ligue 1
* **Major cup competitions**: UEFA Champions League, UEFA Europa League
* **International tournaments**: World Cup, European Championship

Not all fixtures have ball coordinate data available. The availability depends on the stadium's tracking technology and data partnerships.

### Use cases

#### 1. **Heat maps**

Create visual heat maps showing where the ball spent most time during a match. Useful for tactical analysis and identifying attacking patterns.

#### 2. **Territory control**

Calculate which team controlled which areas of the pitch by analyzing ball position data combined with possession information.

#### 3. **Attacking patterns**

Identify common attacking routes and build-up play patterns by analyzing ball movement in the attacking third.

#### 4. **Defensive analysis**

Track how teams defend by analyzing ball positions when the opposition has possession.

#### 5. **Live betting insights**

For live betting applications, ball position combined with match state provides valuable insights into scoring probability.

### Nested includes

The `ballCoordinates` include currently does not support nested includes. Each ball coordinate entry contains only the fields described above.

### Related includes

* [**Pressure Index**](https://docs.sportmonks.com/football/tutorials-and-guides/tutorials/includes/pressure-index) - Combine with ball coordinates for comprehensive tactical analysis
* [**Events**](https://docs.sportmonks.com/football/tutorials-and-guides/tutorials/includes/events) - Cross-reference ball positions with match events (goals, shots, corners)
* [**Statistics**](https://docs.sportmonks.com/football/definitions/types/statistics) - Correlate ball position data with match statistics

### Best practices

1. **Cache the data**: Ball coordinate data doesn't change after a match ends. Cache completed matches to reduce API calls.
2. **Filter by time period**: For specific analysis, filter ball coordinates by `timer` to focus on particular match phases.
3. **Normalize your display**: Always normalize your field display dimensions to match the 0.01-1.01 (x) and -0.02-1.02 (y) coordinate system.
4. **Handle missing data**: Not all fixtures have ball coordinate data. Always check if the `ballcoordinates` array exists and has data.
5. **Combine with other includes**: Use `&include=ballCoordinates,events,statistics` to get comprehensive match analysis data in one request.

### Common issues and solutions

#### Issue: No ball coordinate data returned

**Solution**: Ball coordinates are only available for fixtures with advanced tracking technology. Check if the league/fixture supports this feature.

#### Issue: Coordinates seem inverted

**Solution**: Remember that the coordinate system is normalized. Always map to your display system correctly: x represents length (0.01 = one goal, 1.01 = other goal).

#### Issue: Too much data to process

**Solution**: Use filters to get specific time ranges, or sample the data (e.g., every 10th entry) for visualization purposes.

### Summary

The `ballCoordinates` include provides powerful ball tracking data for tactical analysis, visualization, and betting insights. With normalised coordinates and frequent updates (6-12 times per minute), it enables detailed analysis of ball movement, territory control, and attacking patterns. Combine with other includes like Pressure Index and Events for comprehensive match analysis.
