```import math
import random
import copy
from typing import List, Tuple, Union

"""Calculates a point from a given point, heading and distance.

:param _x: source point x
:param _y: source point y
:param distance: distance from source point
:return: returns a tuple (x, y) of the calculated point
"""

return x, y

def distance(x1, y1, x2, y2):
"""Returns the distance between 2 points

:param x1: x coordinate of point 1
:param y1: y coordinate of point 1
:param x2: x coordinate of point 2
:param y2: y coordinate of point 2
:return: distance in point units(m)
"""
return math.hypot(x2 - x1, y2 - y1)

"""Returns the angle between 2 points in degrees.

:param x1: x coordinate of point 1
:param y1: y coordinate of point 1
:param x2: x coordinate of point 2
:param y2: y coordinate of point 2
:return: angle in degrees
"""
def angle_trunc(a):
while a < 0.0:
a += math.pi * 2
return a
deltax = x2 - x1
deltay = y2 - y1
return math.degrees(angle_trunc(math.atan2(deltay, deltax)))

class Point:
def __init__(self, x, y):
self.x = x
self.y = y

return Point(x, y)

def distance_to_point(self, point):
return distance(self.x, self.y, point.x, point.y)

def random_point_within(self, distance, min_distance=0):
"""Returns a random point within the given distance.

This is a shortcut for Rectangle.from_point().random_point().

Args:
distance: max distance for the random point.
min_distance: minimum distance the random point should have from origin

Returns:
Point: a new random point within the given distance from the point.
"""
random.random() * (distance - min_distance) + min_distance)

return Point(self.x + other.x, self.y + other.y)

if isinstance(other, Point):
return self + other
return Point(self.x + other, self.y + other)

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

def __mul__(self, other):
return Point(self.x * other, self.y * other)

def __rmul__(self, other):
return self * other

def __eq__(self, other):
return self.x == other.x and self.y == other.y

def __ne__(self, other):
return not self == other

def __repr__(self):
return "Point({x}, {y})".format(x=self.x, y=self.y)

class Triangle:
def __init__(self, points: Union[Tuple[Point, Point, Point], List[Point]]):
if len(points) != 3:
raise RuntimeError("Triangle needs 3 points.")
self.points = copy.copy(points)  # type: List[Point]

def area(self):
a = (self.points[0].x * self.points[1].y + self.points[1].x * self.points[2].y +
self.points[2].x * self.points[0].y - self.points[0].y * self.points[1].x -
self.points[1].y * self.points[2].x - self.points[2].y * self.points[0].x)
a /= 2
return a

def random_point(self) -> Point:
r = random.random()
s = random.random()
if r + s >= 1:
r = 1 - r
s = 1 - s

return self.points[0] + (r * (self.points[1] - self.points[0]) + s * (self.points[2] - self.points[0]))

def __repr__(self):
return "Triangle({points})".format(points=", ".join(map(repr, self.points)))

class Rectangle:
def __init__(self, top, left, bottom, right):
self.top = top
self.left = left
self.bottom = bottom
self.right = right

@staticmethod
def from_point(point: Point, side_length):
top = point.x + side_length / 2
left = point.y - side_length / 2
bottom = point.x - side_length / 2
right = point.y + side_length / 2
return Rectangle(top, left, bottom, right)

def point_in_rect(self, point: Point):
return self.bottom <= point.x <= self.top and self.left <= point.y <= self.right

def height(self):
return self.top - self.bottom

def width(self):
return self.right - self.left

def center(self) -> Point:
return Point(self.bottom + (self.height() / 2), self.left + (self.width() / 2))

def resize(self, percentage: float):
w = self.width()
h = self.height()
w *= percentage / 2
h *= percentage / 2
c = self.center()
return Rectangle(c.x + h, c.y - w, c.x - h, c.y + w)

def random_point(self) -> Point:
x = self.bottom + random.random() * (self.top - self.bottom)
y = self.left + random.random() * (self.right - self.left)
return Point(x, y)

def random_distant_points(self, distance) -> Tuple[Point, Point]:
# determine vertical/horizontal
if self.width() > self.height():
axis_y = self.width()
axis_x = self.height()
sy = self.left
sx = self.bottom
hdg_start = 60
else:
axis_y = self.height()
axis_x = self.width()
sy = self.bottom
sx = self.left
hdg_start = 330

d = distance if distance < axis_y else axis_y * 0.2
sy += random.random() * (axis_y - d)
sx += random.random() * axis_x
p1 = Point(sx, sy)
while True:
hdg = random.random() * 60
p2 = p1.point_from_heading(hdg_start + hdg, d)
if self.point_in_rect(p2):
return p1, p2

def __eq__(self, other):
return self.top == other.top and self.bottom == other.bottom \
and self.left == other.left and self.right == other.right

def __ne__(self, other):
return not self == other

def __repr__(self):
return "Rectangle({t}, {l}, {b}, {r})".format(t=self.top, l=self.left, b=self.bottom, r=self.right)

class Polygon:
def __init__(self, points: List[Point]=None):
if points is None:
points = []
self.points = copy.copy(points)

def point_in_poly(self, point: Point):
"""Checks if the given point is within the polygon.

:param point: Point to test
:return: True if point is within the polygon else False
"""
n = len(self.points)
inside = False

p1x = self.points[0].x
p1y = self.points[0].y
for i in range(n+1):
p = self.points[i % n]
p2x = p.x
p2y = p.y
if point.y > min(p1y, p2y):
if point.y <= max(p1y, p2y):
if point.x <= max(p1x, p2x):
xints = 0
if p1y != p2y:
xints = (point.y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
if p1x == p2x or point.x <= xints:
inside = not inside
p1x, p1y = p2x, p2y

return inside

def random_point(self) -> Point:
"""Returns a random point within this polygon object

:return: a random point
"""
# split polygon into triangles using ear clipping
tris = self.triangulate()

# calculate areas of the triangles
areas = [(x, x.area()) for x in tris]
full_area = sum([x[1] for x in areas])  # calculate full area
rtri = random.random() * full_area  # get a random value from the area

# iterate through areas until we found "ours"
s = areas[0][1]
i = 1
while s <= rtri:
s += areas[i][1]
i += 1

# return a random point from this triangle
return areas[i-1][0].random_point()

@staticmethod
def is_convex(a: Point, b: Point, c: Point):
# only convex if traversing anti-clockwise!
crossp = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
if crossp >= 0:
return True
return False

@staticmethod
def in_triangle(a, b, c, p):
l = [0, 0, 0]
eps = 0.0000001
# calculate barycentric coefficients for point p
# eps is needed as error correction since for very small distances denom->0
l[0] = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) /\
(((b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y)) + eps)
l[1] = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) /\
(((b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y)) + eps)
l[2] = 1 - l[0] - l[1]
# check if p lies in triangle (a, b, c)
for x in l:
if x >= 1 or x <= 0:
return False
return True

def is_clockwise(self):
poly_length = len(self.points)
# initialize sum with last element
sum_ = (self.points[0].x - self.points[poly_length-1].x) * (self.points[0].y + self.points[poly_length-1].y)
# iterate over all other elements (0 to n-1)
for i in range(poly_length-1):
sum_ += (self.points[i+1].x - self.points[i].x) * (self.points[i+1].y + self.points[i].y)
return sum_ > 0

@staticmethod
def get_ear(poly):
size = len(poly)
if size < 3:
return []
if size == 3:
tri = (poly[0], poly[1], poly[2])
del poly[:]
return tri
for i in range(size):
tritest = False
p1 = poly[(i-1) % size]
p2 = poly[i % size]
p3 = poly[(i+1) % size]
if Polygon.is_convex(p1, p2, p3):
for x in poly:
if not (x in (p1, p2, p3)) and Polygon.in_triangle(p1, p2, p3, x):
tritest = True
if not tritest:
del poly[i % size]
return p1, p2, p3
print('GetEar(): no ear found')
return []

def triangulate(self):
tri = []
plist = self.points[::-1] if self.is_clockwise() else self.points[:]
while len(plist) >= 3:
a = self.get_ear(plist)
if not a:
break
tri.append(Triangle(a))
return tri

def outbound_rectangle(self) -> Rectangle:
top = max([x.x for x in self.points])
bot = min([x.x for x in self.points])
right = max([x.y for x in self.points])
left = min([x.y for x in self.points])
return Rectangle(top, left, bot, right)

def __repr__(self):
return "Polygon([{points}])".format(points=", ".join(map(repr, self.points)))
```