diff --git a/movement/roi/base.py b/movement/roi/base.py index f4b192c9..0159d083 100644 --- a/movement/roi/base.py +++ b/movement/roi/base.py @@ -287,3 +287,69 @@ def distance_to(self, point: ArrayLike, boundary: bool = False) -> float: """ from_where = self.region.boundary if boundary else self.region return shapely.distance(from_where, shapely.Point(point)) + + @broadcastable_method( + only_broadcastable_along="space", new_dimension_name="vector to" + ) + def vector_to( + self, + point: ArrayLike, + boundary: bool = False, + direction: Literal[ + "point to region", "region to point" + ] = "point to region", + unit: bool = True, + ) -> np.ndarray: + """Compute the vector from a point to the region. + + Specifically, the vector is directed from the given ``point`` to the + nearest point within the region. Points within the region return the + zero vector. + + Parameters + ---------- + point : ArrayLike + Coordinates of a point to compute the vector to (or from) the + region. + boundary : bool + If True, finds the vector to the nearest point on the boundary of + the region, instead of the nearest point within the region. + (See Notes). Default is False. + direction : Literal["point to region", "region to point"] + Which direction the returned vector should point in. Default is + "point to region". + unit : bool + If True, the unit vector in the appropriate direction is returned, + otherwise the displacement vector is returned. Default is False. + + Returns + ------- + np.ndarray + Vector directed between the point and the region. + + Notes + ----- + If given a ``point`` in the interior of the region, the vector from + this ``point`` to the region is treated as the zero vector. The + ``boundary`` argument can be used to force the method to find the + distance from the ``point`` to the nearest point on the boundary of the + region, if so desired. Note that a ``point`` on the boundary still + returns the zero vector. + + """ + from_where = self.region.boundary if boundary else self.region + + # "point to region" by virtue of order of arguments to shapely call + directed_line = shapely.shortest_line(shapely.Point(point), from_where) + + displacement_vector = np.array(directed_line.coords[1]) - np.array( + directed_line.coords[0] + ) + if direction == "region to point": + displacement_vector *= -1.0 + if unit: + norm = np.sqrt(np.sum(displacement_vector**2)) + # Cannot normalise the 0 vector + if norm != 0.0: + displacement_vector /= norm + return displacement_vector