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

The whole code without persistence and timezone corrected
You will need the Astral library (pip install Astral) and the pytz library (sudo pip install pytz)
svg generated is in the shared html folder

from __future__ import print_function

import math
# import time
from datetime import datetime, timedelta, date, time
import sys
import pytz
from astral import Astral
from astral import Location

from myopenhab import openhab
from myopenhab import mapValues
from myopenhab import getJSONValue


WIDTH = 100
HEIGHT = 100
PRIMARY_COLOR = '#1b3024'
LIGHT_COLOR = '#26bf75'
STROKE_WIDTH = '1'
FILENAME = '/etc/openhab2/html/shaddow.svg'
LATITUDE = XX.XXXXXX # Your Latitude
LONGITUDE = XX.XXXXX # Your Longitude
ALTITUDE = XXX # Your Altitude

# Shape of the house in a 100 by 100 units square

SHAPE = [{'x': 23.18, 'y': 19.35}, \
		{'x': 70.11, 'y': 15.53}, \
		{'x': 75.32, 'y': 52.49}, \
		{'x': 79.96, 'y': 52.38}, \
		{'x': 80.65, 'y': 65.59}, \
		{'x': 76.83, 'y': 66.05}, \
		{'x': 78.68, 'y': 77.64}, \
		{'x': 31.52, 'y': 82.16}, \
		{'x': 30.13, 'y': 73.24}, \
		{'x': 18.54, 'y': 74.63}, \
		{'x': 14.72, 'y': 46.70}, \
		{'x': 26.07, 'y': 44.61}]

HOURS = 1
DEGS = []

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

		self.debug = False 
		self.oh = openhab()
		self.astr = Astral()
		timezone = pytz.timezone('Europe/London') # Enter your time zone
		self.l = Location(('HOME', 'TOWN', LATITUDE, LONGITUDE, 'Europe/London', ALTITUDE))
		self.sun = self.l.sun()
		now = timezone.localize(datetime.now())
		self.azimuth = float(self.astr.solar_azimuth(now, LATITUDE, LONGITUDE))
		print(self.azimuth)
		self.elevation = float(self.astr.solar_elevation(now, LATITUDE, LONGITUDE))
		print(self.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)])

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

		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)])

		svg = svg + '<circle cx="' + str(sun['x']) + '" cy="' + str(sun['y']) + '" r="3" stroke="' + LIGHT_COLOR + '" stroke-width="' + STROKE_WIDTH + '" fill="' + LIGHT_COLOR + '" />'

		svg = svg + '</svg>'

		if self.debug:
			print(svg)

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


def main():

	t1 = datetime.now()

	s = shaddow()

	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()
1 Like