# Source code for webtraversallibrary.geometry

```# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at

# Unless required by applicable law or agreed to in writing,
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations

"""Basic 2-dimensional geometric constructs: points, rectangles, etc. """
from __future__ import annotations

from dataclasses import dataclass
from typing import Iterator, Sequence, Tuple, Union

[docs]@dataclass(order=True, frozen=True)
class Point:
"""Represents a point in 2-dimensional plane (e.g. image)."""

x: float
y: float

def __add__(self, other: Point) -> Point:
return Point(self.x + other.x, self.y + other.y)

def __sub__(self, other: Point) -> Point:
return Point(self.x - other.x, self.y - other.y)

def __mul__(self, scalar: float) -> Point:
return Point(self.x * scalar, self.y * scalar)

def __iter__(self) -> Iterator[float]:
return (data for data in (self.x, self.y))

@staticmethod
def zero() -> Point:
return Point(0, 0)

[docs]@dataclass(frozen=True)
class Rectangle:
"""Represents a rectangle in a 2-dimensional plane."""

minima: Point
maxima: Point

def __iter__(self) -> Iterator[float]:
return (data for data in (self.x, self.y, self.width, self.height))

def __contains__(self, other: Union[Point, Rectangle]) -> bool:
return self.contains(other)

[docs]    def contains(self, other: Union[Point, Rectangle]) -> bool:
"""
Tests whether the rectangle contains ``other``.

:return: ``True`` if other contained in the rectangle, ``False`` otherwise.
"""
if isinstance(other, Point):
return self.minima.x <= other.x <= self.maxima.x and self.minima.y <= other.y <= self.maxima.y
if isinstance(other, Rectangle):
return (
self.maxima.x >= other.maxima.x
and self.maxima.y >= other.maxima.y
and self.minima.x <= other.minima.x
and self.minima.y <= other.minima.y
)

raise TypeError(f"Expected a Rectangle or a Point, not {type(other)}")

[docs]    def clip(self, other: Rectangle) -> Rectangle:
"""
Return a new rectangle generated by clipping this one by the bounds of ``other``.

Similar to intersection, but clipping non-intersecting rectangles will result in a degenerate rectangle
located on one of the edges of ``other``

:return: New, clipped ``Rectangle`` (possibly degenerate)
"""
return Rectangle.from_list(
min(max(self.minima.x, other.minima.x), other.maxima.x),
min(max(self.minima.y, other.minima.y), other.maxima.y),
min(max(self.maxima.x, other.minima.x), other.maxima.x),
min(max(self.maxima.y, other.minima.y), other.maxima.y),
)

@property
def x(self) -> float:
"""The x-coordinate of the lower left vertex"""
return self.minima.x

@property
def y(self) -> float:
"""The y-coordinate of the lower-left vertex"""
return self.minima.y

@property
def bounds(self) -> Tuple[float, float, float, float]:
"""Returns min x, min y, max x, max y"""
return self.minima.x, self.minima.y, self.maxima.x, self.maxima.y

@property
def center(self) -> Point:
"""Return the midpoint of the rectangle"""
return (self.minima + self.maxima) * 0.5

@property
def width(self) -> float:
return self.maxima.x - self.minima.x

@property
def height(self) -> float:
return self.maxima.y - self.minima.y

@property
def area(self) -> float:
return self.width * self.height

[docs]    @staticmethod
def empty() -> Rectangle:
"""Returns a rectangle of zero area at origo."""
return Rectangle.from_list(0, 0, 0, 0)

[docs]    def resized(self, delta: float) -> Rectangle:
"""
Returns a resized rectangle, shrinked (inflated for ``delta<0``) by ``2*delta`` in width and in height.
"""
assert 2 * delta >= -min(self.width, self.height), "Operation only defined for delta >= 2*min(width, height)"
delta_point = Point(delta, delta)
return Rectangle(self.minima - delta_point, self.maxima + delta_point)

def __add__(self, vector: Point) -> Rectangle:
"""Returns a copy of the rectangle translated by ``vector``"""
return Rectangle(self.minima + vector, self.maxima + vector)

def __sub__(self, vector: Point) -> Rectangle:
"""Returns a copy of the rectangle inversly translated by ``vector``"""
return Rectangle(self.minima - vector, self.maxima - vector)

def __repr__(self):
return f"({self.minima}), ({self.maxima})"

[docs]    @staticmethod
def bounding_box(rectangles: Sequence[Rectangle]) -> Rectangle:
"""Computes the bounding box of ``rectangles``"""
if not rectangles:
raise ValueError("Expected a non-empty sequence of rectangles")

max_x = max(rect.maxima.x for rect in rectangles)
max_y = max(rect.maxima.y for rect in rectangles)
min_x = min(rect.minima.x for rect in rectangles)
min_y = min(rect.minima.y for rect in rectangles)

return Rectangle.from_list(max_x, max_y, min_x, min_y)

[docs]    @staticmethod
def intersection(rectangles: Sequence[Rectangle]) -> Rectangle:
"""
Computes the rectangle which is the intersection of a sequence of ``rectangles``. In case the
intersection is empty, it returns an empty rectangle.
"""
if not rectangles or len(rectangles) < 2:
raise ValueError("Expected a sequence of at least two rectangles")

if Rectangle.empty() in rectangles:
return Rectangle.empty()

max_x = min(rect.maxima.x for rect in rectangles)
max_y = min(rect.maxima.y for rect in rectangles)
min_x = max(rect.minima.x for rect in rectangles)
min_y = max(rect.minima.y for rect in rectangles)

if max_x < min_x or max_y < min_y:
return Rectangle.empty()

return Rectangle.from_list(max_x, max_y, min_x, min_y)

[docs]    @staticmethod
def centered_at(center: Point, radius: float) -> Rectangle:
"""
A new square centered at ``center`` with side length ``radius``. (The technically correct term here is
`Apothem <https://en.wikipedia.org/wiki/Apothem>`_.)
"""
raise ValueError(f"Cannot instantiate rectangle with non-positive side {2 * radius} centered at {center}")

[docs]    @staticmethod
def from_list(*args) -> Rectangle:
"""Converts tuple/list (x1, y1, x2, y2) to a Rectangle"""
if len(args) == 1:
args = args[0]
if len(args) == 4 and all(isinstance(el, (int, float)) for el in args):
first_x, first_y, second_x, second_y = args
else:
raise ValueError("Invalid argument(s) to create rectangle from!")

minima = Point(min(first_x, second_x), min(first_y, second_y))
maxima = Point(max(first_x, second_x), max(first_y, second_y))
return Rectangle(minima, maxima)
```