Show Current Sun Position and Shadow of House (Generate SVG)

Nice,

Now the original script uses persistence to draw the circles so the whole set-up needs to run for a full 24 hours from midnight to midnight before the persistence can pick up all the values needed to draw the circles.
Let it run for 48 hours and you should be good.

3 Likes

Ah! Thanks! :slightly_smiling_face:

@pmpkk Can you please share this widget that has energy cost, gas usage etc? It looks fantastic.

1 Like

I got errors when i run the script:

File “/etc/openhab2/scripts/shaddow.py”, line 5, in

from myopenhab import openhab

File “/etc/openhab2/scripts/myopenhab.py”, line 3, in

import requests

ImportError: No module named requests

Anyone can help?

You need to download the myopenhab.py script and put it in the same folder as shaddow.py for your fisrt issue

For the second, you need to run:
pip install requests from the console

Hi Guys, really cool!!!
please find below orange version (with brighter sun during the day) and moon.

image

code here.

    import math
    import time
    from datetime import datetime, timedelta, date
    import sys
    from myopenhab import openhab
    from myopenhab import mapValues
    from myopenhab import getJSONValue

    WIDTH = 100
    HEIGHT = 100
    PRIMARY_COLOR = '#cc3300'
    LIGHT_COLOR = '#ff7b00'
    HOUSE_COLOR = '#3f3f3f'
    STROKE_WIDTH = '0.5'
    SUN_COLOR = '#ffff66'
    SUN_COLOR_OFF = '#808033'
    SUN_RADIUS = 5
    MOON_COLOR = '#afafaf'
    MOON_RADIUS = 4

    FILENAME = '/etc/openhab2/html/matrix-theme/shaddow.svg'

    # Shape of the house in a 100 by 100 units square
    SHAPE = [{'x': 32.7, 'y': 29.2}, \
    		{'x': 66.5, 'y': 34.6}, \
    		{'x': 65.1, 'y': 42.2}, \
    		{'x': 70.8, 'y': 42.9}, \
    		{'x': 66.5, 'y': 69.2}, \
    		{'x': 45.4, 'y': 65.9}, \
    		{'x': 45.7, 'y': 63.8}, \
    		{'x': 27.4, 'y': 61.0}]

    HOURS = 1
    DEGS = []

    class shaddow(object):
        """
        
        Shaddow Object
        """
        def __init__(self):

            self.debug = False 
            self.oh = openhab()
            self.azimuth = float(self.oh.getState('LocalSun_Position_Azimuth'))
            self.elevation = float(self.oh.getState('LocalSun_Position_Elevation'))
            self.sr = self.oh.getState('LocalSun_Rise_Azimuth')
            self.ss = self.oh.getState('LocalSun_Set_Azimuth')
            self.sunrise_azimuth = float(self.sr) if self.sr != 'NULL' else 0
            self.sunset_azimuth = float(self.ss) if self.ss != 'NULL' else 0
            self.moon_azimuth = float(self.oh.getState('LocalMoon_Position_Azimuth'))
            self.moon_elevation = float(self.oh.getState('LocalMoon_Position_Elevation'))
            ts = time.time()
            utc_offset = (datetime.fromtimestamp(ts) - datetime.utcfromtimestamp(ts)).total_seconds()/3600
            for h in xrange(0,24,HOURS):
                t = datetime.combine(date.today(), datetime.min.time()) + timedelta(hours=-utc_offset+h-24)
           	    a = self.oh.getStateHistoryFromInflux('LocalSun_Position_Azimuth',t.strftime('%Y-%m-%dT%H:%M:%S') + 'Z')
           	    if (a == None): a = 0
           	    DEGS.extend([a])

        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,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 (attrs != None): 
    				p = p + ' ' + attrs + ' '
    			else:
    				p = p + ' stroke-width="' + STROKE_WIDTH + '" fill="none" '
    			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 = self.degreesToPoint(self.azimuth, 10000)
    		if self.debug: print realSun

    		sun = self.degreesToPoint(self.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['y'],point['x']-sun['x']))
    			#Angle of distant light source (e.g. sun)
    			angle = -math.degrees(math.atan2(point['y']-realSun['y'],point['x']-realSun['x']))
    			distance = math.sqrt(math.pow(sun['y']-point['y'],2) + math.pow(sun['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">'

    		minPointShaddowX = SHAPE[minPoint]['x'] + WIDTH * math.cos(math.radians(minAngle))
    		minPointShaddowY = SHAPE[minPoint]['y'] - HEIGHT * math.sin(math.radians(minAngle))
    		maxPointShaddowX = SHAPE[maxPoint]['x'] + WIDTH * math.cos(math.radians(maxAngle))
    		maxPointShaddowY = SHAPE[maxPoint]['y'] - HEIGHT * math.sin(math.radians(maxAngle))

    		shaddow = [ {'x': maxPointShaddowX, 'y': maxPointShaddowY } ] + \
    				side2 + \
    				[ {'x': minPointShaddowX, 'y': minPointShaddowY } ]
    		
    		svg = svg + '<defs><mask id="shaddowMask">'
    		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',HOUSE_COLOR,SHAPE)

    		shaddow_svg = self.generatePath('none','black',shaddow,'mask="url(#shaddowMask)" fill-opacity="0.5"')

    		if (self.elevation>0): 
    			svg = svg + self.generatePath(LIGHT_COLOR,'none',side1)
    		else:
    			svg = svg + self.generatePath(PRIMARY_COLOR,'none',side1)

    		if (self.elevation>0): svg = svg + shaddow_svg

    		svg = svg + self.generateArc(WIDTH/2,PRIMARY_COLOR,self.sunset_azimuth,self.sunrise_azimuth)
    		svg = svg + self.generateArc(WIDTH/2,LIGHT_COLOR,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,DEGS[i],DEGS[j],'stroke-width="3" fill="none" stroke-opacity="0.1"')	
    			else:
    				svg = svg + self.generateArc(WIDTH/2+8,PRIMARY_COLOR,DEGS[i],DEGS[j],'stroke-width="3" fill="none" stroke-opacity="0.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)])

    		# sun
    		#svg = svg + '<circle cx="' + str(sun['x']) + '" cy="' + str(sun['y']) + '" r="3" stroke="' + LIGHT_COLOR + '" stroke-width="' + STROKE_WIDTH + '" fill="' + LIGHT_COLOR + '" />'
    		if (self.elevation>0): 
    			sun_c = SUN_COLOR
    		else:
    			sun_c = SUN_COLOR_OFF
    		svg = svg + '<circle cx="' + str(sun['x']) + '" cy="' + str(sun['y']) + '" r="' + str(SUN_RADIUS) + '" stroke="none" stroke-width="0" fill="' + sun_c + '55" />'
    		svg = svg + '<circle cx="' + str(sun['x']) + '" cy="' + str(sun['y']) + '" r="' + str(SUN_RADIUS -1) + '" stroke="none" stroke-width="0" fill="' + sun_c + '99" />'
    		svg = svg + '<circle cx="' + str(sun['x']) + '" cy="' + str(sun['y']) + '" r="' + str(SUN_RADIUS -2) + '" stroke="' + sun_c + '" stroke-width="0" fill="' + sun_c + '" />'

    		# moon
    		moon = self.degreesToPoint(self.moon_azimuth, WIDTH / 2)
    		svg = svg + '<circle cx="' + str(moon['x']) + '" cy="' + str(moon['y']) + '" r="' + str(MOON_RADIUS -1) + '" stroke="' + MOON_COLOR + '" stroke-width="0" fill="' + MOON_COLOR + '" />'

    		svg = svg + '</svg>'

    		if self.debug: print svg

    		f = open(FILENAME, 'w')
    		f.write(svg)
    		f.close()


    def main():

        t1 = time.time()

        s = shaddow()

        args = sys.argv
        
        if(len(args) == 1):
            print '\033[91mNo parameters specified\033[0;0m'
        else:
            if(args[1] == "update"):
                s.generateSVG()

        t2 = time.time()
        print "Done in " + str(t2-t1) + " seconds"

    if __name__ == '__main__':
        main()
1 Like

What do you mean by brighter sun during the day?

What do you mean by brighter sun during the day?

when the elevation of the sun is >0 then the color is switched to bright yellow. :slight_smile:

image

It does that already. I thought you implemented the sun getting brighter and dimmer in the course of the day.

Can anyone give me a hint on how to get the inner circle highlighted in respect of where the sun is for someone in the southern hemisphere? Everything else looks good, but my wife just commented that the bright section of the inner circle wasn’t in the right place… now it’s all I can see and i’m not sure where to begin with the script.

The attached image was taken just before midday and the inner and outer markers look good.

Hi @pahansen

These two lines:

svg = svg + self.generateArc(WIDTH/2,PRIMARY_COLOR,self.sunset_azimuth,self.sunrise_azimuth)
svg = svg + self.generateArc(WIDTH/2,LIGHT_COLOR,self.sunrise_azimuth,self.sunset_azimuth)

Change the first one the LIGHT_COLOR and the second one to PRIMARY_COLOR

1 Like

Small evolution, if you happen to have moon age and want to display it directly :

Add a moon_age property :

		self.moon_azimuth = float(self.oh.getState('Astro_Moon_Azimuth').replace(" °",""))
		self.moon_elevation = float(self.oh.getState('Astro_Moon_Elevation').replace(" °",""))
		self.moon_age = self.oh.getState('Astro_Moon_Age')

Modification of the moon rendering :

# moon
moon = self.degreesToPoint(self.moon_azimuth, WIDTH / 2)
#svg = svg + '<circle cx="' + str(moon['x']) + '" cy="' + str(moon['y']) + '" r="' + str(MOON_RADIUS -1) + '" stroke="' + MOON_COLOR + '" stroke-width="0" fill="' + MOON_COLOR + '" />'
		
svg = svg + '<image x="' + str(moon['x']) + '" y="' + str(moon['y']) + '" width="' + str(MOON_RADIUS *2 ) + '" height="' + str(MOON_RADIUS *2) + '" xlink:href="/icon/moon?state=' + self.moon_age +'&amp;format=svg" />'
		svg = svg + '</svg>'

And here you are :
Capture%20d%E2%80%99%C3%A9cran%20du%202019-03-14%2009-17-50

1 Like

Dumb question but is north up on the svg?

Yes, it should

Yes, but you’ll have to make some code changes if you live in the southern hemisphere

1 Like

And altitude in meters?

Ah I do I think there’s a post a bit up about that…

Yes in meters

I have a black background - what line can I change the colour of the shadow on? I found two “black” references but they didnt seem to do anything or is this in CSS? I also dont seem to have the hour slots?

anyone? my other question is - what could I do to use this information to help me raise and lower blinds as they come out of shadow?