def from_polygon(cls, polygon, slav): """Construct a LAV from a list of point coordinates representing a polygon. Args: polygon: list of points (tuple of x,y coordinates). slav: SLAV (a set of circular lists of active vertices). tol: tolerance for point equality. Returns: LAV (single circular list of active vertices). """ lav = cls(slav) for prev, point, next in _window(polygon): lav._len += 1 vertex = _LAVertex(point, LineSegment2D.from_end_points(prev, point), LineSegment2D.from_end_points(point, next), tol=slav.tol) vertex.lav = lav if lav.head is None: lav.head = vertex vertex.prev = vertex.next = vertex else: vertex.next = lav.head vertex.prev = lav.head.prev vertex.prev.next = vertex lav.head.prev = vertex return lav
def test_arc2_intersect_line_ray(): """Test the Arc2D intersect_line_ray method.""" pt = Point2D(2, 0) arc = Arc2D(pt, 1, 0, math.pi) circle = Arc2D(pt, 1) seg1 = LineSegment2D(pt, Vector2D(2, 2)) seg2 = LineSegment2D(Point2D(0, -2), Vector2D(6, 6)) seg3 = LineSegment2D(pt, Vector2D(0.5, 0.5)) int1 = arc.intersect_line_ray(seg1) assert len(int1) == 1 assert int1[0].x == pytest.approx(2.71, rel=1e-2) assert int1[0].y == pytest.approx(0.71, rel=1e-2) int2 = circle.intersect_line_ray(seg1) assert len(int2) == 1 assert int2[0].x == pytest.approx(2.71, rel=1e-2) assert int2[0].y == pytest.approx(0.71, rel=1e-2) int3 = arc.intersect_line_ray(seg2) assert len(int3) == 1 assert int3[0].x == pytest.approx(2.71, rel=1e-2) assert int3[0].y == pytest.approx(0.71, rel=1e-2) int4 = circle.intersect_line_ray(seg2) assert len(int4) == 2 assert int4[0].x == pytest.approx(2.71, rel=1e-2) assert int4[0].y == pytest.approx(0.71, rel=1e-2) assert int4[1].x == pytest.approx(1.29, rel=1e-2) assert int4[1].y == pytest.approx(-0.71, rel=1e-2) assert arc.intersect_line_ray(seg3) is None
def test_linesegment2_to_from_dict(): """Test the to/from dict of LineSegment2D objects.""" pt = Point2D(2, 0) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) seg_dict = seg.to_dict() new_seg = LineSegment2D.from_dict(seg_dict) assert isinstance(new_seg, LineSegment2D) assert new_seg.to_dict() == seg_dict
def intersect_graph_with_segment(self, segment): """Update graph with intersection of partial segment that crosses through polygon. Args: segment: LineSegment2D to intersect. Does not need to be contained within polygon. """ int_key_lst = [] for node in self.ordered_nodes: # Convert graph edge to trimming segment next_node = node.adj_lst[0] trim_seg = LineSegment2D.from_end_points(node.pt, next_node.pt) int_pt = intersection2d.intersect_line2d_infinite( trim_seg, segment) # Add intersection point as new node in graph if int_pt: int_key = self.insert_node(node, int_pt, next_node, exterior=False) int_key_lst.append(int_key) # Add intersection edges if len(int_key_lst) == 2: # Typical case with convex cases # Make edge between intersection nodes n1, n2 = self.node(int_key_lst[0]), self.node(int_key_lst[1]) self.add_node(n1.pt, [n2.pt], exterior=False) self.add_node(n2.pt, [n1.pt], exterior=False) elif len(int_key_lst) > 2: # Edge case with concave geometry creates multiple intersections # Sort distance and add adjacency n = self.node(int_key_lst[0]) distances = [(0, 0.0)] for i, k in enumerate(int_key_lst[1:]): distance = LineSegment2D.from_end_points( n.pt, self.node(k).pt).length distances.append((i + 1, distance)) distances = sorted(distances, key=lambda t: t[1]) for i in range(len(distances) - 1): k1, k2 = distances[i][0], distances[i + 1][0] n1, n2 = self.node(int_key_lst[k1]), self.node(int_key_lst[k2]) # Add bidirection so the min cycle works self.add_node(n1.pt, [n2.pt], exterior=False) self.add_node(n2.pt, [n1.pt], exterior=False)
def test_join_segments_disconnected(): """Test the join_segments method with diconnected polylines.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) extra_pts = (Point2D(3, 3), Point2D(4, 3), Point2D(4, 4), Point2D(3, 4)) l_segs = (LineSegment2D.from_end_points(extra_pts[0], extra_pts[1]), LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[2], pts[3]), LineSegment2D.from_end_points(extra_pts[3], extra_pts[2])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 4
def test_intersect_line_ray_colinear(): """Test the LineSegment2D intersect_line_ray method with colinear segments.""" pt_1 = Point2D(3.5362137509358353, -2.3574758339572237) vec_1 = Vector2D(2.6625609418810905, -1.7750406279207271) seg_1 = LineSegment2D(pt_1, vec_1) pt_2 = Point2D(-1.7889081328263625, 1.1926054218842419) vec_2 = Vector2D(1.7889081328263625, -1.1926054218842419) seg_2 = LineSegment2D(pt_2, vec_2) assert seg_1.intersect_line_ray(seg_2) is None seg_1 = seg_1.flip() assert seg_1.intersect_line_ray(seg_2) is None seg_2 = seg_2.flip() assert seg_1.intersect_line_ray(seg_2) is None pt_1 = Point2D(0, 0) vec_1 = Vector2D(1, 0) seg_1 = LineSegment2D(pt_1, vec_1) pt_2 = Point2D(0, 0) vec_2 = Vector2D(0, 1) seg_2 = LineSegment2D(pt_2, vec_2) assert seg_1.intersect_line_ray(seg_2) == Point2D(0, 0) pt_1 = Point2D(1, 0) vec_1 = Vector2D(-1, 0) seg_1 = LineSegment2D(pt_1, vec_1) assert seg_1.intersect_line_ray(seg_2) == Point2D(0, 0) pt_2 = Point2D(0, 1) vec_2 = Vector2D(0, -1) seg_2 = LineSegment2D(pt_2, vec_2) assert seg_1.intersect_line_ray(seg_2) == Point2D(0, 0) pt_1 = Point2D(0, -1) vec_1 = Vector2D(0, 1) seg_1 = LineSegment2D(pt_1, vec_1) assert seg_1.intersect_line_ray(seg_2) is None pt_2 = Point2D(0, 0) vec_2 = Vector2D(-1, 0) seg_2 = LineSegment2D(pt_2, vec_2) assert seg_1.intersect_line_ray(seg_2) == Point2D(0, 0) pt_2 = Point2D(-1, 0) vec_2 = Vector2D(1, 0) seg_2 = LineSegment2D(pt_2, vec_2) assert seg_1.intersect_line_ray(seg_2) == Point2D(0, 0)
def test_to_from_array(): """Test to/from array method""" test_line = LineSegment2D.from_end_points(Point2D(2, 0), Point2D(2, 2)) line_array = ((2, 0), (2, 2)) assert test_line == LineSegment2D.from_array(line_array) line_array = ((2, 0), (2, 2)) test_line = LineSegment2D.from_end_points(Point2D(2, 0), Point2D(2, 2)) assert test_line.to_array() == line_array test_line_2 = LineSegment2D.from_array(test_line.to_array()) assert test_line == test_line_2
def test_equality(): """Test the equality of LineSegment2D objects.""" pt = Point2D(2, 0) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) seg_dup = seg.duplicate() seg_alt = LineSegment2D(Point2D(2, 0.1), vec) assert seg is seg assert seg is not seg_dup assert seg == seg_dup assert hash(seg) == hash(seg_dup) assert seg != seg_alt assert hash(seg) != hash(seg_alt)
def test_reflect(): """Test the LineSegment2D reflect method.""" pt = Point2D(2, 2) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) origin_1 = Point2D(0, 1) origin_2 = Point2D(1, 1) normal_1 = Vector2D(0, 1) normal_2 = Vector2D(-1, 1).normalize() assert seg.reflect(normal_1, origin_1).p == Point2D(2, 0) assert seg.reflect(normal_1, origin_1).v == Vector2D(0, -2) assert seg.reflect(normal_1, origin_2).p == Point2D(2, 0) assert seg.reflect(normal_1, origin_2).v == Vector2D(0, -2) test_1 = seg.reflect(normal_2, origin_2) assert test_1.p == Point2D(2, 2) assert test_1.v.x == pytest.approx(2, rel=1e-3) assert test_1.v.y == pytest.approx(0, rel=1e-3) test_2 = seg.reflect(normal_2, origin_1) assert test_2.p.x == pytest.approx(1, rel=1e-3) assert test_2.p.y == pytest.approx(3, rel=1e-3) assert test_1.v.x == pytest.approx(2, rel=1e-3) assert test_1.v.y == pytest.approx(0, rel=1e-3)
def test_subdivide(): """Test the LineSegment2D subdivide methods.""" pt = Point2D(2, 2) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) divisions = seg.subdivide(0.5) assert len(divisions) == 5 assert divisions[0] == pt assert divisions[1] == Point2D(2, 2.5) assert divisions[2] == Point2D(2, 3) assert divisions[3] == Point2D(2, 3.5) assert divisions[4] == Point2D(2, 4) divisions = seg.subdivide([1, 0.5, 0.25]) assert len(divisions) == 5 assert divisions[0] == pt assert divisions[1] == Point2D(2, 3) assert divisions[2] == Point2D(2, 3.5) assert divisions[3] == Point2D(2, 3.75) assert divisions[4] == Point2D(2, 4) divisions = seg.subdivide_evenly(4) assert len(divisions) == 5 assert divisions[0] == pt assert divisions[1] == Point2D(2, 2.5) assert divisions[2] == Point2D(2, 3) assert divisions[3] == Point2D(2, 3.5) assert divisions[4] == Point2D(2, 4)
def test_intersect_line_ray(): """Test the LineSegment2D intersect_line_ray method.""" pt_1 = Point2D(2, 2) vec_1 = Vector2D(0, 2) seg_1 = LineSegment2D(pt_1, vec_1) pt_2 = Point2D(0, 3) vec_2 = Vector2D(4, 0) seg_2 = LineSegment2D(pt_2, vec_2) pt_3 = Point2D(0, 0) vec_3 = Vector2D(1, 1) seg_3 = LineSegment2D(pt_3, vec_3) assert seg_1.intersect_line_ray(seg_2) == Point2D(2, 3) assert seg_1.intersect_line_ray(seg_3) is None
def ticks_from_angles(self, angles, factor=0.3): """Get a list of Linesegment2Ds from a list of angles between 0 and 360.""" pts_in = self.label_points_from_angles(angles, 0) pts_out = self.label_points_from_angles(angles, factor) return [ LineSegment2D.from_end_points(pi, po) for pi, po in zip(pts_in, pts_out) ]
def to_polyline2d(polyline): """Ladybug Polyline2D from a Rhino PolyLineCurve. A LineSegment2D will be returned if the input polyline has only two points. """ pts = [to_point2d(polyline.Point(i)) for i in range(polyline.PointCount)] return Polyline2D(pts) if len(pts) != 2 else LineSegment2D.from_end_points( *pts)
def _compute_enthalpy_range(self): """Compute the values for enthalpy range and lines.""" # constants used throughout the calculation low_y = self.base_point.y + 1e-6 up_y = self.hr_y_value(self._max_humidity_ratio) border, sat_line = self.chart_border, self._saturation_line all_enthalpies, ref_temp = tuple(range(0, 160, 10)), 0 enth_lbl = all_enthalpies if self.use_ip: enth_lbl = tuple(range(0, 65, 5)) all_enthalpies = self.ENTH_TYPE.to_unit(enth_lbl, 'kJ/kg', 'Btu/lb') ref_temp = self.TEMP_TYPE.to_unit([0], 'C', 'F')[0] # loop through the enthalpies and compute the lines of constant enthalpy enth_range, enth_lines = [], [] for i, enthalpy in enumerate(all_enthalpies): st_db = db_temp_from_enth_hr(enthalpy, 0.0, ref_temp) end_db = db_temp_from_enth_hr(enthalpy, 0.03, ref_temp) if self.use_ip: st_db, end_db = self.TEMP_TYPE.to_unit((st_db, end_db), 'F', 'C') enth_line = LineSegment2D.from_end_points( Point2D(self.t_x_value(st_db), low_y), Point2D(self.t_x_value(end_db), up_y)) border_ints = border.intersect_line_ray(enth_line) if len(border_ints) == 2: enth_range.append(enth_lbl[i]) seg = LineSegment2D.from_end_points(border_ints[0], border_ints[1]) enth_lines.append(seg) else: sat_ints = sat_line.intersect_line_ray(enth_line) if len(sat_ints) != 0: enth_range.append(enth_lbl[i]) if len(border_ints) == 1: seg = LineSegment2D.from_end_points( border_ints[0], sat_ints[0]) else: seg = LineSegment2D.from_end_points( enth_line.p, sat_ints[0]) enth_lines.append(seg) # set the properties on this class self._enth_range = enth_range self._enth_lines = enth_lines
def test_connector_dict_methods(): """Test the ElectricalConnector to/from dict methods.""" geo = LineSegment2D.from_end_points(Point2D(0, 0), Point2D(100, 0)) wire = Wire('OH AL 2/0 A') connector = ElectricalConnector('Connector_1', geo, [wire]) connector_dict = connector.to_dict() new_connector = ElectricalConnector.from_dict(connector_dict) assert connector_dict == new_connector.to_dict()
def _split_polygon_graph(node1, node2, distance, poly_graph, tol, recurse_limit=3000, print_recurse=False): """Split the PolygonDirectedGraph by an edge offset at a distance. Args: node1: A node defining the edge to offset for splitting. If the graph defines a counter-clockwise polygon, this node should represent the left hand-most point. node2: A node defining the edge to offset for splitting. If the graph defines a counter-clockwise polygon, this node should represent the right hand-most point. distance: The distance to offset the splitting edge, in model units. poly_graph: A PolygonDirectedGraph representing a single polygon. tol: Tolerance for point equivalence. recurse_limit: optional parameter to limit the number of cycles looking for polygons in skeleton graph. (Default: 3000). print_recurse: optional boolean to print cycle loop cycles, for debugging. (Default: False). Returns: A list of nodes representing the split polygon adjacent to the exterior edge defined by node1 and node2. """ ext_seg = LineSegment2D.from_end_points(node1.pt, node2.pt) # Compute normal facing into the polygon ext_arr = ext_seg.v.to_array() ext_seg_v = Vector3D(ext_arr[0], ext_arr[1], 0) normal = ext_seg_v.cross(Vector3D(0, 0, -1)).normalize() * distance # Move segment by normal to get offset segment offset_seg = ext_seg.move(normal) poly_graph = PolygonDirectedGraph.from_point_array( [n.pt for n in poly_graph], tol=tol) poly_graph.outer_root_key = poly_graph.ordered_nodes[0].key # Update graph by intersecting offset segment with other edges poly_graph.intersect_graph_with_segment(offset_seg) # Get the minimum cycle. Since we start at the exterior edge, this will # return the perimeter offset. root_node = poly_graph.node(poly_graph.outer_root_key) next_node = root_node.adj_lst[0] split_poly_nodes = poly_graph.min_ccw_cycle(root_node, next_node, recurse_limit=recurse_limit, print_recurse=print_recurse) return split_poly_nodes
def custom_hour_lines2d(self, hour_labels): """Get a list of LineSegment2D for a list of numbers representing hour labels. Args: hour_labels: An array of numbers from 0 to 24 representing the hours to display. (eg. [0, 3, 6, 9, 12, 15, 18, 21, 24]) """ _hour_points, _hour_text = self._compute_hour_line_pts(hour_labels) vec = Vector2D(self._num_x * self._x_dim) return [LineSegment2D(pt, vec) for pt in _hour_points]
def test_closest_points_between_line(): """Test the LineSegement2D distance_to_point method.""" pt_1 = Point2D(2, 2) vec_1 = Vector2D(0, 2) seg_1 = LineSegment2D(pt_1, vec_1) pt_2 = Point2D(0, 3) vec_2 = Vector2D(1, 0) seg_2 = LineSegment2D(pt_2, vec_2) pt_3 = Point2D(0, 0) vec_3 = Vector2D(1, 1) seg_3 = LineSegment2D(pt_3, vec_3) assert seg_1.closest_points_between_line(seg_2) == (Point2D(2, 3), Point2D(1, 3)) assert seg_1.closest_points_between_line(seg_3) == (Point2D(2, 2), Point2D(1, 1)) assert seg_1.distance_to_line(seg_2) == 1 assert seg_1.distance_to_line(seg_3) == pytest.approx(1.41421, rel=1e-3)
def rh_label_points(self): """Get a tuple of Point2Ds for the relative humidity labels on the chart.""" last_sgs = (LineSegment2D.from_end_points(p[-2], p[-1]) for p in self._rh_lines) last_dirs = (seg.v.reverse().normalize() * (self._x_dim * 2) for seg in last_sgs) move_vec = (Vector2D(vec.x - (self._x_dim * 0.4), vec.y) for vec in last_dirs) return tuple(pl[-1].move(vec) for pl, vec in zip(self._rh_lines, move_vec))
def test_scale_world_origin(): """Test the LineSegment2D scale method with None origin.""" pt = Point2D(2, 2) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) new_seg = seg.scale(2) assert new_seg.p == Point2D(4, 4) assert new_seg.v == Point2D(0, 4) assert new_seg.length == 4
def test_init_from_sdl(): """Test the initialization of LineSegment2D from start, direction, length.""" pt = Point2D(2, 0) vec = Vector2D(0, 1) seg = LineSegment2D.from_sdl(pt, vec, 2) assert seg.p == Point2D(2, 0) assert seg.v == Vector2D(0, 2) assert seg.p1 == Point2D(2, 0) assert seg.p2 == Point2D(2, 2) assert seg.length == 2
def test_init_from_endpoints(): """Test the initialization of LineSegment2D from end points.""" pt_1 = Point2D(2, 0) pt_2 = Point2D(2, 2) seg = LineSegment2D.from_end_points(pt_1, pt_2) assert seg.p == Point2D(2, 0) assert seg.v == Vector2D(0, 2) assert seg.p1 == Point2D(2, 0) assert seg.p2 == Point2D(2, 2) assert seg.length == 2
def test_equivalent(): """Testing the equivalence of LineSegment2D objects.""" seg1 = LineSegment2D(Point2D(0.5, 0.5), Vector2D(1.5, 2.5)) # LineSegment2D (<0.50, 0.50> to <2.00, 3.00>) # Test equal seg1, same order seg2 = LineSegment2D(Point2D(0.5, 0.5), Vector2D(1.5, 2.5)) assert seg1.is_equivalent(seg2, 1e-10) # Test equal, diff order seg2 = LineSegment2D(Point2D(2.00, 3.00), Vector2D(-1.5, -2.5)) assert seg1.is_equivalent(seg2, 1e-10) # Test not equal first point seg2 = LineSegment2D(Point2D(2.001, 3.00), Vector2D(-1.5, -2.5)) assert not seg1.is_equivalent(seg2, 1e-10) # Test not equal second point seg2 = LineSegment2D(Point2D(0.5001, 0.5), Vector2D(1.5, 2.5)) assert not seg1.is_equivalent(seg2, 1e-10)
def test_move(): """Test the LineSegment2D move method.""" pt = Point2D(2, 0) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) vec_1 = Vector2D(2, 2) new_seg = seg.move(vec_1) assert new_seg.p == Point2D(4, 2) assert new_seg.v == vec assert new_seg.p1 == Point2D(4, 2) assert new_seg.p2 == Point2D(4, 4)
def test_distance_to_point(): """Test the LineSegment2D distance_to_point method.""" pt = Point2D(2, 2) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) near_pt = Point2D(3, 3) assert seg.distance_to_point(near_pt) == 1 near_pt = Point2D(2, 0) assert seg.distance_to_point(near_pt) == 2 near_pt = Point2D(2, 5) assert seg.distance_to_point(near_pt) == 1
def test_closest_point(): """Test the LineSegment2D closest_point method.""" pt = Point2D(2, 2) vec = Vector2D(0, 2) seg = LineSegment2D(pt, vec) near_pt = Point2D(3, 3) assert seg.closest_point(near_pt) == Point2D(2, 3) near_pt = Point2D(2, 0) assert seg.closest_point(near_pt) == Point2D(2, 2) near_pt = Point2D(2, 5) assert seg.closest_point(near_pt) == Point2D(2, 4)
def test_intersect_line_ray(): """Test the Polygon2D intersect_line_ray method.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) polygon = Polygon2D(pts) ray_1 = Ray2D(Point2D(-1, 1), Vector2D(1, 0)) ray_2 = Ray2D(Point2D(1, 1), Vector2D(1, 0)) ray_3 = Ray2D(Point2D(1, 1), Vector2D(11, 0)) ray_4 = Ray2D(Point2D(-1, 1), Vector2D(-1, 0)) assert len(polygon.intersect_line_ray(ray_1)) == 2 assert len(polygon.intersect_line_ray(ray_2)) == 1 assert len(polygon.intersect_line_ray(ray_3)) == 1 assert len(polygon.intersect_line_ray(ray_4)) == 0 line_1 = LineSegment2D(Point2D(-1, 1), Vector2D(0.5, 0)) line_2 = LineSegment2D(Point2D(-1, 1), Vector2D(2, 0)) line_3 = LineSegment2D(Point2D(-1, 1), Vector2D(3, 0)) assert len(polygon.intersect_line_ray(line_1)) == 0 assert len(polygon.intersect_line_ray(line_2)) == 1 assert len(polygon.intersect_line_ray(line_3)) == 2
def hr_lines(self): """Get a tuple of LineSegment2Ds for the humidity ratio labels on the chart.""" hr_lines, xmax = [], self._x_range[-1] for hr, y in zip(self._hr_range, self._y_range): tmin = db_temp_from_rh_hr(100, hr, self.average_pressure) tmin = self.TEMP_TYPE.to_unit([tmin], 'F', 'C')[0] if self.use_ip else tmin xmin = self.t_x_value(tmin) xmin = xmin if xmin > self.base_point.x else self.base_point.x l_seg = LineSegment2D.from_end_points(Point2D(xmax, y), Point2D(xmin, y)) hr_lines.append(l_seg) return hr_lines
def orientation_lines(self): """Get the orientation lines for windrose as a LineSegment2D list.""" # Reset computed graphics to account for changes to cached viz properties self._compass = None self._container = None # Compute x-axis bin boundaries in polar coordinates vec_cpt = (0, 0) max_bar_radius = self.compass_radius # Vector multiplication with max_bar_radius segs = [] for (vec1, vec2) in self.bin_vectors: _seg1 = vec_cpt, (max_bar_radius * vec1[0], max_bar_radius * vec1[1]) _seg2 = vec_cpt, (max_bar_radius * vec2[0], max_bar_radius * vec2[1]) segs.extend((LineSegment2D.from_array(_seg1), LineSegment2D.from_array(_seg2))) return [self._transform(seg) for seg in segs]
def __init__(self, polygon, holes, tol): self.tol = tol contours = [_normalize_contour(polygon, tol)] contours.extend([_normalize_contour(hole, tol) for hole in holes]) self._lavs = [_LAV.from_polygon(contour, self) for contour in contours] # Store original polygon edges for calculating split events self._original_edges = [ _OriginalEdge( LineSegment2D.from_end_points(vertex.prev.point, vertex.point), vertex.prev.bisector, vertex.bisector) for vertex in chain.from_iterable(self._lavs) ]