File size: 1,544 Bytes
23680f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
"""Selection / geometry helpers.

This module contains small, backend-agnostic utilities used by selection endpoints
(e.g. lasso selection over 2D embeddings).
"""

from __future__ import annotations

import numpy as np


def points_in_polygon(points_xy: np.ndarray, polygon_xy: np.ndarray) -> np.ndarray:
    """Vectorized point-in-polygon (even-odd rule / ray casting).

    Args:
        points_xy: Array of shape (m, 2) with point coordinates.
        polygon_xy: Array of shape (n, 2) with polygon vertices.

    Returns:
        Boolean mask of length m, True where point lies inside polygon.

    Notes:
        Boundary points may be classified as outside depending on floating point
        ties (common for lasso selection tools).
    """
    if polygon_xy.shape[0] < 3:
        return np.zeros((points_xy.shape[0],), dtype=bool)

    x = points_xy[:, 0]
    y = points_xy[:, 1]
    poly_x = polygon_xy[:, 0]
    poly_y = polygon_xy[:, 1]

    inside = np.zeros((points_xy.shape[0],), dtype=bool)
    j = polygon_xy.shape[0] - 1

    for i in range(polygon_xy.shape[0]):
        xi = poly_x[i]
        yi = poly_y[i]
        xj = poly_x[j]
        yj = poly_y[j]

        # Half-open y-interval to avoid double-counting vertices.
        intersects = (yi > y) != (yj > y)

        denom = yj - yi
        # denom == 0 => intersects is always False; add tiny epsilon to avoid warnings.
        x_intersect = (xj - xi) * (y - yi) / (denom + 1e-30) + xi

        inside ^= intersects & (x < x_intersect)
        j = i

    return inside