diff --git a/main.py b/main.py index 190bd5d..6ea285c 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ -from networks.geometry.Enums import LINE_OVERLAP, LINE_THICKNESS_MODE +from Enums import LINE_OVERLAP, LINE_THICKNESS_MODE, ROTATION from PIL import Image, ImageDraw import matplotlib.pyplot as plt from networks.geometry.Point3D import Point3D @@ -20,7 +20,6 @@ from buildings.Building import Building import random from networks.roads import Road as Road -from networks.geometry.Enums import ROTATION from networks.roads.intersections import Intersection as Intersection from networks.geometry.point_tools import curved_corner_by_curvature, curved_corner_by_distance @@ -319,27 +318,33 @@ image = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(image) -for i in range(len(p.coordinates)-1): - if p.coordinates[i] != None: - s = Segment2D(Point2D(p.coordinates[i].x, p.coordinates[i].y), Point2D( - p.coordinates[i+1].x, p.coordinates[i+1].y)) +for i in range(len(p.output_points)-1): + print("iiii", i) + if p.output_points[i] != None: + s = Segment2D(Point2D(p.output_points[i].x, p.output_points[i].y), Point2D( + p.output_points[i+1].x, p.output_points[i+1].y)) s.segment_thick(ww, LINE_THICKNESS_MODE.MIDDLE) - print(s.coordinates) - for j in range(len(s.coordinates)-1): + + for j in range(len(s.points_thick)-1): + print("j", j) # editor.placeBlock( # s.coordinates[j].coordinate, Block("cyan_concrete")) - draw.point((s.coordinates[j].x+w, - w-s.coordinates[j].y), fill='red') + draw.point((s.points_thick[j].x+w, + w-s.points_thick[j].y), fill='red') + print(s.points_thick[j]) for i in range(len(center)): + print("iiii", i) if center[i]: - circle = Circle(center[i], radius[i]-ww/2+1, radius[i]+ww/2+1) - for j in range(len(circle.coordinates)-1): + circle = Circle(center[i]) + circle.circle_thick(radius[i]-ww/2+1, radius[i]+ww/2+1) + for j in range(len(circle.points_thick)-1): # editor.placeBlock( # (circle.coordinates[j].x, y, circle.coordinates[j].y), Block("white_concrete")) - draw.point((circle.coordinates[j].x+w, - w-circle.coordinates[j].y), fill='black') + draw.point((circle.points_thick[j].x+w, + w-circle.points_thick[j].y), fill='black') + print(circle.points_thick[j]) image.save('output_image.png') diff --git a/networks/geometry/Circle.py b/networks/geometry/Circle.py index 1c17a7a..c6f78a2 100644 --- a/networks/geometry/Circle.py +++ b/networks/geometry/Circle.py @@ -8,14 +8,14 @@ class Circle: self.center = center self.radius = None - self.coordinates = [] + self.points = [] self.inner = None self.outer = None - self.coordinates_thick = [] + self.points_thick = [] self.spaced_radius = None - self.spaced_coordinates = [] + self.spaced_points = [] def __repr__(self): return f"Circle(center: {self.center}, radius: {self.radius}, spaced_radius: {self.spaced_radius}, inner: {self.inner}, outer: {self.outer})" @@ -28,10 +28,10 @@ class Circle: y = 0 error = 2-2*radius while (True): - self.coordinates.append(Point2D(center.x-x, center.y+y)) - self.coordinates.append(Point2D(center.x-y, center.y-x)) - self.coordinates.append(Point2D(center.x+x, center.y-y)) - self.coordinates.append(Point2D(center.x+y, center.y+x)) + self.points.append(Point2D(center.x-x, center.y+y)) + self.points.append(Point2D(center.x-y, center.y-x)) + self.points.append(Point2D(center.x+x, center.y-y)) + self.points.append(Point2D(center.x+y, center.y+x)) r = error if (r <= y): y += 1 @@ -96,7 +96,7 @@ class Circle: else: xi -= 1 erri += 2 * (y - xi + 1) - return self.coordinates_thick + return self.points_thick def circle_spaced(self, number: int, radius: int) -> List[Point2D]: """Get evenly spaced coordinates of the circle. @@ -113,25 +113,25 @@ class Circle: self.spaced_radius = radius center = self.center - self.spaced_coordinates = [ + self.spaced_points = [ Point2D(cos(2 * pi / number * i) * radius, sin(2 * pi / number * i) * radius) for i in range(0, number + 1) ] - for i in range(len(self.spaced_coordinates)): - self.spaced_coordinates[i] = Point2D( - self.spaced_coordinates[i].x + center.x, - self.spaced_coordinates[i].y + center.y + for i in range(len(self.spaced_points)): + self.spaced_points[i] = Point2D( + self.spaced_points[i].x + center.x, + self.spaced_points[i].y + center.y ).round() - return self.spaced_coordinates + return self.spaced_points def _x_line(self, x1, x2, y): while x1 <= x2: - self.coordinates_thick.append(Point2D(x1, y)) + self.points_thick.append(Point2D(x1, y)) x1 += 1 def _y_line(self, x, y1, y2): while y1 <= y2: - self.coordinates_thick.append(Point2D(x, y1)) + self.points_thick.append(Point2D(x, y1)) y1 += 1 diff --git a/networks/geometry/Point2D.py b/networks/geometry/Point2D.py index 4d2b49a..b2bedff 100644 --- a/networks/geometry/Point2D.py +++ b/networks/geometry/Point2D.py @@ -8,7 +8,7 @@ class Point2D: def __init__(self, x: int, y: int): self.x = x self.y = y - self.coordinate = (self.x, self.y) + self.coordinates = (self.x, self.y) def copy(self): return Point2D(self.x, self.y) @@ -167,8 +167,8 @@ class Point2D: """ if xy2 is None: xy2 = xy1.coordinate + np.array([1, 0]) - v0 = np.array(xy1.coordinate) - np.array(self.coordinate) - v1 = np.array(xy2.coordinate) - np.array(self.coordinate) + v0 = np.array(xy1.coordinate) - np.array(self.coordinates) + v1 = np.array(xy2.coordinate) - np.array(self.coordinates) angle = atan2(np.linalg.det([v0, v1]), np.dot(v0, v1)) return np.degrees(angle) @@ -176,17 +176,24 @@ class Point2D: def round(self, ndigits: int = None) -> "Point2D": self.x = round(self.x, ndigits) self.y = round(self.y, ndigits) - self.coordinate = (self.x, self.y) + self.coordinates = (self.x, self.y) return self def distance(self, point: "Point2D") -> int: return sqrt((point.x - self.x) ** 2 + (point.y - self.y) ** 2) + @staticmethod + def collinear(p0: "Point2D", p1: "Point2D", p2: "Point2D") -> bool: + # https://stackoverflow.com/questions/9608148/python-script-to-determine-if-x-y-coordinates-are-colinear-getting-some-e + x1, y1 = p1.x - p0.x, p1.y - p0.y + x2, y2 = p2.x - p0.x, p2.y - p0.y + 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.coordinate)) + vectors.append(np.array(point.coordinates)) if (len(vectors) == 1): return vectors[0] diff --git a/networks/geometry/Point3D.py b/networks/geometry/Point3D.py index 03474de..7873612 100644 --- a/networks/geometry/Point3D.py +++ b/networks/geometry/Point3D.py @@ -8,7 +8,7 @@ class Point3D: self.x = x self.y = y self.z = z - self.coordinate = (x, y, z) + self.coordinates = (x, y, z) def copy(self): return Point3D(self.x, self.y, self.z) @@ -29,7 +29,7 @@ class Point3D: Returns: Point3D: The nearest point, and if multiple, the first in the list. - >>> print(Point3D(0, 0, 0).nearest((Point3D(-10, 10, 5), Point3D(10, 10, 1)))) + >>> Point3D(0, 0, 0).nearest((Point3D(-10, 10, 5), Point3D(10, 10, 1))) Point3D(x: 10, y: 10, z: 1) """ return min(points, key=lambda point: self.distance(point)) @@ -64,7 +64,7 @@ class Point3D: self.x = round(self.x, ndigits) self.y = round(self.y, ndigits) self.z = round(self.z, ndigits) - self.coordinate = (self.x, self.y, self.z) + self.coordinates = (self.x, self.y, self.z) return self def distance(self, point: "Point3D"): @@ -74,7 +74,7 @@ class Point3D: def to_vectors(points: List["Point3D"]): vectors = [] for point in points: - vectors.append(np.array(point.coordinate)) + vectors.append(np.array(point.coordinates)) if (len(vectors) == 1): return vectors[0] diff --git a/networks/geometry/Polyline.py b/networks/geometry/Polyline.py index 145fb32..6988140 100644 --- a/networks/geometry/Polyline.py +++ b/networks/geometry/Polyline.py @@ -18,8 +18,7 @@ class Polyline: >>> Polyline((Point2D(0, 0), Point2D(0, 10), Point2D(50, 10), Point2D(20, 20))) """ - self.coordinates = points - self.points = Point2D.to_vectors(points) + self.points = Point2D.to_vectors(self._remove_collinear_points(points)) self.length_polyline = len(points) if self.length_polyline < 4: @@ -34,12 +33,15 @@ class Polyline: self.radii = [None] * self.length_polyline # r self.centers = [None] * self.length_polyline # c + self.connections = [None] * self.length_polyline self._compute_requirements() self._compute_alpha_radii() self._alpha_assign(0, self.length_polyline-1) + self.output_points = points + def __repr__(self): return str(self.alpha_radii) @@ -51,6 +53,9 @@ class Polyline: return self.radii def get_centers(self): + if self.radii == [None] * self.length_polyline: + raise ValueError("No radii found. Run get_radii before.") + 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])) @@ -61,6 +66,15 @@ class Polyline: self.centers[i] = Point2D(array[0], array[1]).round() return self.centers + def get_arcs(self): + for i in range(1, self.length_polyline-1): + point_1 = self.points[i] - \ + self.alpha_radii[i] * self.unit_vectors[i-1] + point_2 = self.points[i] + \ + self.alpha_radii[i] * self.unit_vectors[i] + self.connections[i] = (point_1, point_2) + return self.connections + def _alpha_assign(self, start_index: int, end_index: int): """ The alpha-assign procedure assigning radii based on a polyline. @@ -131,3 +145,15 @@ class Polyline: def _compute_alpha_radii(self): self.alpha_radii[0] = 0 self.alpha_radii[self.length_polyline-1] = 0 + + @staticmethod + def _remove_collinear_points(points): + output_points = [points[0]] + + for i in range(1, len(points) - 1): + if not Point2D.collinear( + points[i-1], points[i], points[i+1]): + output_points.append(points[i]) + + output_points.append(points[-1]) + return output_points diff --git a/networks/geometry/Segment2D.py b/networks/geometry/Segment2D.py index 15a1067..46cc70c 100644 --- a/networks/geometry/Segment2D.py +++ b/networks/geometry/Segment2D.py @@ -8,14 +8,14 @@ class Segment2D: def __init__(self, start: Point2D, end: Point2D): self.start = start self.end = end - self.coordinates = [] - self.coordinates_thick = [] + self.points = [] + self.points_thick = [] self.thickness = None def __repr__(self): - return str(self.coordinates) + return str(self.points) - def segment(self, 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) -> List[Point2D]: """Modified Bresenham draw (line) with optional overlap. From: https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp @@ -24,12 +24,17 @@ class Segment2D: start (Point2D): Start point of the segment. end (Point2D): End point of the segment. overlap (LINE_OVERLAP): Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE. Can also be LINE_OVERLAP_MAJOR or LINE_OVERLAP_MINOR. + _is_computing_thickness (bool, optionnal): Used by segment_thick. Don't touch. >>> Segment2D(Point2D(0, 0), Point2D(10, 15)) """ - start = self.start.copy() - end = self.end.copy() + if start == None or end == None: + start = self.start.copy() + end = self.end.copy() + else: + start = start.copy() + end = end.copy() # Direction delta_x = end.x - start.x @@ -50,7 +55,7 @@ class Segment2D: delta_2x = 2*delta_x delta_2y = 2*delta_y - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) if (delta_x > delta_y): error = delta_2y - delta_x @@ -58,33 +63,33 @@ class Segment2D: start.x += step_x if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) start.y += step_y if (overlap == LINE_OVERLAP.MINOR): - self._add_coordinates( - Point2D(start.copy().x - step_x, start.copy().y), is_computing_thickness) + self._add_points( + Point2D(start.copy().x - step_x, start.copy().y), _is_computing_thickness) error -= delta_2x error += delta_2y - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) else: error = delta_2x - delta_y while (start.y != end.y): start.y += step_y if (error >= 0): if (overlap == LINE_OVERLAP.MAJOR): - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) start.x += step_x if (overlap == LINE_OVERLAP.MINOR): - self._add_coordinates( - Point2D(start.copy().x, start.copy().y - step_y), is_computing_thickness) + self._add_points( + Point2D(start.copy().x, start.copy().y - step_y), _is_computing_thickness) error -= delta_2y error += delta_2x - self._add_coordinates(start, is_computing_thickness) + self._add_points(start, _is_computing_thickness) - if not is_computing_thickness: - return self.coordinates + if not _is_computing_thickness: + return self.points def segment_thick(self, thickness: int, thickness_mode: LINE_THICKNESS_MODE) -> List[Point2D]: """Bresenham with thickness. @@ -150,7 +155,7 @@ class Segment2D: error += delta_2x self.segment( - start, end, LINE_OVERLAP.NONE, is_computing_thickness=True) + start, end, overlap=LINE_OVERLAP.NONE, _is_computing_thickness=True) error = delta_2x - delta_x for i in range(thickness, 1, -1): @@ -165,7 +170,7 @@ class Segment2D: error += delta_2y self.segment( - start, end, overlap, is_computing_thickness=True) + start, end, overlap=overlap, _is_computing_thickness=True) else: if swap: @@ -186,7 +191,7 @@ class Segment2D: error += delta_2x self.segment( - start, end, LINE_OVERLAP.NONE, is_computing_thickness=True) + start, end, overlap=LINE_OVERLAP.NONE, _is_computing_thickness=True) error = delta_2x - delta_y for i in range(thickness, 1, -1): @@ -201,9 +206,9 @@ class Segment2D: error += delta_2x self.segment( - start, end, overlap, is_computing_thickness=True) + start, end, overlap=overlap, _is_computing_thickness=True) - return self.coordinates + return self.points def perpendicular(self, distance: int) -> List[Point2D]: """Compute perpendicular points from both side of the segment placed at start level. @@ -232,8 +237,8 @@ class Segment2D: np.round((self.start.y + self.end.y) / 2.0).astype(int), ) - def _add_coordinates(self, coordinates, is_computing_thickness): + def _add_points(self, points, is_computing_thickness): if is_computing_thickness: - self.coordinates_thick.append(coordinates.copy()) + self.points_thick.append(points.copy()) else: - self.coordinates.append(coordinates.copy()) + self.points.append(points.copy()) diff --git a/networks/geometry/Segment3D.py b/networks/geometry/Segment3D.py index 89fbe96..e5dac0f 100644 --- a/networks/geometry/Segment3D.py +++ b/networks/geometry/Segment3D.py @@ -7,22 +7,22 @@ class Segment3D: def __init__(self, start: Point3D, end: Point3D): self.start = start self.end = end - self.coordinates = [] + self.output_points = [] def __repr__(self): - return str(self.coordinates) + return str(self.output_points) - def discrete_coordinates(self, overlap: bool = False): + def segment(self, overlap: bool = False): """Calculate a segment between two points in 3D space. 3d Bresenham algorithm. From: https://www.geeksforgeeks.org/bresenhams-algorithm-for-3-d-line-drawing/ Args: - overlap (bool, optional): If False, remove unnecessary coordinates connecting to other coordinates side by side, leaving only a diagonal connection. Defaults to False. + overlap (bool, optional): If False, remove unnecessary points connecting to other points side by side, leaving only a diagonal connection. Defaults to False. >>> Segment3D(Point3D(0, 0, 0), Point3D(10, 10, 15)) """ - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) dx = abs(self.end.x - self.start.x) dy = abs(self.end.y - self.start.y) dz = abs(self.end.z - self.start.z) @@ -45,18 +45,18 @@ class Segment3D: p2 = 2 * dz - dx while start.x != end.x: start.x += xs - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) if p1 >= 0: start.y += ys if not overlap: - if self.coordinates[-1].y != start.y: - self.coordinates.append(start.copy()) + if self.output_points[-1].y != start.y: + self.output_points.append(start.copy()) p1 -= 2 * dx if p2 >= 0: start.z += zs if not overlap: - if self.coordinates[-1].z != start.z: - self.coordinates.append(start.copy()) + if self.output_points[-1].z != start.z: + self.output_points.append(start.copy()) p2 -= 2 * dx p1 += 2 * dy p2 += 2 * dz @@ -67,18 +67,18 @@ class Segment3D: p2 = 2 * dz - dy while start.y != end.y: start.y += ys - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) if p1 >= 0: start.x += xs if not overlap: - if self.coordinates[-1].x != start.x: - self.coordinates.append(start.copy()) + if self.output_points[-1].x != start.x: + self.output_points.append(start.copy()) p1 -= 2 * dy if p2 >= 0: start.z += zs if not overlap: - if self.coordinates[-1].z != start.z: - self.coordinates.append(start.copy()) + if self.output_points[-1].z != start.z: + self.output_points.append(start.copy()) p2 -= 2 * dy p1 += 2 * dx p2 += 2 * dz @@ -89,22 +89,22 @@ class Segment3D: p2 = 2 * dx - dz while start.z != end.z: start.z += zs - self.coordinates.append(start.copy()) + self.output_points.append(start.copy()) if p1 >= 0: start.y += ys if not overlap: - if self.coordinates[-1].y != start.y: - self.coordinates.append(start.copy()) + if self.output_points[-1].y != start.y: + self.output_points.append(start.copy()) p1 -= 2 * dz if p2 >= 0: start.x += xs if not overlap: - if self.coordinates[-1].x != start.x: - self.coordinates.append(start.copy()) + if self.output_points[-1].x != start.x: + self.output_points.append(start.copy()) p2 -= 2 * dz p1 += 2 * dy p2 += 2 * dx - return self.coordinates + return self.output_points def middle_point(self): return (np.round((self.start.x + self.end.x) / 2.0).astype(int), diff --git a/networks/geometry/point_tools.py b/networks/geometry/point_tools.py index 1ca56bb..9f4f400 100644 --- a/networks/geometry/point_tools.py +++ b/networks/geometry/point_tools.py @@ -1,6 +1,5 @@ from math import sqrt, cos, pi, sin import numpy as np -from networks.geometry.segment_tools import discrete_segment, middle_point, parallel def segments_intersection(line0, line1, full_line=True): diff --git a/output_image.png b/output_image.png index df9422f..8d29fc5 100644 Binary files a/output_image.png and b/output_image.png differ