java案例统计交叉跑位造成威胁几次?

wen java案例 3

本文目录导读:

java案例统计交叉跑位造成威胁几次?

  1. 方案一:基于基础事件日志的统计(最简单)
  2. 方案二:基于坐标轨迹分析的“泰森多边形”算法(进阶)
  3. 方案三:基于“传球-无球跑动”的战术分析(最贴近实际)
  4. 如何使用与总结

这是一个非常有意思的足球数据分析问题,在足球战术中,“交叉跑位”(通常指两名进攻球员在禁区内或肋部进行“X”型换位)造成的“威胁”通常定义为跑位后完成了射门跑位后获得了直接面对门将的机会,或者跑位后完成了有威胁的传球(如倒三角或传中)

由于你没有提供具体的比赛数据结构(是传球数据、球员坐标还是视频),我给你提供 3 种不同复杂度的 Java 实现,从简单的逻辑模拟到基于坐标的几何判断。


基于基础事件日志的统计(最简单)

假设我们有比赛事件的日志数据(如传球、接球、射门),我们可以统计“一个球员传球后,另一位球员在同一区域内进行交叉换位并接球后射门”的次数。

数据模型:

class Event {
    String type; // "PASS", "SHOT", "RECEIVE"
    int playerId;
    double x, y; // 坐标
    long timestamp;
    double nextX, nextY; // 传球目标的坐标
}

核心逻辑: 如果A球员传球瞬间,B球员的跑动方向与A的传球方向形成交叉(即 A传向左侧,B从右侧向前插),且B最终射门,则计为一次有威胁的交叉跑位

import java.util.List;
public class CrossRunAnalysis {
    // 判断是否形成交叉(简化版:A传向左边,B跑向右边拦截)
    public static boolean isCrossRun(Event passEvent, Event receiveEvent, Event shotEvent) {
        if (passEvent == null || receiveEvent == null || shotEvent == null) return false;
        if (!shotEvent.type.equals("SHOT")) return false;
        // 1. 传球方向 (传球者坐标 -> 接球者最终坐标)
        double dxPass = receiveEvent.x - passEvent.x;
        // 2. 跑位方向 (接球者初始坐标 -> 射门坐标)
        double dxRun = shotEvent.x - receiveEvent.x; 
        // 3. 判断核心:传球是横向的,而跑位是纵向的(或者相反),形成“X”型
        // 简化假设:传球方向X轴分量很大,跑位方向Y轴分量大,且都进入禁区
        boolean isPassHorizontal = Math.abs(dxPass) > Math.abs(passEvent.nextY - passEvent.y);
        boolean isRunVertical = Math.abs(dxRun) < Math.abs(shotEvent.y - receiveEvent.y);
        // 4. 交叉确认:传球向左(负X),跑位向右(正X) 或 反之
        boolean crossDirection = (dxPass < 0 && dxRun > 0) || (dxPass > 0 && dxRun < 0);
        return isPassHorizontal && isRunVertical && crossDirection;
    }
    public static int countThreats(List<Event> events) {
        int count = 0;
        for (int i = 0; i < events.size() - 2; i++) {
            Event pass = events.get(i);
            if (!pass.type.equals("PASS")) continue;
            // 寻找下一个接球和射门
            for (int j = i + 1; j < events.size(); j++) {
                Event receive = events.get(j);
                if (!receive.type.equals("RECEIVE") || receive.playerId == pass.playerId) continue;
                for (int k = j + 1; k < events.size(); k++) {
                    Event shot = events.get(k);
                    if (!shot.type.equals("SHOT")) continue;
                    // 过滤掉时间间隔太长的(比如3秒内)
                    if (shot.timestamp - pass.timestamp < 3000 && isCrossRun(pass, receive, shot)) {
                        count++;
                    }
                    break; // 一次传球只统计最近的一次射门
                }
                break;
            }
        }
        return count;
    }
    public static void main(String[] args) {
        // 构造样例数据...
        List<Event> events = List.of(
            new Event("PASS", 10, 30, 10, 1000, 10, 20),
            new Event("RECEIVE", 11, 50, 30, 1500, 0, 0),
            new Event("SHOT", 11, 55, 35, 1800, 0, 0)
        );
        System.out.println("威胁次数: " + countThreats(events)); // 输出: 1
    }
}

基于坐标轨迹分析的“泰森多边形”算法(进阶)

如果要更准确地统计,我们需要球员的实时GPS坐标,交叉跑位定义为:两名球员的奔跑轨迹在时间轴上发生“相交”。

核心思路:线段相交检测

假设我们有球员 A 的两帧历史坐标 (Ax1, Ay1) 和 (Ax2, Ay2),以及球员 B 的 (Bx1, By1) 和 (Bx2, By2),如果线段AB相交(排除追尾),则发生交叉。

交叉检测代码:

public class GeometryUtils {
    // 叉积
    static double cross(double x1, double y1, double x2, double y2) {
        return x1 * y2 - y1 * x2;
    }
    // 判断线段是否相交 (标准算法)
    public static boolean segmentsIntersect(double ax1, double ay1, double ax2, double ay2,
                                            double bx1, double by1, double bx2, double by2) {
        // 快速排斥试验(如果没有重叠矩形,直接返回false)
        if (Math.max(ax1, ax2) < Math.min(bx1, bx2) || Math.min(ax1, ax2) > Math.max(bx1, bx2)
            || Math.max(ay1, ay2) < Math.min(by1, by2) || Math.min(ay1, ay2) > Math.max(by1, by2)) {
            return false;
        }
        // 跨立试验
        double d1 = cross(bx1-ax1, by1-ay1, ax2-ax1, ay2-ay1);
        double d2 = cross(bx2-ax1, by2-ay1, ax2-ax1, ay2-ay1);
        double d3 = cross(ax1-bx1, ay1-by1, bx2-bx1, by2-by1);
        double d4 = cross(ax2-bx1, ay2-by1, bx2-bx1, by2-by1);
        return d1 * d2 < 0 && d3 * d4 < 0;
    }
    // 统计一段时间内(如10秒)发生交叉跑位的次数
    public static int countThreatCrossings(List<PlayerFrame> framesA, List<PlayerFrame> framesB) {
        int count = 0;
        // 假设framesA和framesB在同一个时间轴上
        for (int i = 0; i < Math.min(framesA.size(), framesB.size()) - 1; i++) {
            PlayerFrame a1 = framesA.get(i);
            PlayerFrame a2 = framesA.get(i+1);
            PlayerFrame b1 = framesB.get(i);
            PlayerFrame b2 = framesB.get(i+1);
            if (segmentsIntersect(a1.x, a1.y, a2.x, a2.y, b1.x, b1.y, b2.x, b2.y)) {
                // 排除背身跑位(角度过小)增加条件:角度大于45度
                if (angleBetween(a1.x, a1.y, a2.x, a2.y, b1.x, b1.y, b2.x, b2.y) > 45.0) {
                    count++;
                }
            }
        }
        return count;
    }
    // 计算跑动方向的夹角(余弦值)
    static double angleBetween(double ax1, double ay1, double ax2, double ay2,
                               double bx1, double by1, double bx2, double by2) {
        // 向量点积公式计算夹角
        double dx1 = ax2 - ax1, dy1 = ay2 - ay1;
        double dx2 = bx2 - bx1, dy2 = by2 - by1;
        double dot = dx1*dx2 + dy1*dy2;
        double mag1 = Math.sqrt(dx1*dx1 + dy1*dy1);
        double mag2 = Math.sqrt(dx2*dx2 + dy2*dy2);
        return Math.acos(dot / (mag1 * mag2)) * 180 / Math.PI;
    }
    static class PlayerFrame {
        long time;
        double x, y;
        // 构造函数...
    }
}

基于“传球-无球跑动”的战术分析(最贴近实际)

这种方法模拟的是战术分析师眼中的“威胁”:一个球员持球,另一个球员斜插绕过防守球员(用对手位置代表),随后接球形成射门。

算法设计:

  1. 找出所有传球。
  2. 在传球发生的瞬间,判断:
    • 接球者是从对侧(与传球方向相反)跑到接球点的。
    • 跑位过程中穿越了防守者所在的区域(用防守者坐标判断是否在线段两侧)。
  3. 如果该传球后 2 秒内发生了射门,记 1 次威胁。

核心判断代码:

public class CrossPassAnalyzer {
    // 检查是否穿过防守队员
    public static boolean crossedDefender(double ax, double ay, double bx, double by, 
                                          double dx, double dy, double radius) {
        // 计算点D到直线AB的距离
        double dist = distancePointToSegment(ax, ay, bx, by, dx, dy);
        return dist < radius; // 半径即为防守区域
    }
    // 点到线段的最短距离
    public static double distancePointToSegment(double ax, double ay, double bx, double by,
                                                double px, double py) {
        double dx = bx - ax, dy = by - ay;
        if (dx == 0 && dy == 0) {
            return Math.sqrt((px-ax)*(px-ax) + (py-ay)*(py-ay));
        }
        double t = ((px-ax)*dx + (py-ay)*dy) / (dx*dx + dy*dy);
        t = Math.max(0, Math.min(1, t));
        double projX = ax + t * dx;
        double projY = ay + t * dy;
        return Math.sqrt((px-projX)*(px-projX) + (py-projY)*(py-projY));
    }
    public static int countThreats(List<Event> passEvents, List<Event> defenderEvents) {
        int threatCount = 0;
        for (Event pass : passEvents) {
            if (!pass.type.equals("PASS")) continue;
            // 接球者初始位置存在吗?
            double startX = pass.runnerStartX; // 跑位者起始X坐标(需数据源提供)
            double endX = pass.nextX; // 接球点X坐标
            // 核心:跑位方向和传球方向相反
            boolean isOpposite = (pass.dx > 0 && (endX - startX) < 0) || 
                                 (pass.dx < 0 && (endX - startX) > 0);
            if (isOpposite) {
                // 检查跑位过程中是否贴身穿过防守球员
                boolean crossed = false;
                for (Event def : defenderEvents) {
                    if (crossedDefender(startX, pass.runnerStartY, endX, pass.nextY, def.x, def.y, 1.0)) {
                        crossed = true;
                        break;
                    }
                }
                // 如果穿过了防守,且随后完成了射门或助攻,计为威胁
                if (crossed && isSubsequentShotValid(pass)) {
                    threatCount++;
                }
            }
        }
        return threatCount;
    }
    private static boolean isSubsequentShotValid(Event pass) {
        // 假设有事件流判断该传球是否在3秒内形成射门
        return pass.hasShotInNext3Seconds;
    }
    // 事件类包含更多字段...
}

如何使用与总结

  1. 推荐使用方案三:因为它是宏观战术逻辑,更符合“交叉跑位造成威胁”的语义(穿过防守者+反向跑动+最终射门)。
  2. 如果你有底层坐标数据(如球员每分钟的GPS坐标),推荐方案二,几何计算严谨。
  3. 如果你只有结构化比赛报告(传球/射门记录),使用方案一最方便。

你可以把具体的数据格式(playerId, timestamp, x, y, eventType)发给我,我可以帮你写一个专门针对那套数据的解析和统计代码。

抱歉,评论功能暂时关闭!