From 07c3db5ade30f7153df11d395e1d9a65efe9e905 Mon Sep 17 00:00:00 2001 From: Tolga Yurtseven <tolgayurt02@outlook.de> Date: Mon, 16 Nov 2020 13:12:02 +0100 Subject: [PATCH] refactored and wrote doc_string for the whole packing_algo.py --- mysite/plots/packing_algo.py | 564 +++++++++++------- .../templates/plots/packed_polygons.html | 2 +- mysite/plots/views.py | 2 +- 3 files changed, 351 insertions(+), 217 deletions(-) diff --git a/mysite/plots/packing_algo.py b/mysite/plots/packing_algo.py index d791ef49..e2710c59 100644 --- a/mysite/plots/packing_algo.py +++ b/mysite/plots/packing_algo.py @@ -3,7 +3,7 @@ import numpy import math import itertools -# tree datastructure +# tree data structure from . import avl_tree # shapely/polygon packages @@ -12,7 +12,7 @@ from shapely import affinity # plotting packages from bokeh.plotting import figure, Figure -from bokeh.io import output_notebook, show +from bokeh.io import show from bokeh.layouts import gridplot, layout, column, Column, Row from bokeh.models import Div from bokeh.models import Legend @@ -67,7 +67,7 @@ class ConvexPolygon(object): self.__slope = self.set_slope() self.plot_fill = False - # all these @property decorators helps to make the attributes private + # all these @property decorators helps to make the attributes "private" @property def area(self): return self.__area @@ -124,7 +124,8 @@ class ConvexPolygon(object): def slope(self): return self.__slope - def convex_hull_and_area(self, shell: [Point_xy]) -> ([Point_xy], float): + @staticmethod + def convex_hull_and_area(shell: [Point_xy]) -> ([Point_xy], float): """Builds the convex hull of the input points and creates a convex polygon with his area This function creates a shapely (a python package) polygon to use the shapely convex hull function, @@ -138,7 +139,7 @@ class ConvexPolygon(object): Returns: shell, area ([Point_xy], float): a tuple of the convex polygon shell and polygon area """ - if shell == None: + if shell is None: raise ValueError("a convex polygon can't intialized with None") elif shell == [] or len(shell) < 3: raise ValueError("a convex polygon needs at least 3 points") @@ -284,7 +285,7 @@ class ConvexPolygon(object): spine_top_found = False spine_bottom_not_horizontal = False # cycling threw the vertices and searching for the spine bottom, which will be the start point - while spine_bottom_found == False: + while not spine_bottom_found: if help_array[0] == spine_bottom: # if the bottom_spine_vertex has a horizontal neighbour, do not take it to the right_visible_vertices if spine_bottom[1] != help_array[-1][1]: @@ -293,7 +294,7 @@ class ConvexPolygon(object): left.append(help_array.pop(0)) else: help_array.append(help_array.pop(0)) - while spine_top_found == False: # searching the spine top + while not spine_top_found: # searching the spine top if help_array[0] == spine_top: spine_top_found = True # if the top_spine_vertex has a horizontal neighbour, do not take it to the left_visible_vertices @@ -333,7 +334,7 @@ class HighClass(object): self.h_max_polygon = h_max_polygon self.w_max_polygon = w_max_polygon self.min_border = alpha ** (i + 1) * h_max_polygon - self.max_border = alpha ** (i) * h_max_polygon + self.max_border = alpha ** i * h_max_polygon self.polygons = [] self.spine_ordered_polygons = [] @@ -349,7 +350,7 @@ class HighClass(object): spine_ordered_polygons = sorted(self.polygons, key=lambda polygon: polygon.slope) self.spine_ordered_polygons = spine_ordered_polygons - def plot_hc(self, ordered=False, render=True, plot_width=300, plot_height=300) -> Column: + def plot_hc(self, ordered=False, render=True, plot_width=300, plot_height=300, columns=4) -> Column: """Plot object of the polygons of the HighClass with ordered or unordered spines Args: @@ -357,9 +358,9 @@ class HighClass(object): render (bool): if True -> the plot will be rendered else not plot_width (int): width of the plot figure plot_height (int): height of the plot figure - + columns (int): the number of columns in which the plot is shown Returns: - grid_layout (Colums): plot object which can be rendered with show(grid_layout) + grid_layout (Column): plot object which can be rendered with show(grid_layout) """ plots = [] if ordered: @@ -369,7 +370,7 @@ class HighClass(object): for counter, polygon in enumerate(polygon_list): title = "Polygon {}".format(counter) plots.append(polygon.plot_polygon(title=title, render=False)) - grid = gridplot(plots, ncols=4, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") + grid = gridplot(plots, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") grid_title = Div(text="<b>Hc_{} height: {}-{} alpha: {}<b>".format(self.i, truncate( self.min_border, 1), truncate(self.max_border, 1), self.alpha)) grid_layout = column(grid_title, grid) @@ -389,7 +390,7 @@ class Container(object): y_boundary (float): y coordinate boundary for the container dependent of the High_Class max_boundary x_boundary (float): x coordinate boundary fot the container Tree (Tree): avl-tree data structure - root (TreeNode): the root of the avl-tree datastructure + root (TreeNode): the root of the avl-tree data structure sigma ([ConvexPolygon]): the list of packed convex polygons from the HighClass plot_steps ([Figure]): list of steps which are done to pack the container as plot objects @@ -404,7 +405,8 @@ class Container(object): self.root = None self.sigma, self.plot_steps = self.packing_container(self.hc.spine_ordered_polygons) - def distance(self, edge_points: (Point_xy, Point_xy), point: Point_xy) -> float: + @staticmethod + def distance(edge_points: (Point_xy, Point_xy), point: Point_xy) -> float: """Calculates the shortest distance between an edge and a point Args: @@ -429,7 +431,8 @@ class Container(object): distance = math.sqrt((intersection_point[0] - point[0]) ** 2 + (intersection_point[1] - point[1]) ** 2) return distance - def find_corresponding_edge(self, vertex: Point_xy, vertices_visible_left: [Point_xy]) -> (Point_xy, Point_xy): + @staticmethod + def find_corresponding_edge(vertex: Point_xy, vertices_visible_left: [Point_xy]) -> (Point_xy, Point_xy): """This function finds the corresponding edge for a vertex which is already packed and visible from right. To find the corresponding edge the vertices visible from left of the polygon which will be moved to the left @@ -597,7 +600,6 @@ class Container(object): x = vertex_r[0] corresponding_point_x = x - distance y = vertex_r[1] - text_point_x, text_point_y = (x - distance / 2, y) distance_int = int(distance) color = "blue" if distance_int == int(min_distance): @@ -618,7 +620,6 @@ class Container(object): x = vertex_l[0] corresponding_point_x = x + distance y = vertex_l[1] - text_point_x, text_point_y = (x + distance / 2, y) distance_int = int(distance) color = "green" if distance_int == int(min_distance): @@ -651,21 +652,21 @@ class Container(object): show(fig) return fig - def plot_container_steps(self, render=True, colums=2, plot_width=600, plot_height=600) -> Column: + def plot_container_steps(self, render=True, columns=2, plot_width=600, plot_height=600) -> Column: """Build a plot object which includes all container packing steps/plots This plot is a grid plot, which means that it is result of merging several plots. Args: render (bool): If True the plot will be rendered else not - colums (int): the number of colums in which the packing steps are listed + columns (int): the number of columns in which the packing steps are listed plot_width (int): the width of the single plot objects plot_height (int): the height of the single plot objects Returns: grid_layout(Column): A plot object with all packing steps inside """ - grid = gridplot(self.plot_steps, ncols=colums, plot_width=plot_width, plot_height=plot_height, + grid = gridplot(self.plot_steps, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") min_border = (int(self.hc.min_border * 10)) / 10 max_border = (int(self.hc.max_border * 10)) / 10 @@ -684,7 +685,7 @@ class MiniContainer(object): The Container which got packed/build from a HighClass got "theoretically" divided into boxes. A mini-container is an extension of the width of one these boxes. The purpose is to have mini-containers with fixed widths so they easily can be stacked, when the - RectengularContainer is build. + RectangularContainer is build. Args: max_width (float): is the maximum width for all mini-containers which will be created @@ -755,12 +756,12 @@ class MiniContainer(object): # building the custom legend fig_boundary = fig.line([0, self.max_width, self.max_width, 0, 0], [0 + y_offset, 0 + y_offset, self.height + y_offset, self.height + y_offset, - 0 + y_offset] - , line_color="black", alpha=0.7, line_width=1, muted_alpha=0.2, ) + 0 + y_offset], + line_color="black", alpha=0.7, line_width=1, muted_alpha=0.2) fig_polygon_boundary = fig.line([self.current_right_boundary, self.current_right_boundary], - [0 + y_offset, self.height + y_offset] - , line_color="black", line_dash="dotted", alpha=0.7, line_width=1, - muted_alpha=0.2, ) + [0 + y_offset, self.height + y_offset], + line_color="black", line_dash="dotted", alpha=0.7, line_width=1, + muted_alpha=0.2) items = legend_polygons + [("spine", legend_spine)] items.append(("container-boundary", [fig_boundary])) items.append(("last polygon-boundary", [fig_polygon_boundary])) @@ -977,7 +978,7 @@ class ConvexContainer(object): Args: polygons ([ConvexPolygon]): convex input polygons to pack into the smallest ConvexContainer - steps (int): the angle for the rotation and backrotation increments + steps (int): the angle for the rotation and back rotation increments Attributes: angle_0_rectangular_container (ConvexContainer): the input polygons packed to a not rotated RectangularContainer angle_0_rectangular_container_polygons ([ConvexPolygon]): the polygons of the 0° RectangularContainer @@ -1005,7 +1006,7 @@ class ConvexContainer(object): self.rotated_rectangular_container_list = [] # Liste aller RectangularContainer self.rotated_container_plots = [] self.back_rotated_polygons_plots = [] - self.smallest_rectangular_container, self.polygons, self.angle, self.area = self.find_convex_container(steps,build_plots) + self.smallest_rectangular_container, self.polygons, self.angle, self.area = self.find_convex_container(steps, build_plots) self.polygon_shells = self.collect_polygon_shells() self.width = self.smallest_rectangular_container.x_boundary self.height = self.smallest_rectangular_container.y_boundary @@ -1047,9 +1048,10 @@ class ConvexContainer(object): truncate(rotated_rectangular_container.container_area, 1), truncate(rotated_rectangular_container.container_not_clipped_area, 1), rotated_rectangular_container.angle) - self.rotated_container_plots.append(rotated_rectangular_container.plot_container(render=False, title=title)) + self.rotated_container_plots.append(rotated_rectangular_container.plot_container(render=False, + title=title)) back_rotated_polygons = rotate_polygons(rotated_rectangular_container.polygons, angle, - origin=self.rotate_center) # kreiert neue Polygone + origin=self.rotate_center) box_x_v = rotated_rectangular_container.x_values_border box_y_v = rotated_rectangular_container.y_values_border boundaries = list(zip(box_x_v, box_y_v)) @@ -1057,7 +1059,8 @@ class ConvexContainer(object): origin=self.rotate_center) title2 = 'Convex-Container area: {} not-clipped-area: {} angle: {}'.format( truncate(rotated_rectangular_container.container_area, 1), - truncate(rotated_rectangular_container.container_not_clipped_area, 1), -rotated_rectangular_container.angle) + truncate(rotated_rectangular_container.container_not_clipped_area, 1), + -rotated_rectangular_container.angle) back_rotated_polygon_plot = plot_polygons_in_single_plot(back_rotated_polygons, render=False, border=(rotated_x_values, rotated_y_values), title=title2) @@ -1136,37 +1139,37 @@ class ConvexContainer(object): show(self.plot_steps_all) return self.plot_steps_all - def plot_rotated_containers(self, render=True, colums=9, plot_width=600, plot_height=600) -> Row: + def plot_rotated_containers(self, render=True, columns=9, plot_width=600, plot_height=600) -> Row: """Plot including all rotated RectangularContainers Args: render (bool): if True render the plot else not - colums (int): the colum count in which the container plots will be rendered + columns (int): the column count in which the container plots will be rendered plot_width (int): the width of the plot figure plot_height (int): the height of the plot figure Returns: grid (Row): grid plot including all rotated RectangularContainers """ - grid = gridplot(self.rotated_container_plots, ncols=colums, plot_width=plot_width, plot_height=plot_height, + grid = gridplot(self.rotated_container_plots, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") if render: show(grid) return grid - def plot_back_rotated_polygons(self, render=True, colums=9, plot_width=700, plot_height=600) -> Row: + def plot_back_rotated_polygons(self, render=True, columns=9, plot_width=700, plot_height=600) -> Row: """Plot including all back rotated polygons of the RectangularContainers Args: render (bool): if True render the plot else not - colums (int): the colum count in which the container plots will be rendered + columns (int): the column count in which the container plots will be rendered plot_width (int): the width of the plot figure plot_height (int): the height of the plot figure Returns: grid (Row): grid plot including all back rotated polygons of the RectangularContainers """ - grid = gridplot(self.back_rotated_polygons_plots, ncols=colums, plot_width=plot_width, plot_height=plot_height, + grid = gridplot(self.back_rotated_polygons_plots, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") if render: show(grid) @@ -1193,76 +1196,96 @@ class ConvexContainer(object): return fig -def rotate_points_and_split(point_list: [Point_xy], angle, origin=(0, 0)) -> ([float], [float]): - """Rotates the input points and give a tuple with x and y values back +def create_multiple_convex_polygons(number: int, max_ngon: int, max_rand=1000) -> [ConvexPolygon]: + """Creates multiple convex polygons - Uses the affinity.rotated function from the shapely package which creates a dependency, another way would be to use - a own function (rotation matrix). + Args: + number (int): the number of the polygons to create + max_ngon (int): the maximum vertices a polygon can have + max_rand (int): the maximum value for the x and y coordinate of the polygon vertices - Hint: - when writing an own function to rotate the points, their are some special cases in python which need to be - handled, + Returns: + polygon_list ([ConvexPolygon]): a list of convex polygons + """ + polygon_list = [] + for count in range(0, number): + polygon_list.append(create_convex_polygon(max_ngon, max_rand)) + return polygon_list - this is a code snipped from the shapely package to show these cases - code snipped from: https://github.com/Toblerity/Shapely/blob/master/shapely/affinity.py - angle = angle * pi/180.0 - cosp = cos(angle) - sinp = sin(angle) - if abs(cosp) < 2.5e-16: # if not checked this case could create confusion - cosp = 0.0 - if abs(sinp) < 2.5e-16: # if not checked this case could create confusion - sinp = 0.0 + +def create_convex_polygon(max_ngon: int, max_rand=1000) -> ConvexPolygon: + """Creates a convex Polygon + + Uses the convex hull function from the shapely package. + The way the polygon is created is to create max_ngon points and then use the convex hull on these points which means + that the output polygon does not have allways max_ngon vertices. Args: - point_list ([Point_xy]): a list of points which need to be rotated - angle (int): the rotation angle - origin (float): the rotation point for all rotations + max_ngon (int): the maximum vertices a polygon can have + max_rand (int): the maximum value for the x and y coordinate of the polygon vertices Returns: - poly_wrapper_rotated.exterior.xy (([float], [float])): tuple with rotated x and y values + c_polygon (ConvexPolygon): """ - # wrapping the points into a shapely Polygon to use the rotation function - poly_wrapper = Polygon(point_list) - poly_wrapper_rotated = affinity.rotate(poly_wrapper, angle, origin=origin) - return poly_wrapper_rotated.exterior.xy - + ngon = numpy.random.randint(3, max_ngon + 1) + convex_polygon = None + while type(convex_polygon) is not Polygon: # the shapely convex hull could create a point or stretch + polygon_points = [] + while len(polygon_points) < ngon: + x = numpy.random.randint(0, max_rand + 1) + y = numpy.random.randint(0, max_rand + 1) + while (x, y) in polygon_points: + x = numpy.random.randint(0, max_rand + 1) + y = numpy.random.randint(0, max_rand + 1) + polygon_points.append((x, y)) + convex_polygon = Polygon(polygon_points).convex_hull + c_polygon = ConvexPolygon(list(convex_polygon.exterior.coords)) + return c_polygon -def rotate_and_create_new_convex_polygon(polygon: ConvexPolygon, angle: int, origin=(0, 0)) -> ConvexPolygon: - """Creates a new rotated convex polygon from an input convex polygon - This function uses again the affinity.rotate() function from shapely. - This creates a dependency to the shapely package. +def build_height_classes(polygon_list: [ConvexPolygon]) -> [HighClass]: + """Creates HighClasses and assigns the input polygons to them Args: - polygon (ConvexPolygon): convex polygon which will "copied" and rotated - angle (int): the rotation angle - origin (float): the rotation point for all vertex rotations + polygon_list ([ConvexPolygons]): list of the convex input polygons Returns: - rotated_convex_polygon (ConvexPolygon): rotated convex polygon + height_classes ([HeightClass]): a list of created height classes """ - poly_wrapper = Polygon(polygon.shell) - poly_wrapper_rotated = affinity.rotate(poly_wrapper, angle, origin=origin) - rotated_convex_polygon = ConvexPolygon(poly_wrapper_rotated.exterior.coords) - return rotated_convex_polygon + ordered_polygons = sorted(polygon_list, key=lambda polygon: polygon.height, reverse=True) + h_max_polygon = ordered_polygons[0].height + w_max_polygon = max([polygon.width for polygon in polygon_list]) + alpha = 0.407 + i = 0 + height_classes = [] + polygon_count = len(ordered_polygons) + while polygon_count > 0: + hc = HighClass(i, alpha, h_max_polygon, w_max_polygon) + hc_polygons = [] + while polygon_count > 0 and hc.min_border < ordered_polygons[0].height <= hc.max_border: + hc_polygons.append(ordered_polygons.pop(0)) + polygon_count -= 1 + hc.set_polygons(hc_polygons) + if len(hc.polygons) > 0: + height_classes.append(hc) + i += 1 + return height_classes -def rotate_polygons(polygons: [ConvexPolygon], angle: int, origin=(0, 0)) -> [ConvexPolygon]: - """Creates several new rotated convex polygons +def build_containers(height_classes: [HighClass]) -> [Container]: + """Wrapper function for building a Container for each HighClass Args: - polygons ([ConvexPolygon]): convex polygons which will "copied" and rotated - angle (int): the rotation angle - origin (float): the rotation point for all vertex rotations + height_classes ([HeighClass]): list of input high classes Returns: - rotated_polygons ([ConvexPolygon]): rotated convex polygons + container ([Container]): a list of build containers """ - rotated_polygons = [] - for polygon in polygons: - rotated_polygon = rotate_and_create_new_convex_polygon(polygon, angle, origin) - rotated_polygons.append(rotated_polygon) - return rotated_polygons + containers = [] + for height_class in height_classes: + container = Container(height_class) + containers.append(container) + return containers def build_mini_containers_and_plots(container_array: [Container], c=2.214) -> ([MiniContainer], [[Figure]]): @@ -1276,7 +1299,7 @@ def build_mini_containers_and_plots(container_array: [Container], c=2.214) -> ([ Returns: mini_container_array, mini_container_plots_list ([MiniContainer], [[Figure]]): a tuple of all build - mini-containers and all import + mini-containers and all important plot steps, every list of plots represent a Container and the resulting mini-containers @@ -1299,8 +1322,8 @@ def build_mini_containers_and_plots(container_array: [Container], c=2.214) -> ([ background_color_list.append(next(colors)) box_boundaries_x_values_colors_tuple = (box_boundaries_x_values, background_color_list) container_and_mini_container_plots.append(container.plot_container(container_sigma, render=False, - box_boundaries_x_values_colors_tuple= - box_boundaries_x_values_colors_tuple)) + box_boundaries_x_values_colors_tuple + =box_boundaries_x_values_colors_tuple)) max_width_mini_container = box_width + container.hc.w_max_polygon background_counter = 0 while len(container_sigma) > 0: @@ -1329,7 +1352,7 @@ def build_mini_containers_and_plots(container_array: [Container], c=2.214) -> ([ mini_container.height = mini_container_y_border mini_container.current_right_boundary = mini_container_x_border mini_container_array.append(mini_container) - if (box_width * box_counter) < (container.x_boundary): + if (box_width * box_counter) < container.x_boundary: title = "Mini-Container{} height: {} (hc_{})".format(box_counter, truncate(mini_container.height, 1), container.hc.i) b_color = background_color_list[background_counter] @@ -1337,7 +1360,7 @@ def build_mini_containers_and_plots(container_array: [Container], c=2.214) -> ([ mini_container.plot_container(title=title, render=False, background_c=b_color)) box_counter += 1 background_counter += 1 - # for the smaller box + # for the last box which can be smaller else: title = "Mini-Container{} height: {} (hc_{})".format(box_counter, truncate(mini_container.height, 1), container.hc.i) @@ -1346,57 +1369,162 @@ def build_mini_containers_and_plots(container_array: [Container], c=2.214) -> ([ return mini_container_array, mini_container_plots_list -def plot_mini_containers(plot_steps: [[Figure]], render=True, colums=3, plot_width=600, plot_height=600) -> Column: - plots = [] - for plot in plot_steps: - grid = gridplot(plot, ncols=colums, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") - plots.append(grid) - if render: - show(layout(plots)) - return layout(plots) +def pack_polygons(polygons: [ConvexPolygon], angle=0) -> RectangularContainer: + """Wrapper for the packing algorithm, packs the convex input polygons into a RectangularContainer + Packing Steps: + 0. Condition that the input polygons are convex + 1. Sort the polygons into HighClasses + 2. For every HighClass build a Container with fixed height + 3. Divide every Container into MiniContainers with fixed width + 3.1 optimization: clip the mini-container height to the highest vertex point of the mini-container + 4. Stack the MiniContainer to a RectangularContainer + 4.1 optimization: clip the RectangularContainer width to the rightest polygon vertex of all polygons in the + RectangularContainer -def create_multiple_convex_polygons(number: int, max_ngon: int, max_rand=1000) -> [ConvexPolygon]: - polygon_list = [] - for count in range(0, number): - polygon_list.append(create_convex_polygon(max_ngon, max_rand)) - return polygon_list + The pack_polygons function also bundles all important packing steps in one plot. + Args: + polygons ([ConvexPolygon]): convex input polygon which should packed + angle (int): the angle of the Rectangular container default 0, this argument helps for finding the + ConvexContainer -def create_convex_polygon(max_ngon: int, - max_rand=1000) -> ConvexPolygon: # die erste Koordinate der konvexen Polygone wird dupliziert zum schließen des Polygons - ngon = numpy.random.randint(3, max_ngon + 1) - # polygon_points=[] - convex_polygon = None - while type(convex_polygon) is not Polygon: # da bei der Convexen Hülle ein Punkt oder String rauskommen kann - polygon_points = [] - while len(polygon_points) < ngon: - x = numpy.random.randint(0, max_rand + 1) - y = numpy.random.randint(0, max_rand + 1) - while (x, y) in polygon_points: - x = numpy.random.randint(0, max_rand + 1) - y = numpy.random.randint(0, max_rand + 1) - polygon_points.append((x, y)) - convex_polygon = Polygon(polygon_points).convex_hull - c_polygon = ConvexPolygon(list(convex_polygon.exterior.coords)) - return c_polygon + Returns: + rectangular_container (RectangularContainer): the packed polygons in a RectangularContainer, the packing steps + are saved in this object + """ + # building the RectangularContainer + list_hc = build_height_classes(polygons) + list_containers = build_containers(list_hc) + list_mini_containers, mini_container_plot_steps = build_mini_containers_and_plots(list_containers) + rectangular_container = RectangularContainer(list_mini_containers, angle) + + # plots + p_tab = polygons_to_tab_plot(polygons) + p_header_tab = Panel(child=p_tab, title="Polygons") + hc_tab = hc_to_tab_plot(list_hc) + hc_header_tab = Panel(child=hc_tab, title="Height-Classes") + c_tab = containers_to_tab_plot(list_containers) + c_header_tab = Panel(child=c_tab, title="Containers") + mc_tab = mini_container_plots_to_tab_plot(mini_container_plot_steps) + mc_header_tab = Panel(child=mc_tab, title="Mini-Containers") + ec_tab = rectangular_container_to_tab_plot(rectangular_container) + ec_header_tab = Panel(child=ec_tab, title="Rectangular-Container") + all_tabs_with_header = Tabs(tabs=[p_header_tab, hc_header_tab, c_header_tab, mc_header_tab, ec_header_tab]) + + rectangular_container.plot_steps_all = all_tabs_with_header + return rectangular_container + + +def rotate_points_and_split(point_list: [Point_xy], angle, origin=(0, 0)) -> ([float], [float]): + """Rotates the input points and give a tuple with x and y values back + + Uses the affinity.rotated function from the shapely package which creates a dependency, another way would be to use + a own function (rotation matrix). + + Hint: + when writing an own function to rotate the points, their are some special cases in python which need to be + handled, + + this is a code snipped from the shapely package to show these cases + code snipped from: https://github.com/Toblerity/Shapely/blob/master/shapely/affinity.py + angle = angle * pi/180.0 + cosp = cos(angle) + sinp = sin(angle) + if abs(cosp) < 2.5e-16: # if not checked this case could create confusion + cosp = 0.0 + if abs(sinp) < 2.5e-16: # if not checked this case could create confusion + sinp = 0.0 + + Args: + point_list ([Point_xy]): a list of points which need to be rotated + angle (int): the rotation angle + origin ((float,float)): the rotation point for all rotations + + Returns: + poly_wrapper_rotated.exterior.xy (([float], [float])): tuple with rotated x and y values + """ + # wrapping the points into a shapely Polygon to use the rotation function + poly_wrapper = Polygon(point_list) + poly_wrapper_rotated = affinity.rotate(poly_wrapper, angle, origin=origin) + return poly_wrapper_rotated.exterior.xy -def plot_polygons(polygon_list: [ConvexPolygon], render=True, plot_width=450, plot_height=450, ncols=4) -> Column: +def rotate_and_create_new_convex_polygon(polygon: ConvexPolygon, angle: int, origin=(0, 0)) -> ConvexPolygon: + """Creates a new rotated convex polygon from an input convex polygon + + This function uses again the affinity.rotate() function from shapely. + This creates a dependency to the shapely package. + + Args: + polygon (ConvexPolygon): convex polygon which will "copied" and rotated + angle (int): the rotation angle + origin ((float,float)): the rotation point for all vertex rotations + + Returns: + rotated_convex_polygon (ConvexPolygon): rotated convex polygon + """ + poly_wrapper = Polygon(polygon.shell) + poly_wrapper_rotated = affinity.rotate(poly_wrapper, angle, origin=origin) + rotated_convex_polygon = ConvexPolygon(poly_wrapper_rotated.exterior.coords) + return rotated_convex_polygon + + +def rotate_polygons(polygons: [ConvexPolygon], angle: int, origin=(0, 0)) -> [ConvexPolygon]: + """Creates several new rotated convex polygons + + Args: + polygons ([ConvexPolygon]): convex polygons which will "copied" and rotated + angle (int): the rotation angle + origin ((float,float)): the rotation point for all vertex rotations + + Returns: + rotated_polygons ([ConvexPolygon]): rotated convex polygons + """ + rotated_polygons = [] + for polygon in polygons: + rotated_polygon = rotate_and_create_new_convex_polygon(polygon, angle, origin) + rotated_polygons.append(rotated_polygon) + return rotated_polygons + + +def truncate(number: float, decimals=0) -> float: + """Returns a value truncated to a specific number of decimal places. + + Source of the this function code is from: https://kodify.net/python/math/truncate-decimals/ + References: + Python.org (n.d.). math — Mathematical functions. Retrieved on October 22, 2019,from https://docs.python.org/3.8/library/math.html + """ + if not isinstance(decimals, int): + raise TypeError("decimal places must be an integer.") + elif decimals < 0: + raise ValueError("decimal places has to be 0 or more.") + elif decimals == 0: + return math.trunc(number) + factor = 10.0 ** decimals + return math.trunc(number * factor) / factor + + +def plot_polygons(polygon_list: [ConvexPolygon], render=True, plot_width=450, plot_height=450, columns=4) -> Column: + """Build a plot object for a list of convex polygons + + Args: + polygon_list ([ConvexPolygon]): list of convex polygons + render (bool): if True the plot will be render else not + plot_width (int): the width of the plot figure + plot_height (int): the height of the plot figure + columns (int): the column count in which polygons of the plot are shown + + Returns: + grid (Column): a plot of all input polygons + """ plots = [] colors = itertools.cycle(palette) for counter, polygon in enumerate(polygon_list): color = next(colors) title = "Polygon {}".format(counter) plots.append(polygon.plot_polygon(title=title, color=color, render=False)) - grid = gridplot(plots, ncols=ncols, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") - if render: - show(grid) - return grid - - -def plot_figures_as_grid(plot_list: [Figure], render=True, plot_width=600, plot_height=500, ncols=4) -> Column: - grid = gridplot(plot_list, ncols=ncols, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") + grid = gridplot(plots, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") if render: show(grid) return grid @@ -1404,7 +1532,20 @@ def plot_figures_as_grid(plot_list: [Figure], render=True, plot_width=600, plot_ def plot_polygons_in_single_plot(polygon_list: [ConvexPolygon], title="", plot_width=750, plot_height=500, render=True, border=None, reverse_legend=False) -> Figure: - polygon_number = len(polygon_list) + """Creates a plot object for several polygons in one coordinate system + + Args: + polygon_list ([ConvexPolygon]): a list of convex polygons + title (str): the title of the plot + plot_width (int): the width of the plot figure + plot_height (int): the height of the plot figure + render (bool): if True the plot will be rendered else not + border (([float],[float])): a tuple of x and y values to create border for the polygons + reverse_legend (bool): if True the plot legend will be reversed + + Returns: + fig (Figure): a plot object which shows all input polygons in one coordinate system + """ tooltips = [("index", "$index"), ("(x,y)", "($x{0.0}, $y{0.0})"), ] fig = figure(title=title, x_axis_label='x', y_axis_label='y', width=plot_width, height=plot_height, @@ -1428,7 +1569,7 @@ def plot_polygons_in_single_plot(polygon_list: [ConvexPolygon], title="", plot_w legend_polygons.append((legend_label, [poly_fig])) legend_vertices.append(circle_fig) legend_all_polygons = legend_all_polygons + [poly_fig] - if border != None: + if border is not None: fig_border = fig.line(border[0], border[1], line_color="red", line_width=1, muted_alpha=0.2) legend_items.append(("border", [fig_border])) @@ -1446,61 +1587,20 @@ def plot_polygons_in_single_plot(polygon_list: [ConvexPolygon], title="", plot_w return fig -def build_height_classes(polygon_list: [ConvexPolygon]) -> [HighClass]: - ordered_polygons = sorted(polygon_list, key=lambda polygon: polygon.height, reverse=True) - h_max_polygon = ordered_polygons[0].height - w_max_polygon = max([polygon.width for polygon in polygon_list]) - alpha = 0.407 - i = 0 - height_classes = [] - polygon_count = len(ordered_polygons) - while polygon_count > 0: - hc = HighClass(i, alpha, h_max_polygon, w_max_polygon) - hc_polygons = [] - while polygon_count > 0 and hc.min_border < ordered_polygons[0].height and \ - ordered_polygons[0].height <= hc.max_border: - hc_polygons.append(ordered_polygons.pop(0)) - polygon_count -= 1 - hc.set_polygons(hc_polygons) - if len(hc.polygons) > 0: - height_classes.append(hc) # will man höhen Klassen ohne Polygone drinne ? - i += 1 - return height_classes - - -def building_containers(height_classes: [HighClass]) -> [Container]: - containers = [] - for height_class in height_classes: - container = Container(height_class) - containers.append(container) - return containers - - -def pack_polygons(polygons: [ConvexPolygon], angle=0) -> RectangularContainer: - # building the RectangularContainer - list_hc = build_height_classes(polygons) - list_containers = building_containers(list_hc) - list_mini_containers, mini_container_plot_steps = build_mini_containers_and_plots(list_containers) - rectangular_container = RectangularContainer(list_mini_containers, angle) - - # plots - p_tab = polygons_to_tab_plot(polygons) - p_header_tab = Panel(child=p_tab, title="Polygons") - hc_tab = hc_to_tab_plot(list_hc) - hc_header_tab = Panel(child=hc_tab, title="Height-Classes") - c_tab = containers_to_tab_plot(list_containers) - c_header_tab = Panel(child=c_tab, title="Containers") - mc_tab = mini_container_plots_to_tab_plot(mini_container_plot_steps) - mc_header_tab = Panel(child=mc_tab, title="Mini-Containers") - ec_tab = rectangular_container_to_tab_plot(rectangular_container) - ec_header_tab = Panel(child=ec_tab, title="Rectangular-Container") - all_tabs_with_header = Tabs(tabs=[p_header_tab, hc_header_tab, c_header_tab, mc_header_tab, ec_header_tab]) - - rectangular_container.plot_steps_all = all_tabs_with_header - return rectangular_container +def polygons_to_tab_plot(polygons: [ConvexPolygon], tab_poly_count=9, columns=3, plot_width=450, plot_height=450) -> \ + Tabs: + """Plot of convex input polygons structured with tabs + Args: + polygons ([ConvexPolyogn]): convex input Polygons + tab_poly_count (int): number of polygons in one tab + columns (int): the columns to present the polygons + plot_width (int): the width of the plot figure + plot_height (int): the width of the plot figure -def polygons_to_tab_plot(polygons: [ConvexPolygon], tab_poly_count=9, ncols=3, plot_width=450, plot_height=450) -> Tabs: + Returns: + polygon_tabs (Tabs): a plot object which represents all convex input polygons structured with tabs + """ polygon_tab_list = [] polygons_in_one_tab = [] colors = itertools.cycle(palette) @@ -1510,13 +1610,12 @@ def polygons_to_tab_plot(polygons: [ConvexPolygon], tab_poly_count=9, ncols=3, p polygon_plot = polygon.plot_polygon(title=polygon_title, color=color, render=False) polygons_in_one_tab.append(polygon_plot) if len(polygons_in_one_tab) >= tab_poly_count or (counter + 1 == len(polygons)): - # tab_layout= ([polygons_in_one_tab]) if counter + 1 - tab_poly_count < 0: title = "Polygons {}-{}".format(0, counter) else: title = "Polygons {}-{}".format(counter + 1 - len(polygons_in_one_tab), counter) grid_title = Div(text="<b>{}<b>".format(title)) - polygons_grid = gridplot(polygons_in_one_tab, ncols=ncols, plot_width=plot_width, plot_height=plot_height, + polygons_grid = gridplot(polygons_in_one_tab, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") tab_layout = layout(grid_title, polygons_grid) tab = Panel(child=tab_layout, title=title) @@ -1527,6 +1626,16 @@ def polygons_to_tab_plot(polygons: [ConvexPolygon], tab_poly_count=9, ncols=3, p def hc_to_tab_plot(list_hc: [HighClass], plot_width=450, plot_height=450) -> Tabs: + """Plot of the input HighClasses structured with tabs + + Args: + list_hc ([HighClass]): list of the input HighClasses + plot_width (int): width of the plot figure + plot_height (int): height of the plot figure + + Returns: + hc_tabs (Tabs): a plot object which represents the input HighClasses structured with tabs + """ hc_tab_list = [] for counter, hc in enumerate(list_hc): hc_plot = hc.plot_hc(render=False, plot_width=plot_width, plot_height=plot_height) @@ -1536,10 +1645,21 @@ def hc_to_tab_plot(list_hc: [HighClass], plot_width=450, plot_height=450) -> Tab return hc_tabs -def containers_to_tab_plot(list_containers: [Container], plot_width=700, plot_height=700) -> Tabs: +def containers_to_tab_plot(list_containers: [Container], plot_width=700, plot_height=700, columns=3) -> Tabs: + """Plot of the input Containers structured with tabs + + Args: + list_containers (): list of the input Containers + plot_width (int): width of the plot figure + plot_height (int): height of the plot figure + columns (int): the columns to present the polygons + + Returns: + container_tabs (Tabs): a plot object which represents the input Containers structured with tabs + """ container_tab_list = [] for counter, container in enumerate(list_containers): - container_plot = container.plot_container_steps(plot_width=plot_width, colums=3, plot_height=plot_height, + container_plot = container.plot_container_steps(plot_width=plot_width, columns=columns, plot_height=plot_height, render=False) tab = Panel(child=container_plot, title="Hc_{} Container".format(container.hc.i)) container_tab_list.append(tab) @@ -1547,12 +1667,25 @@ def containers_to_tab_plot(list_containers: [Container], plot_width=700, plot_he return container_tabs -def mini_container_plots_to_tab_plot(mini_container_plot_steps: [Figure], plot_width=700, plot_height=700, ncols=3) \ +def mini_container_plots_to_tab_plot(mini_container_plot_steps: [[Figure]], plot_width=700, plot_height=700, columns=3)\ -> Tabs: + """Plot of the input mini-container plot object structured with tabs + + Args: + mini_container_plot_steps ([[Figure]]): a list of plot objects lists, every list includes all steps from packing + a container to mini-containers + plot_width (int): the width of the plot figure + plot_height (int): the height of the plot figure + columns (int): the columns to present the polygons + + Returns: + container_tabs (Tabs): a plot object which represents the MiniContainers structured with tabs + """ mini_plots_tabs = [] for counter, m_plot in enumerate(mini_container_plot_steps): title = m_plot[0].title.text - m_plot_grid = gridplot(m_plot, ncols=ncols, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") + m_plot_grid = gridplot(m_plot, ncols=columns, plot_width=plot_width, plot_height=plot_height, + toolbar_location="left") grid_title = Div(text="<b>{}<b>".format(title)) mc_layout = layout(grid_title, m_plot_grid) tab = Panel(child=mc_layout, title=title) @@ -1561,7 +1694,18 @@ def mini_container_plots_to_tab_plot(mini_container_plot_steps: [Figure], plot_w return mini_tabs -def rectangular_container_to_tab_plot(rectangular_container: RectangularContainer, plot_width=800, plot_height=800) -> Tabs: +def rectangular_container_to_tab_plot(rectangular_container: RectangularContainer, plot_width=800, plot_height=800)\ + -> Tabs: + """Plot of the input RectangularContainer structured with tabs, one tab shows the mini-containers and the other hide + + Args: + rectangular_container (RectangularContainer): + plot_width (int): the width of the plot figure + plot_height (int): the height of the plot figure + + Returns: + end_tabs (Tabs) -> a plot object which represents the RectangularContainer structured with tabs + """ rectangular_container_tabs = [] polygons_plot = rectangular_container.plot_polygons(render=False, plot_width=plot_width, plot_height=plot_height) tab = Panel(child=polygons_plot, title="Rectangular-Container") @@ -1573,30 +1717,20 @@ def rectangular_container_to_tab_plot(rectangular_container: RectangularContaine return end_tabs -def plot_containers(container_list: [Container], render=True, colums=3, plot_width=500, plot_height=500) -> Column: - container_plots = [] - for container in container_list: - container_plots.append(container.plot_container(render=False)) - grid = gridplot(container_plots, ncols=colums, plot_width=plot_width, plot_height=plot_height, - toolbar_location="left") - if render: - show(grid) - return grid +def plot_figures_as_grid(plot_list: [Figure], render=True, plot_width=600, plot_height=500, columns=4) -> Column: + """Creates a grid plot for multiple plot objects + Args: + plot_list ([Figure]): list of plot objects + render (bool): if True the build plot object will be rendered else not + plot_width (int): the width of the for the plot objects + plot_height (height): the height of the plot objects + columns (int): shows the plot objects in columns -# https://kodify.net/python/math/truncate-decimals/ -# Python.org (n.d.). math — Mathematical functions. Retrieved on October 22, 2019, from https://docs.python.org/3.8/library/math.html -def truncate(number: float, decimals=0) -> float: - """ - Returns a value truncated to a specific number of decimal places. - """ - if not isinstance(decimals, int): - raise TypeError("decimal places must be an integer.") - elif decimals < 0: - raise ValueError("decimal places has to be 0 or more.") - elif decimals == 0: - return math.trunc(number) - factor = 10.0 ** decimals - return math.trunc(number * factor) / factor + Returns: -# output_notebook() # for using Bokeh in Jupyter Lab \ No newline at end of file + """ + grid = gridplot(plot_list, ncols=columns, plot_width=plot_width, plot_height=plot_height, toolbar_location="left") + if render: + show(grid) + return grid diff --git a/mysite/plots/templates/plots/packed_polygons.html b/mysite/plots/templates/plots/packed_polygons.html index 97e35ffd..33081653 100644 --- a/mysite/plots/templates/plots/packed_polygons.html +++ b/mysite/plots/templates/plots/packed_polygons.html @@ -19,7 +19,7 @@ </div> <div> <h1>Final Container Polygons Coordinates</h1> -<p>every List stands for one Polygon<p> +<p>every list stands for one polygon<p> {% for polygon in coordinates%} {{polygon}}<br> {%endfor%}</div> diff --git a/mysite/plots/views.py b/mysite/plots/views.py index ffd208ea..54d16633 100644 --- a/mysite/plots/views.py +++ b/mysite/plots/views.py @@ -197,7 +197,7 @@ class PolygonEditView(View): PolygonEditView.plot_min_y = min(polygon_min_y_list) PolygonEditView.plot_max_x = max(polygon_max_x_list) PolygonEditView.plot_min_x = min(polygon_min_x_list) - plot_drawn = poly.polygons_to_tab_plot(drawn_polygons, ncols=4, tab_poly_count=8) + plot_drawn = poly.polygons_to_tab_plot(drawn_polygons, columns=4, tab_poly_count=8) polygons_drawn_plot_html = file_html(plot_drawn, CDN, "my plot") PolygonEditView.context["drawn_polygons"] = polygons_drawn_plot_html polygons_single_plot = poly.plot_polygons_in_single_plot(drawn_polygons, plot_height=850, plot_width=2000, -- GitLab