Finalize Polyline parsing but still arcs precision issue

This commit is contained in:
2024-06-15 01:41:26 +02:00
parent 9b87874e13
commit 4a611a4aa2
7 changed files with 260 additions and 113 deletions

View File

@@ -1,21 +1,24 @@
from networks.geometry.Point2D import Point2D
from math import cos, sin, pi
from math import cos, pi, sin
from typing import List
import numpy as np
from networks.geometry.Point2D import Point2D
class Circle:
def __init__(self, center: Point2D):
self.center = center
self.radius = None
self.points = []
self.points: List[Point2D] = []
self.inner = None
self.outer = None
self.points_thick = []
self.points_thick: List[Point2D] = []
self.spaced_radius = None
self.spaced_points = []
self.spaced_points: List[Point2D] = []
def __repr__(self):
return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})"
@@ -43,6 +46,7 @@ class Circle:
continue
else:
break
return self.points
def circle_thick(self, inner: int, outer: int) -> List[Point2D]:
"""Compute discrete value of a 2d-circle with thickness.
@@ -114,16 +118,17 @@ class Circle:
center = self.center
self.spaced_points = [
Point2D(cos(2 * pi / number * i) * radius,
sin(2 * pi / number * i) * radius)
Point2D(round(cos(2 * pi / number * i) * radius),
round(sin(2 * pi / number * i) * radius))
for i in range(0, number + 1)
]
for i in range(len(self.spaced_points)):
self.spaced_points[i] = Point2D(
current_point = Point2D(
self.spaced_points[i].x + center.x,
self.spaced_points[i].y + center.y
).round()
self.spaced_points[i] = current_point
return self.spaced_points
def _x_line(self, x1, x2, y):

View File

@@ -1,6 +1,8 @@
import numpy as np
from typing import List
from math import atan2, sqrt
from typing import List, Union
import numpy as np
from Enums import ROTATION
@@ -21,10 +23,26 @@ class Point2D:
return self.x == other.x and self.y == other.y
return False
def __add__(self, other):
if isinstance(other, np.ndarray) and other.shape == (2,):
return Point2D(self.x + other[0], self.y + other[1])
elif isinstance(other, Point2D):
return Point2D(self.x + other.x, self.y + other.y)
else:
raise TypeError(f"Unsupported type for addition: {type(other)}")
def __sub__(self, other):
if isinstance(other, np.ndarray) and other.shape == (2,):
return Point2D(self.x - other[0], self.y - other[1])
elif isinstance(other, Point2D):
return Point2D(self.x - other.x, self.y - other.y)
else:
raise TypeError(f"Unsupported type for subtraction: {type(other)}")
def is_in_triangle(self, xy0: "Point2D", xy1: "Point2D", xy2: "Point2D"):
"""Returns True is the point is in a triangle defined by 3 others points.
From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#:~:text=A%20simple%20way%20is%20to,point%20is%20inside%20the%20triangle.
From: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle
Args:
xy0 (Type[Point2D]): Point of the triangle.
@@ -190,12 +208,21 @@ class Point2D:
return abs(x1 * y2 - x2 * y1) < 1e-12
@staticmethod
def to_vectors(points: List["Point3D"]) -> List[np.array]:
vectors = []
for point in points:
vectors.append(np.array(point.coordinates))
if (len(vectors) == 1):
return vectors[0]
else:
def to_arrays(points: Union[List["Point2D"], "Point2D"]) -> Union[List[np.array], "Point2D"]:
if isinstance(points, list):
vectors = []
for point in points:
vectors.append(np.array(point.coordinates))
return vectors
else:
return np.array(points.coordinates)
@staticmethod
def from_arrays(vectors: Union[List[np.array], "Point2D"]) -> Union[List["Point2D"], "Point2D"]:
if isinstance(vectors, list):
points = []
for vector in vectors:
points.append(Point2D(vector[0], vector[1]))
return points
else:
return Point2D(vectors[0], vectors[1])

View File

@@ -1,5 +1,6 @@
from math import sqrt
from typing import List
from math import atan2, sqrt
import numpy as np
@@ -80,3 +81,14 @@ class Point3D:
return vectors[0]
else:
return vectors
@staticmethod
def from_arrays(vectors: List[np.array]) -> List["Point3D"]:
points = []
for vector in vectors:
points.append(Point3D(vector[0], vector[1], vector[2]))
if (len(points) == 1):
return points[0]
else:
return points

View File

@@ -1,11 +1,17 @@
from networks.geometry.Point2D import Point2D
from math import inf, sqrt
from typing import List, Tuple, Union
from math import sqrt, inf
import numpy as np
from networks.geometry.Circle import Circle
from networks.geometry.Point2D import Point2D
from networks.geometry.Segment2D import Segment2D
# from Enums import LINE_THICKNESS_MODE, LINE_OVERLAP
class Polyline:
def __init__(self, points: list["Point2D"]):
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.
@@ -18,7 +24,8 @@ class Polyline:
>>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20)))
"""
self.points_array = Point2D.to_vectors(
self.output_points = points
self.points_array = Point2D.to_arrays(
self._remove_collinear_points(points))
self.length_polyline = len(self.points_array)
@@ -26,36 +33,46 @@ class Polyline:
raise ValueError("The list must contain at least 4 elements.")
self.vectors = [None] * self.length_polyline # v
self.lengths = [None] * (self.length_polyline - 1) # l
self.lengths = [0] * (self.length_polyline - 1) # l
self.unit_vectors = [None] * self.length_polyline # n
self.tangente = [0] * self.length_polyline # f
# alpha, maximum radius factor
self.alpha_radii = [None] * self.length_polyline
self.alpha_radii = [0] * self.length_polyline
self.radii = [None] * self.length_polyline # r
self.centers = [None] * self.length_polyline # c
# Useful outputs. In order to not break indexation, each list has the same length, even if for n points, there is n-2 radius.
# Lists will start and end with None.
self.radii = [0] * self.length_polyline # r, list of points
self.centers = [None] * self.length_polyline # c, list of points
# list of tuple of points (first intersection, corresponding corner, last intersection)
self.acrs_intersections = [None] * self.length_polyline
self.arcs = [[]] * self.length_polyline # list of points
# self.not_arcs = [[]] * self.length_polyline
# For n points, there is n-1 segments. Last element should stays None.
self.segments = [None] * \
self.length_polyline # list of segments
# Run procedure
self._compute_requirements()
self._compute_alpha_radii()
self._alpha_assign(0, self.length_polyline-1)
self.output_points = points
self.get_radii()
self.get_centers()
self.get_arcs_intersections()
self.get_arcs()
self.get_segments()
def __repr__(self):
return str(self.alpha_radii)
def get_radii(self):
def get_radii(self) -> List[Union[int]]:
for i in range(1, self.length_polyline-1):
self.radii[i] = round(self.alpha_radii[i] * self.tangente[i])
return self.radii
def get_centers(self):
if self.radii == [None] * self.length_polyline:
raise ValueError("No radii found. Run get_radii before.")
def get_centers(self) -> List[Union[Point2D, None]]:
for i in range(1, self.length_polyline-1):
bisector = (self.unit_vectors[i] - self.unit_vectors[i-1]) / (
np.linalg.norm(self.unit_vectors[i] - self.unit_vectors[i-1]))
@@ -65,16 +82,68 @@ class Polyline:
self.centers[i] = Point2D(array[0], array[1]).round()
return self.centers
def get_arcs_intersections(self):
def get_arcs_intersections(self) -> List[Tuple[Point2D]]:
"""Get arcs intersections points.
First and last elements elements of the list should be None. For n points, there are n-1 segments, and n-2 angle.
Returns:
list[tuple(Point2D)]: List of tuples composed - in order - of the first arc points, the corner points, the last arc points. The corresponding arc circle is inside this triangle.
"""
for i in range(1, self.length_polyline-1):
point_1 = self.points_array[i] - \
self.alpha_radii[i] * self.unit_vectors[i-1]
point_2 = self.points_array[i] + \
self.alpha_radii[i] * self.unit_vectors[i]
self.acrs_intersections[i] = Point2D(
point_1[0], point_1[1]).round(), Point2D(self.points_array[i][0], self.points_array[i][1]), Point2D(point_2[0], point_2[1]).round()
point_1 = Point2D.from_arrays(self.points_array[i] -
self.alpha_radii[i] * self.unit_vectors[i-1])
point_2 = Point2D.from_arrays(self.points_array[i] +
self.alpha_radii[i] * self.unit_vectors[i])
self.acrs_intersections[i] = point_1.round(), Point2D.from_arrays(
self.points_array[i]), point_2.round()
return self.acrs_intersections
def get_arcs(self) -> List[Point2D]:
for i in range(1, self.length_polyline-1):
circle = Circle(self.centers[i])
circle.circle(self.radii[i])
for j in range(len(circle.points)):
if circle.points[j].is_in_triangle(self.acrs_intersections[i][0], self.acrs_intersections[i][1], self.acrs_intersections[i][2]):
self.arcs[i].append(circle.points[j])
# for j in range(len(circle.points)):
# if (circle.points[j] in Segment2D(self.acrs_intersections[i][0], self.acrs_intersections[i][1]).segment(LINE_OVERLAP.MINOR)):
# self.not_arcs[i].append(circle.points[j])
# print("hzeh")
# if (circle.points[j] in Segment2D(self.acrs_intersections[i][1], self.acrs_intersections[i][2]).segment(LINE_OVERLAP.MINOR)):
# self.not_arcs[i].append(circle.points[j])
# print("hzeh")
# if (circle.points[j] in Segment2D(self.acrs_intersections[i][2], self.acrs_intersections[i][0]).segment(LINE_OVERLAP.MINOR)):
# self.not_arcs[i].append(circle.points[j])
# print("hzeh")
return self.arcs
def get_segments(self) -> List[Segment2D]:
"""Get the segments between the circle arcs and at the start and end.
Last list element should be None, and last usable index is -2 or self.length_polyline - 2. For n points, there are n-1 segments.
Returns:
list[Segment2D]: List of segments in order.
"""
# Get first segment.
# segments index is 0, corresponding to the first points_array to the first point ([0]) of the first arc (acrs_intersections[1]).
# First arc index is 1 because index 0 is None due to fix list lenght. Is it a good choice?
self.segments[1] = Segment2D(Point2D.from_arrays(
self.points_array[0]), self.acrs_intersections[1][0])
# Get segments between arcs
for i in range(2, self.length_polyline - 2):
self.segments[i] = Segment2D(Point2D(self.acrs_intersections[i][0].x, self.acrs_intersections[i][0].y), Point2D(
self.acrs_intersections[i-1][-1].x, self.acrs_intersections[i-1][-1].y))
# Get last segment. Index is -2 because last index -1 should be None due to the same list lenght.
# For n points, there are n-1 segments.
self.segments[-2] = Segment2D(Point2D.from_arrays(
self.points_array[-1]), self.acrs_intersections[-2][2])
return self.segments
def _alpha_assign(self, start_index: int, end_index: int):
"""
The alpha-assign procedure assigning radii based on a polyline.
@@ -101,8 +170,8 @@ class Polyline:
minimum_radius, minimum_index = current_radius, i
alpha_low, alpha_high = alpha_a, alpha_b
alpha_a = min(self.lengths[end_index-2],
self.lengths[end_index-1]-self.alpha_radii[end_index])
alpha_a = min(
self.lengths[end_index-2], self.lengths[end_index-1]-self.alpha_radii[end_index])
current_radius = max(self.tangente[end_index-1]*alpha_a, self.tangente[end_index]
* self.alpha_radii[end_index]) # Radius at final segment
@@ -123,9 +192,8 @@ class Polyline:
"""
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_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, min(self.tangente[i]*alpha_a, self.tangente[i+1]*alpha_b)

View File

@@ -1,21 +1,23 @@
from typing import List
from typing import List, Union
import numpy as np
from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE
from networks.geometry.Point2D import Point2D
from math import sqrt
class Segment2D:
def __init__(self, start: Point2D, end: Point2D):
self.start = start
self.end = end
self.points = []
self.points_thick = []
self.points: List[Point2D] = []
self.points_thick: List[Point2D] = []
self.thickness = None
def __repr__(self):
return str(self.points)
return str(f"Segment2D(start: {self.start}, end: {self.end}, points: {self.points})")
def segment(self, start: Point2D = None, end: Point2D = None, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, _is_computing_thickness: bool = False) -> List[Point2D]:
def segment(self, start: Point2D = None, end: Point2D = None, overlap: LINE_OVERLAP = LINE_OVERLAP.NONE, _is_computing_thickness: bool = False) -> Union[List[Point2D], None]:
"""Modified Bresenham draw (line) with optional overlap.
From: https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp
@@ -29,7 +31,7 @@ class Segment2D:
>>> Segment2D(Point2D(0, 0), Point2D(10, 15))
"""
if start == None or end == None:
if start is None or end is None:
start = self.start.copy()
end = self.end.copy()
else:
@@ -90,6 +92,7 @@ class Segment2D:
if not _is_computing_thickness:
return self.points
return None
def segment_thick(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE) -> List[Point2D]:
"""Bresenham with thickness.
@@ -208,7 +211,7 @@ class Segment2D:
self.segment(
start, end, overlap=overlap, _is_computing_thickness=True)
return self.points
return self.points_thick
def perpendicular(self, distance: int) -> List[Point2D]:
"""Compute perpendicular points from both side of the segment placed at start level.