"""Solar analemma."""
from ._skyBase import RadianceSky
from ..material.light import Light
from ..geometry.source import Source
from ladybug.epw import EPW
from ladybug.sunpath import Sunpath
import os
try:
from itertools import izip as zip
writemode = 'wb'
except ImportError:
# python 3
writemode = 'w'
[docs]class Analemma(RadianceSky):
"""Generate a radiance-based analemma.
Use Analemma for solar access/sunlight hours studies. For annual daylight/radiation
studies see AnalemmaReversed.
Analemma consists of two files:
1. *.ann file which includes sun geometries and materials.
2. *.mod file includes list of modifiers that are included in *.ann file.
"""
def __init__(self, sun_vectors, sun_up_hours):
"""Radiance-based analemma.
Args:
sun_vectors: A list of sun vectors as (x, y, z).
sun_up_hours: List of hours of the year that corresponds to sun_vectors.
"""
RadianceSky.__init__(self)
vectors = sun_vectors or []
# reverse sun vectors
self._sun_vectors = tuple(tuple(v) for v in vectors)
self._sun_up_hours = sun_up_hours
assert len(sun_up_hours) == len(vectors), \
ValueError(
'Length of vectors [%d] does not match the length of hours [%d]' %
(len(vectors), len(sun_up_hours))
)
[docs] @classmethod
def from_json(cls, inp):
"""Create an analemma from a dictionary."""
return cls(inp['sun_vectors'], inp['sun_up_hours'])
[docs] @classmethod
def from_location(cls, location, hoys=None, north=0, is_leap_year=False):
"""Generate a radiance-based analemma for a location.
Args:
location: A ladybug location.
hoys: A list of hours of the year (default: range(8760)).
north: North angle from Y direction (default: 0).
is_leap_year: A boolean to indicate if hours are for a leap year
(default: False).
"""
sun_vectors = []
sun_up_hours = []
hoys = hoys or range(8760)
north = north or 0
sp = Sunpath.from_location(location, north)
sp.is_leap_year = is_leap_year
for hour in hoys:
sun = sp.calculate_sun_from_hoy(hour)
if sun.altitude < 0:
continue
sun_vectors.append(sun.sun_vector)
sun_up_hours.append(hour)
return cls(sun_vectors, sun_up_hours)
[docs] @classmethod
def from_location_sun_up_hours(cls, location, sun_up_hours, north=0,
is_leap_year=False):
"""Generate a radiance-based analemma for a location.
Args:
location: A ladybug location.
sun_up_hours: A list of hours of the year to be included in analemma.
north: North angle from Y direction (default: 0).
is_leap_year: A boolean to indicate if hours are for a leap year
(default: False).
"""
sun_vectors = []
north = north or 0
sp = Sunpath.from_location(location, north)
sp.is_leap_year = is_leap_year
for hour in sun_up_hours:
sun = sp.calculate_sun_from_hoy(hour)
sun_vectors.append(sun.sun_vector)
return cls(sun_vectors, sun_up_hours)
[docs] @classmethod
def from_wea(cls, wea, hoys=None, north=0, is_leap_year=False):
"""Generate a radiance-based analemma from a ladybug wea.
NOTE: Only the location from wea will be used for creating analemma. For
climate-based sun materix see SunMatrix class.
Args:
wea: A ladybug Wea.
sun_up_hours: A list of hours of the year to be included in analemma.
north: North angle from Y direction (default: 0).
is_leap_year: A boolean to indicate if hours are for a leap year
(default: False).
"""
return cls.from_location(wea.location, hoys, north, is_leap_year)
[docs] @classmethod
def from_wea_sun_up_hours(cls, wea, sun_up_hours, north=0, is_leap_year=False):
"""Generate a radiance-based analemma from a ladybug wea.
NOTE: Only the location from wea will be used for creating analemma. For
climate-based sun materix see SunMatrix class.
Args:
wea: A ladybug Wea.
sun_up_hours: A list of hours of the year to be included in analemma.
north: North angle from Y direction (default: 0).
is_leap_year: A boolean to indicate if hours are for a leap year
(default: False).
"""
return cls.from_location_sun_up_hours(wea.location, sun_up_hours, north,
is_leap_year)
[docs] @classmethod
def from_epw_file(cls, epw_file, hoys=None, north=0, is_leap_year=False):
"""Create sun matrix from an epw file.
NOTE: Only the location from epw file will be used for creating analemma. For
climate-based sun materix see SunMatrix class.
Args:
epw_file: Full path to an epw file.
hoys: A list of hours of the year (default: range(8760)).
north: North angle from Y direction (default: 0).
is_leap_year: A boolean to indicate if hours are for a leap year
(default: False).
"""
return cls.from_location(EPW(epw_file).location, hoys, north, is_leap_year)
[docs] @classmethod
def from_epw_file_sun_up_hours(cls, epw_file, sun_up_hours, north=0,
is_leap_year=False):
"""Create sun matrix from an epw file.
NOTE: Only the location from epw file will be used for creating analemma. For
climate-based sun materix see SunMatrix class.
Args:
epw_file: Full path to an epw file.
sun_up_hours: A list of hours of the year to be included in analemma.
north: North angle from Y direction (default: 0).
is_leap_year: A boolean to indicate if hours are for a leap year
(default: False).
"""
return cls.from_location_sun_up_hours(EPW(epw_file).location, sun_up_hours,
north, is_leap_year)
@property
def isAnalemma(self):
"""Return True."""
return True
@property
def is_climate_based(self):
"""Return True if generated based on values from weather file."""
return False
@property
def analemma_file(self):
"""Analemma file name.
Use this file to create the octree.
"""
return 'analemma.rad'
@property
def sunlist_file(self):
"""Sun list file name.
Use this file as the list of modifiers in rcontrib.
"""
return 'analemma.mod'
@property
def sun_vectors(self):
"""Return list of sun vectors."""
return self._sun_vectors
@property
def sun_up_hours(self):
"""Return list of hours for sun vectors."""
return self._sun_up_hours
[docs] def execute(self, working_dir):
fp = os.path.join(working_dir, self.analemma_file) # analemma file (geo and mat)
sfp = os.path.join(working_dir, self.sunlist_file) # modifier list
with open(fp, writemode) as outf, open(sfp, writemode) as outm:
for hoy, vector in zip(self.sun_up_hours, self.sun_vectors):
# use minute of the year to name sun positions
moy = int(round(hoy * 60))
mat = Light('sol_%06d' % moy, 1e6, 1e6, 1e6)
sun = Source('sun_%06d' % moy, vector, 0.533, mat)
outf.write(sun.to_rad_string(True).replace('\n', ' ') + '\n')
outm.write('sol_%06d\n' % moy)
[docs] def duplicate(self):
"""Duplicate this class."""
return Analemma(self.sun_vectors, self.sun_up_hours)
[docs] def to_rad_string(self):
"""Get the radiance command line as a string."""
raise AttributeError(
'analemma does not have a single line command. Try execute method.'
)
[docs] def to_json(self):
"""Convert analemma to a dictionary."""
return {'sun_vectors': self.sun_vectors, 'sun_up_hours': self.sun_up_hours}
[docs] def ToString(self):
"""Overwrite .NET ToString method."""
return self.__repr__()
def __repr__(self):
"""Analemma representation."""
return 'Analemma: #%d' % len(self.sun_vectors)
[docs]class AnalemmaReversed(Analemma):
"""Generate a radiance-based analemma.
Reversed Analemma reverses direction of input sun vectors. Use reversed Analemma for
radiation and daylight studies.
Analemma consists of two files:
1. *_reversed.ann file which includes sun geometries and materials.
2. *.mod file includes list of modifiers that are included in
*_reversed.ann file.
"""
@property
def analemma_file(self):
"""Analemma file name.
Use this file to create the octree.
"""
return 'analemma_reversed.rad'
[docs] def execute(self, working_dir):
fp = os.path.join(working_dir, self.analemma_file) # analemma file (geo and mat)
sfp = os.path.join(working_dir, self.sunlist_file) # modifier list
with open(fp, writemode) as outf, open(sfp, writemode) as outm:
for hoy, vector in zip(self.sun_up_hours, self.sun_vectors):
# use minute of the year to name sun positions
moy = int(round(hoy * 60))
# reverse sun vector
r_vector = tuple(-1 * i for i in vector)
mat = Light('sol_%06d' % moy, 1e6, 1e6, 1e6)
sun = Source('sun_%06d' % moy, r_vector, 0.533, mat)
outf.write(sun.to_rad_string(True).replace('\n', ' ') + '\n')
outm.write('sol_%06d\n' % moy)