def linear_time_find_feasible_subtree(polygon: Polygon, u: DelaunayTriangle, v: DelaunayTriangle, s: DelaunayTriangle, t: DelaunayTriangle = None) -> DelaunayTriangle: """Return a child of u whose subtree contains t. Starts at v.""" for edge in u.edges_from(u.common_edge(v)): w = polygon._complete_other_delaunay_triangle_of_edge(edge, u) if subtree_search(polygon, u, w, t): return w
def subtree_search(polygon: Polygon, u: DelaunayTriangle, v: DelaunayTriangle, t: DelaunayTriangle) -> bool: """Check whether the subtree of u rooted at v contains t.""" current_triangle = u neighbor = v while current_triangle != t and (current_triangle != v or neighbor != u): next_node = polygon.delaunay_next_neighbour(neighbor, current_triangle) current_triangle = neighbor neighbor = next_node return current_triangle == t
def adv_search(polygon: Polygon, u: DelaunayTriangle, v: DelaunayTriangle, u_: DelaunayTriangle, v_: DelaunayTriangle, t: DelaunayTriangle) -> Tuple[bool, bool, DelaunayTriangle, DelaunayTriangle]: """Advance the Eulerian tour in the subtree rooted at v.""" v__ = polygon.delaunay_next_neighbour(v_, u_) u__ = v_ if u__ == v and v__ == u: return (False, False, u__, v__) if v__ == t: return (True, False, u__, v__) return (False, True, u__, v__)
def draw_polygon(ax: Axes, polygon: Polygon, size: int = size, ticks: int = ticks, font_size: int = font_size) -> None: """Plot a polygon to the current matplotlib figure.""" max_x, min_x = ceil(max(p.x for p in polygon.points)), floor(min(p.x for p in polygon.points)) max_y, min_y = ceil(max(p.y for p in polygon.points)), floor(min(p.y for p in polygon.points)) size_x = max_x - min_x size_y = max_y - min_y ax.set_aspect('equal') ax.set_xlim(min_x - size_x * 0.05, max_x + size_x * 0.05) ax.set_ylim(min_y - size_y * 0.05, max_y + size_y * 0.05) ticks -= 1 ax.xaxis.set_major_locator(ticker.MultipleLocator(base=round(size_x / ticks))) ax.yaxis.set_major_locator(ticker.MultipleLocator(base=round(size_y / ticks))) ax.grid(color='grey') polygon_points = list(polygon.points_as_tuples()) plt_polygon = pyplot.Polygon(polygon_points, fill=None, color='0.25', linewidth=.5) ax.add_patch(plt_polygon) pyplot.plot(list([x[0] for x in polygon_points]), list([x[1] for x in polygon_points]), color='black', linestyle='None', marker='.', markersize=3) if isinstance(polygon, TriangulatedPolygon): edges = set() for triangle in polygon.triangles: for edge in triangle.edges: if (edge.a.index, edge.b.index) in edges: # The edge was already plotted continue if edge.a.index in ((edge.b.index - 1) % len(polygon), (edge.b.index + 1) % len(polygon)): # The edge is a polygon edge, thus we ignore it continue pyplot.plot([edge.a.x, edge.b.x], [edge.a.y, edge.b.y], color='0.5', linestyle='--', alpha=0.5) edges.add((edge.a.index, edge.b.index)) edges.add((edge.b.index, edge.a.index)) for i in range(0, len(polygon), 2): ax.annotate(str(i), xy=polygon_points[i], xytext=( polygon_points[i][0] + round(size_x / ticks) / 20, polygon_points[i][1] + round(size_y / ticks) / 20), fontsize=font_size, color='0.3', alpha=.8)
def jarvis_march(polygon: Polygon, start_index: int, end_index: int, direction: int, good_turn: int, predicate: Callable[[Point], T], ignore: Callable[[Point], bool]=lambda x: False ) -> Iterable[Point]: """Do a Jarvis march on the given polygon. We start at start_index going into direction stopping at end_index. For every vertex we consider appropriate predicate is applied. If it yields something which not evaluates to False we immediately stop the march and return the predicate result together with the vertex and a list of all points visited beforehand. Args: ignore: A function which is called for every vertex and should return True iff this vertex is to be ignored during the jarvis march. polygon: A polygon. start_index: The index of the starting vertex. end_index: The index of the last vertex to consider. direction: The direction in which we walk along the polygon edge. Needs to be either 1 or -1. good_turn: If the current, the next and a third vertex form a turn that is the same as good_turn, the third will be chosen over the next. predicate: A function that takes a vertex as an argument and decides whether to continue the march or stop. Returns: A 3-tuple (result, point, visited) in which result is the result of the predicate function, point is the point which fulfils the predicate and visited is a list of vertices visited in between. Raises: AssertionError: a) A type check fails. b) None of the vertices in the specified range fulfilled the predicate. """ # ========================================================================== # Type checking. # ========================================================================== assert isinstance(polygon, Polygon) assert isinstance(start_index, int) assert isinstance(end_index, int) assert isinstance(direction, int) assert isinstance(good_turn, int) first = polygon.point(start_index) while True: shortest_path.properties['predicates'] += 1 result = predicate(first) # If the result does not evaluate to False return if result: return result, first # If this assertion fails none of the vertices fulfilled the predicate assert first.index != end_index second = polygon.point(first.index + direction) if second.index != end_index: for index in polygon.indices(second.index + direction, end_index, direction): point = polygon.point(index) shortest_path.properties['ignores_theo'] += 1 if Point.turn(first, second, point) == good_turn: shortest_path.properties['ignores'] += 1 if not ignore(point): second = polygon.point(index) yield first first = second
def shortest_path(polygon: Polygon, s: Point, t: Point) -> Iterable[Point]: """Find the shortest path from s to t inside polygon.""" # ========================================================================== # Reset properties which can be accessed later on. # ========================================================================== shortest_path.properties = dict(iterations=0, jarvis_marches=0, predicates=0, ignores=0, ignores_theo=0) # ========================================================================== # Type checking. # ========================================================================== assert isinstance(polygon, Polygon) assert isinstance(s, Point) assert isinstance(t, Point) # ========================================================================== # Trivial case: s == t. # ========================================================================== # In the very trivial case the start and end point are identical thus we can # just return without any calculation. if s == t: yield s return # ========================================================================== # Locate s and t. Trivial case: both in same triangle. # ========================================================================== # Locate start and end point inside our polygon. s_triangle = polygon.locate_point_in_triangle(s) t_triangle = polygon.locate_point_in_triangle(t) # If any point is not inside the polygon return if s_triangle is None or t_triangle is None: return # If both points are located inside the same triangle just return both in order if s_triangle == t_triangle: yield s yield t return # ========================================================================== # Preparation. # ========================================================================== # The cusp is the point we are always standing at and from which we can see the triangle boundaries we are visiting. # It gets updated in case we lose visibility. We obviously start at the starting point. cusp = s # The funnel is our visibility angle. funnel = None # We also need to save the trapezoid we are currently in. current_triangle = s_triangle previous_triangle = current_triangle boundary = None # We choose the first neighbour to look at start_neighbour = polygon.delaunay_first_neighbour(current_triangle) # ========================================================================== # Walking the triangles. # ========================================================================== while current_triangle != t_triangle: shortest_path.properties['iterations'] += 1 previous_triangle = current_triangle current_triangle = parallel_find_feasible_subtree(polygon, previous_triangle, start_neighbour, s_triangle, t_triangle) previous_boundary = boundary # Get the boundary between the old and the new current triangle boundary = current_triangle.common_edge(previous_triangle) # Edges need to be oriented in counter-clockwise direction if Point.turn(cusp, boundary.a, boundary.b) == Point.CW_TURN: boundary.reverse() elif Point.turn(cusp, boundary.a, boundary.b) == Point.NO_TURN: # FIXME: We should do something if edge is aligned with cusp # Idea: Look at boundary from third point of previous triangle previous_triangle_points = list(previous_triangle.points) previous_triangle_points.remove(boundary.a) previous_triangle_points.remove(boundary.b) assert len(previous_triangle_points) == 1 previous_triangle_point = previous_triangle_points[0] if Point.turn(previous_triangle_point, boundary.a, boundary.b) == Point.CW_TURN: boundary.reverse() # On encountering the first boundary we do not have a funnel yet. We # then create it and start looking for the next trapezoid if funnel is None: funnel = Funnel(cusp, boundary.a, boundary.b) # ---------------------------------------------------------------------- # Checking (and possibly updating) the visibility. # ---------------------------------------------------------------------- # Check where both boundary end points are in respect to the funnel position_of_a = funnel.position_of(boundary.a) position_of_b = funnel.position_of(boundary.b) # Save whether both end points are on the same side of the funnel both_right_of = position_of_a == position_of_b == Funnel.RIGHT_OF both_left_of = position_of_a == position_of_b == Funnel.LEFT_OF # ---------------------------------------------------------------------- # CASE 1: We do not see the boundary any more. # ---------------------------------------------------------------------- if both_left_of or both_right_of: # The current view point will definitely change now yield cusp # ------------------------------------------------------------------ # Prepare the Jarvis march # ------------------------------------------------------------------ shortest_path.properties['jarvis_marches'] += 1 params = prepare_jarvis_march(polygon, funnel, current_triangle, both_right_of, boundary) # ------------------------------------------------------------------ # Actually perform the Jarvis march # ------------------------------------------------------------------ if both_right_of: ignore_func = ignore_function(previous_boundary or boundary, funnel, Funnel.RIGHT_OF) else: ignore_func = ignore_function(previous_boundary or boundary, funnel, Funnel.LEFT_OF) # Since polygon.point_sees_edge2 returns a tuple of the two funnel # points we directly extract them. Additionally we get the new cusp # and yield all vertices visited until finding cusp. (v1, v2), cusp = yield from jarvis_march( polygon=polygon, predicate=partial(polygon.point_sees_edge2, edge=boundary), ignore=ignore_func, **params ) # ------------------------------------------------------------------ # Update the cusp and the funnel # ------------------------------------------------------------------ # In the special case in which the cusp falls together with an end # point of our edge we advance the funnel point on the next edge # into the right direction. # (We only compare cusp with v1 since polygon.point_sees_edge # guarantees to return the funnel point which falls together first.) if v1 == cusp: triangle_points = current_triangle.points assert v1 in triangle_points assert v2 in triangle_points # Going in clockwise direction if params['direction'] == 1: v1 = polygon.point(cusp.index + 1) else: v1 = polygon.point(cusp.index - 1) # We need to swap v1 and v2 to have them in the right order v1, v2 = v2, v1 funnel.cusp = cusp funnel.first = v1 funnel.second = v2 # ---------------------------------------------------------------------- # CASE 2: We see the boundary but we need to shrink the visibility. # ---------------------------------------------------------------------- else: # If needed (i.e. if the edge reduces the funnel) update the # second and first funnel point if position_of_a == Funnel.INSIDE: funnel.first = boundary.a if position_of_b == Funnel.INSIDE: funnel.second = boundary.b start_neighbour = polygon.delaunay_next_neighbour(current_triangle, previous_triangle) # ========================================================================== # Do the final Jarvis march # ========================================================================== yield cusp if not polygon.point_sees_other_point(cusp, t): shortest_path.properties['jarvis_marches'] += 1 params = prepare_jarvis_march(polygon, funnel, current_triangle, funnel.position_of(t) == Funnel.RIGHT_OF, current_triangle.common_edge(previous_triangle)) # ------------------------------------------------------------------ # Actually perform the Jarvis march # ------------------------------------------------------------------ # Since we are nearly finished we only care about the list of visited # nodes and the new cusp (which is not contained in the list) _, cusp = yield from jarvis_march( polygon=polygon, predicate=partial(polygon.point_sees_other_point, other_point=t), ignore=ignore_function(boundary, funnel, funnel.position_of(t)), **params ) yield cusp yield t
def parallel_find_feasible_subtree(polygon: Polygon, u: DelaunayTriangle, v: DelaunayTriangle, s: DelaunayTriangle, t: DelaunayTriangle) -> DelaunayTriangle: """Return a child of u whose subtree contains t. Starts at v.""" f_neighbor = v s_neighbor = polygon.delaunay_next_neighbour(u, v) last_neighbor = s_neighbor num_of_neighbors = polygon.delaunay_neighbour_number(u) - int(u != s) # NOTE: The second condition is an addition to the pseudo code because # otherwise the code won't terminate in all cases if num_of_neighbors == 1 or f_neighbor == t: return f_neighbor # NOTE: This check is an addition to the pseudo code because otherwise the # code won't terminate in all cases elif s_neighbor == t: return s_neighbor one = u one_next = f_neighbor two = u two_next = s_neighbor while True: (sigf1, sigc1, one, one_next) = adv_search( polygon, u, f_neighbor, one, one_next, t ) (sigf2, sigc2, two, two_next) = adv_search( polygon, u, s_neighbor, two, two_next, t ) if sigf1: return f_neighbor if sigf2: return s_neighbor if not sigc1: f_neighbor = polygon.delaunay_next_neighbour(u, last_neighbor) one_next = f_neighbor last_neighbor = f_neighbor one = u num_of_neighbors -= 1 if num_of_neighbors == 1: return s_neighbor # NOTE: This check is an addition to the pseudo code because # otherwise the code won't find the shortest path if f_neighbor == t: return f_neighbor if not sigc2: s_neighbor = polygon.delaunay_next_neighbour(u, last_neighbor) two_next = s_neighbor last_neighbor = s_neighbor two = u num_of_neighbors -= 1 if num_of_neighbors == 1: return f_neighbor # NOTE: This check is an addition to the pseudo code because # otherwise the code won't find the shortest path if s_neighbor == t: return s_neighbor
from geometry.point import Point import sympy.geometry.point import sympy.geometry.polygon import time start_time = time.clock() p1_my = Point(1, 0) p2_my = Point(0, 0) p3_my = Point(0, 1) p4_my = Point(1, 1) p5_my = Point(1, 0) p6_my = Point(10, 0) poly1 = Polygon(p1_my, p2_my, p3_my, p4_my) poly2 = Polygon(p1_my, p2_my, p3_my, p6_my) # print(poly1) # print(poly1.area) # print(poly1.vertices) # print(poly1.bounds) # print(poly1.angles) # print(poly1.perimeter) # print(poly1 == poly2) # print(poly1.encloses_point(p4_my)) # print(poly1.encloses_point((6, 7))) # print(poly1.contains((6, 7))) # print(p4_my in poly1) # print(Polygon._isright(p1_my, p2_my, p3_my)) # print(Polygon._isright(p2_my, p3_my, p1_my))