本文目录导读:

我来提供一个Java案例,用于统计足球比赛中交叉跑位造成威胁的次数,这个案例会模拟进攻球员通过交叉跑位摆脱防守、形成威胁(如射门、关键传球)的场景。
完整Java代码
import java.util.*;
import java.util.stream.Collectors;
/**
* 球员位置
*/
class Position {
private double x; // 横向坐标 0-100
private double y; // 纵向坐标 0-100
public Position(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public double distanceTo(Position other) {
return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
}
@Override
public String toString() {
return String.format("(%.1f, %.1f)", x, y);
}
}
/**
* 球员跑位轨迹点
*/
class TrackPoint {
private final String playerId;
private final Position position;
private final long timestamp; // 毫秒
public TrackPoint(String playerId, Position position, long timestamp) {
this.playerId = playerId;
this.position = position;
this.timestamp = timestamp;
}
public String getPlayerId() { return playerId; }
public Position getPosition() { return position; }
public long getTimestamp() { return timestamp; }
}
/**
* 威胁事件(射门、关键传球等)
*/
class ThreatEvent {
private final long timestamp;
private final String playerId;
private final String type; // SHOT, KEY_PASS, ASSIST
public ThreatEvent(long timestamp, String playerId, String type) {
this.timestamp = timestamp;
this.playerId = playerId;
this.type = type;
}
public long getTimestamp() { return timestamp; }
public String getPlayerId() { return playerId; }
public String getType() { return type; }
}
/**
* 交叉跑位威胁统计分析器
*/
public class CrossRunThreatAnalyzer {
// 交叉跑位判定参数
private static final double CROSS_DISTANCE_THRESHOLD = 3.0; // 两名球员交叉时最近距离(米)
private static final long CROSS_TIME_WINDOW = 1500; // 交叉时间窗口(毫秒)
private static final long THREAT_TIME_WINDOW = 5000; // 交叉后多久内形成威胁(毫秒)
/**
* 统计交叉跑位造成威胁的次数
*/
public int countCrossRunThreats(List<TrackPoint> trackPoints, List<ThreatEvent> threatEvents) {
// 1. 按球员分组轨迹
Map<String, List<TrackPoint>> byPlayer = trackPoints.stream()
.collect(Collectors.groupingBy(TrackPoint::getPlayerId));
// 2. 按时间排序
byPlayer.values().forEach(list ->
list.sort(Comparator.comparingLong(TrackPoint::getTimestamp)));
Set<Long> countedThreats = new HashSet<>(); // 去重,避免同一威胁重复计数
int crossThreatCount = 0;
// 3. 两两球员检测交叉跑位
List<String> playerIds = new ArrayList<>(byPlayer.keySet());
for (int i = 0; i < playerIds.size(); i++) {
for (int j = i + 1; j < playerIds.size(); j++) {
String p1 = playerIds.get(i);
String p2 = playerIds.get(j);
List<TrackPoint> track1 = byPlayer.get(p1);
List<TrackPoint> track2 = byPlayer.get(p2);
List<Long> crossTimes = detectCrossRuns(track1, track2);
// 4. 对每次交叉,判断其后是否产生威胁
for (long crossTime : crossTimes) {
for (ThreatEvent threat : threatEvents) {
long delta = threat.getTimestamp() - crossTime;
// 交叉后 0 ~ THREAT_TIME_WINDOW 内产生威胁
if (delta >= 0 && delta <= THREAT_TIME_WINDOW) {
// 威胁由交叉的两名球员之一发起
if ((threat.getPlayerId().equals(p1) || threat.getPlayerId().equals(p2))
&& countedThreats.add(threat.getTimestamp() * 1000
+ threat.getPlayerId().hashCode())) {
crossThreatCount++;
System.out.printf("交叉跑位[p1=%s, p2=%s] 于 t=%d ms," +
"在 %d ms 后由 %s 形成威胁(%s)%n",
p1, p2, crossTime, delta,
threat.getPlayerId(), threat.getType());
}
}
}
}
}
}
return crossThreatCount;
}
/**
* 检测两名球员之间发生的所有交叉跑位时刻
*/
private List<Long> detectCrossRuns(List<TrackPoint> t1, List<TrackPoint> t2) {
List<Long> crossTimes = new ArrayList<>();
int i = 0, j = 0;
boolean lastWasClose = false;
while (i < t1.size() && j < t2.size()) {
TrackPoint a = t1.get(i);
TrackPoint b = t2.get(j);
long dt = Math.abs(a.getTimestamp() - b.getTimestamp());
if (dt > CROSS_TIME_WINDOW) {
// 时间差太大,推进时间较早的点
if (a.getTimestamp() < b.getTimestamp()) i++; else j++;
continue;
}
double dist = a.getPosition().distanceTo(b.getPosition());
boolean isClose = dist <= CROSS_DISTANCE_THRESHOLD;
// 由远及近再拉开:从"近"状态第一次出现即视为交叉点
if (isClose && !lastWasClose) {
long crossTime = Math.max(a.getTimestamp(), b.getTimestamp());
crossTimes.add(crossTime);
}
lastWasClose = isClose;
if (a.getTimestamp() < b.getTimestamp()) i++; else j++;
}
return crossTimes;
}
// ================== 测试 ==================
public static void main(String[] args) {
List<TrackPoint> tracks = new ArrayList<>();
// 球员 A7 从左向右跑
tracks.add(new TrackPoint("A7", new Position(30, 50), 1000));
tracks.add(new TrackPoint("A7", new Position(40, 50), 1500));
tracks.add(new TrackPoint("A7", new Position(50, 50), 2000)); // 交叉点
tracks.add(new TrackPoint("A7", new Position(60, 50), 2500));
// 球员 B9 从右向左跑
tracks.add(new TrackPoint("B9", new Position(70, 50), 1000));
tracks.add(new TrackPoint("B9", new Position(60, 50), 1500));
tracks.add(new TrackPoint("B9", new Position(50, 50), 2000)); // 交叉点
tracks.add(new TrackPoint("B9", new Position(40, 50), 2500));
// 无关球员 C10
tracks.add(new TrackPoint("C10", new Position(20, 20), 1000));
tracks.add(new TrackPoint("C10", new Position(25, 25), 2000));
// 威胁事件:交叉后 3 秒,A7 射门
List<ThreatEvent> threats = Arrays.asList(
new ThreatEvent(5000, "A7", "SHOT"),
new ThreatEvent(9000, "C10", "KEY_PASS") // 无关球员的威胁
);
CrossRunThreatAnalyzer analyzer = new CrossRunThreatAnalyzer();
int count = analyzer.countCrossRunThreats(tracks, threats);
System.out.println("\n>>> 交叉跑位造成威胁的次数:" + count);
}
}
运行结果
交叉跑位[p1=A7, p2=B9] 于 t=2000 ms,在 3000 ms 后由 A7 形成威胁(SHOT)
>>> 交叉跑位造成威胁的次数:1
核心算法思路
| 步骤 | 说明 |
|---|---|
| 数据建模 | TrackPoint 记录球员位置+时间;ThreatEvent 记录射门/关键传球等威胁 |
| 分组排序 | 按球员ID分组,按时间戳排序 |
| 交叉检测 | 双指针遍历两条轨迹,在同一时间窗口内,两名球员距离 ≤ 3米判定为"接近",从"远"到"近"的状态跳变即视为一次交叉点 |
| 威胁关联 | 在交叉点之后的 5 秒内,若任一交叉球员产生威胁事件,则计为交叉跑位威胁 +1 |
| 去重 | 用 Set 避免同一次威胁被多次统计 |
可扩展方向
- 速度方向判断:加入方向向量,判断是否真的"反向交叉"(真正的cross run),而非静态接近。
- 防守球员关联:加入防守球员数据,判断交叉是否造成"摆脱防守"。
- 威胁权重:射门=1,关键传球=0.8,助攻=1.5,而非简单计数。
- xG(预期进球):结合位置计算威胁值,而非布尔判断。
- 真实数据接入:可对接 Sportradar、StatsBomb 等 API 的 tracking data。
如果你有真实的追踪数据格式(如 JSON/CSV),我可以帮你把解析部分也补上,需要针对11人全场或特定战术场景扩展吗?
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播