ladybugdynamo.sunpath module
from ladybug.sunpath import *
import geometryoperations as go
# import Dynamo libraries
try:
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
except ImportError:
print "Failed to import Dynamo libraries. Make sure path is added to sys.path"
class Sunpath(LBSunpath):
"""
Calculates sun path for Dynamo
Attributes:
latitude: The latitude of the location. Values must be between -90 and 90. Default is set to the equator.
northAngle: Angle to north (0-360). 90 is west and 270 is east (Default: 0)
longitude: The longitude of the location (Default: 0)
timeZone: A number representing the time zone of the location you are constructing. This can improve the accuracy of the resulting sun plot. The time zone should follow the epw convention and should be between -12 and +12, where 0 is at Greenwich, UK, positive values are to the East of Greenwich and negative values are to the West.
daylightSavingPeriod: An analysis period for daylight saving. (Default = None)
basePoint: A DS point that will be used as the center of sunpath. Default is Origin
scale: A number larger than 0 for scale of sunpath
sunScale: A number larger than 0 for scale of sun spheres
Usage:
import ladybugdynamo.epw as epw
import ladybugdynamo.sunpath as sunpath
# initiate sunpath based on location
epwWeatherFile = r"c:\yourEpwFile.epw"
location = epw.EPW(epwWeatherFile).location
sp = sunpath.Sunpath.fromLocation(location)
hourlyCurves = sp.draw()
"""
def __init__(self, latitude = 0, northAngle = 0, longitude = 0, timeZone = 0,
daylightSavingPeriod = None, basePoint = None, scale = 1, sunScale = 1):
# NOTE
# The use of self.__class__ instead of name of subclass can be tricky in case of creating
# a new subclass from sunpath unless it provides it's own init but it was the only way that I could
# get it to work for now.
# Read more here: http://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods
super(self.__class__, self).__init__(latitude, northAngle, longitude, timeZone, daylightSavingPeriod)
if not basePoint: basePoint = Point.Origin()
self.basePoint = Point.ByCoordinates(basePoint.X, basePoint.Y, basePoint.Z)
self.scale = float(scale) #scale of the whole sunpath
self.sunScale = float(sunScale) * self.scale #scale for sun s
self.__radius = 50
self.__suns = [] # placeholder for sun(s)
self.__dailyCurves = []
self.__analemmaCurves = []
self.__baseCurves = []
@classmethod
def fromLocation(cls, location, northAngle = 0, daylightSavingPeriod = None, \
basePoint = None, scale = 1, sunScale = 1):
"""Create sunpath from location data"""
sp = super(Sunpath, cls).fromLocation(location, northAngle, daylightSavingPeriod)
if not basePoint: basePoint = Point.Origin()
sp.basePoint = Point.ByCoordinates(basePoint.X, basePoint.Y, basePoint.Z)
sp.scale = float(scale) #scale of the whole sunpath
sp.sunScale = float(sunScale) * sp.scale #scale for sun s
return sp
@property
def geometries(self):
return {
'dailyCurves': self.__dailyCurves,
'analemmaCurves': self.__analemmaCurves,
'baseCurves': self.__baseCurves,
'suns': self.sunGeometries
}
@property
def suns(self):
"""Get list of suns"""
return self.__suns
@property
def sunGeometries(self):
"""Get list of suns"""
if not len(self.__suns): return self.__suns #emapty list
return [sun.geometry for sun in self.__suns]
def removeSuns(self):
self.__suns = []
return True
"""Remove all suns from the sunpath"""
@property
def dailyCurves(self):
"""Get daily curves as a list"""
return self.__dailyCurves
@property
def analemmaCurves(self):
"""Get daily curves as a list"""
return self.__analemmaCurves
@property
def baseCurves(self):
"""Get daily curves as a list"""
return self.__baseCurves
def drawSun(self, month, day, hour, isSolarTime = False):
"""Draw a sun based on month, day and hour"""
#create a dateTime to check the input
sun = self.calculateSunPosition(month, day, hour, isSolarTime)
if sun.isDuringDay: self.__suns.append(sun)
def drawSunFromDateTime(self, dateTime, isSolarTime = False):
"""Draw a sun based on datetime"""
self.drawSun(dateTime.month, dateTime.day, dateTime.floatHour, isSolarTime)
def drawDailySunpath(self, month, day = 21):
# draw curve for the day
self.calculateDailyCurve(month, day)
# draw baseline circles
self.calculateBaseCurves()
def drawAnnualSunpath(self):
# draw analemma curves
self.calculateAnalemmaCurves()
# draw hourly curves
self.calculateDailyCurves()
# draw baseline circles
self.calculateBaseCurves()
def calculateSunPositionFromDateTime(self, dateTime, isSolarTime = False):
""" Calculate the position of sun based on origin and scale
Returns:
sun: Returns a sun object
"""
return self.calculateSunPosition(dateTime.month, dateTime.day, dateTime.floatHour, isSolarTime)
def calculateSunPosition(self, month = 12, day = 21, hour = 12, isSolarTime = False):
""" Calculate the position of sun based on origin and scale
Returns:
sun: A Ladybug sun object
"""
# calculate ladybug sun for this time
lbSun = self.calculateSun(month, day, hour, isSolarTime)
# create a dynamo sun from ladybug sun
sun = Sun.fromLBSun(lbSun, self.basePoint, self.__radius, self.scale, self.sunScale)
return sun
def calculateDailyCurve(self, month, day = 21, isSolarTime = False):
"""Calculate daily curve the day
After calculating the curves check 'dailyCurves' property for curve geometries
"""
# calculate sunrise, noon and sunset
datetimesDictionary = self.calculateSunriseSunset(month, day = day, depression = 0, isSolarTime = isSolarTime)
datetimes = [
datetimesDictionary['sunrise'], \
datetimesDictionary['noon'], \
datetimesDictionary['sunset']
]
dailySunPositions = []
for datetime in datetimes:
sun = self.calculateSunPositionFromDateTime(datetime, isSolarTime = False)
dailySunPositions.append(sun.position)
dailyCurve = Arc.ByThreePoints(*dailySunPositions)
self.__dailyCurves.append(dailyCurve)
go.disposeGeometries(dailySunPositions)
def calculateDailyCurves(self):
"""Calculate daily curves for an annual sunpath
After calculating the curves check 'dailyCurves' property for curve geometries
"""
# calculate curves for 21st of all the months
for month in range(1,13):
self.calculateDailyCurve(month)
def calculateAnalemmaCurves(self):
"""Calculate analemma curves for an annual sunpath
After calculating the curves check 'dailyCurves' property for curve geometries
"""
day = 21
# point and plane for triming the curves
pt = Point.ByCoordinates(0, 0, -10 + self.basePoint.Z)
plane = Plane.ByOriginNormal(self.basePoint, Vector.ZAxis())
for hour in range(1, 25):
anySunUpHour = False
anySunDownHour = False
monthlySunPositions = []
for month in range(1,13):
sun = self.calculateSunPosition(month, day, hour = hour)
if sun.isDuringDay:
anySunUpHour = True
else:
anySunDownHour = True
monthlySunPositions.append(sun.position)
# all night hour
if not anySunUpHour: continue
# create the curve
analemmaCurve = NurbsCurve.ByPoints(monthlySunPositions, True)
if anySunDownHour + anySunUpHour == 2:
# some of the hours are up hours and some are down
# trim the curve
curves = Geometry.Trim(analemmaCurve, plane, pt)
# Dynamo trim doesn't work as expected
# or I don't know how it is supposed to work so I check the curves
selectedCurves = []
for curve in curves:
# find mid point
midPar = (curve.EndParameter() + curve.StartParameter())/2
midPt = curve.PointAtParameter(midPar)
if midPt.Z >= self.basePoint.Z:
selectedCurves.append(curve)
else:
curve.Dispose()
if len(selectedCurves)==1:
analemmaCurve = selectedCurves[0]
else:
try:
# join curves
analemmaCurve = selectedCurves[0].Join(selectedCurves[1:])
except StandardError:
analemmaCurve = selectedCurves
go.disposeGeometries(monthlySunPositions)
self.__analemmaCurves.append(analemmaCurve)
def calculateBaseCurves(self):
"""Calculate base circles for sunpath"""
self.__drawBaseCircles()
self.__drawDirectionLines()
# TODO: Add north angle
def __drawDirectionLines(self):
"""Draw direction lines"""
# create North Arrow
startVector = Vector.Scale(Vector.YAxis(), self.scale * self.__radius)
startPt = Point.Add(self.basePoint, startVector)
endVector = Vector.Scale(Vector.YAxis(), 1.12 * self.scale * self.__radius)
endPt = Point.Add(self.basePoint, endVector)
northArrow = Line.ByStartPointEndPoint(startPt, endPt)
# draw it for the 4 direction
for angle in range(0, 360, 90):
self.__baseCurves.append(northArrow.Rotate(Plane.XY(), angle + self.northAngle))
# create mid curves
endVector = Vector.Scale(Vector.YAxis(), 1.08 * self.scale * self.__radius)
endPt = Point.Add(self.basePoint, endVector)
shortArrow = Line.ByStartPointEndPoint(startPt, endPt)
# draw it for the 4 direction
for angle in range(0, 360, 30):
if angle%90!=0:
self.__baseCurves.append(shortArrow.Rotate(Plane.XY(), angle + self.northAngle))
def __drawBaseCircles(self):
"""draw base circles"""
innerCircle = Circle.ByCenterPointRadius(self.basePoint, self.scale * self.__radius)
outerCircle = Circle.ByCenterPointRadius(self.basePoint, 1.02 * self.scale * self.__radius)
outerCircle2 = Circle.ByCenterPointRadius(self.basePoint, 1.08 * self.scale * self.__radius)
self.__baseCurves.extend([outerCircle, innerCircle, outerCircle2])
class Sun(LBSun):
"""Convert a ladybug sun to a ladybug dynamo sun
Attributes:
datetime: A Ladybug datetime that represents the datetime for this sunVector
altitude: Solar Altitude in radians
azimuth: Solar Azimuth in radians
isSolarTime: A Boolean that indicates if datetime represents the solar time
isDaylightSaving: A Boolean that indicates if datetime is calculated for Daylight saving period
northAngle: North angle of the sunpath in Degrees. This will be only used to calculate the solar vector.
"""
def __init__(self, datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle,
sunpathBasePoint, sunpathScale, sunpathRadius, sunScale):
#LBSun.__init__(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle)
super(self.__class__, self).__init__(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle)
self.__sunpathBasePoint = sunpathBasePoint
self.__sunpathScale = sunpathScale
self.__sunpathRadius = sunpathRadius
self.__sunRadius = 1
self.scale = sunScale
# calculate vector, position and geometry
self.__calculateGeometricalSun()
self.__hourlyData = [] # Place holder for hourly data I'm not sure how it will work yet
# NOTE: I assume there should be cleaner solution to do this but I couldn't figure it out
# Ladybug sun is called under the hood in ladybug.sunpath that's
@classmethod
def fromLBSun(cls, LBSun, basePoint, scale, radius, sunScale):
return cls(LBSun.datetime, LBSun.altitudeInRadians, LBSun.azimuthInRadians, LBSun.isSolarTime, \
LBSun.isDaylightSaving, LBSun.northAngle, basePoint, scale, radius, sunScale)
def __calculateGeometricalSun(self):
"""calculate sun vector, base point and sphere"""
# convert sun vector to Dynamo Vector
self.__vector = Vector.ByCoordinates(self.sunVector.x, self.sunVector.y, self.sunVector.z)
movingVector = self.__vector.Reverse().Scale(self.__sunpathRadius * self.__sunpathScale)
# calculate position
self.__position = self.__sunpathBasePoint.Add(movingVector)
# create sun sphere
self.__createSunSphere()
def __createSunSphere(self):
"""Create the sun sphere"""
sp = Sphere.ByCenterPointRadius(self.__position, self.scale * self.__sunRadius)
self.__geometry = sp
@property
def vector(self):
"""get sun vector as a Vector"""
return self.__vector
@property
def position(self):
"""get sun position as Point"""
return self.__position
@property
def geometry(self):
"""get sun sphere"""
return self.__geometry
Classes
class Sun
Convert a ladybug sun to a ladybug dynamo sun
Attributes: datetime: A Ladybug datetime that represents the datetime for this sunVector altitude: Solar Altitude in radians azimuth: Solar Azimuth in radians isSolarTime: A Boolean that indicates if datetime represents the solar time isDaylightSaving: A Boolean that indicates if datetime is calculated for Daylight saving period northAngle: North angle of the sunpath in Degrees. This will be only used to calculate the solar vector.
class Sun(LBSun):
"""Convert a ladybug sun to a ladybug dynamo sun
Attributes:
datetime: A Ladybug datetime that represents the datetime for this sunVector
altitude: Solar Altitude in radians
azimuth: Solar Azimuth in radians
isSolarTime: A Boolean that indicates if datetime represents the solar time
isDaylightSaving: A Boolean that indicates if datetime is calculated for Daylight saving period
northAngle: North angle of the sunpath in Degrees. This will be only used to calculate the solar vector.
"""
def __init__(self, datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle,
sunpathBasePoint, sunpathScale, sunpathRadius, sunScale):
#LBSun.__init__(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle)
super(self.__class__, self).__init__(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle)
self.__sunpathBasePoint = sunpathBasePoint
self.__sunpathScale = sunpathScale
self.__sunpathRadius = sunpathRadius
self.__sunRadius = 1
self.scale = sunScale
# calculate vector, position and geometry
self.__calculateGeometricalSun()
self.__hourlyData = [] # Place holder for hourly data I'm not sure how it will work yet
# NOTE: I assume there should be cleaner solution to do this but I couldn't figure it out
# Ladybug sun is called under the hood in ladybug.sunpath that's
@classmethod
def fromLBSun(cls, LBSun, basePoint, scale, radius, sunScale):
return cls(LBSun.datetime, LBSun.altitudeInRadians, LBSun.azimuthInRadians, LBSun.isSolarTime, \
LBSun.isDaylightSaving, LBSun.northAngle, basePoint, scale, radius, sunScale)
def __calculateGeometricalSun(self):
"""calculate sun vector, base point and sphere"""
# convert sun vector to Dynamo Vector
self.__vector = Vector.ByCoordinates(self.sunVector.x, self.sunVector.y, self.sunVector.z)
movingVector = self.__vector.Reverse().Scale(self.__sunpathRadius * self.__sunpathScale)
# calculate position
self.__position = self.__sunpathBasePoint.Add(movingVector)
# create sun sphere
self.__createSunSphere()
def __createSunSphere(self):
"""Create the sun sphere"""
sp = Sphere.ByCenterPointRadius(self.__position, self.scale * self.__sunRadius)
self.__geometry = sp
@property
def vector(self):
"""get sun vector as a Vector"""
return self.__vector
@property
def position(self):
"""get sun position as Point"""
return self.__position
@property
def geometry(self):
"""get sun sphere"""
return self.__geometry
Ancestors (in MRO)
- Sun
- ladybugdynamo.ladybug.sunpath.LBSun
- __builtin__.object
Instance variables
var HOY
Return Hour of the year
var altitude
Return solar altitude in degrees
var altitudeInRadians
Return solar altitude in radians
var azimuth
Return solar azimuth in degrees
var azimuthInRadians
Return solar azimuth in radians
var datetime
Return datetime
var geometry
get sun sphere
var hourlyData
var isDaylightSaving
Return a Boolean that indicates is datetime is solar time
var isDuringDay
var isSolarTime
Return a Boolean that indicates is datetime is solar time
var northAngle
Return north angle for +YAxis
var position
get sun position as Point
var scale
var sunVector
Return sun vector for this sun Sun vector will face
var vector
get sun vector as a Vector
Methods
def __init__(
self, datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle, sunpathBasePoint, sunpathScale, sunpathRadius, sunScale)
def __init__(self, datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle,
sunpathBasePoint, sunpathScale, sunpathRadius, sunScale):
#LBSun.__init__(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle)
super(self.__class__, self).__init__(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, northAngle)
self.__sunpathBasePoint = sunpathBasePoint
self.__sunpathScale = sunpathScale
self.__sunpathRadius = sunpathRadius
self.__sunRadius = 1
self.scale = sunScale
# calculate vector, position and geometry
self.__calculateGeometricalSun()
self.__hourlyData = [] # Place holder for hourly data I'm not sure how it will work yet
def appendHourlyData(
self, data)
Append Ladybug hourly data to this sun
def appendHourlyData(self, data):
"""Append Ladybug hourly data to this sun"""
assert data.datetime.HOY == self.HOY
self.__hourlyData.append(data)
return True
def fromLBSun(
cls, LBSun, basePoint, scale, radius, sunScale)
@classmethod
def fromLBSun(cls, LBSun, basePoint, scale, radius, sunScale):
return cls(LBSun.datetime, LBSun.altitudeInRadians, LBSun.azimuthInRadians, LBSun.isSolarTime, \
LBSun.isDaylightSaving, LBSun.northAngle, basePoint, scale, radius, sunScale)
class Sunpath
Calculates sun path for Dynamo
Attributes: latitude: The latitude of the location. Values must be between -90 and 90. Default is set to the equator. northAngle: Angle to north (0-360). 90 is west and 270 is east (Default: 0) longitude: The longitude of the location (Default: 0) timeZone: A number representing the time zone of the location you are constructing. This can improve the accuracy of the resulting sun plot. The time zone should follow the epw convention and should be between -12 and +12, where 0 is at Greenwich, UK, positive values are to the East of Greenwich and negative values are to the West. daylightSavingPeriod: An analysis period for daylight saving. (Default = None) basePoint: A DS point that will be used as the center of sunpath. Default is Origin scale: A number larger than 0 for scale of sunpath sunScale: A number larger than 0 for scale of sun spheres
Usage: import ladybugdynamo.epw as epw import ladybugdynamo.sunpath as sunpath
# initiate sunpath based on location
epwWeatherFile = r"c:\yourEpwFile.epw"
location = epw.EPW(epwWeatherFile).location
sp = sunpath.Sunpath.fromLocation(location)
hourlyCurves = sp.draw()
class Sunpath(LBSunpath):
"""
Calculates sun path for Dynamo
Attributes:
latitude: The latitude of the location. Values must be between -90 and 90. Default is set to the equator.
northAngle: Angle to north (0-360). 90 is west and 270 is east (Default: 0)
longitude: The longitude of the location (Default: 0)
timeZone: A number representing the time zone of the location you are constructing. This can improve the accuracy of the resulting sun plot. The time zone should follow the epw convention and should be between -12 and +12, where 0 is at Greenwich, UK, positive values are to the East of Greenwich and negative values are to the West.
daylightSavingPeriod: An analysis period for daylight saving. (Default = None)
basePoint: A DS point that will be used as the center of sunpath. Default is Origin
scale: A number larger than 0 for scale of sunpath
sunScale: A number larger than 0 for scale of sun spheres
Usage:
import ladybugdynamo.epw as epw
import ladybugdynamo.sunpath as sunpath
# initiate sunpath based on location
epwWeatherFile = r"c:\yourEpwFile.epw"
location = epw.EPW(epwWeatherFile).location
sp = sunpath.Sunpath.fromLocation(location)
hourlyCurves = sp.draw()
"""
def __init__(self, latitude = 0, northAngle = 0, longitude = 0, timeZone = 0,
daylightSavingPeriod = None, basePoint = None, scale = 1, sunScale = 1):
# NOTE
# The use of self.__class__ instead of name of subclass can be tricky in case of creating
# a new subclass from sunpath unless it provides it's own init but it was the only way that I could
# get it to work for now.
# Read more here: http://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods
super(self.__class__, self).__init__(latitude, northAngle, longitude, timeZone, daylightSavingPeriod)
if not basePoint: basePoint = Point.Origin()
self.basePoint = Point.ByCoordinates(basePoint.X, basePoint.Y, basePoint.Z)
self.scale = float(scale) #scale of the whole sunpath
self.sunScale = float(sunScale) * self.scale #scale for sun s
self.__radius = 50
self.__suns = [] # placeholder for sun(s)
self.__dailyCurves = []
self.__analemmaCurves = []
self.__baseCurves = []
@classmethod
def fromLocation(cls, location, northAngle = 0, daylightSavingPeriod = None, \
basePoint = None, scale = 1, sunScale = 1):
"""Create sunpath from location data"""
sp = super(Sunpath, cls).fromLocation(location, northAngle, daylightSavingPeriod)
if not basePoint: basePoint = Point.Origin()
sp.basePoint = Point.ByCoordinates(basePoint.X, basePoint.Y, basePoint.Z)
sp.scale = float(scale) #scale of the whole sunpath
sp.sunScale = float(sunScale) * sp.scale #scale for sun s
return sp
@property
def geometries(self):
return {
'dailyCurves': self.__dailyCurves,
'analemmaCurves': self.__analemmaCurves,
'baseCurves': self.__baseCurves,
'suns': self.sunGeometries
}
@property
def suns(self):
"""Get list of suns"""
return self.__suns
@property
def sunGeometries(self):
"""Get list of suns"""
if not len(self.__suns): return self.__suns #emapty list
return [sun.geometry for sun in self.__suns]
def removeSuns(self):
self.__suns = []
return True
"""Remove all suns from the sunpath"""
@property
def dailyCurves(self):
"""Get daily curves as a list"""
return self.__dailyCurves
@property
def analemmaCurves(self):
"""Get daily curves as a list"""
return self.__analemmaCurves
@property
def baseCurves(self):
"""Get daily curves as a list"""
return self.__baseCurves
def drawSun(self, month, day, hour, isSolarTime = False):
"""Draw a sun based on month, day and hour"""
#create a dateTime to check the input
sun = self.calculateSunPosition(month, day, hour, isSolarTime)
if sun.isDuringDay: self.__suns.append(sun)
def drawSunFromDateTime(self, dateTime, isSolarTime = False):
"""Draw a sun based on datetime"""
self.drawSun(dateTime.month, dateTime.day, dateTime.floatHour, isSolarTime)
def drawDailySunpath(self, month, day = 21):
# draw curve for the day
self.calculateDailyCurve(month, day)
# draw baseline circles
self.calculateBaseCurves()
def drawAnnualSunpath(self):
# draw analemma curves
self.calculateAnalemmaCurves()
# draw hourly curves
self.calculateDailyCurves()
# draw baseline circles
self.calculateBaseCurves()
def calculateSunPositionFromDateTime(self, dateTime, isSolarTime = False):
""" Calculate the position of sun based on origin and scale
Returns:
sun: Returns a sun object
"""
return self.calculateSunPosition(dateTime.month, dateTime.day, dateTime.floatHour, isSolarTime)
def calculateSunPosition(self, month = 12, day = 21, hour = 12, isSolarTime = False):
""" Calculate the position of sun based on origin and scale
Returns:
sun: A Ladybug sun object
"""
# calculate ladybug sun for this time
lbSun = self.calculateSun(month, day, hour, isSolarTime)
# create a dynamo sun from ladybug sun
sun = Sun.fromLBSun(lbSun, self.basePoint, self.__radius, self.scale, self.sunScale)
return sun
def calculateDailyCurve(self, month, day = 21, isSolarTime = False):
"""Calculate daily curve the day
After calculating the curves check 'dailyCurves' property for curve geometries
"""
# calculate sunrise, noon and sunset
datetimesDictionary = self.calculateSunriseSunset(month, day = day, depression = 0, isSolarTime = isSolarTime)
datetimes = [
datetimesDictionary['sunrise'], \
datetimesDictionary['noon'], \
datetimesDictionary['sunset']
]
dailySunPositions = []
for datetime in datetimes:
sun = self.calculateSunPositionFromDateTime(datetime, isSolarTime = False)
dailySunPositions.append(sun.position)
dailyCurve = Arc.ByThreePoints(*dailySunPositions)
self.__dailyCurves.append(dailyCurve)
go.disposeGeometries(dailySunPositions)
def calculateDailyCurves(self):
"""Calculate daily curves for an annual sunpath
After calculating the curves check 'dailyCurves' property for curve geometries
"""
# calculate curves for 21st of all the months
for month in range(1,13):
self.calculateDailyCurve(month)
def calculateAnalemmaCurves(self):
"""Calculate analemma curves for an annual sunpath
After calculating the curves check 'dailyCurves' property for curve geometries
"""
day = 21
# point and plane for triming the curves
pt = Point.ByCoordinates(0, 0, -10 + self.basePoint.Z)
plane = Plane.ByOriginNormal(self.basePoint, Vector.ZAxis())
for hour in range(1, 25):
anySunUpHour = False
anySunDownHour = False
monthlySunPositions = []
for month in range(1,13):
sun = self.calculateSunPosition(month, day, hour = hour)
if sun.isDuringDay:
anySunUpHour = True
else:
anySunDownHour = True
monthlySunPositions.append(sun.position)
# all night hour
if not anySunUpHour: continue
# create the curve
analemmaCurve = NurbsCurve.ByPoints(monthlySunPositions, True)
if anySunDownHour + anySunUpHour == 2:
# some of the hours are up hours and some are down
# trim the curve
curves = Geometry.Trim(analemmaCurve, plane, pt)
# Dynamo trim doesn't work as expected
# or I don't know how it is supposed to work so I check the curves
selectedCurves = []
for curve in curves:
# find mid point
midPar = (curve.EndParameter() + curve.StartParameter())/2
midPt = curve.PointAtParameter(midPar)
if midPt.Z >= self.basePoint.Z:
selectedCurves.append(curve)
else:
curve.Dispose()
if len(selectedCurves)==1:
analemmaCurve = selectedCurves[0]
else:
try:
# join curves
analemmaCurve = selectedCurves[0].Join(selectedCurves[1:])
except StandardError:
analemmaCurve = selectedCurves
go.disposeGeometries(monthlySunPositions)
self.__analemmaCurves.append(analemmaCurve)
def calculateBaseCurves(self):
"""Calculate base circles for sunpath"""
self.__drawBaseCircles()
self.__drawDirectionLines()
# TODO: Add north angle
def __drawDirectionLines(self):
"""Draw direction lines"""
# create North Arrow
startVector = Vector.Scale(Vector.YAxis(), self.scale * self.__radius)
startPt = Point.Add(self.basePoint, startVector)
endVector = Vector.Scale(Vector.YAxis(), 1.12 * self.scale * self.__radius)
endPt = Point.Add(self.basePoint, endVector)
northArrow = Line.ByStartPointEndPoint(startPt, endPt)
# draw it for the 4 direction
for angle in range(0, 360, 90):
self.__baseCurves.append(northArrow.Rotate(Plane.XY(), angle + self.northAngle))
# create mid curves
endVector = Vector.Scale(Vector.YAxis(), 1.08 * self.scale * self.__radius)
endPt = Point.Add(self.basePoint, endVector)
shortArrow = Line.ByStartPointEndPoint(startPt, endPt)
# draw it for the 4 direction
for angle in range(0, 360, 30):
if angle%90!=0:
self.__baseCurves.append(shortArrow.Rotate(Plane.XY(), angle + self.northAngle))
def __drawBaseCircles(self):
"""draw base circles"""
innerCircle = Circle.ByCenterPointRadius(self.basePoint, self.scale * self.__radius)
outerCircle = Circle.ByCenterPointRadius(self.basePoint, 1.02 * self.scale * self.__radius)
outerCircle2 = Circle.ByCenterPointRadius(self.basePoint, 1.08 * self.scale * self.__radius)
self.__baseCurves.extend([outerCircle, innerCircle, outerCircle2])
Ancestors (in MRO)
- Sunpath
- ladybugdynamo.ladybug.sunpath.LBSunpath
- __builtin__.object
Instance variables
var analemmaCurves
Get daily curves as a list
var baseCurves
Get daily curves as a list
var basePoint
var dailyCurves
Get daily curves as a list
var geometries
var latitude
get latitude in degrees
var longitude
get latitude in degrees
var scale
var sunGeometries
Get list of suns
var sunScale
var suns
Get list of suns
Methods
def __init__(
self, latitude=0, northAngle=0, longitude=0, timeZone=0, daylightSavingPeriod=None, basePoint=None, scale=1, sunScale=1)
def __init__(self, latitude = 0, northAngle = 0, longitude = 0, timeZone = 0,
daylightSavingPeriod = None, basePoint = None, scale = 1, sunScale = 1):
# NOTE
# The use of self.__class__ instead of name of subclass can be tricky in case of creating
# a new subclass from sunpath unless it provides it's own init but it was the only way that I could
# get it to work for now.
# Read more here: http://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods
super(self.__class__, self).__init__(latitude, northAngle, longitude, timeZone, daylightSavingPeriod)
if not basePoint: basePoint = Point.Origin()
self.basePoint = Point.ByCoordinates(basePoint.X, basePoint.Y, basePoint.Z)
self.scale = float(scale) #scale of the whole sunpath
self.sunScale = float(sunScale) * self.scale #scale for sun s
self.__radius = 50
self.__suns = [] # placeholder for sun(s)
self.__dailyCurves = []
self.__analemmaCurves = []
self.__baseCurves = []
def calculateAnalemmaCurves(
self)
Calculate analemma curves for an annual sunpath After calculating the curves check 'dailyCurves' property for curve geometries
def calculateAnalemmaCurves(self):
"""Calculate analemma curves for an annual sunpath
After calculating the curves check 'dailyCurves' property for curve geometries
"""
day = 21
# point and plane for triming the curves
pt = Point.ByCoordinates(0, 0, -10 + self.basePoint.Z)
plane = Plane.ByOriginNormal(self.basePoint, Vector.ZAxis())
for hour in range(1, 25):
anySunUpHour = False
anySunDownHour = False
monthlySunPositions = []
for month in range(1,13):
sun = self.calculateSunPosition(month, day, hour = hour)
if sun.isDuringDay:
anySunUpHour = True
else:
anySunDownHour = True
monthlySunPositions.append(sun.position)
# all night hour
if not anySunUpHour: continue
# create the curve
analemmaCurve = NurbsCurve.ByPoints(monthlySunPositions, True)
if anySunDownHour + anySunUpHour == 2:
# some of the hours are up hours and some are down
# trim the curve
curves = Geometry.Trim(analemmaCurve, plane, pt)
# Dynamo trim doesn't work as expected
# or I don't know how it is supposed to work so I check the curves
selectedCurves = []
for curve in curves:
# find mid point
midPar = (curve.EndParameter() + curve.StartParameter())/2
midPt = curve.PointAtParameter(midPar)
if midPt.Z >= self.basePoint.Z:
selectedCurves.append(curve)
else:
curve.Dispose()
if len(selectedCurves)==1:
analemmaCurve = selectedCurves[0]
else:
try:
# join curves
analemmaCurve = selectedCurves[0].Join(selectedCurves[1:])
except StandardError:
analemmaCurve = selectedCurves
go.disposeGeometries(monthlySunPositions)
self.__analemmaCurves.append(analemmaCurve)
def calculateBaseCurves(
self)
Calculate base circles for sunpath
def calculateBaseCurves(self):
"""Calculate base circles for sunpath"""
self.__drawBaseCircles()
self.__drawDirectionLines()
def calculateDailyCurve(
self, month, day=21, isSolarTime=False)
Calculate daily curve the day After calculating the curves check 'dailyCurves' property for curve geometries
def calculateDailyCurve(self, month, day = 21, isSolarTime = False):
"""Calculate daily curve the day
After calculating the curves check 'dailyCurves' property for curve geometries
"""
# calculate sunrise, noon and sunset
datetimesDictionary = self.calculateSunriseSunset(month, day = day, depression = 0, isSolarTime = isSolarTime)
datetimes = [
datetimesDictionary['sunrise'], \
datetimesDictionary['noon'], \
datetimesDictionary['sunset']
]
dailySunPositions = []
for datetime in datetimes:
sun = self.calculateSunPositionFromDateTime(datetime, isSolarTime = False)
dailySunPositions.append(sun.position)
dailyCurve = Arc.ByThreePoints(*dailySunPositions)
self.__dailyCurves.append(dailyCurve)
go.disposeGeometries(dailySunPositions)
def calculateDailyCurves(
self)
Calculate daily curves for an annual sunpath After calculating the curves check 'dailyCurves' property for curve geometries
def calculateDailyCurves(self):
"""Calculate daily curves for an annual sunpath
After calculating the curves check 'dailyCurves' property for curve geometries
"""
# calculate curves for 21st of all the months
for month in range(1,13):
self.calculateDailyCurve(month)
def calculateSun(
self, month, day, hour, isSolarTime=False)
Get Sun data for an hour of the year
Args: month: An integer between 1-12 day: An integer between 1-31 hour: A positive number <= 24 isSolarTime: A boolean to indicate if the input hour is solar time. (Default: False)
Returns: A sun object for this particular time
def calculateSun(self, month, day, hour, isSolarTime = False):
"""Get Sun data for an hour of the year
Args:
month: An integer between 1-12
day: An integer between 1-31
hour: A positive number <= 24
isSolarTime: A boolean to indicate if the input hour is solar time. (Default: False)
Returns:
A sun object for this particular time
"""
datetime = core.LBDateTime(month, day, hour)
return self.calculateSunFromDataTime(datetime, isSolarTime)
def calculateSunFromDataTime(
self, datetime, isSolarTime=False)
Get Sun data for an hour of the year This code is originally written by Trygve Wastvedt (Trygve.Wastvedt@gmail.com) based on (NOAA) and modified by Chris Mackey and Mostapha Roudsari
Args: datetime: Ladybug datetime isSolarTime: A boolean to indicate if the input hour is solar time. (Default: False)
Returns: A sun object for this particular time
def calculateSunFromDataTime(self, datetime, isSolarTime = False):
"""Get Sun data for an hour of the year
This code is originally written by Trygve Wastvedt (Trygve.Wastvedt@gmail.com)
based on (NOAA) and modified by Chris Mackey and Mostapha Roudsari
Args:
datetime: Ladybug datetime
isSolarTime: A boolean to indicate if the input hour is solar time. (Default: False)
Returns:
A sun object for this particular time
"""
solDec, eqOfTime = self.__calculateSolarGeometry(datetime)
month, day, hour = datetime.month, datetime.day, datetime.floatHour
isDaylightSaving = self.isDaylightSavingHour(datetime.HOY)
if isDaylightSaving: hour += 1
#hours
solTime = self.__calculateSolarTime(hour, eqOfTime, isSolarTime)
#degrees
hourAngle = (solTime*15 + 180) if (solTime*15 < 0) else (solTime*15 - 180)
#RADIANS
zenith = math.acos(math.sin(self.__latitude)*math.sin(solDec) \
+ math.cos(self.__latitude)*math.cos(solDec)*math.cos(math.radians(hourAngle)))
altitude = (math.pi/2) - zenith
if hourAngle == 0.0 or hourAngle == -180.0 or hourAngle == 180.0:
if solDec < self.__latitude: azimuth = math.pi
else: azimuth = 0.0
else:
azimuth = ((math.acos(((math.sin(self.__latitude)*math.cos(zenith)) \
- math.sin(solDec))/(math.cos(self.__latitude)*math.sin(zenith))) + math.pi) % (2*math.pi)) \
if (hourAngle > 0) else \
((3*math.pi - math.acos(((math.sin(self.__latitude)*math.cos(zenith)) \
- math.sin(solDec))/(math.cos(self.__latitude)*math.sin(zenith)))) % (2*math.pi))
# create the sun for this hour
return LBSun(datetime, altitude, azimuth, isSolarTime, isDaylightSaving, self.northAngle)
def calculateSunFromHOY(
self, HOY, isSolarTime=False)
Get Sun data for an hour of the year
Args: datetime: Ladybug datetime isSolarTime: A boolean to indicate if the input hour is solar time. (Default: False)
Returns: A sun object for this particular time
def calculateSunFromHOY(self, HOY, isSolarTime = False):
"""Get Sun data for an hour of the year
Args:
datetime: Ladybug datetime
isSolarTime: A boolean to indicate if the input hour is solar time. (Default: False)
Returns:
A sun object for this particular time
"""
datetime = core.LBDateTime.fromHOY(HOY)
return self.calculateSunFromDataTime(datetime, isSolarTime)
def calculateSunPosition(
self, month=12, day=21, hour=12, isSolarTime=False)
Calculate the position of sun based on origin and scale
Returns: sun: A Ladybug sun object
def calculateSunPosition(self, month = 12, day = 21, hour = 12, isSolarTime = False):
""" Calculate the position of sun based on origin and scale
Returns:
sun: A Ladybug sun object
"""
# calculate ladybug sun for this time
lbSun = self.calculateSun(month, day, hour, isSolarTime)
# create a dynamo sun from ladybug sun
sun = Sun.fromLBSun(lbSun, self.basePoint, self.__radius, self.scale, self.sunScale)
return sun
def calculateSunPositionFromDateTime(
self, dateTime, isSolarTime=False)
Calculate the position of sun based on origin and scale
Returns: sun: Returns a sun object
def calculateSunPositionFromDateTime(self, dateTime, isSolarTime = False):
""" Calculate the position of sun based on origin and scale
Returns:
sun: Returns a sun object
"""
return self.calculateSunPosition(dateTime.month, dateTime.day, dateTime.floatHour, isSolarTime)
def calculateSunriseSunset(
self, month, day, depression=0.833, isSolarTime=False)
def calculateSunriseSunset(self, month, day, depression = 0.833, isSolarTime = False):
datetime = core.LBDateTime(month, day, hour = 12)
return self.calculateSunriseSunsetFromDateTime(datetime, depression, isSolarTime)
def calculateSunriseSunsetFromDateTime(
self, datetime, depression=0.833, isSolarTime=False)
Calculate sunrise, sunset and noon for a day of year
def calculateSunriseSunsetFromDateTime(self, datetime, depression = 0.833, isSolarTime = False):
"""Calculate sunrise, sunset and noon for a day of year"""
solDec, eqOfTime = self.__calculateSolarGeometry(datetime)
# calculate sunrise and sunset hour
#if isSolarTime:
# noon = .5
#else:
noon = (720 - 4 * math.degrees(self.__longitude) - eqOfTime + self.timeZone * 60) / 1440
sunRiseHourAngle = self.__calculateSunriseHourAngle(solDec, depression)
sunrise = noon - sunRiseHourAngle * 4 / 1440
sunset = noon + sunRiseHourAngle * 4 / 1440
# convert demical hour to solar hour
# noon = self.__calculateSolarTime(24*noon, eqOfTime, isSolarTime)
# sunrise = self.__calculateSolarTime(24*sunrise, eqOfTime, isSolarTime)
# sunset = self.__calculateSolarTime(24*sunset, eqOfTime, isSolarTime)
return {
"sunrise" : core.LBDateTime(datetime.month, datetime.day, 24 * sunrise),
"noon" : core.LBDateTime(datetime.month, datetime.day, 24 * noon),
"sunset" : core.LBDateTime(datetime.month, datetime.day, 24 * sunset)}
def drawAnnualSunpath(
self)
def drawAnnualSunpath(self):
# draw analemma curves
self.calculateAnalemmaCurves()
# draw hourly curves
self.calculateDailyCurves()
# draw baseline circles
self.calculateBaseCurves()
def drawDailySunpath(
self, month, day=21)
def drawDailySunpath(self, month, day = 21):
# draw curve for the day
self.calculateDailyCurve(month, day)
# draw baseline circles
self.calculateBaseCurves()
def drawSun(
self, month, day, hour, isSolarTime=False)
Draw a sun based on month, day and hour
def drawSun(self, month, day, hour, isSolarTime = False):
"""Draw a sun based on month, day and hour"""
#create a dateTime to check the input
sun = self.calculateSunPosition(month, day, hour, isSolarTime)
if sun.isDuringDay: self.__suns.append(sun)
def drawSunFromDateTime(
self, dateTime, isSolarTime=False)
Draw a sun based on datetime
def drawSunFromDateTime(self, dateTime, isSolarTime = False):
"""Draw a sun based on datetime"""
self.drawSun(dateTime.month, dateTime.day, dateTime.floatHour, isSolarTime)
def fromLocation(
cls, location, northAngle=0, daylightSavingPeriod=None, basePoint=None, scale=1, sunScale=1)
Create sunpath from location data
@classmethod
def fromLocation(cls, location, northAngle = 0, daylightSavingPeriod = None, \
basePoint = None, scale = 1, sunScale = 1):
"""Create sunpath from location data"""
sp = super(Sunpath, cls).fromLocation(location, northAngle, daylightSavingPeriod)
if not basePoint: basePoint = Point.Origin()
sp.basePoint = Point.ByCoordinates(basePoint.X, basePoint.Y, basePoint.Z)
sp.scale = float(scale) #scale of the whole sunpath
sp.sunScale = float(sunScale) * sp.scale #scale for sun s
return sp
def isDaylightSavingHour(
self, datetime)
def isDaylightSavingHour(self, datetime):
if not self.daylightSavingPeriod: return False
return self.daylightSavingPeriod.isTimeIncluded(datetime.HOY)
def removeSuns(
self)
def removeSuns(self):
self.__suns = []
return True
"""Remove all suns from the sunpath"""