Source code for honeybee_radiance.lightsource.sky.climatebased

"""Generate a point-in-time climate-based sky."""
from __future__ import division
import argparse
import shlex

import ladybug.futil as futil
from ladybug.dt import DateTime
from ladybug.analysisperiod import AnalysisPeriod
from ladybug.location import Location
from ladybug.sunpath import Sunpath
from ladybug.wea import Wea
from ladybug.epw import EPW
import honeybee.typing as typing

from ._skybase import _PointInTime


[docs]class ClimateBased(_PointInTime): """Point-in-time Climate-based sky. The output of Climatebased sky is similar to using command Radiance's gendaylit command. For more information see gendaylit documentation. Args: altitude: Solar altitude. The altitude is measured in degrees above the horizon. azimuth: Solar azimuth. The azimuth is measured in degrees east of North. East is 90, South is 180 and West is 270. Note that this input is different from the Radiance convention. In Radiance the azimuth degrees are measured in west of South. direct_normal_irradiance: Direct normal irradiance (W/m2). diffuse_horizontal_irradiance: Diffuse horizontal irradiance (W/m2). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). Properties: * altitude * azimuth * direct_normal_irradiance * diffuse_horizontal_irradiance * ground_hemisphere * sky_hemisphere * ground_reflectance * is_colored * is_point_in_time * is_climate_based """ __slots__ = ( '_altitude', '_azimuth', '_diffuse_horizontal_irradiance', '_direct_normal_irradiance', '_is_colored' ) def __init__( self, altitude, azimuth, direct_normal_irradiance, diffuse_horizontal_irradiance, ground_reflectance=0.2, is_colored=False ): """Create a climate-based standard sky.""" _PointInTime.__init__(self, ground_reflectance) self.altitude = altitude self.azimuth = azimuth self.direct_normal_irradiance = direct_normal_irradiance self.diffuse_horizontal_irradiance = diffuse_horizontal_irradiance self.is_colored = is_colored @property def altitude(self): """Get or set a number between -90 and 90 for the solar altitude. The altitude is measured in degrees above the horizon. """ return self._altitude @altitude.setter def altitude(self, value): value = typing.float_in_range(value, -90, 90, 'Solar altitude') self._altitude = value @property def azimuth(self): """Get or set a number between 0 and 360 for the solar azimuth. The azimuth is measured in degrees east of North. East is 90, South is 180 and West is 270. Note that this input is different from Radiance convention. In Radiance the azimuth degrees are measured in west of South. """ return self._azimuth @azimuth.setter def azimuth(self, value): value = typing.float_in_range(value, 0, 360, 'Solar azimuth') self._azimuth = value @property def direct_normal_irradiance(self): """Get or set direct normal irradiance (W/m2). """ return self._direct_normal_irradiance @direct_normal_irradiance.setter def direct_normal_irradiance(self, value): value = typing.int_positive(value, 'Direct normal irradiance') self._direct_normal_irradiance = value @property def diffuse_horizontal_irradiance(self): """Get or set diffuse horizontal irradiance (W/m2). """ return self._diffuse_horizontal_irradiance @diffuse_horizontal_irradiance.setter def diffuse_horizontal_irradiance(self, value): value = typing.int_positive(value, 'Diffuse horizontal irradiance') self._diffuse_horizontal_irradiance = value @property def is_colored(self): """Get or set a boolean for whether the sky is rendered in full color.""" return self._is_colored @is_colored.setter def is_colored(self, value): self._is_colored = bool(value) @property def is_climate_based(self): """Return True if the sky is created based on values from weather data.""" return True
[docs] @classmethod def from_lat_long( cls, latitude, longitude, time_zone, month, day, hour, direct_normal_irradiance, diffuse_horizontal_irradiance, north_angle=0, ground_reflectance=0.2, is_colored=False ): """Create a climate based sky from latitude, longitude and a date/time. Args: latitude: Location latitude between -90 and 90. longitude: Location longitude between -180 (west) and 180 (east). time_zone: Time zone between -12 hours (west) and +14 hours (east). If None, the time will be interpreted as solar time at the given longitude. month: An integer between 1-12 for month. day: An integer between 1 to 28-31 depending on the input month. hour: A float number larger or equal to 0 and smaller than 24. direct_normal_irradiance: Direct normal irradiance (W/m2). diffuse_horizontal_irradiance: Diffuse horizontal irradiance (W/m2). north_angle: North angle in degrees. A number between -360 and 360 for the counterclockwise difference between the North and the positive Y-axis in degrees. 90 is West and 270 is East (Default: 0). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). """ # calculate altitude and azimuth using ladybug's sunpath sp = Sunpath(latitude, longitude, time_zone, north_angle) sun = sp.calculate_sun(month, day, hour) return cls( sun.altitude, sun.azimuth_from_y_axis, direct_normal_irradiance, diffuse_horizontal_irradiance, ground_reflectance, is_colored )
[docs] @classmethod def from_location( cls, location, month, day, hour, direct_normal_irradiance, diffuse_horizontal_irradiance, north_angle=0, ground_reflectance=0.2, is_colored=False ): """Create a standard climate-based sky for a location. Args: location: A Ladybug location. month: An integer between 1-12 for month. day: An integer between 1 to 28-31 depending on the input month. hour: A float number larger or equal to 0 and smaller than 24. direct_normal_irradiance: Direct normal irradiance (W/m2). diffuse_horizontal_irradiance: Diffuse horizontal irradiance (W/m2). north_angle: North angle in degrees. A number between -360 and 360 for the counterclockwise difference between the North and the positive Y-axis in degrees. 90 is West and 270 is East (Default: 0). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). """ assert isinstance(location, Location), \ 'location must be from type Location not {}'.format(type(location)) return cls.from_lat_long( location.latitude, location.longitude, location.time_zone, month, day, hour, direct_normal_irradiance, diffuse_horizontal_irradiance, north_angle, ground_reflectance, is_colored)
[docs] @classmethod def from_wea( cls, wea, month, day, hour, north_angle=0, ground_reflectance=0.2, is_colored=False ): """Create a standard climate-based sky from a Wea. Args: wea: A Ladybug wea object. month: An integer between 1-12 for month. day: An integer between 1 to 28-31 depending on the input month. hour: A float number larger or equal to 0 and smaller than 24. north_angle: North angle in degrees. A number between -360 and 360 for the counterclockwise difference between the North and the positive Y-axis in degrees. 90 is West and 270 is East (Default: 0). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). """ assert isinstance(wea, Wea), \ 'wea must be from type Wea not {}'.format(type(wea)) location = wea.location direct_normal_irradiance, diffuse_horizontal_irradiance = \ wea.get_irradiance_value(month, day, hour) return cls.from_lat_long( location.latitude, location.longitude, location.time_zone, month, day, hour, direct_normal_irradiance, diffuse_horizontal_irradiance, north_angle, ground_reflectance, is_colored)
[docs] @classmethod def from_wea_monthly_average( cls, wea, month, hour, north_angle=0, ground_reflectance=0.2, is_colored=False ): """Create a monthly averaged climate-based sky from a Wea and a hour of the day. Args: wea: A Ladybug wea object. month: An integer between 1-12 for month. hour: A float number larger or equal to 0 and smaller than 24. north_angle: North angle in degrees. A number between -360 and 360 for the counterclockwise difference between the North and the positive Y-axis in degrees. 90 is West and 270 is East (Default: 0). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). """ assert isinstance(wea, Wea), \ 'wea must be from type Wea not {}'.format(type(wea)) a_period = AnalysisPeriod( st_month=month, st_hour=hour, end_month=month, end_hour=hour, timestep=wea.timestep, is_leap_year=wea.is_leap_year) filtered_wea = wea.filter_by_analysis_period(a_period) location = wea.location dir_normal_irradiance = filtered_wea.direct_normal_irradiance.average dif_horizontal_irradiance = filtered_wea.diffuse_horizontal_irradiance.average return cls.from_lat_long( location.latitude, location.longitude, location.time_zone, month, 15, hour, dir_normal_irradiance, dif_horizontal_irradiance, north_angle, ground_reflectance, is_colored)
[docs] @classmethod def from_epw( cls, epw, month, day, hour, north_angle=0, ground_reflectance=0.2, is_colored=False ): """Create a standard climate-based sky from a EPW. Args: epw: A Ladybug EPW objects. month: An integer between 1-12 for month. day: An integer between 1 to 28-31 depending on the input month. hour: A float number larger or equal to 0 and smaller than 24. north_angle: North angle in degrees. A number between -360 and 360 for the counterclockwise difference between the North and the positive Y-axis in degrees. 90 is West and 270 is East (Default: 0). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). """ assert isinstance(epw, EPW), \ 'epw must be from type EPW not {}'.format(type(epw)) location = epw.location hoy = int(DateTime(month, day, hour).hoy) direct_normal_irradiance = epw.direct_normal_radiation[hoy] diffuse_horizontal_irradiance = epw.diffuse_horizontal_radiation[hoy] return cls.from_lat_long( location.latitude, location.longitude, location.time_zone, month, day, hour, direct_normal_irradiance, diffuse_horizontal_irradiance, north_angle, ground_reflectance, is_colored)
[docs] @classmethod def from_epw_monthly_average( cls, epw, month, hour, north_angle=0, ground_reflectance=0.2, is_colored=False ): """Create a monthly averaged climate-based sky from an EPW and a hour of the day. Args: epw: A Ladybug EPW objects. month: An integer between 1-12 for month. hour: A float number larger or equal to 0 and smaller than 24. north_angle: North angle in degrees. A number between -360 and 360 for the counterclockwise difference between the North and the positive Y-axis in degrees. 90 is West and 270 is East (Default: 0). ground_reflectance: Average ground reflectance (Default: 0.2). is_colored: Boolean to note whether the sky will be rendered in full color (True) or it will simple be a grey sky with the same average value as the colored sky (False). (Default: False). """ assert isinstance(epw, EPW), \ 'epw must be from type EPW not {}'.format(type(epw)) location = epw.location a_period = AnalysisPeriod( st_month=month, st_hour=hour, end_month=month, end_hour=hour, is_leap_year=epw.is_leap_year) dn = epw.direct_normal_radiation.filter_by_analysis_period(a_period).average dh = epw.diffuse_horizontal_radiation.filter_by_analysis_period(a_period).average return cls.from_lat_long( location.latitude, location.longitude, location.time_zone, month, 15, hour, dn, dh, north_angle, ground_reflectance, is_colored)
[docs] @classmethod def from_dict(cls, data): """Create a ClimateBased sky from a dictionary. Args: data: A python dictionary in the following format .. code-block:: python { 'type': 'ClimateBased', 'altitude': 0.0, 'azimuth': 0.0, 'direct_normal_irradiance': 800, 'diffuse_horizontal_irradiance': 120, 'ground_reflectance': 0.2, # optional float for ground reflectance 'is_colored': True # boolean for whether the sky is colored } """ assert 'type' in data, \ 'Input dict is missing type. Not a valid ClimateBased dictionary.' assert data['type'] == 'ClimateBased', \ 'Input type must be ClimateBased not %s' % data['type'] gr = data['ground_reflectance'] if 'ground_reflectance' in data else 0.2 ic = data['is_colored'] if 'is_colored' in data else False return cls( data['altitude'], data['azimuth'], data['direct_normal_irradiance'], data['diffuse_horizontal_irradiance'], gr, ic )
[docs] @classmethod def from_string(cls, sky_string): """Create a ClimateBased sky from a string. Args: sky_string: A text string representing a ClimateBased sky. This can either be a minimal string representation of the sky (eg. "climate-based -alt 71.6 -az 185.2 -dni 800 -dhi 120"). Or it can be a detailed specification of time and location (eg. "climate-based 21 Jun 12:00 -lat 41.78 -lon -87.75 -dni 800 -dhi 120"). Any sky string can optionally have a "-g" property of a fractional number, which sets the reflectance of the ground. If unspecified, the ground will have a reflectance of 0.2. The detailed string can optionally have a "-tz" property with an integer between -12 and +14 to denote the time zone. If unspecified, the time will be interpreted as solar time at the given longitude. The detailed string can also have a "-n" property between 0 and 360 to set the counterclockwise difference between the North and the positive Y-axis in degrees. All other properties specified in the string are required. Usage: .. code-block:: python # minimal string representation of the sky sky_string = "climate-based -alt 71.6 -az 185.2 -dni 800 -dhi 120" sky = ClimateBased.from_string(sky_string) # detailed location-specific representation of the sky sky_string = "climate-based 21 Jun 12:00 -lat 41.78 -lon -87.75 -tz -6 " \ " -dni 800 -dhi 120 -n 0 -g 0.2" sky = ClimateBased.from_string(sky_string) """ # check the input and parse the datetime if it exists lower_str = sky_string.lower() assert lower_str.startswith('climate-based'), 'Expected string representation ' \ 'of ClimateBased sky "{}" to start with "climate-based".'.format(sky_string) split_str = shlex.split(lower_str) try: dtime = DateTime.from_date_time_string(' '.join(split_str[1:4])) except (ValueError, IndexError): # simpler sky representation dtime = None # make a parser for all of the other sky properties pars = argparse.ArgumentParser() pars.add_argument('-dni', action='store', dest='dni', type=float, default=0) pars.add_argument('-dhi', action='store', dest='dhi', type=float, default=0) pars.add_argument('-g', action='store', dest='g', type=float, default=0.2) pars.add_argument('-c', action='store_true', dest='c', default=False) # create the sky object if dtime is None: pars.add_argument('-alt', action='store', dest='alt', type=float, default=90) pars.add_argument('-az', action='store', dest='az', type=float, default=0) props = pars.parse_args(split_str[1:]) return cls(props.alt, props.az, props.dni, props.dhi, props.g, props.c) else: pars.add_argument('-lat', action='store', dest='lat', type=float, default=0) pars.add_argument('-lon', action='store', dest='lon', type=float, default=0) pars.add_argument('-tz', action='store', dest='tz', type=int, default=0) pars.add_argument('-n', action='store', dest='n', type=float, default=0) props = pars.parse_args(split_str[4:]) return cls.from_lat_long( props.lat, props.lon, props.tz, dtime.month, dtime.day, dtime.float_hour, props.dni, props.dhi, props.n, props.g, props.c)
# TODO: add support for additional parameters # TODO: add gendaylit to radiance-command and use it for validating inputs
[docs] def to_radiance(self, output_type=0): """Return Radiance description of the sky. Args: output_type: An integer between 0 to 2 for output type. * 0 = output in W/m2/sr visible (default) * 1 = output in W/m2/sr solar * 2 = output in lm/m2/sr luminance """ full_color = ' -C' if self.is_colored else '' output = typing.int_in_range(output_type, 0, 2, 'Sky output type') command = '!gendaylit -ang %.6f %.6f -O %d -W %d %d -g %.3f%s' % ( self.altitude, self.azimuth - 180.0, output, self.direct_normal_irradiance, self._diffuse_horizontal_irradiance, self.ground_reflectance, full_color ) return '%s\n\n%s\n\n%s\n' % ( command, self.sky_hemisphere, self.ground_hemisphere )
[docs] def to_dict(self): """Translate sky to a dictionary.""" base = { 'type': 'ClimateBased', 'altitude': self.altitude, 'azimuth': self.azimuth, 'direct_normal_irradiance': self.direct_normal_irradiance, 'diffuse_horizontal_irradiance': self.diffuse_horizontal_irradiance, 'ground_reflectance': self.ground_reflectance, 'ground_hemisphere': self.ground_hemisphere.to_dict(), 'sky_hemisphere': self.sky_hemisphere.to_dict() } if self.is_colored: base['is_colored'] = True return base
[docs] def to_file(self, folder, name=None, mkdir=False): """Write sky hemisphere to a sky_hemisphere.rad Radiance file. Args: folder: Target folder. name: File name. mkdir: A boolean to note if the directory should be created if doesn't exist (default: False). Returns: Full path to the newly created file. """ content = self.to_radiance() name = typing.valid_string(name) if name \ else '%.3f_%.3f_%d_%d.sky' % ( self.altitude, self.azimuth, self.direct_normal_irradiance, self.diffuse_horizontal_irradiance ) return futil.write_to_file_by_name(folder, name, content, mkdir)
def __eq__(self, value): if type(value) != type(self) \ or value.altitude != self.altitude \ or value.azimuth != self.azimuth \ or value.direct_normal_irradiance != self.direct_normal_irradiance \ or value.diffuse_horizontal_irradiance != \ self.diffuse_horizontal_irradiance \ or self.ground_reflectance != value.ground_reflectance \ or self.is_colored != value.is_colored \ or self.ground_hemisphere != value.ground_hemisphere \ or self.sky_hemisphere != value.sky_hemisphere: return False return True def __ne__(self, value): return not self.__eq__(value) def __repr__(self): """Sky representation.""" is_colored = ' -c' if self.is_colored else '' return 'climate-based -alt {} -az {} -dni {} -dhi {} -g {}{}'.format( self.altitude, self.azimuth, self.direct_normal_irradiance, self.diffuse_horizontal_irradiance, self.ground_reflectance, is_colored )