"""A Radiance-based sunpath.
A Radiance-based sunpath is a list of light sources with radius of 0.533
A sunpath can be climate-based or non-climate-based. In non climate-based sunpath
irradiance values are set to 1e6 for red, green and blue channels.
Use the climate-based sunpath for direct solar radiation studies and use the
non climate-based sunpath for solar access studies.
"""
from ..modifier.material import Light
from ..geometry import Source
from ._gendaylit import gendaylit
from ladybug.sunpath import Sunpath as LBSunpath
from ladybug.location import Location
from ladybug.wea import Wea
import os
import warnings
try:
from itertools import izip as zip
writemode = 'wb'
except ImportError:
# python 3
writemode = 'w'
[docs]
class Sunpath(object):
"""A Radiance-based sun-path.
Args:
location: A Ladybug location.
north: Sunpath north angle.
Properties:
* location
* north
"""
__slots__ = ('_location', '_north')
def __init__(self, location, north=0):
self.location = location
self.north = north
@property
def location(self):
"""Sunpath location."""
return self._location
@location.setter
def location(self, loc):
assert isinstance(loc, Location), \
'Location must be a Ladybug Location not %s' % type(loc)
self._location = loc
@property
def north(self):
"""Sunpath north angle."""
return self._north
@north.setter
def north(self, n):
assert isinstance(n, (int, float)), 'north must be a numerical value.'
self._north = n
def _solar_calc(self, hoys, wea, output_type, leap_year=False,
reverse_vectors=False):
"""Calculate sun vectors and radiance values from the properties."""
solar_calc = LBSunpath.from_location(self.location, self.north)
solar_calc.is_leap_year = leap_year
if not hoys:
# set hours to an annual hourly sunpath
hoys = range(8760) if not leap_year else range(8760 + 24)
sun_up_hours = []
sun_vectors = []
radiance_values = []
altitudes = []
for hour in hoys:
sun = solar_calc.calculate_sun_from_hoy(hour)
if sun.altitude < 0:
continue
sun_vectors.append(sun.sun_vector)
sun_up_hours.append(hour)
altitudes.append(sun.altitude)
# calculate irradiance value
if wea:
# this is a climate_based sunpath. Get the values from wea
assert isinstance(wea, Wea), 'Expected Wea not %s' % type(wea)
for altitude, hoy in zip(altitudes, sun_up_hours):
dnr, dhr = wea.get_irradiance_value_for_hoy(hoy)
if dnr == 0:
radiance_value = 0
else:
radiance_value = gendaylit(
altitude, hoy, dnr, dhr, output_type, leap_year)
radiance_values.append(int(radiance_value))
else:
radiance_values = [1e6] * len(sun_up_hours)
if reverse_vectors:
sun_vectors = [v.reverse() for v in sun_vectors]
return sun_vectors, sun_up_hours, radiance_values
[docs]
def to_file(self, folder='.', file_name='sunpath', hoys=None, wea=None,
output_type=0, leap_year=False, reverse_vectors=False,
split_mod_files=True):
r"""Write sunpath to file.
This method will generate a sunpath file and one or several files for sun
modifiers.
In sunpath file each sun is defined as a radiance source in a separate line. The
naming is based on the minute of the year.
void light {sol_moy} 0 0 3 {irr} {irr} {irr} {sol_moy} source {sun_moy} 0 0 4 x y z 0.533
This method also generate a mod file which includes all the modifiers in sunpath.
mod file is usually used with rcontrib command to indicate the list of modifiers.
Since rcontrib command has a hard limit of 10,000 modifiers in a single run you
can use split_mod_files to split the modifier files not to exceed 10,000
modifiers.
Args:
folder: Target folder to write the sunpath files (default: '.')
file_name: Optional file name for generated files. By default files will be
named as sunpath.rad and sunpath.mod.
hoys: An optional list of hoys to be included in sunpath. By default sunpath
includes all the sun up hours during the year.
wea: A Ladybug wea. If wea is provided a climate-based sunpath will be
generated otherwise all suns will be assigned the same value of 1e6.
output_type: An integer between 0-2. 0=output in W/m^2/sr visible,
1=output in W/m^2/sr solar, 2=output in candela/m^2 (default: 0).
leap_year: Set to True if hoys are for a leap year (default: False).
reverse_vector: Set to True to reverse the vector direction of suns. By
default sun vectors are coming from sun towards the ground. This option
will reverse the direction of the vectors. Reversed sunpath is mainly
useful for radiation studies (default: False).
split_mod_files: A boolean to split the modifier file into multiple files to
ensure none of them includes more than 10,000 modifiers.
Returns:
dict -- A dictionary with with two keys for sunpath and suns. sunpath returns
the path to the sunpath file and suns returns a list of path to modifier
files.
"""
sun_vectors, sun_up_hours, radiance_values = \
self._solar_calc(hoys, wea, output_type, leap_year, reverse_vectors)
if not os.path.isdir(folder):
os.makedirs(folder)
file_name = file_name or 'sunpath'
# write them to files
fp = os.path.join(folder, file_name + '.rad')
if not wea:
if output_type != 0:
warnings.warn(
'Output type will not affect a non climate-base sunpath.'
' To create a climate-based sunpath you must provide the weather'
' data.'
)
suns = []
with open(fp, writemode) as outf:
for vector, hoy, irr in zip(sun_vectors, sun_up_hours, radiance_values):
# use minute of the year to name sun positions
moy = int(round(hoy * 60))
mat = Light('sol_%06d' % moy, irr, irr, irr)
sun = Source('sun_%06d' % moy, vector, 0.533, mat)
outf.write(sun.to_radiance(True).replace('\n', ' ') + '\n')
suns.append('sol_%06d' % moy)
file_count = int(len(suns) / 10000) + 1 if split_mod_files else 1
if file_count != 1:
length = int(round(len(suns) / file_count))
sun_files = [
os.path.join(folder, '%s_%d.mod' % (file_name, count))
for count in range(file_count)
]
else:
length = len(suns)
sun_files = [os.path.join(folder, '%s.mod' % file_name)]
open_files = [open(sfp, 'w') for sfp in sun_files]
try:
for count, sun in enumerate(suns):
file_index = min(int(count / length), file_count - 1)
open_files[file_index].write(sun + '\n')
except Exception as e:
raise ValueError(e)
finally:
for f in open_files:
f.close()
return {'sunpath': fp, 'suns': sun_files}
[docs]
def to_dict(self):
"""Convert this sunpath to a dictionary.
Args:
input_dict: A python dictionary in the following format
.. code-block:: python
{
'type': 'Sunpath',
'location': {} # Location dictionary,
'north': 0,
}
"""
return {
'type': 'Sunpath',
'location': self.location.to_dict(),
'north': self.north
}
[docs]
@classmethod
def from_dict(cls, data):
"""Create a sunpath from a dictionary.
Dictionary keys are type, location and north.
"""
assert 'type' in data, 'type key is missing.'
assert data['type'] == 'Sunpath', 'Expected type Sunpath not %s' % data['type']
assert 'location' in data, 'location key is missing.'
location = Location.from_dict(data['location'])
north = dict.get('north')
return cls(location, north)
[docs]
def ToString(self):
"""Overwrite .NET ToString method."""
return self.__repr__()
def __repr__(self):
"""Sunpath representation."""
return "Sunpath: %s" % self.location.city