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


(Christian) #105

Yeah, easter holiday makes it much harder to get time, for my home automation hobbies :smiley: But I have finally got around and tested your solution. Unfortunately there is no change though. I also tried to force reinstall the astral libraries. I can also confirm that OH2 has the right astro data, and astral is off by 2 hours.

It seems that whatever i put in the location parameters, there are no changes in the output data:

self.l = Location(('HOME', 'XXXX', LATITUDE, LONGITUDE, 'Europe/XXXXX', ALTITUDE))

So with your original location data, WATFORD and Europe/London, That should be 1 hour difference from Oslo. But no change if I use Oslo or London :confused:


(Vincent Regaud) #106

Try that:

self.debug = False 
		self.oh = openhab()
		self.astr = Astral()
		self.l = Location(('HOME', 'WATFORD', LATITUDE, LONGITUDE, 'Europe/London', ALTITUDE))
		self.sun = self.l.sun()
		ts = time.time()
                print(ts)
		ts = ts - (2 * 86400)
                print(ts)
		self.azimuth = float(self.astr.solar_azimuth(datetime.fromtimestamp(ts), LATITUDE, LONGITUDE))
		print(self.azimuth)
		self.elevation = float(self.astr.solar_elevation(datetime.fromtimestamp(ts), LATITUDE, LONGITUDE))
		print(self.elevation)

What does your log show?


(Frederic Mariën) #107

Hi @vzorglub and @sjef86,

I’m also using Astral now and I also encountered the issue with the wrong timezone.
I found in the Astral documentation that you need to localize your timestamps by using pytz.
(see https://astral.readthedocs.io/en/latest/#note-on-localized-timezones)

This is my init function:

    def __init__(self):
        self.debug = False 
        self.astr = Astral()
        timezone = pytz.timezone('Europe/Brussels')
        self.l = Location(('HOME', 'TOWN', LATITUDE, LONGITUDE, 'Europe/Brussels', ALTITUDE)) # The HOME and TOWN values are not important. Replace Europe/London with your local time string
        self.sun = self.l.sun()
        now = timezone.localize(datetime.now())
        
        self.azimuth = float(self.astr.solar_azimuth(now, LATITUDE, LONGITUDE))
        self.elevation = float(self.astr.solar_elevation(now, LATITUDE, LONGITUDE))
        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 range(0,24):
            a = float(self.astr.solar_azimuth(timezone.localize(datetime.combine(date.today(), time(i))), LATITUDE, LONGITUDE))
            if (a == None): a = 0
            DEGS.append(float(a))

It is possible that you will need to import pytz and some parts of datetime (if not imported yet):

from datetime import datetime, date, time
import pytz

Maybe you also need to install pytz via pip.
By using the localize() the timestamp will be set to the correct timezone.
I compared the generated output and it matches the “old” method that uses OpenHAB data.

I hope this helps!

Greetings,
Frederic


(Vincent Regaud) #108

Thanks @FredericMa
That would not have occured to me as I am in GMT…

@sjef86
Does that work?


(Christian) #109

Hi @vzorglub and @FredericMa,

I tried to replace my existing code with yours, Frederic. But now I get this error:

 python /etc/openhab2/scripts/shaddow.py
Traceback (most recent call last):
  File "/etc/openhab2/scripts/shaddow.py", line 37, in <module>
    class shaddow(object):
  File "/etc/openhab2/scripts/shaddow.py", line 46, in shaddow
    self.l = Location(('HOME', 'TOWN', LATITUDE, LONGITUDE, 'Europe/Oslo', ALTITUDE))
NameError: name 'self' is not defined


(Vincent Regaud) #110

@sjef86, @FredericMa

Works for me! Thanks

But I had to make a few changes:

Import section:

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

Init function

	def __init__(self):

		self.debug = False 
		self.oh = openhab()
		self.astr = Astral()
		timezone = pytz.timezone('Europe/London')
		self.l = Location(('HOME', 'WATFORD', 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))
		self.elevation = float(self.astr.solar_elevation(now, LATITUDE, LONGITUDE))
		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))

Main function (At the bottom)

	t1 = datetime.now()
...
	t2 = datetime.now()

Note that I have removed the print statements
Change 'Europe/London' with 'London/Olso' as required
Did you check that pytz was installed?
sudo pip install pytz
Did you add the statement import pytz at the top of the script?


(Frederic Mariën) #111

Woeps, forgot those 2 indeed… I already got rid of the time import since I removed the duration calculation a while ago.
@sjef86 have you copied the exact code? Maybe something went wrong while copying and pasting?


(Vincent Regaud) #112

@FredericMa
Hi there, thanks for you good find indeed!!
I copied the code manually, I don’t like copying and pasting because you don’t get inside the code and get to learn and understand it.
I got it to work after a few tries.

@sjef86
With python be very careful with trailing spaces at the end of lines and with your indents

Thanks again…


(Christian) #113

Thanks guys! I finally got it working here :slight_smile: having some indent problems after copy/paste. I added:

	t1 = datetime.now()
...
	t2 = datetime.now()

And now everything is working great!! :smiley:
Thanks once more

Br
Chris


(Salexes) #114

diffToFasade was bedeuted das genau? Was muss ich eintragen ? Eine haus seite zeigt genau nach norden bei mir.


(Vincent Regaud) #115

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

Examples of HABPanel Solutions
(Rot Ulet) #116

Hi,
I added the moon with an approx drawing of its phase. It needs the pylunar library (sudo pip install pylunar).
I also added a background color to have a better shadow view and beveled the sun drawing.

from __future__ import print_function

import math
# import time
from datetime import datetime, timedelta, date, time
import sys
import pytz
import pylunar

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'
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': 14.00637, 'y': 26.779708}, \
		{'x': 25.393663, 'y': 51.717879}, \
		{'x': 31.542801, 'y': 49.098802}, \
		{'x': 48.62374, 'y': 86.79074}, \
		{'x': 58.530685, 'y': 82.008077}, \
		{'x': 63.313348, 'y': 91.231785}, \
		{'x': 88.479264, 'y': 79.161254}, \
		{'x': 76.978099, 'y': 53.3121}, \
		{'x': 73.266786, 'y': 54.804337}, \
		{'x': 69.74343, 'y': 48.223985}, \
		{'x': 65.992327, 'y': 49.584331}, \
		{'x': 48.698439, 'y': 11.044762}]


	
HOURS = 1
DEGS = []

class shadow(object):
	"""
	Shadow Object
	"""
	def __init__(self):

		self.debug = False 
		self.oh = openhab()
		self.astr = Astral()
		timezone = pytz.timezone('Europe/Paris') # Enter your time zone
		self.l = Location(('HOME', 'YOUR TOWN', LATITUDE, LONGITUDE, 'Europe/Paris', ALTITUDE))
		self.sun = self.l.sun()
		self.now = timezone.localize(datetime.now())
		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.now)
		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',side2)
		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()


(Kurt S) #117

Hi,

Thanks for the update-work!! Also to @vzorglub!

One question though: In the original script, the illuminated part of the house’s fassade would be highlighted in the SVG.
With your code and on my setup, the part where the shadow is cast on is actually highlighted (the part of the house not faced by the sun).
Is there a way to update/modify the script in order to have the side of the house, touched by sunlight being highlighted?

Kurt


(Vincent Regaud) #118

Yes, there is a slight bug!!
Look for that bit and change the first one to side1

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

(Rot Ulet) #119

I inverted side1 and side2 in this condition as my svg output had the opposite side highlighted! With the correction purpose by @vzorglub I got the attached result. Any idea?

shadow


(Vincent Regaud) #120

I had to tinker with your code to make it work as I wanted. I didn’t like your background, sorry!!

I use this snippet:
My test is sun_elevation because I don’t want an illuminated side for the moon!!

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

That works for me

image


(Rot Ulet) #121

Gotcha! This is linked to the clockwise/counter clockwise points in SHAPE used to draw the path! It must be in clockwise to work.


(Vincent Regaud) #122

Yep, agree there, I drew mine clockwise. Well spotted young man


(Rot Ulet) #123

Yes, I understand! You can just set BG_COLOR to ‘none’ to cancel the background.
In my case I want to see the moon shadows :star_struck:!


(Vincent Regaud) #124

I just commented out the line…