def test_includes_point_buffer(self): '''The point is slightly outside the line, so by increasing the buffer we use to test collinearity between vectors we make it pass''' segment = LineSegment(Point(0, 0), Point(1, 0)) self.assertTrue(segment.includes_point(Point(0.5, 0))) self.assertFalse(segment.includes_point(Point(0.5, 0.01))) self.assertTrue(segment.includes_point(Point(0.5, 0.01), 0.01))
def perimeter(self): return [ LineSegment(self.top_left(), self.top_right()), LineSegment(self.top_right(), self.bottom_right()), LineSegment(self.bottom_right(), self.bottom_left()), LineSegment(self.bottom_left(), self.top_left()) ]
def test_elements_multiple_segments_path(self): [a, b, c, d] = self._road_points() lane = Street.from_control_points([a, b, c, d]).lane_at(0) geometry = lane.path_for(PolylineGeometry) expected_elements = [ LineSegment(a, b), LineSegment(b, c), LineSegment(c, d) ] self.assertEquals(geometry.elements(), expected_elements)
def test_elements_short_S_path(self): a = Point(0, 0) b = Point(50, 0) c = Point(50, 10) d = Point(100, 10) lane = Street.from_control_points([a, b, c, d]).lane_at(0) geometry = lane.path_for(LinesAndArcsGeometry) expected_elements = [ LineSegment(a, Point(45, 0)), Arc(Point(45, 0), 0, 5, 90), Arc(Point(50, 5), 90, 5, -90), LineSegment(Point(55, 10), d)] self.assertAlmostEqual(geometry.elements(), expected_elements)
def test_includes_point_3d(self): segment = LineSegment(Point(0, 0, 0), Point(1, 1, 1)) self.assertTrue(segment.includes_point(Point(0, 0, 0))) self.assertTrue(segment.includes_point(Point(1, 1, 1))) self.assertTrue(segment.includes_point(Point(0.5, 0.5, 0.5))) self.assertFalse(segment.includes_point(Point(1, 1, 1.01))) self.assertFalse(segment.includes_point(Point(1, 1.01, 1))) self.assertFalse(segment.includes_point(Point(1.01, 1, 1))) self.assertFalse(segment.includes_point(Point(-0.01, 0, 0))) self.assertFalse(segment.includes_point(Point(0, -0.01, 0))) self.assertFalse(segment.includes_point(Point(0, 0, -0.01))) self.assertFalse(segment.includes_point(Point(0.51, 0.5, 0.5))) self.assertFalse(segment.includes_point(Point(0.5, 0.51, 0.5))) self.assertFalse(segment.includes_point(Point(0.5, 0.5, 0.51)))
def test_elements_single_segment_path(self): a = Point(0, 0) b = Point(50, -50) lane = Street.from_control_points([a, b]).lane_at(0) geometry = lane.path_for(LinesAndArcsGeometry) expected_elements = [LineSegment(a, b)] self.assertAlmostEqual(geometry.elements(), expected_elements)
def polyline_from_points(cls, points): if len(points) < 2: raise ValueError( "{0} points given. At least two points are needed".format( points)) pairs = zip(points, points[1:]) segments = map(lambda (p1, p2): LineSegment(p1, p2), pairs) return cls(segments)
def test_elements_two_non_collinear_segment_path(self): a = Point(0, 0) b = Point(50, 0) c_up = Point(100, 10) c_down = Point(100, -10) lane = Street.from_control_points([a, b, c_up]).lane_at(0) geometry = lane.path_for(LinesAndArcsGeometry) expected_elements = [ LineSegment(a, Point(45.0, 0.0)), Arc(Point(45.0, 0.0), 0.0, 50.49509756, 11.30993247), LineSegment(Point(54.90290337, 0.98058067), c_up)] self.assertAlmostEqual(geometry.elements(), expected_elements) lane = Street.from_control_points([a, b, c_down]).lane_at(0) geometry = lane.path_for(LinesAndArcsGeometry) expected_elements = [ LineSegment(a, Point(45.0, 0.0)), Arc(Point(45.0, 0.0), 0.0, 50.49509756, -11.30993247), LineSegment(Point(54.90290337, -0.98058067), c_down)] self.assertAlmostEqual(geometry.elements(), expected_elements)
def build_path_and_waypoints(cls, lane, mapped_centers): if lane.road_nodes_count() < 2: raise ValueError( "At least two nodes are required to build a geometry") road_nodes = lane.road_nodes() node_pairs = zip(road_nodes, road_nodes[1:]) path = Path() waypoints = [] for start_node, end_node in node_pairs: start_point = mapped_centers[start_node] end_point = mapped_centers[end_node] segment = LineSegment(start_point, end_point) path.add_element(segment) waypoints.append(cls._new_waypoint(lane, segment, start_node)) # The last waypoint is missing, as we have been processing the # start of each segment waypoint = cls._new_waypoint(lane, path.last_element(), road_nodes[-1], False) waypoints.append(waypoint) return (path, waypoints)
def connect(cls, exit_waypoint, entry_waypoint): return LineSegment(exit_waypoint.center(), entry_waypoint.center())
def build_path_and_waypoints(cls, lane, mapped_centers): if lane.road_nodes_count() < 2: raise ValueError( "At least two nodes are required to build a geometry") road_nodes = list(lane.road_nodes()) is_circular = mapped_centers[road_nodes[0]].almost_equal_to( mapped_centers[road_nodes[-1]], 5) path = Path() waypoints = [] previous_node = road_nodes.pop(0) previous_point = mapped_centers[previous_node] nodes_count = len(road_nodes) for index, node in enumerate(road_nodes): point = mapped_centers[node] is_last_node = index + 1 == nodes_count if is_last_node: if is_circular: next_point = mapped_centers[road_nodes[0]] else: next_point = None else: next_point = mapped_centers[road_nodes[index + 1]] if path.is_empty(): previous_element_end_point = previous_point else: previous_element_end_point = path.element_at(-1).end_point() if next_point is None: element = LineSegment(previous_element_end_point, point) path.add_element(element) waypoints.append( cls._new_waypoint(lane, element, previous_node)) waypoints.append( cls._new_waypoint(lane, element, road_nodes[-1], False)) else: previous_vector = point - previous_point next_vector = next_point - point if previous_vector.is_collinear_with(next_vector): element = LineSegment(previous_element_end_point, point) path.add_element(element) waypoints.append( cls._new_waypoint(lane, element, previous_node)) if is_last_node: waypoints.append( cls._new_waypoint(lane, element, road_nodes[0], False)) else: inverted_previous_segment = LineSegment( point, previous_point) real_inverted_previous_segment = LineSegment( point, previous_element_end_point) next_segment = LineSegment(point, next_point) if is_last_node: real_next_segment = LineSegment( point, path.element_at(0).end_point()) delta = min(real_inverted_previous_segment.length(), real_next_segment.length(), 5) else: delta = min(real_inverted_previous_segment.length(), next_segment.length() / 2.0, 5) previous_segment_new_end_point = real_inverted_previous_segment.point_at_offset( delta) next_segment_new_start_point = next_segment.point_at_offset( delta) previous_segment = LineSegment( previous_element_end_point, previous_segment_new_end_point) # Try to avoid small segments if previous_segment.length() < 0.25: # `- 1e-10` to avoid length overflow due to floating point math new_delta = delta + previous_segment.length() - 1e-10 if next_segment.length() > new_delta: previous_segment_new_end_point = real_inverted_previous_segment.point_at_offset( new_delta) next_segment_new_start_point = next_segment.point_at_offset( new_delta) previous_segment = LineSegment( previous_element_end_point, previous_segment_new_end_point) angle_between_vectors = previous_vector.angle(next_vector) d2 = previous_segment_new_end_point.squared_distance_to( next_segment_new_start_point) cos = math.cos(math.radians(angle_between_vectors)) radius = math.sqrt(d2 / (2.0 * (1.0 - cos))) # If there should be no segment, just an arc. Use previous_element_end_point to # avoid rounding errors and make a perfect overlap if previous_segment.length() < 1e-8: if path.not_empty(): heading = path.element_at(-1).end_heading() else: heading = inverted_previous_segment.inverted( ).start_heading() connection_arc = Arc(previous_element_end_point, heading, radius, angle_between_vectors) path.add_element(connection_arc) waypoints.append( cls._new_waypoint(lane, connection_arc, node)) if not connection_arc.end_point().almost_equal_to( next_segment_new_start_point, 3): raise RuntimeError( "Expecting arc end {0} to match next segment entry point {1}" .format(connection_arc.end_point(), next_segment_new_start_point)) else: heading = inverted_previous_segment.inverted( ).start_heading() connection_arc = Arc(previous_segment_new_end_point, heading, radius, angle_between_vectors) path.add_element(previous_segment) waypoints.append( cls._new_waypoint(lane, previous_segment, previous_node)) path.add_element(connection_arc) waypoints.append( cls._new_waypoint(lane, connection_arc, node)) if not connection_arc.end_point().almost_equal_to( next_segment_new_start_point, 3): raise RuntimeError( "Expecting arc end {0} to match next segment entry point {1}" .format(connection_arc.end_point(), next_segment_new_start_point)) if is_last_node: if connection_arc.end_point().distance_to( path.element_at(1).start_point()) < 1e-8: path.remove_first_element() waypoints.pop(0) else: first_element = path.element_at(0) new_first_element = LineSegment( connection_arc.end_point(), first_element.end_point()) path.replace_first_element(new_first_element) waypoints[0] = cls._new_waypoint( lane, new_first_element, lane.road_nodes()[0]) waypoints.append( cls._new_waypoint(lane, connection_arc, road_nodes[0], False)) previous_node = node previous_point = mapped_centers[previous_node] return (path, waypoints)
def test_elements_single_segment_path(self): a, b = self._road_points()[0:2] lane = Street.from_control_points([a, b]).lane_at(0) geometry = lane.path_for(PolylineGeometry) expected_elements = [LineSegment(a, b)] self.assertEquals(geometry.elements(), expected_elements)
def test_is_orthogonal_to_non_touching_segments(self): target_segment = LineSegment(Point(2, 2), Point(4, 2)) orthogonal_segment = LineSegment(Point(3, 1), Point(3, 1.9)) non_orthogonal_segment = LineSegment(Point(3, 1), Point(3.01, 1.9)) self.assertTrue(target_segment.is_orthogonal_to(orthogonal_segment)) self.assertFalse( target_segment.is_orthogonal_to(non_orthogonal_segment)) target_segment = LineSegment(Point(2, 2), Point(2, 4)) orthogonal_segment = LineSegment(Point(1, 3), Point(1.9, 3)) non_orthogonal_segment = LineSegment(Point(1, 3), Point(1.9, 3.01)) self.assertTrue(target_segment.is_orthogonal_to(orthogonal_segment)) self.assertFalse( target_segment.is_orthogonal_to(non_orthogonal_segment)) target_segment = LineSegment(Point(2, 2), Point(5, 5)) orthogonal_segment = LineSegment(Point(5, 3), Point(4.1, 3.9)) non_orthogonal_segment = LineSegment(Point(5.01, 3), Point(3.9, 3.9)) self.assertTrue(target_segment.is_orthogonal_to(orthogonal_segment)) self.assertFalse( target_segment.is_orthogonal_to(non_orthogonal_segment))
# -*- coding: utf-8 -*- from tkinter import * from geometry.line_segment import LineSegment from geometry.oval import Oval from gui import Painter if __name__ == '__main__': objects = [] objects.append(LineSegment()) objects.append(Oval()) root = Tk() root.title("ma paint") root.geometry("600x741+210+120") painter = Painter(objects) root.mainloop()
def test_extended_by(self): original = LineSegment(Point(0, 0), Point(0, 1)) expected = LineSegment(Point(0, 0), Point(0, 2)) self.assertAlmostEqual(original.extended_by(1), expected) original = LineSegment(Point(0, 0), Point(1, 0)) expected = LineSegment(Point(0, 0), Point(3, 0)) self.assertAlmostEqual(original.extended_by(2), expected) original = LineSegment(Point(0, 0), Point(1, 1)) expected = LineSegment(Point(0, 0), Point(2, 2)) self.assertAlmostEqual(original.extended_by(sqrt(2)), expected)
def test_two_segments_intersect(self): first_segment = LineSegment(Point(1, 0), Point(3, 4)) second_segment = LineSegment(Point(1, 4), Point(3, 0)) non_intersect_segment = LineSegment(Point(3, 1), Point(3, 2)) self.assertEqual(first_segment.find_intersection(second_segment), [Point(2, 2, 0)]) self.assertEqual( first_segment.find_intersection(non_intersect_segment), []) first_segment = LineSegment(Point(28.5299698219, 160.06688421), Point(-48.1969708416, -28.3309145497)) second_segment = LineSegment(Point(-193.740103155, 132.470681), Point(193.740103155, 132.470681)) non_intersect_segment = LineSegment(Point(193.740103155, 132.470681), Point(193.740103155, -132.470681)) self.assertAlmostEqual(first_segment.find_intersection(second_segment), [Point(17.2911323186, 132.470681, 0)]) self.assertEqual( first_segment.find_intersection(non_intersect_segment), []) # Intersection happens in the edges first_segment = LineSegment(Point(27.78, -86.73), Point(22.79, -77.32)) second_segment = LineSegment(Point(18.39, -78.46), Point(22.79, -77.32)) self.assertAlmostEqual(first_segment.find_intersection(second_segment), [Point(22.79, -77.32)])
def connect(cls, exit_waypoint, entry_waypoint): if abs(exit_waypoint.heading() - entry_waypoint.heading()) < 1e-3: waypoints_angle = math.degrees(exit_waypoint.center().yaw( entry_waypoint.center())) if abs(exit_waypoint.heading() - waypoints_angle) < 1e-3: # Waypoints are in collinear lanes and can be connected by a line # segment with the same heading return LineSegment(exit_waypoint.center(), entry_waypoint.center()) else: # Waypoints are in collinear lanes but have different offsets, # so they must be connected by an S-shaped path exit_line = exit_waypoint.defining_line() entry_line = entry_waypoint.defining_line() cutting_line = entry_line.perpendicular_line_at( entry_waypoint.center()) delta_length = (exit_waypoint.center() - exit_line.intersection(cutting_line)).norm() segment_extension = delta_length / 5.0 exit_extension = LineSegment.from_point_and_heading( exit_waypoint.center(), exit_waypoint.heading(), segment_extension) entry_extension = LineSegment.from_point_and_heading( entry_waypoint.center(), entry_waypoint.heading() + 180, segment_extension) connecting_segment = LineSegment(exit_extension.end_point(), entry_extension.end_point()) connecting_segment = connecting_segment.extended_by( -segment_extension).inverted() connecting_segment = connecting_segment.extended_by( -segment_extension).inverted() start_arc = cls._build_arc(exit_waypoint.center(), exit_waypoint.heading(), connecting_segment.start_point(), connecting_segment.start_heading()) end_arc = cls._build_arc(connecting_segment.end_point(), connecting_segment.end_heading(), entry_waypoint.center(), entry_waypoint.heading()) path = Path() path.add_element(start_arc) path.add_element(connecting_segment) path.add_element(end_arc) return path else: exit_point = exit_waypoint.center() exit_line = exit_waypoint.defining_line() entry_point = entry_waypoint.center() entry_line = entry_waypoint.defining_line() intersection = exit_line.intersection(entry_line) exit_distance = exit_point.distance_to(intersection) entry_distance = entry_point.distance_to(intersection) path = None delta = abs(exit_distance - entry_distance) if delta > 1e-1: path = Path() if exit_distance > entry_distance: end_point = exit_point + (exit_waypoint.heading_vector() * delta) segment = LineSegment(exit_point, end_point) arc = cls._build_arc(end_point, exit_waypoint.heading(), entry_point, entry_waypoint.heading()) path.add_element(segment) path.add_element(arc) else: start_point = entry_point - ( entry_waypoint.heading_vector() * delta) arc = cls._build_arc(exit_point, exit_waypoint.heading(), start_point, entry_waypoint.heading()) segment = LineSegment(arc.end_point(), entry_point) path.add_element(arc) path.add_element(segment) return path else: return cls._build_arc(exit_point, exit_waypoint.heading(), entry_point, entry_waypoint.heading())