From 5b35173431ea095dbadd0f7e8887acb96ae053fa Mon Sep 17 00:00:00 2001 From: Pieter Robberechts Date: Sun, 3 Dec 2023 23:09:28 +0100 Subject: [PATCH] Fix shot end coordinates for Opta deserializer The end coordinates of blocked and saved shots were based on the projection of the shot on the goal mouth instead of the shot's actual end location. This commit uses the x and y coordinates of the location were the shots was blocked (qualifiers 146 and 147) and inversely projects the goalmouth z coordinate (qualifier 103) on the location were the shot was blocked. Fixes #244 --- .../serializers/event/opta/deserializer.py | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/kloppy/infra/serializers/event/opta/deserializer.py b/kloppy/infra/serializers/event/opta/deserializer.py index 805625c4..945111b1 100644 --- a/kloppy/infra/serializers/event/opta/deserializer.py +++ b/kloppy/infra/serializers/event/opta/deserializer.py @@ -1,3 +1,4 @@ +import math from typing import Tuple, Dict, List, NamedTuple, IO, Optional import logging from datetime import datetime @@ -342,7 +343,9 @@ def _parse_shot( result = None qualifiers = _get_event_qualifiers(raw_qualifiers) - result_coordinates = _get_end_coordinates(raw_qualifiers) + result_coordinates = _get_end_coordinates( + raw_qualifiers, start_coordinates=coordinates + ) if result == ShotResult.OWN_GOAL: if isinstance(result_coordinates, Point3D): result_coordinates = Point3D( @@ -489,28 +492,43 @@ def _team_from_xml_elm(team_elm, f7_root) -> Team: return team -def _get_end_coordinates(raw_qualifiers: Dict[int, str]) -> Optional[Point]: +def _get_end_coordinates( + raw_qualifiers: Dict[int, str], start_coordinates: Optional[Point] = None +) -> Optional[Point]: x, y, z = None, None, None + # pass - if 140 in raw_qualifiers: + if 140 in raw_qualifiers and 141 in raw_qualifiers: x = float(raw_qualifiers[140]) - if 141 in raw_qualifiers: y = float(raw_qualifiers[141]) + # blocked shot - if 146 in raw_qualifiers: + elif 146 in raw_qualifiers and 147 in raw_qualifiers: x = float(raw_qualifiers[146]) - if 147 in raw_qualifiers: y = float(raw_qualifiers[147]) + if 102 in raw_qualifiers and 103 in raw_qualifiers: + # the goal mouth z-coordinate is projected back to the location + # where the shot was blocked + assert start_coordinates is not None + x0, y0 = start_coordinates.x, start_coordinates.y + x_proj = float(100) + y_proj = float(raw_qualifiers[102]) + z_proj = float(raw_qualifiers[103]) + adj_proj = math.sqrt((x_proj - x0) ** 2 + (y_proj - y0) ** 2) + adj_block = math.sqrt((x - x0) ** 2 + (y - y0) ** 2) + z = z_proj / adj_proj * adj_block + # passed the goal line - if 102 in raw_qualifiers: + elif 102 in raw_qualifiers and 103 in raw_qualifiers: x = float(100) y = float(raw_qualifiers[102]) - if 103 in raw_qualifiers: z = float(raw_qualifiers[103]) + if x is not None and y is not None and z is not None: return Point3D(x=x, y=y, z=z) if x is not None and y is not None: return Point(x=x, y=y) + return None