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

well, for me australia is working just fine, so am not saying script cant have issue, but as far as I can test, it does not.

actual servertime does not mean much if shown in paperui as it is affected by paperui setting.

what really and only matters is what time returns python datetime.now on machine from which you are running the script.

I’m going to try again this evening based on kriznik’s feedback, I’m sure all my TZ’s are correct but will check. I’ll also look at changing everything to north, including server TZ to compare.

Ive output self.now and it it correct as well. Im not sure wht i dont get the hours but im not really fussed its great to visualise the shadow which is really what I am after

PM your lat/long, tz and time on server
I’ll try it on my environment

Thanks for the widget. Its a nice widget.

The first time I made mistake and now I can get it right. I deleted the svg file but it didn’t work either. It stays the old way like this:

image

try to clear your browser cache… or ctrl+f5 or whatever is for your browser to force it for renew cached picture

So I’ve been looking into that southern hemisphere issue and it does look like it really has some difficulties here and there.
So here is updated version which (should) work, please let me know if it does not correctly for some lat/lon combinations

thanks

actual version of Aug 2019

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'
SUN_COLOR = '#ffff66'
SUN_RADIUS = 5

MOON_COLOR = '#999999'
MOON_RADIUS = 3
STROKE_WIDTH = '1'
FILENAME = '/etc/openhab2/html/shaddow.svg'

LATITUDE = 36.174301
LONGITUDE = 19.099455
ALTITUDE = 0.0
TIMEZONE = 'Europe/Athens'
TOWN = 'Sea'

# Shape of the house in a 100 by 100 units square
SHAPE = [{'x': 16.37, 'y': 62.07}, \
		{'x': 29.57, 'y': 39.87}, \
		{'x': 38.68, 'y': 74.68}]
	
HOURS = 1
DEGS = []

## not really needed to edit anything below
class shadow(object):
	"""
	Shadow Object
	"""
	def __init__(self):

		self.debug = False
		self.astr = Astral()
		self.l = Location(('HOME', TOWN, LATITUDE, LONGITUDE, TIMEZONE, ALTITUDE))
		self.sun = self.l.sun()

		timezone = pytz.timezone(TIMEZONE)
		self.now = timezone.localize(datetime.now())
        #for debug purposes
		#self.now = timezone.localize(datetime.now() + timedelta(hours=7) + timedelta(minutes=30))
		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)])

			if self.debug:
				print(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,orig_start,orig_end,attrs=None):
		
		if(LATITUDE<0):
			start = orig_end
			end = orig_start
		else:
			start = orig_start
			end = orig_end

		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)
		realMoon_pos = self.degreesToPoint(self.moon_azimuth, 10000)
		if self.debug:
			print("")
			print("real sun position: " + str(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
		if(self.sun_elevation>0):
			angle_pos = sun_pos
			real_pos = realSun_pos
		else:
			angle_pos = moon_pos
			real_pos = realMoon_pos

		if self.debug:
			print("")
			print("House:")

		for point in SHAPE:
			#Angle of close light source
			angle = -math.degrees(math.atan2(point['y']-angle_pos['y'],point['x']-angle_pos['x']))
			#Angle of distant light source (e.g. sun_pos)
			angle = -math.degrees(math.atan2(point['y']-real_pos['y'],point['x']-real_pos['x']))
			distance = math.sqrt(math.pow(angle_pos['y']-point['y'],2) + math.pow(angle_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), "angle: ", str(round(angle,7)).ljust(10), "dist: ", str(round(distance)).ljust(10))
			i = i + 1

		if self.debug: 
			print("")
			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"')

			if self.debug:
				print(DEGS[i])


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

Will try it out and report back - thanks!

OK ive copied in the new script but no hour band still - although it is noght time should I get it during the night?

with data you sent me, it should look like this now:

I had been using the original script for a while, so when I came upon the updated code eliminating persistence, I copied the code from post #215 (actual version of Aug 2019) and pasted it into a new file in VS Code (no errors show up), but I am getting this:

[20:54:32] openhabian@openHABianPi:~$ python /etc/openhab2/scripts/shaddow.py update
  File "/etc/openhab2/scripts/shaddow.py", line 106
    def decdeg2dms(self,dd):
                           ^
IndentationError: unindent does not match any outer indentation level

The only thing I added to the script is the LAT, LON & ALT, along with my house coordinates, my time zone (America/New_York) and the item names I use for Sunset and Sunrise Azimuth. I did search through each line looking for trailing spaces and deleted a few. I also deleted some tabs on blank lines, but nothing seems to be working. I cannot see anything wrong with line 106, and nothing is coming up in VS Code…

Any help would be appreciated.

~John

EDIT: Didn’t have Python Extensions installed on VS Code… the above has been fixed, but now I am getting this:

[21:26:17] openhabian@openHABianPi:/etc/openhab2/scripts$ python /etc/openhab2/scripts/shaddow.py update
Traceback (most recent call last):
  File "/etc/openhab2/scripts/shaddow.py", line 5, in <module>
    import pytz
ImportError: No module named pytz
[21:26:26] openhabian@openHABianPi:/etc/openhab2/scripts$ sudo pip3 install pytz --upgrade
Requirement already up-to-date: pytz in /usr/local/lib/python3.4/dist-packages
Cleaning up...

VS Code is showing unresolved import for pylunar & astral and undefined variable for ‘Astral’ and ‘Location’
This is the top of my script:

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

I’m out of ideas…

pip install pytz

you installed pytz for root to python3, but you are executing python2 as non root

@kriznik, thanks, I made the correction with pytz, astral and pylunar. Now to correct some other errors that just came up!

@kriznik, apologies it’s taken me some time to feedback, like @Andrew_Pawelski I’m still not getting any hour bands.

Below is my SVG for Brisbane Australia along with a screenshot of my server time/zone and script time/zone.

Server

#BRISBANE
LATITUDE = -27.474648
LONGITUDE = 153.015681

ALTITUDE = 37.0
TIMEZONE = 'Australia/Queensland'
TOWN = 'Brisbane'

Note: this is just random co-ords in brisbane, not my actual home co-ords

grab new code from here

and try again.
Your data looks ok on my environment

@kriznik thank you and I’m a numpty. I forgot I changed the path for where my SVG was being saved in the previous version of the script so it was not actually updating. I can confirm the hour bands are now present for the southern hemisphere.

:+1:

I got it working:

[20:46:40] openhabian@openHABianPi:/etc/openhab2/html$ python /etc/openhab2/scripts/shaddow.py update
Sun azimuth: 299.589545552
Sun elevation: -12.2902011317
Moon azimuth: 54.2078919705
Moon elevation: -20.0187090139
Done in 0:00:00.401640 seconds
[20:46:42] openhabian@openHABianPi:/etc/openhab2/html$ 

But in VS Code, I am still getting the unresolved import ‘pylunar’ and ‘astral’ along with undefined variable for ‘Astral’ and ‘Location’, but both Astral and Pylunar have been installed with python 2.7:

20:48:25] openhabian@openHABianPi:/etc/openhab2/html$ pip install astral
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
Requirement already satisfied: astral in /usr/local/lib/python2.7/dist-packages (1.10.1)
Requirement already satisfied: pytz in /usr/local/lib/python2.7/dist-packages (from astral) (2019.2)
[20:48:36] openhabian@openHABianPi:/etc/openhab2/html$ pip install pylunar
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
Requirement already satisfied: pylunar in /usr/local/lib/python2.7/dist-packages (0.5.1)
Requirement already satisfied: enum34; python_version == "2.7" in /usr/local/lib/python2.7/dist-packages (from pylunar) (1.1.6)
Requirement already satisfied: ephem in /usr/local/lib/python2.7/dist-packages (from pylunar) (3.7.6.0)
Requirement already satisfied: pytz in /usr/local/lib/python2.7/dist-packages (from pylunar) (2019.2)
[20:48:54] openhabian@openHABianPi:/etc/openhab2/html$ 

The sun is working, however, the moon is not. Any ideas as to why this would happen?

Thanks,

~John

if you have pylunar it have to work. How do you assume “moon is not working” statement? based on ?
as your moon elevation is -20 eg. it’s not visible eg can’t be shown

I seem to have taken a step backwards - its 2.48pm and no sun but moon? (and no time bands)
using August script

image

@kriznik,
This is my results from just now (5:43 am), about a half hour before sunrise:

[05:43:32] openhabian@openHABianPi:~$ python /etc/openhab2/scripts/shaddow.py update
Sun azimuth: 72.2814807047
Sun elevation: -4.48626415385
Moon azimuth: 82.5192446462
Moon elevation: 21.5990372029
Done in 0:00:00.354876 seconds
[05:43:38] openhabian@openHABianPi:~$ 

By looking at this data, I would expect to be able to see the moon, but not the sun… I cannot see either one.

~John