From 0c1841417689c302c623d75cd6a49b1f526bc010 Mon Sep 17 00:00:00 2001 From: Xeon0X Date: Tue, 11 Jun 2024 01:29:07 +0200 Subject: [PATCH] Add and clean proper objects for Polyline, Point2D, Circle --- networks/geometry/Circle.py | 68 +++++++++ networks/geometry/Point2D.py | 16 +++ .../{polylines.py => geometry/Polyline.py} | 135 +++++++++--------- networks/geometry/point_tools.py | 11 ++ 4 files changed, 160 insertions(+), 70 deletions(-) create mode 100644 networks/geometry/Circle.py create mode 100644 networks/geometry/Point2D.py rename networks/{polylines.py => geometry/Polyline.py} (66%) diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py new file mode 100644 index 0000000..50c153e --- /dev/null +++ b/networks/geometry/Circle.py @@ -0,0 +1,68 @@ +from typing import Type +import Point2D + + +class Circle: + def __init__(center: Type[Point2D], inner: int, outer: int): + self.center = center + self.inner = inner + self.outer = outer + self.coordinates = [] + + circle(self.center, self.inner, self.outer) + + def circle(center: Type[Point2D], inner: int, outer: int): + """Compute discrete value of a 2d-circle with thickness. + + https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm + + Args: + center (Type[Point2D]): Center of the circle. Circles always have an odd diameter due to the central coordinate. + inner (int): The minimum radius at which the disc is filled (included). + outer (int): The maximum radius where disc filling stops (included). + """ + xo = outer + xi = inner + y = 0 + erro = 1 - xo + erri = 1 - xi + + while xo >= y: + _x_line(center.x + xi, center.x + xo, center.y + y) + _y_line(center.x + y, center.y + xi, center.y + xo) + _x_line(center.x - xo, center.x - xi, center.y + y) + _y_line(center.x - y, center.y + xi, center.y + xo) + _x_line(center.x - xo, center.x - xi, center.y - y) + _y_line(center.x - y, center.y - xo, center.y - xi) + _x_line(center.x + xi, center.x + xo, center.y - y) + _y_line(center.x + y, center.y - xo, center.y - xi) + + y += 1 + + if erro < 0: + erro += 2 * y + 1 + else: + xo -= 1 + erro += 2 * (y - xo + 1) + + if y > inner: + xi = y + else: + if erri < 0: + erri += 2 * y + 1 + else: + xi -= 1 + erri += 2 * (y - xi + 1) + + def _x_line(x1, x2, y): + while x1 <= x2: + self.coordinates.append(Point2D(x1, y)) + x1 += 1 + + def _y_line(x, y1, y2): + while y1 <= y2: + self.coordinate.append(Point2D(x, y1)) + y1 += 1 + + def __repr__(self): + return f"Circle(center: {self.center}, inner: {self.inner}, outer: {self.outer})" diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py new file mode 100644 index 0000000..435172a --- /dev/null +++ b/networks/geometry/Point2D.py @@ -0,0 +1,16 @@ +from typing import Type + + +class Point2D: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + def copy(self): + return Point2D(self.x, self.y) + + def coordinates(self): + return (self.x, self.y) + + def __repr__(self): + return f"Point2D(x: {self.x}, y: {self.y})" diff --git a/networks/polylines.py b/networks/geometry/Polyline.py similarity index 66% rename from networks/polylines.py rename to networks/geometry/Polyline.py index 01268a3..ace65e5 100644 --- a/networks/polylines.py +++ b/networks/geometry/Polyline.py @@ -1,38 +1,28 @@ +from typing import Type +import Point2D + from math import sqrt, inf import numpy as np -class Point2D: - def __init__(self, x, y): - self.x = x - self.y = y - - def __repr__(self): - return f"({self.x} {self.y})" - - def copy(self): - return Point2D(self.x, self.y) - - def get_coordinates(self): - return (self.x, self.y) - - -def coordinates_to_vectors(coordinates): - vectors = [] - for coordinate in coordinates: - vectors.append(np.array(coordinate.get_coordinates())) - - if (len(vectors) == 1): - return vectors[0] - else: - return vectors - - class Polyline: - def __init__(self, points): + def __init__(self, points: List[Point2D]): + """A polyline with smooth corners, only composed of segments and circle arc. + + Mathematics and algorithms behind this can be found here: https://cdr.lib.unc.edu/concern/dissertations/pz50gw814?locale=en, E2 Construction of arc roads from polylines, page 210. + + Args: + points (List[Point2D]): List of 2d-points in order describing the polyline. + + Raises: + ValueError: At least 4 points required. + """ self.points = coordinates_to_vectors(points) self.length_polyline = len(points) + if self.length_polyline < 4: + raise ValueError("The list must contain at least 4 elements.") + self.vectors = [None] * self.length_polyline self.lengths = [None] * self.length_polyline self.unit_vectors = [None] * self.length_polyline @@ -40,46 +30,14 @@ class Polyline: self.alpha_radii = [None] * self.length_polyline - self.compute_requirements() - self.compute_alpha_radii() + self._compute_requirements() + self._compute_alpha_radii() - def compute_requirements(self): + _alpha_assign(0, self.length_polyline-1) - # Between two points, there is only one segment - for j in range(self.length_polyline-1): - self.vectors[j] = self.points[j+1] - self.points[j] - self.lengths[j] = np.linalg.norm(self.vectors[j]) - self.unit_vectors[j] = self.vectors[j]/self.lengths[j] - - # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") - - # Between two segments, there is only one angle - for k in range(1, self.length_polyline-1): - cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) - self.tangente[k] = sqrt((1+cross)/(1-cross)) - - def compute_alpha_radii(self): - self.alpha_radii[0] = 0 - self.alpha_radii[self.length_polyline-1] = 0 - - for i in range(1, self.length_polyline-2): - self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] - * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) - - def radius_balance(self, i): + def _alpha_assign(self, start_index, end_index): """ - Returns the radius that balances the radii on either end segement i. - """ - - alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / - (self.tangente[i] + self.tangente[i+1])) - alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) - - return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) - - def alpha_assign(self, start_index, end_index): - """ - The Alpha-assign procedure assigning radii based on a polyline. + The alpha-assign procedure assigning radii based on a polyline. """ minimum_radius, minimum_index = inf, end_index @@ -96,7 +54,7 @@ class Polyline: alpha_low, alpha_high = self.alpha_radii[start_index], alpha_b for i in range(start_index + 1, end_index - 2): # Radii for internal segments - alpha_a, alpha_b, current_radius = self.radius_balance(i) + alpha_a, alpha_b, current_radius = self._radius_balance(i) if current_radius < minimum_radius: alpha_low, alpha_high = alpha_a, self.alpha_radii[end_index] @@ -106,15 +64,52 @@ class Polyline: print(alpha_low, alpha_high) # Recur on lower segments - self.alpha_assign(start_index, minimum_index) + self._alpha_assign(start_index, minimum_index) # Recur on higher segments - self.alpha_assign(minimum_index + 1, end_index) + self._alpha_assign(minimum_index + 1, end_index) + + def _radius_balance(self, i: int): + """ + Returns the radius that balances the radii on either end segement i. + """ + + alpha_a = min(self.lengths[i-1], (self.lengths[i]*self.tangente[i+1]) / + (self.tangente[i] + self.tangente[i+1])) + alpha_b = min(self.lengths[i+1], self.lengths[i]-alpha_a) + + return alpha_a, alpha_b, max(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b) + + def _compute_requirements(self): + # Between two points, there is only one segment + for j in range(self.length_polyline-1): + self.vectors[j] = self.points[j+1] - self.points[j] + self.lengths[j] = np.linalg.norm(self.vectors[j]) + self.unit_vectors[j] = self.vectors[j]/self.lengths[j] + + # print("\n\n", vectors, "\n\n", lengths, "\n\n", unit_vectors, "\n\n") + + # Between two segments, there is only one angle + for k in range(1, self.length_polyline-1): + cross = np.dot(self.unit_vectors[k], self.unit_vectors[k-1]) + self.tangente[k] = sqrt((1+cross)/(1-cross)) + + def _compute_alpha_radii(self): + self.alpha_radii[0] = 0 + self.alpha_radii[self.length_polyline-1] = 0 + + for i in range(1, self.length_polyline-2): + self.alpha_radii[i] = min(self.lengths[i-1] - self.alpha_radii[i-1], (self.lengths[i] + * self.tangente[i+1])/(self.tangente[i]+self.tangente[i+1])) -polyline = Polyline((Point2D(0, 0), Point2D(0, 10), Point2D( - 10, 10), Point2D(10, 20), Point2D(20, 20), Point2D(20, 30), Point2D(60, 60), Point2D(60, 0))) +# polyline = Polyline((Point2D(0, 9), Point2D(0, 10), Point2D( +# 10, 10), Point2D(10, 20), Point2D(20, 20), Point2D(20, 30), Point2D(60, 60), Point2D(-60, -60))) + +polyline = Polyline((Point2D(0, 10), Point2D(-10, -10), + Point2D(20, 0), Point2D(20, 20))) + # print(polyline.radius_balance(2)) -polyline.alpha_assign(1, polyline.length_polyline-1) +polyline._alpha_assign(1, polyline.length_polyline-1) print(polyline.alpha_radii) diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 1bfce76..ae85eba 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -511,3 +511,14 @@ def curved_corner_by_curvature( distance_from_intersection = round(distance(start_curve_point, center)) return curve_corner_points, center, distance_from_intersection, parallel( (xyz0, intersection), -curvature_radius), parallel((xyz1, intersection), curvature_radius) + + +def coordinates_to_vectors(coordinates): + vectors = [] + for coordinate in coordinates: + vectors.append(np.array(coordinate.get_coordinates())) + + if (len(vectors) == 1): + return vectors[0] + else: + return vectors