Source code for mathematicskit.geometry.systems.intersections

r"""Line/segment intersection and point-in-polygon tests.

No direct scipy equivalent for either -- these are elementary
computational-geometry primitives with no dedicated library routine.
See de Berg et al., *Computational Geometry*, 3rd ed., Ch. 2
(segment intersection) and O'Rourke, *Computational Geometry in C*,
2nd ed., Ch. 7 (point-in-polygon).
"""

from __future__ import annotations

from typing import Optional

import numpy as np

__all__ = ["segment_intersection", "point_in_polygon"]


[docs] def segment_intersection(p1: np.ndarray, p2: np.ndarray, p3: np.ndarray, p4: np.ndarray) -> Optional[np.ndarray]: r"""Intersection point of segments ``p1-p2`` and ``p3-p4``, if they cross. Writes both segments parametrically (:math:`p_1 + t(p_2-p_1)`, :math:`p_3 + u(p_4-p_3)`) and solves the resulting :math:`2\times2` linear system for :math:`t, u \in [0, 1]` via Cramer's rule; returns ``None`` if the segments are parallel or don't overlap within their endpoints. See de Berg et al., *Computational Geometry*, 3rd ed., Ch. 2. Parameters ---------- p1, p2 : ndarray, shape (2,) Endpoints of the first segment. p3, p4 : ndarray, shape (2,) Endpoints of the second segment. Returns ------- ndarray, shape (2,), or None Examples -------- >>> import numpy as np >>> p = segment_intersection(np.array([0.0, 0.0]), np.array([2.0, 2.0]), np.array([0.0, 2.0]), np.array([2.0, 0.0])) >>> np.allclose(p, [1.0, 1.0]) True >>> segment_intersection(np.array([0.0, 0.0]), np.array([1.0, 0.0]), np.array([0.0, 1.0]), np.array([1.0, 1.0])) is None True """ p1, p2, p3, p4 = (np.asarray(p, dtype=np.float64) for p in (p1, p2, p3, p4)) d1 = p2 - p1 d2 = p4 - p3 denom = d1[0] * d2[1] - d1[1] * d2[0] if abs(denom) < 1e-12: return None # parallel (or collinear) diff = p3 - p1 t = (diff[0] * d2[1] - diff[1] * d2[0]) / denom u = (diff[0] * d1[1] - diff[1] * d1[0]) / denom if 0.0 <= t <= 1.0 and 0.0 <= u <= 1.0: return p1 + t * d1 return None
[docs] def point_in_polygon(point: np.ndarray, polygon: np.ndarray) -> bool: r"""Whether `point` lies inside `polygon`, via the ray-casting (crossing-number) algorithm. Casts a ray from `point` in the :math:`+x` direction and counts how many polygon edges it crosses; an odd count means the point is inside (the standard Jordan-curve-theorem argument). See O'Rourke, *Computational Geometry in C*, 2nd ed., Ch. 7.4. Parameters ---------- point : ndarray, shape (2,) polygon : ndarray, shape (n, 2) Vertices in order (either winding direction); the polygon implicitly closes back to its first vertex. Returns ------- bool Examples -------- >>> import numpy as np >>> square = np.array([[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]]) >>> point_in_polygon(np.array([2.0, 2.0]), square) True >>> point_in_polygon(np.array([5.0, 2.0]), square) False """ x, y = point poly = np.asarray(polygon, dtype=np.float64) n = poly.shape[0] inside = False x1, y1 = poly[-1] for i in range(n): x2, y2 = poly[i] if (y1 > y) != (y2 > y): x_intersect = x1 + (y - y1) * (x2 - x1) / (y2 - y1) if x < x_intersect: inside = not inside x1, y1 = x2, y2 return inside