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 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_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 _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 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 _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 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 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_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 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 __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) ]
def test_connector_init(): """Test the initialization of ElectricalConnector and basic properties.""" geo = LineSegment2D.from_end_points(Point2D(0, 0), Point2D(100, 0)) wire = Wire('OH AL 2/0 A') connector = ElectricalConnector('Connector_1', geo, [wire]) str(connector) # test the string representation assert connector.identifier == 'Connector_1' assert connector.geometry == geo assert len(connector) == 1 assert len(connector.wires) == 1 assert connector.wires[0] == wire assert connector[0] == wire assert connector.wire_ids == ['OH AL 2/0 A']
def _compute_wb_range(self): """Compute the values for wet bulb range and lines.""" # constants used throughout the calculation low_y, border = self.base_point.y - 1e-6, self.chart_border all_wbs = wb_c = tuple( range(self._min_temperature, self._max_temperature, 5)) if self.use_ip: wb_c = self.TEMP_TYPE.to_unit(wb_c, 'C', 'F') # loop through the wet bulb and compute the lines of constant wet bulb wb_range, wb_lines = [], [] for i, wb in enumerate(wb_c): st_db = db_temp_and_hr_from_wb_rh(wb, 0, self._average_pressure)[0] end_db, end_hr = db_temp_and_hr_from_wb_rh(wb, 100, self._average_pressure) 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), self.hr_y_value(end_hr))) border_ints = border.intersect_line_ray(enth_line) if len(border_ints) == 2: wb_range.append(all_wbs[i]) seg = LineSegment2D.from_end_points(border_ints[0], border_ints[1]) wb_lines.append(seg) elif len(border_ints) == 1: wb_range.append(all_wbs[i]) seg = LineSegment2D.from_end_points(border_ints[0], enth_line.p2) wb_lines.append(seg) # set the properties on this class self._wb_range = wb_range self._wb_lines = wb_lines
def temperature_lines(self): """Get a tuple of LineSegment2Ds for the temperature labels on the chart.""" # get the Y-values for the top of the temperature lines hr_vals = (humid_ratio_from_db_rh(t, 100, self.average_pressure) for t in self._temp_range) top_y = [] for hr in hr_vals: y_val = self.hr_y_value(hr) if hr < self._max_humidity_ratio \ else self.hr_y_value(self._max_humidity_ratio) top_y.append(y_val) t_lines = [] # create the array of line segments for x_val, y_val in zip(self._x_range, top_y): l_seg = LineSegment2D.from_end_points( Point2D(x_val, self._base_point.y), Point2D(x_val, y_val)) t_lines.append(l_seg) return t_lines
def _offset_bisector(seg1, seg2, distance): """ Calculates the magnitude of the offset bisector.""" p1 = infinite_line2d_intersection(seg1, seg2) # Normal offset outward seg1_3d = Point3D(*(seg1.p2 - seg1.p1).to_array(), 0) seg2_3d = Point3D(*(seg2.p2 - seg2.p1).to_array(), 0) z = Point3D(0, 0, 1) norm1 = seg1_3d.cross(z) norm2 = seg2_3d.cross(z) # Get offset off1 = seg1.move((norm1 / norm1.magnitude) * distance) off2 = seg2.move((norm2 / norm2.magnitude) * distance) p2 = infinite_line2d_intersection(off1, off2) if p1 is None or p2 is None: return return LineSegment2D.from_end_points(p1, p2)
def test_connector_transform(): """Test the ElectricalConnector transform 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]) new_connector = connector.duplicate() new_connector.move(Vector3D(100, 0)) assert new_connector.geometry.p1 == Point2D(100, 0) new_connector = connector.duplicate() new_connector.rotate_xy(90, Point3D()) assert new_connector.geometry.p2.y == pytest.approx(100, rel=1e-3) new_connector = connector.duplicate() new_connector.reflect(Plane(n=Vector3D(1, 0))) assert new_connector.geometry.p2.x == pytest.approx(-100, rel=1e-3) new_connector = connector.duplicate() new_connector.scale(0.5) assert new_connector.geometry.p2.x == pytest.approx(50, rel=1e-3)
def test_join_segments(): """Test the join_segments method.""" pts = (Point2D(0, 0), Point2D(2, 0), Point2D(2, 2), Point2D(0, 2)) l_segs = (LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[1], pts[2]), LineSegment2D.from_end_points(pts[2], pts[3]), LineSegment2D.from_end_points(pts[3], pts[0])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 1 assert isinstance(p_lines[0], Polyline2D) assert len(p_lines[0]) == 5 assert p_lines[0].is_closed(0.01) l_segs = (LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[2], pts[3]), LineSegment2D.from_end_points(pts[1], pts[2]), LineSegment2D.from_end_points(pts[3], pts[0])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 1 assert isinstance(p_lines[0], Polyline2D) assert len(p_lines[0]) == 5 assert p_lines[0].is_closed(0.01) l_segs = (LineSegment2D.from_end_points(pts[0], pts[1]), LineSegment2D.from_end_points(pts[1], pts[2]), LineSegment2D.from_end_points(pts[0], pts[3]), LineSegment2D.from_end_points(pts[3], pts[2])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 1 assert isinstance(p_lines[0], Polyline2D) assert len(p_lines[0]) == 5 assert p_lines[0].is_closed(0.01)
if 'geometry' in geo and geo['geometry']['type'] in geo_types ] if not all_to_bldg: # exclude anything with a Building key geo_data = [ geo for geo in geo_data if 'type' not in geo['properties'] or geo['properties']['type'] != 'Building' ] # convert all of the geoJSON data into Rhino geometry other_geo = [] for geo_dat in geo_data: if geo_dat['geometry']['type'] == 'LineString': coords = lon_lat_to_polygon(geo_dat['geometry']['coordinates'], origin_lon_lat, convert_facs) pts = tuple(Point2D.from_array(pt) for pt in coords) line = LineSegment2D.from_end_points(pts[0], pts[1]) \ if len(pts) == 2 else Polyline2D(pts) if con_fac != 1: line = line.scale(con_fac, pt) if len(pts) == 2: other_geo.append(from_linesegment2d(line)) else: other_geo.append(from_polyline2d(line)) else: # is's a polygon coords = lon_lat_to_polygon( geo_dat['geometry']['coordinates'][0], origin_lon_lat, convert_facs) pts = tuple(Point2D.from_array(pt) for pt in coords) poly = Polyline2D(pts) if con_fac != 1: poly = poly.scale(con_fac, pt)
def to_linesegment2d(line): """Ladybug LineSegment2D from Rhino LineCurve.""" return LineSegment2D.from_end_points(to_point2d(line.PointAtStart), to_point2d(line.PointAtEnd))
def test_join_segments_multiple_pline(): """Test the join_segments method with multiple 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[1], pts[2]), LineSegment2D.from_end_points(pts[0], pts[3]), LineSegment2D.from_end_points(pts[3], pts[2])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 2 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[1], pts[2]), LineSegment2D.from_end_points(pts[0], pts[3]), LineSegment2D.from_end_points(pts[3], pts[2]), LineSegment2D.from_end_points(extra_pts[2], extra_pts[3]), LineSegment2D.from_end_points(extra_pts[1], extra_pts[2]), LineSegment2D.from_end_points(extra_pts[0], extra_pts[3])) p_lines = Polyline2D.join_segments(l_segs, 0.01) assert len(p_lines) == 2 for p_line in p_lines: assert isinstance(p_line, Polyline2D) assert len(p_line) == 5 assert p_line.is_closed(0.01)