@Daniel_Hampson
it is because moon elevation is below 0
but there might be issue with pylunar itself as now (0:27) i can see moon on sky physically, but pylunar is saying elevation -9 degrees (which is below horizon)
at same time astro binding from OH is saying 8,95 degrees which would result of moon being displayed.
so I suspect pylunar is having wrong data.
edit: well no, it’s wrongly implemented in script. Because per documentation pylunar requires UTC time, but in here it uses local time.
Corrected and cleaned whole working code is:
(change LAT + LONG ALT + SHAPE and your time eg. Europe/Berlin )
from __future__ import print_function
import math
from datetime import datetime, timedelta, date, time
import sys
import pytz
import pylunar
from astral import Astral
from astral import Location
WIDTH = 100
HEIGHT = 100
PRIMARY_COLOR = '#1b3024'
LIGHT_COLOR = '#26bf75'
BG_COLOR = '#1a1919'
#BG_COLOR = '#555555'
SUN_COLOR = '#ffff66'
SUN_RADIUS = 5
MOON_COLOR = '#999999'
MOON_RADIUS = 3
STROKE_WIDTH = '1'
FILENAME = '/etc/openhab2/html/shaddow.svg'
LATITUDE = XXX
LONGITUDE = XXX
ALTITUDE = XXX
# Shape of the house in a 100 by 100 units square
SHAPE = [{'x': 16.37, 'y': 62.07}, \
{'x': 28.32, 'y': 40.16}, \
{'x': 29.57, 'y': 39.87}]
HOURS = 1
DEGS = []
class shadow(object):
"""
Shadow Object
"""
def __init__(self):
self.debug = False
self.astr = Astral()
timezone = pytz.timezone('Europe/Berlin') # Enter your time zone
self.l = Location(('HOME', 'YOUR TOWN', LATITUDE, LONGITUDE, 'Europe/Berlin', ALTITUDE))
self.sun = self.l.sun()
self.now = timezone.localize(datetime.now())
self.nowUTC = datetime.utcnow()
self.sun_azimuth = float(self.astr.solar_azimuth(self.now, LATITUDE, LONGITUDE))
print('Sun azimuth: ' + str(self.sun_azimuth))
self.sun_elevation = float(self.astr.solar_elevation(self.now, LATITUDE, LONGITUDE))
print('Sun elevation: ' + str(self.sun_elevation))
self.sunrise_azimuth = float(self.astr.solar_azimuth(self.sun['sunrise'], LATITUDE, LONGITUDE))
self.sunset_azimuth = float(self.astr.solar_azimuth(self.sun['sunset'], LATITUDE, LONGITUDE))
for i in xrange(0, 24, HOURS):
a = float(self.astr.solar_azimuth(timezone.localize(datetime.combine(date.today(), time(i))), LATITUDE, LONGITUDE))
if (a == None): a = 0
DEGS.extend([float(a)])
self.moon_info = pylunar.MoonInfo(self.decdeg2dms(LATITUDE), self.decdeg2dms(LONGITUDE))
self.moon_info.update(self.nowUTC)
self.moon_azimuth = self.moon_info.azimuth()
print('Moon azimuth: ' + str(self.moon_azimuth))
self.moon_elevation = self.moon_info.altitude()
print('Moon elevation: ' + str(self.moon_elevation))
if (self.sun_elevation>0):
self.elevation = self.sun_elevation
else:
self.elevation = self.moon_elevation
#
#
#
def decdeg2dms(self,dd):
negative = dd < 0
dd = abs(dd)
minutes,seconds = divmod(dd*3600,60)
degrees,minutes = divmod(minutes,60)
if negative:
if degrees > 0:
degrees = -degrees
elif minutes > 0:
minutes = -minutes
else:
seconds = -seconds
return (degrees,minutes,seconds)
#
#
#
def generatePath(self,stroke,fill,points,attrs=None):
p = ''
p = p + '<path stroke="' + stroke + '" stroke-width="' + STROKE_WIDTH + '" fill="' + fill + '" '
if (attrs != None): p = p + ' ' + attrs + ' '
p = p + ' d="'
for point in points:
if (points.index(point) == 0):
p = p + 'M' + str(point['x']) + ' ' + str(point['y'])
else:
p = p + ' L' + str(point['x']) + ' ' + str(point['y'])
p = p + '" />'
return p
#
#
#
def generateArc(self,dist,stroke,fill,start,end,attrs=None):
p = ''
try:
angle = end-start
if (angle<0):
angle = 360 + angle
p = p + '<path d="M' + str(self.degreesToPoint(start,dist)['x']) + ' ' + str(self.degreesToPoint(start,dist)['y']) + ' '
p = p + 'A' + str(dist) + ' ' + str(dist) + ' 0 '
if (angle<180):
p = p + '0 1 '
else:
p = p + '1 1 '
p = p + str(self.degreesToPoint(end,dist)['x']) + ' ' + str(self.degreesToPoint(end,dist)['y']) + '"'
p = p + ' stroke="' + stroke + '"'
if (fill != None):
p = p + ' fill="' + fill + '" '
else:
p = p + ' fill="none" '
if (attrs != None):
p = p + ' ' + attrs + ' '
else:
p = p + ' stroke-width="' + STROKE_WIDTH + '"'
p = p + ' />'
except:
p = ''
return p
#
#
#
def degreesToPoint(self,d,r):
coordinates = {'x': 0, 'y': 0}
cx = WIDTH / 2
cy = HEIGHT / 2
d2 = 180 - d
coordinates['x'] = cx + math.sin(math.radians(d2))*r
coordinates['y'] = cy + math.cos(math.radians(d2))*r
return coordinates
#
#
#
def generateSVG(self):
realSun_pos = self.degreesToPoint(self.sun_azimuth, 10000)
if self.debug:
print(realSun_pos)
sun_pos = self.degreesToPoint(self.sun_azimuth, WIDTH / 2)
moon_pos = self.degreesToPoint(self.moon_azimuth, WIDTH / 2)
minPoint = -1
maxPoint = -1
i = 0
minAngle = 999
maxAngle = -999
for point in SHAPE:
#Angle of close light source
angle = -math.degrees(math.atan2(point['y']-sun_pos['y'],point['x']-sun_pos['x']))
#Angle of distant light source (e.g. sun_pos)
angle = -math.degrees(math.atan2(point['y']-realSun_pos['y'],point['x']-realSun_pos['x']))
distance = math.sqrt(math.pow(sun_pos['y']-point['y'],2) + math.pow(sun_pos['x']-point['x'],2))
if (angle<minAngle):
minAngle = angle
minPoint = i
if (angle>maxAngle):
maxAngle = angle
maxPoint = i
point['angle'] = angle
point['distance'] = distance
if self.debug:
print(str(i).ljust(10),":", str(point['x']).ljust(10), str(point['y']).ljust(10), str(round(angle,7)).ljust(10), str(round(distance)).ljust(10))
i = i + 1
if self.debug:
print("Min Point = ",minPoint)
print("Max Point = ",maxPoint)
print("")
i = minPoint
k = 0
side1Distance = 0
side2Distance = 0
side1Done = False
side2Done = False
side1 = []
side2 = []
while True:
if (side1Done == False):
side1Distance = side1Distance + SHAPE[i]['distance']
if(i != minPoint and i != maxPoint): SHAPE[i]['side'] = 1
if (i == maxPoint): side1Done = True
side1.append( { 'x': SHAPE[i]['x'], 'y': SHAPE[i]['y'] } )
if (side1Done == True):
side2Distance = side2Distance + SHAPE[i]['distance']
if(i != minPoint and i != maxPoint): SHAPE[i]['side'] = 2
if (i == minPoint): side2Done = True
side2.append( { 'x': SHAPE[i]['x'], 'y': SHAPE[i]['y'] } )
i = i + 1
if( i > len(SHAPE)-1): i = 0
if (side1Done and side2Done): break
k = k + 1
if (k == 20): break
svg = '<?xml version="1.0" encoding="utf-8"?>'
svg = svg + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'
svg = svg + '<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="-10 -10 120 120" xml:space="preserve">'
# background
svg = svg + '<circle cx="' + str(WIDTH/2) + '" cy="' + str(HEIGHT/2) + '" r="' + str(WIDTH/2-1) + '" fill="' + BG_COLOR + '"/>'
minPointShadowX = SHAPE[minPoint]['x'] + WIDTH * math.cos(math.radians(minAngle))
minPointShadowY = SHAPE[minPoint]['y'] - HEIGHT * math.sin(math.radians(minAngle))
maxPointShadowX = SHAPE[maxPoint]['x'] + WIDTH * math.cos(math.radians(maxAngle))
maxPointShadowY = SHAPE[maxPoint]['y'] - HEIGHT * math.sin(math.radians(maxAngle))
shadow = [ {'x': maxPointShadowX, 'y': maxPointShadowY } ] + \
side2 + \
[ {'x': minPointShadowX, 'y': minPointShadowY } ]
svg = svg + '<defs><mask id="shadowMask">'
svg = svg + ' <rect width="100%" height="100%" fill="black"/>'
svg = svg + ' <circle cx="' + str(WIDTH/2) + '" cy="' + str(HEIGHT/2) + '" r="' + str(WIDTH/2-1) + '" fill="white"/>'
svg = svg + '</mask></defs>'
svg = svg + self.generatePath('none',PRIMARY_COLOR,SHAPE)
shadow_svg = self.generatePath('none','black',shadow,'mask="url(#shadowMask)" fill-opacity="0.5"')
if (self.elevation>0):
svg = svg + self.generatePath(LIGHT_COLOR,'none',side1)
else:
svg = svg + self.generatePath(PRIMARY_COLOR,'none',side2)
if (self.elevation>0): svg = svg + shadow_svg
svg = svg + self.generateArc(WIDTH/2,PRIMARY_COLOR,'none',self.sunset_azimuth,self.sunrise_azimuth)
svg = svg + self.generateArc(WIDTH/2,LIGHT_COLOR,'none',self.sunrise_azimuth,self.sunset_azimuth)
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(self.sunrise_azimuth,WIDTH/2-2), self.degreesToPoint(self.sunrise_azimuth,WIDTH/2+2)])
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(self.sunset_azimuth,WIDTH/2-2), self.degreesToPoint(self.sunset_azimuth,WIDTH/2+2)])
for i in range(0,len(DEGS)):
if (i == len(DEGS)-1):
j = 0
else:
j = i + 1
if (i % 2 == 0):
svg = svg + self.generateArc(WIDTH/2+8,PRIMARY_COLOR,'none',DEGS[i],DEGS[j],'stroke-width="3" stroke-opacity="0.2"')
else:
svg = svg + self.generateArc(WIDTH/2+8,PRIMARY_COLOR,'none',DEGS[i],DEGS[j],'stroke-width="3" ')
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(DEGS[0],WIDTH/2+5), self.degreesToPoint(DEGS[0],WIDTH/2+11)])
svg = svg + self.generatePath(LIGHT_COLOR,'none',[self.degreesToPoint(DEGS[(len(DEGS))/2],WIDTH/2+5), self.degreesToPoint(DEGS[(len(DEGS))/2],WIDTH/2+11)])
# moon drawing: compute left and right arcs
phase = self.astr.moon_phase(self.now)
if self.debug:
print('phase: ' + str(phase))
left_radius=MOON_RADIUS
left_sweep=0
right_radius=MOON_RADIUS
right_sweep=0
if (phase > 14):
right_radius = MOON_RADIUS - (2.0*MOON_RADIUS* (1.0 - ((phase%14)*0.99 / 14.0)))
if (right_radius < 0):
right_radius = right_radius * -1.0
right_sweep = 0
else:
right_sweep = 1
if (phase < 14):
left_radius = MOON_RADIUS - (2.0*MOON_RADIUS* (1.0 - ((phase%14)*0.99 / 14.0)))
if (left_radius < 0):
left_radius = left_radius * -1.0
left_sweep = 1
if (self.moon_elevation>0):
svg = svg + '<path stroke="none" stroke-width="0" fill="' + MOON_COLOR \
+ '" d="M ' + str(moon_pos['x']) + ' ' + str(moon_pos['y']-MOON_RADIUS) \
+ ' A ' + str(left_radius) + ' ' + str(MOON_RADIUS) + ' 0 0 ' + str(left_sweep) + ' ' + str(moon_pos['x']) + ' ' + str(moon_pos['y']+MOON_RADIUS) \
+ ' ' + str(right_radius) + ' ' + str(MOON_RADIUS) + ' 0 0 ' + str(right_sweep) + ' ' + str(moon_pos['x']) + ' ' + str(moon_pos['y']-MOON_RADIUS) + ' z" />'
# sun drawing
if (self.sun_elevation>0):
svg = svg + '<circle cx="' + str(sun_pos['x']) + '" cy="' + str(sun_pos['y']) + '" r="' + str(SUN_RADIUS) + '" stroke="none" stroke-width="0" fill="' + SUN_COLOR + '55" />'
svg = svg + '<circle cx="' + str(sun_pos['x']) + '" cy="' + str(sun_pos['y']) + '" r="' + str(SUN_RADIUS -1) + '" stroke="none" stroke-width="0" fill="' + SUN_COLOR + '99" />'
svg = svg + '<circle cx="' + str(sun_pos['x']) + '" cy="' + str(sun_pos['y']) + '" r="' + str(SUN_RADIUS -2) + '" stroke="' + SUN_COLOR + '" stroke-width="0" fill="' + SUN_COLOR + '" />'
svg = svg + '</svg>'
if self.debug:
print(svg)
f = open(FILENAME, 'w')
f.write(svg)
f.close()
def main():
t1 = datetime.now()
s = shadow()
args = sys.argv
if(len(args) == 1):
dummy = 0
#print('\033[91mNo parameters specified\033[0;0m')
else:
if(args[1] == "update"):
s.generateSVG()
t2 = datetime.now()
print("Done in " + str(t2-t1) + " seconds")
if __name__ == '__main__':
main()
I might update that script so moon will cast proper shadow when sun is below horizon… later