Second Spectrum adapter

Second Spectrum: Tracking and physical packets for analysis views.

Second Spectrum data is not an event feed. Campos now ships reusable packets for tracking frames, physical windows, sequence sync, and team-shape overlays so analysis apps can build freeze views, sequence atlases, and load timelines without burying provider metadata in UI code.

Input shape
Parsed tracking frames, parsed physical-summary rows, parsed physical split blocks, and app-owned sequence moments.
Status
Tracking / visual packets
Raw feed
Second Spectrum
Provider-shaped. Their coordinates, their qualifier codes, their outcome strings.
Messy
Campos adapter
fromSecondSpectrum.trackingFrame(rawFrame, ctx)
Flips direction, standardises outcomes, filters own-goals and shootouts, resolves player labels.
One call
Typed data
TrackingFrameSnapshot
Drop into <ShotMap />, your own React view, a server-side report, or an analysis script.
Ready
Tracking is not event space

Second Spectrum frames are normalised into an absolute full-pitch frame because a freeze view shows both teams at once. That is intentionally different from the attacker-relative event co-ordinates used by Opta, StatsBomb, Wyscout, and WhoScored.

Get it running

One import, one call per surface.

import { fromSecondSpectrum } from "@withqwerty/campos-adapters";

const frame = fromSecondSpectrum.trackingFrame(rawFrame, {
  matchId,
  pitchDimensions,
});

const physical = fromSecondSpectrum.physicalSummaryWindows(summaryRows, { matchId });
const shape = fromSecondSpectrum.teamShapeSnapshot(snapshot, {
  matchId,
  pitchDimensions,
});
What this adapter ships

The calls you can make today.

Shipped
trackingFrame(s)() returns TrackingFrameSnapshot[]

Players and ball in absolute full-pitch space.

Second Spectrum notes: Absolute full-pitch frame for players and ball; not attacker-relative event space.

Shipped
physicalWindow(s)() returns PhysicalWindow[]

Physical output rows with missingness preserved.

Second Spectrum notes: Physical summary rows and team split blocks map to stable nullable metrics.

Shipped
sequenceMoment(s)() returns SequenceMomentSnapshot[]

Video, event, and tracking sync packets.

Second Spectrum notes: Sync packet only: event IDs, frame IDs, video target time, evidence, and caveats.

Shipped
teamShapeSnapshot(s)() returns TeamShapeSnapshot[]

Lines, centroids, hulls, movements, and candidate lanes.

Second Spectrum notes: Lines, centroids, polygons, movement arrows, facing hints, and candidate lanes.

What you can build with this

Charts that drop straight onto this adapter's output.

Supported cards plot from this adapter with no extra work. Partial cards plot, but read the scope note. Dimmed cards need a surface this provider doesn't ship.

Pitch charts

Plot events, shots, passes, and formations directly on the pitch.

Match-time + xG

Time-series and xG-driven views from shots().

Bring your own aggregates

These charts are adapter-independent — they take pre-aggregated inputs.

Tracking frames

Players and ball, without tactical overclaiming.

trackingFrame() and trackingFrames() project player and ball positions into Campos percentages. They preserve IDs, shirt numbers, speeds, ball height, provider frame IDs, and source metadata. They do not infer body orientation, intent, pressing quality, or responsibility.

const frame = fromSecondSpectrum.trackingFrame(rawFrame, {
  matchId,
  pitchDimensions: { length: 105, width: 68 },
});

frame.coordinateFrame; // "absolute-pitch"
frame.players[0];      // { side, playerId, optaId, shirtNumber, x, y, speed }
Physical windows

Summary rows and split blocks become stable load packets.

physicalSummaryWindows() maps parsed player summary rows into full-match windows. physicalTeamSplitWindows() maps parsed team split blocks into time bins. Missing HSR, sprint, and count values stay null instead of becoming zeros.

const playerLoad = fromSecondSpectrum.physicalSummaryWindows(summaryRows, {
  matchId,
});

const teamBins = fromSecondSpectrum.physicalTeamSplitBlocks(splitBlocks, {
  matchId,
});
Sequence and shape

The packet for freeze views and sequence atlases.

sequenceMoment() carries video time, event IDs, tracking frame IDs, evidence, and caveats. teamShapeSnapshot() carries reusable overlays: average line proxies, centroids, convex-hull polygons, movement arrows, movement-derived facing hints, and candidate lanes. They are visual evidence, not a tactical conclusion.

Current scope

What's honest about this adapter today.

What Campos owns

Reusable packets.

Co-ordinate projection, stable IDs, nullable physical metrics, source metadata, evidence, and caveats.

What the app owns

Parsing and interpretation.

Irregular CSV report parsing, sequence selection, classifications, calibration, pressure quality, repeatability, and prose claims remain outside this adapter.

Current warning

No body orientation.

Movement-derived facing hints are only movement vectors. They are not body shape, scanning, cover shadow, or decision-quality evidence.

Lineage

What this adapter borrows from whom.

Concrete credits for the projects that did the underlying research or laid the reference tables we read from.

kloppy Python

Provider type-ID → canonical kind mapping, coordinate transformers, and direction-by-period normalisation patterns. Our reference for Opta, StatsBomb, Wyscout, Stats Perform, and Sportec.

mplsoccer Python

Pitch-standardisation conventions we cross-checked our canonical Campos coordinate frame against.

Compare providers