Java 案例:统计斜长传精准度
下面我用一个完整的 Java 案例来演示如何统计足球比赛中斜长传(Diagonal Long Pass)的精准度,核心思路是:定义传球事件、判定斜长传、判定是否精准、汇总统计。

数据模型
import java.util.*;
import java.util.stream.*;
/**
* 传球事件
*/
class PassEvent {
private String playerName; // 传球球员
private double startX, startY; // 起点坐标(球场 105x68,原点左下角)
private double endX, endY; // 终点坐标
private boolean reachedTeammate; // 是否被队友接到(未被断/未出界)
private String receiverTeam; // 接球方队伍(用于判定是否队友)
public PassEvent(String playerName, double startX, double startY,
double endX, double endY,
boolean reachedTeammate, String receiverTeam) {
this.playerName = playerName;
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
this.reachedTeammate = reachedTeammate;
this.receiverTeam = receiverTeam;
}
// getter
public String getPlayerName() { return playerName; }
public double getStartX() { return startX; }
public double getStartY() { return startY; }
public double getEndX() { return endX; }
public double getEndY() { return endY; }
public boolean isReachedTeammate() { return reachedTeammate; }
public String getReceiverTeam() { return receiverTeam; }
/** 传球距离(米) */
public double getDistance() {
return Math.hypot(endX - startX, endY - startY);
}
/** 传球方向与场地横向(x轴)的夹角(度) */
public double getAngleToXAxis() {
return Math.toDegrees(Math.atan2(endY - startY, endX - startX));
}
}
斜长传判定 + 精准度统计
class PassAnalyzer {
/** 长传阈值:>=25 米 */
private static final double LONG_PASS_MIN_DISTANCE = 25.0;
/** 斜传判定:与 x 轴夹角在 [30°, 150°] 之间(即明显带纵向分量) */
private static final double MIN_ANGLE = 30.0;
private static final double MAX_ANGLE = 150.0;
/**
* 判断是否为斜长传
*/
public static boolean isDiagonalLongPass(PassEvent p) {
double dist = p.getDistance();
double angle = Math.abs(p.getAngleToXAxis());
// 归一到 [0,180]
if (angle > 180) angle = 360 - angle;
boolean isLong = dist >= LONG_PASS_MIN_DISTANCE;
boolean isDiagonal = angle >= MIN_ANGLE && angle <= MAX_ANGLE;
return isLong && isDiagonal;
}
/**
* 统计某球员斜长传精准度
* 精准定义:球被本方队友接到
*/
public static void printPlayerStats(List<PassEvent> allPasses, String teamName) {
Map<String, List<PassEvent>> byPlayer = allPasses.stream()
.filter(PassAnalyzer::isDiagonalLongPass)
.collect(Collectors.groupingBy(PassEvent::getPlayerName));
System.out.printf("%-10s %6s %6s %8s%n",
"球员", "总数", "成功", "精准度");
System.out.println("-------------------------------------");
byPlayer.forEach((name, list) -> {
long total = list.size();
long success = list.stream()
.filter(p -> p.isReachedTeammate()
&& teamName.equals(p.getReceiverTeam()))
.count();
double accuracy = total == 0 ? 0 : success * 100.0 / total;
System.out.printf("%-10s %6d %6d %7.2f%%%n",
name, total, success, accuracy);
});
}
/**
* 统计全队总体斜长传精准度
*/
public static double teamAccuracy(List<PassEvent> allPasses, String teamName) {
List<PassEvent> diag = allPasses.stream()
.filter(PassAnalyzer::isDiagonalLongPass)
.collect(Collectors.toList());
long total = diag.size();
long success = diag.stream()
.filter(p -> p.isReachedTeammate() && teamName.equals(p.getReceiverTeam()))
.count();
return total == 0 ? 0 : success * 100.0 / total;
}
}
测试用例
public class DiagonalPassDemo {
public static void main(String[] args) {
List<PassEvent> passes = new ArrayList<>();
// 球员A:3次斜长传,2次成功
passes.add(new PassEvent("A", 30, 20, 60, 50, true, "Home")); // 斜长传 ✔
passes.add(new PassEvent("A", 20, 10, 55, 45, true, "Home")); // 斜长传 ✔
passes.add(new PassEvent("A", 40, 30, 70, 10, false, "Away")); // 斜长传,失败
// 球员B:2次斜长传,1次成功
passes.add(new PassEvent("B", 50, 55, 80, 25, true, "Home")); // 斜长传 ✔
passes.add(new PassEvent("B", 10, 60, 40, 30, false, "Away")); // 斜长传 ✘
// 干扰项:直长传(角度接近0),不算斜长传
passes.add(new PassEvent("C", 20, 34, 60, 34, true, "Home"));
// 干扰项:短斜传(距离<25),不算
passes.add(new PassEvent("C", 30, 30, 40, 40, true, "Home"));
PassAnalyzer.printPlayerStats(passes, "Home");
System.out.println("-------------------------------------");
System.out.printf("全队斜长传精准度:%.2f%%%n",
PassAnalyzer.teamAccuracy(passes, "Home"));
}
}
输出结果
球员 总数 成功 精准度
-------------------------------------
A 3 2 66.67%
B 2 1 50.00%
-------------------------------------
全队斜长传精准度:60.00%
关键设计点说明
| 环节 | 说明 |
|---|---|
| 斜长传定义 | 距离 ≥ 25m 且 与 x 轴夹角在 30°~150°(保留横向推进又带纵向变化) |
| 精准判定 | 优先看 reachedTeammate;进一步验证 receiverTeam == 本方,避免把“对方解围后本方捡到”算成功 |
| 角度归一化 | atan2 会返回负角度,需要 Math.abs 或 360-angle 处理,避免方向判断遗漏 |
| 球场地坐标 | 采用 105×68 米真实球场尺度,便于把像素坐标换算 |
| 可扩展 | isDiagonalLongPass 中的阈值可配置;如需按“传向进攻三区”进一步筛选,只需再加禁区判定 |
进一步优化建议
- 按传球方向细分:区分“左路→右路”和“右路→左路”,统计习惯脚差异。
- 加入压力因子:距离传球起点最近的对方球员 < 3m 时标记为“受压迫传球”,单独统计精准度。
- 落点热区:用
(endX, endY)做 2D 直方图,分析斜长传常见落点是否集中(常用于分析“斜长传找边锋/中锋”战术)。 - 接入真实数据:可用 StatsBomb 或 Wyscout 的 JSON 事件流,替换
PassEvent构造即可复用整套逻辑。
如果需要加入落点热区绘制或接入 StatsBomb 数据解析的代码,我可以继续补充。
版权声明:本站文章为原创整理,转载请注明出处。