Source code for ladybug_radiance.visualize.skydome

"""Class for visualizing sky matrices on a dome."""
from __future__ import division
import math

from ladybug_geometry.geometry2d.pointvector import Point2D
from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D
from ladybug_geometry.geometry3d.mesh import Mesh3D

from ladybug.datatype.energyintensity import Radiation
from ladybug.datatype.energyflux import Irradiance
from ladybug.viewsphere import view_sphere
from ladybug.compass import Compass
from ladybug.graphic import GraphicContainer
from ladybug.legend import LegendParameters
from ladybug.color import Colorset


[docs] class SkyDome(object): """Visualize a sky matrix as a colored dome, subdivided into patches. Args: sky_matrix: A SkyMatrix object, which describes the radiation coming from the various patches of the sky. legend_parameters: An optional LegendParameter object to change the display of the sky dome. If None, some default legend parameters will be used. (Default: None). plot_irradiance: Boolean to note whether the sky dome should be plotted with units of total Radiation (kWh/m2) [False] or with units of average Irradiance (W/m2) [True]. (Default: False). center_point: A point for the center of the dome. (Default: (0, 0, 0)). radius: A number to set the radius of the sky dome. (Default: 100). projection: Optional text for the name of a projection to use from the sky dome hemisphere to the 2D plane. If None, a 3D sky dome will be drawn instead of a 2D one. (Default: None) Choose from the following. * Orthographic * Stereographic Properties: * legend_parameters * plot_irradiance * center_point * radius * projection * north * patch_vectors * total_values * direct_values * diffuse_values * metadata * is_benefit """ __slots__ = ( '_north', '_total_values', '_direct_values', '_diffuse_values', '_metadata', '_is_benefit', '_legend_parameters', '_plot_irradiance', '_center_point', '_radius', '_projection') PROJECTIONS = ('Orthographic', 'Stereographic') def __init__(self, sky_matrix, legend_parameters=None, plot_irradiance=False, center_point=Point3D(0, 0, 0), radius=100, projection=None): """Initialize SkyDome.""" # deconstruct the sky matrix and derive key data from it metadata, direct, diffuse = sky_matrix.data self._north = metadata[0] # first item is the north angle self._plot_irradiance = bool(plot_irradiance) if not plot_irradiance: self._direct_values = direct self._diffuse_values = diffuse else: factor = 1000 / sky_matrix.wea_duration \ if hasattr(sky_matrix, 'wea_duration') else \ 1000 / (((metadata[3] - metadata[2]).total_seconds() / 3600) + 1) self._direct_values = tuple(v * factor for v in direct) self._diffuse_values = tuple(v * factor for v in diffuse) zip_obj = zip(self._direct_values, self._diffuse_values) self._total_values = tuple(dr + df for dr, df in zip_obj) self._metadata = metadata # override the legend default min and max to make sense for domes if legend_parameters is not None: assert isinstance(legend_parameters, LegendParameters), \ 'Expected LegendParameters. Got {}.'.format(type(legend_parameters)) l_par = legend_parameters.duplicate() else: l_par = LegendParameters() if hasattr(sky_matrix, 'benefit_matrix') and \ sky_matrix.benefit_matrix is not None: if l_par.min is None: l_par.min = min((min(self._total_values), -max(self._total_values))) if l_par.max is None: l_par.max = max((-min(self._total_values), max(self._total_values))) if l_par.are_colors_default: l_par.colors = reversed(Colorset.benefit_harm()) self._is_benefit = True else: if l_par.min is None: l_par.min = 0 if l_par.max is None: l_par.max = max(self._total_values) self._is_benefit = False self._legend_parameters = l_par # process the geometry parameters of the dome assert isinstance(center_point, Point3D), 'Expected Point3D for dome center. ' \ 'Got {}.'.format(type(center_point)) self._center_point = center_point assert isinstance(radius, (float, int)), 'Expected number for radius. ' \ 'Got {}.'.format(type(radius)) assert radius > 0, \ 'Dome radius must be greater than zero. Got {}.'.format(radius) self._radius = radius if projection is not None: assert projection in self.PROJECTIONS, 'Projection "{}" is not recognized.' \ ' Choose from: {}.'.format(projection, self.PROJECTIONS) self._projection = projection @property def legend_parameters(self): """Get the legend parameters assigned to this sky dome object.""" return self._legend_parameters @property def plot_irradiance(self): """Get a boolean for whether the sky dome values are for irradiance in (W/m2).""" return self._plot_irradiance @property def center_point(self): """Get a Point3D for the center of the dome.""" return self._center_point @property def radius(self): """Get a number for the radius of the dome.""" return self._radius @property def projection(self): """Get text for the projection of the dome.""" return self._projection @property def north(self): """Get a number north direction.""" return self._north @property def patch_vectors(self): """Get a list of vectors for each of the patches of the sky dome. All vectors are unit vectors and point from the center towards each of the patches. They can be used to construct visualizations of the rays used to perform radiation analysis. """ return view_sphere.tregenza_dome_vectors if len(self._total_values) == 145 \ else view_sphere.reinhart_dome_vectors @property def total_values(self): """Get a tuple of values for the total radiation/irradiance of each patch.""" return self._total_values @property def direct_values(self): """Get a tuple of values for the direct radiation/irradiance of each patch.""" return self._direct_values @property def diffuse_values(self): """Get a tuple of values for the diffuse radiation/irradiance of each patch.""" return self._diffuse_values @property def metadata(self): """Get a tuple of information about the metadata assigned to the sky dome.""" return self._metadata @property def is_benefit(self): """Boolean to note whether the sky matrix includes benefit information.""" return self._is_benefit
[docs] def draw(self, rad_type='total', center=None): """Draw a dome mesh, compass, graphic/legend, and title for the sky dome. Args: rad_type: Text for the type of radiation to use. Choose from total, direct, diffuse. (Default: total). center: A Point3D to override the center of the sky dome. This is useful when rendering all of the sky components together and one dome should not be on top of another. If None, the center point assigned to the object instance is used. (Default: None). Returns: A tuple with five values. - dome_mesh -- A colored Mesh3D for the dome. - dome_compass -- A ladybug Compass object for the dome. - graphic -- A GraphicContainer for the colored dome mesh, indicating the legend and title location for the dome. - dome_title -- Text for the title of the dome. - values -- A list of radiation values that align with the dome_mesh faces. """ # get the dome data to be plotted if rad_type.lower() == 'total': dome_data = self.total_values elif rad_type.lower() == 'direct': dome_data = self.direct_values elif rad_type.lower() == 'diffuse': dome_data = self.diffuse_values else: raise ValueError('Radiation type "{}" not recognized.'.format(rad_type)) # create the dome mesh and ensure patch values align with mesh faces if len(dome_data) == 145: # tregenza sky dome_mesh = view_sphere.tregenza_dome_mesh_high_res.scale(self.radius) values = [] # high res dome has 3 x 3 faces per patch; we must convert tot_i = 0 # track the total number of patches converted for patch_i in view_sphere.TREGENZA_PATCHES_PER_ROW: row_vals = [] for val in dome_data[tot_i:tot_i + patch_i]: row_vals.extend([val] * 3) for i in range(3): values.extend(row_vals) tot_i += patch_i values = values + [dome_data[-1]] * 18 # last patch has triangular faces else: # reinhart sky dome_mesh = view_sphere.reinhart_dome_mesh.scale(self.radius) values = list(dome_data) + [dome_data[-1]] * 11 # triangular last patches # move and/or rotate the mesh as needed if self.north != 0: dome_mesh = dome_mesh.rotate_xy(math.radians(self.north), Point3D()) center = self.center_point if center is None else center if center != Point3D(): dome_mesh = dome_mesh.move(Vector3D(center.x, center.y, center.z)) # project the mesh if requested if self.projection is not None: if self.projection.title() == 'Orthographic': pts = (Compass.point3d_to_orthographic(pt) for pt in dome_mesh.vertices) elif self.projection.title() == 'Stereographic': pts = (Compass.point3d_to_stereographic(pt, self.radius, center) for pt in dome_mesh.vertices) pts3d = tuple(Point3D(pt.x, pt.y, center.z) for pt in pts) dome_mesh = Mesh3D(pts3d, dome_mesh.faces) # output the dome visualization, including graphic and compass move_fac = self.radius * 1.15 min_pt = center.move(Vector3D(-move_fac, -move_fac, 0)) max_pt = center.move(Vector3D(move_fac, move_fac, 0)) if self.plot_irradiance: d_type, unit, typ_str = Irradiance(), 'W/m2', 'Irradiance' else: d_type, unit, typ_str = Radiation(), 'kWh/m2', 'Radiation' graphic = GraphicContainer( values, min_pt, max_pt, self.legend_parameters, d_type, unit) dome_mesh.colors = graphic.value_colors dome_compass = Compass(self.radius, Point2D(center.x, center.y), self.north) # construct a title from the metadata st, end = self.metadata[2], self.metadata[3] time_str = '{} - {}'.format(st, end) if st != end else st if self.is_benefit: typ_str = '{} Benefit/Harm'.format(typ_str) dome_title = '{} {}\n{}\n{}'.format( rad_type.title(), typ_str, time_str, '\n'.join([dat for dat in self.metadata[4:]])) return dome_mesh, dome_compass, graphic, dome_title, values
[docs] def ToString(self): """Overwrite .NET ToString.""" return self.__repr__()
def __len__(self): return len(self._total_values) def __getitem__(self, key): return self._total_values[key], self._direct_values[key], \ self._diffuse_values[key] def __iter__(self): return zip(self._total_values, self._direct_values, self._diffuse_values) def __repr__(self): """Sky Dome object representation.""" return 'SkyDome [{} patches]'.format(len(self._total_values))