Lunar Phase Calculator
I’ve always been interested in the planets, stars, and moon up in space. Calculating how the moon will look from your exact spot on earth sound really interesting to me. The more and more I looked into different aspects of the moon the more excited I became.
Check the project out live for today’s LunarPhase.info for Culver city, California.
In case you were wondering, LunarPhase.info is:
Designed and coded on a PC in Culver city, California. Coded with Visual Studio Code. Built on Jekyll, the open source static site generator. Hosted on GitHub Pages.
Calculating Lunar phase
This solution had multiple problems to solve.
- Know where ‘you’, the Observer() are at on the Earth
- Find out current date and time
- Find out how many days are in the current month
- Determine what percent of illumination for each date
There are the libraries to reference:
Python lib: Calendar Python lib: Math PyEphem
Here is a Jupyter Notebook:
In [1]:
import ephem
import calendar
from datetime import datetime
from math import radians as rad, degrees as deg
In [2]:
# set month key variables
months = ('January','February','March','April','May','June', 'July','August','September','October','November','December')
jan=0
feb=1
mar=2
apr=3
may=4
jun=5
jul=6
aug=7
sep=8
oct=9
nov=10
dec=11
In [3]:
now = datetime.today()
#year = now.year # for current year
#month = now.month - 1 # for currnet month
year=2021
month=feb
#print(month)
In [4]:
# Find number of days per month of year
days_per_month = {}
dpm = days_per_month
for i in range(0,12):
k = months[i]
v = calendar.monthrange(year, i+1)
days_per_month[i] = (k, v[1])
# print(months[month], year)
In [5]:
g = ephem.Observer()
g.name = 'Los Angeles'
g.lat=rad(34.0211)
g.long=rad(-118.3965)
m = ephem.Moon()
i = 0
days=1
try:
for i in range(0, dpm[month][days]):
g.date = datetime(year,month+1, i+1)
g.date -= ephem.hour
m.compute(g)
print(dpm[month][0][:3],i+1, year,"- " "%.1f" % m.phase)
except ValueError:
if i > dpm[month][days]:
i - 1
print('error')
[ OUTPUT ]:
Feb 1 2021 - 88.0
Feb 2 2021 - 79.7
Feb 3 2021 - 69.9
Feb 4 2021 - 58.9
Feb 5 2021 - 47.6
Feb 6 2021 - 36.4
Feb 7 2021 - 25.9
Feb 8 2021 - 16.7
Feb 9 2021 - 9.3
Feb 10 2021 - 4.0
Feb 11 2021 - 0.9
Feb 12 2021 - 0.2
Feb 13 2021 - 1.7
Feb 14 2021 - 5.1
Feb 15 2021 - 10.4
Feb 16 2021 - 17.0
Feb 17 2021 - 24.7
Feb 18 2021 - 33.3
Feb 19 2021 - 42.4
Feb 20 2021 - 51.8
Feb 21 2021 - 61.2
Feb 22 2021 - 70.4
Feb 23 2021 - 79.0
Feb 24 2021 - 86.7
Feb 25 2021 - 92.9
Feb 26 2021 - 97.4
Feb 27 2021 - 99.6
Feb 28 2021 - 99.3
Putting it all together
Now that the current observation is ready, lets create the html file.
# # # # # # # # # # # # #
# CREATE HTML PAGE
# # # # # # # # # # # # #
def create_page(lunar_info):
# # # # # # # # # # # # #
# CREATE HTML FILE CONSTRUCTOR
doc_start ="""
<!DOCTYPE html>
<html lang="en">
""".encode()
# # # # # # # # # # # # #
# HEADER
# Page title and CSS
head = """
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="css/style.css" />
<title>LunarPhase.info for Los Angeles, California</title>
</head>
""".encode()
# # # # # # # # # # # # #
# CONTAINER
start_of_body = """
<div class="container text-center" style="font-family:'Trebuchet MS', 'Lucida Sans Unicode',
'Lucida Grande', 'Lucida Sans', Arial, sans-serif">
<body style="background-color: #070719;" class="text-white">
""".encode()
# # # # # # # # # # # # #
# LOCATION TITLE
start_page_title = """
<p class="pt-3">Lunarphase.info for Culver city, California</p>
""".encode()
# # # # # # # # # # # # #
# HERO IMAGE
# Start of HERO HTML and style
start_current_phase_symbol = """
<!-- Current-lunar-phase-symbol -->
<div>
<div id="contain-all-moon">
<svg viewBox="0 0 400 400">
<style type="text/css">
.st0{fill:#EBEBEB;fill-opacity:0.16;}
.st1{fill:#FFFFFF;}
.st2{opacity:0.45;}
.st3{fill:#D9D9BE;}
.st4{display:none;}
</style>
""".encode()
# # # # # # # # # # # # #
# CREATE HERO IMG SVG GROUP
start_moon_group = """
<!-- start of moon group-->
<g id="moon-group">
""".encode()
# # # # # # # # # # # # #
# AURA IMG SVG GROUP
start_aura_group = """
<g id="aura-group">
<path id="aura-1" class="st0" d="M91.1,197.5c0-60.7,49.6-110.3,110.3-110.3s110.3,49.6,110.3,110.3s-49.6,110.3-110.3,110.3
S91.1,258.3,91.1,197.5z"/>
<path id="aura-2" class="st0" d="M70.9,197.5c0-71.9,57.7-130.6,130.6-130.6c71.9,0,130.6,57.7,130.6,130.6
c0,71.9-57.7,130.6-130.6,130.6C129.6,328.1,70.9,270.4,70.9,197.5z"/>
<path id="aura-3" class="st0" d="M51.6,197.5c0-83,66.8-149.8,149.8-149.8s150.8,65.8,150.8,149.8s-66.8,149.8-149.8,149.8
S51.6,280.5,51.6,197.5z"/>
</g>
""".encode()
# # # # # # # # # # # # #
# Current SVG MOON PHASE
start_current_phase = """ <path id="waxing_x5F_crescent_x5F_fill"
class="st1" d="M294.7,199.7c0,51.6-41.8,93.4-93.4,93.4c-21.8,0,56-18.7,56-93.4
s-77.8-93.4-56-93.4C252.9,106.3,294.7,148.2,294.7,199.7z"/>
""".encode()
# # # # # # # # # # # # #
# SVG MOON CRATERS GROUP
start_moon_craters = """
<g id="crater-group_2_" class="st2">
<path id="_x31_" class="st3"
d="M162.2,243.7c0.6,2.3-0.8,4.7-3.2,5.3c-2.3,0.6-4.7-0.8-5.3-3.2c-0.6-2.4,0.8-4.7,3.2-5.3
C159.2,239.9,161.6,241.4,162.2,243.7z"/>
<path id="_x32_" class="st3"
d="M148.1,232.8c1,4.1-1.5,8.3-5.6,9.4c-4.1,1-8.3-1.5-9.4-5.6c-1-4.1,1.5-8.3,5.6-9.4
C142.9,226.1,147.1,228.7,148.1,232.8z"/>
<path id="_x33_" class="st3"
d="M164.7,200.9c2.2,9-3.3,18.2-12.3,20.4c-9,2.2-18.2-3.3-20.4-12.3c-2.2-9,3.3-18.2,12.3-20.4
C153.3,186.4,162.5,191.9,164.7,200.9z"/>
<path id="_x34_" class="st3"
d="M148.9,167.5c3.4,5.8,1.4,13.3-4.4,16.7c-5.8,3.4-13.3,1.4-16.7-4.4c-3.4-5.8-1.4-13.3,4.4-16.7
C138,159.7,145.5,161.7,148.9,167.5z"/>
<path id="_x35_" class="st3"
d="M181.3,144c6.4,11,2.7,25.2-8.3,31.6c-11,6.4-25.2,2.7-31.6-8.3c-6.4-11-2.7-25.2,8.3-31.6
C160.7,129.3,174.9,133,181.3,144z"/>
<path id="_x36_" class="st3"
d="M191.7,161.8c1.5,2.6,0.6,5.9-1.9,7.5c-2.6,1.5-5.9,0.6-7.5-1.9c-1.5-2.6-0.6-5.9,1.9-7.5
C186.8,158.3,190.2,159.2,191.7,161.8z"/>
<path id="_x37_" class="st3"
d="M228.2,146c4.7,8,1.9,18.4-6.1,23.1c-8,4.7-18.4,1.9-23.1-6.1c-4.7-8-1.9-18.4,6.1-23.1
S223.5,138,228.2,146z"/>
<path id="_x38_" class="st3"
d="M259.6,175.6c0.7,2.9-1.1,5.9-4,6.6c-2.9,0.7-5.9-1.1-6.6-4c-0.7-2.9,1.1-5.9,4-6.6
C255.9,170.9,258.9,172.6,259.6,175.6z"/>
<path id="_x39_" class="st3"
d="M252.9,185.1c1.6,6.5-2.4,13.2-8.9,14.8c-6.5,1.6-13.2-2.4-14.8-8.9c-1.6-6.5,2.4-13.2,8.9-14.8
C244.6,174.6,251.2,178.6,252.9,185.1z"/>
<path id="_x31_0" class="st3"
d="M261.5,199.9c1.2,4.7-1.7,9.5-6.5,10.7c-4.7,1.2-9.5-1.7-10.7-6.5c-1.2-4.7,1.7-9.5,6.5-10.7
C255.4,192.3,260.3,195.2,261.5,199.9z"/>
<path id="_x31_1" class="st3"
d="M259.6,225.6c0.7,2.9-1.1,5.9-4,6.6c-2.9,0.7-5.9-1.1-6.6-4c-0.7-2.9,1.1-5.9,4-6.6
C255.9,220.9,258.9,222.7,259.6,225.6z"/>
""".encode()
end_of_moon_group = """
</g>
<!-- end of moon group-->
""".encode()
# # # # # # # # # # # # # end MOON SVG GROUP
# # # # # # # # # # # # #
# SVG Mountains group
start_mountains_image_group = """
<!-- start of mountain group-->
<g id="mountain-group">
<path id="mountain-5" class="st4"
d="M299.7,339.3l11.1-20.2l19.2,32.4l19.2,32.4H79l19.2-32.4l19.2-32.4l11.1,20.2l7.1-16.2
l4,10.1l17.2-38.5l20.2,45.6l7.1,15.2l14.2-24.3l15.2,27.3l11.1,19.2l17.2-37.5l20.2-45.6l17.2,38.5l4-10.1l7.1,16.2H299.7z"/>
<path id="mountain-4" d="M393.8,349.4l7.1,50.6H167l19.2-35.4l19.2-35.4l11.1,22.3l7.1-17.2l4,11.1l18.2,42.5l21.3-89.1l7.1,16.2
l14.2,26.3l16.2,29.4l11.1,21.3l17.2-40.5l21.3-49.6l18.2,42.5l4-11.1l7.1,17.2L393.8,349.4z"/>
<path id="mountain-3" d="M0,400h98.2L79,302.8l-19.2-97.2l-11.1,59.7l-7.1-47.6l-4,30.4L19.2,133.8L0,222.8V400z"/>
<path id="mountain-2" d="M404.9,400H299.7l20.2-80l20.2-80l12.1,48.6l8.1-38.5l5.1,24.3l19.2-94.1l15.2,74.9V400H404.9z"/>
<path id="mountain-1" d="M329,330.1l17.2-30.4l28.3,49.6l25.7,50.6H0l28.3-49.6l28.3-49.6l17.2,30.4L86,305.9l7.1,15.2l26.3-57.7
l31.4,67.8l10.1,22.3l21.3-36.4l23.3,40.5l16.2,29.4l25.3-55.7l31.4-67.8l26.3,57.7l7.1-15.2l11.1,24.3L329,330.1L329,330.1z"/>
</g>
""".encode()
end_of_symbol_group = """
</svg>
</div>
""".encode()
# # # # # # # # # # # # # # end HERO IMAGE
# # # # # # # # # # # # # #
# HEADLINE
# specific lunar phase and percent
start_lunar_card_title = """
<!-- Lunar-Card-title -->
<div">
<span class="h1">""".encode() + str(lunar_info['pf_phase']).encode()
start_lunar_card_title += """ | """.encode() + str(lunar_info['pf_percent']).encode()
start_lunar_card_title +="""</span>
</div>
<!-- Horizontal Rule 1-->
<div>
<hr style="border:0;border-bottom: 1px dashed #ccc"/>
</div>
""".encode()
# # # # # # # # # # # # # # # end HEADLINE
# # # # # # # # # # # # # # #
# Group 1 - Row 1
# n Relevant Moon Phase icons
start_relevant_phase_info = """
<div>
<!-- Relevant Moon Phases info-->
<div class="row mb-2">
""".encode()
start_relevant_phase_icons = """
<!-- Relevant Moon Phases icons-->
""".encode()
# # # # # # # # # # # # #
# 4 Moon Phase Icons
# SVG Icons
phase_spot_first_quarter = """
<div class="col-3">
<!-- first quarter moon -->
<svg viewBox="0 0 6 6">
<path style="opacity:0.541;fill:#FFCC00;enable-background:new"
d="M3,6c1.7,0,3-1.3,3-3S4.7,0,3,0S0,1.3,0,3S1.3,6,3,6z M3,5.7C1.5,5.7,0.3,4.5,0.3,3S1.5,0.3,3,0.3"/>
</svg>
</div>
""".encode()
phase_spot_full_moon = """
<div class="col-3">
<!-- full moon -->
<svg viewbox="0 0 6 6">
<circle
r="3"
cy="3"
cx="3"
style="display:inline;opacity:0.95;fill:#fff;fill-opacity:1;stroke:none;
stroke-width:7.61669302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
</svg>
</div>
""".encode()
phase_spot_third_quarter = """
<div class="col-3">
<!-- third quarter moon -->
<svg viewBox="0 0 6 6" style="enable-background:new 0 0 6 6;">
<path style="fill:#FFCC00;opacity:0.541"
d="M3,0C1.3,0,0,1.3,0,3s1.3,3,3,3s3-1.3,3-3S4.7,0,3,0z M3,0.3c1.5,0,2.7,1.2,2.7,2.7S4.5,5.7,3,5.7"/>
</svg>
</div>
""".encode()
phase_spot_new_moon = """
<div class="col-3">
<!-- new moon -->
<svg viewbox="0 0 6 6">
<circle
r="3"
cy="3"
cx="3"
style="display:inline;opacity:0.54100001;fill:rgb(99, 99, 85);
fill-opacity:.5;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
</svg>
</div>
""".encode()
if lunar_info['phase_spot_1_title'] == "New Moon":
phase_spot_1 = phase_spot_new_moon
phase_spot_2 = phase_spot_first_quarter
phase_spot_3 = phase_spot_full_moon
phase_spot_4 = phase_spot_third_quarter
elif lunar_info['phase_spot_1_title'] == "First Quarter":
phase_spot_1 = phase_spot_first_quarter
phase_spot_2 = phase_spot_full_moon
phase_spot_3 = phase_spot_third_quarter
phase_spot_4 = phase_spot_new_moon
elif lunar_info['phase_spot_1_title'] == "Full Moon":
phase_spot_1 = phase_spot_full_moon
phase_spot_2 = phase_spot_third_quarter
phase_spot_3 = phase_spot_new_moon
phase_spot_4 = phase_spot_first_quarter
elif lunar_info['phase_spot_1_title'] == "Third Quarter":
phase_spot_1 = phase_spot_third_quarter
phase_spot_2 = phase_spot_new_moon
phase_spot_3 = phase_spot_first_quarter
phase_spot_4 = phase_spot_full_moon
end_relevant_phase_icons = """
</div>
<!-- end of relevant phase icon-->
""".encode()
# # # # # # # # # # # # #
# Group 1 - Row 2
# n Relevant Moon phase titles
start_relevant_phase_titles = """
<!-- START Relevant Phase Titles -->
<div class='row'>
""".encode()
phase_spot_1_title = """
<div class="col-3">
<!-- Phase Spot 1 title -->
<p>""".encode() + str(lunar_info['phase_spot_1_title']).encode() + """</p>
</div>""".encode()
phase_spot_2_title = """
<div class="col-3">
<!-- Phase Spot 2 title -->
<p>""".encode() + str(lunar_info['phase_spot_2_title']).encode() + """</p>
</div>""".encode()
phase_spot_3_title = """
<div class="col-3">
<!-- Phase Spot 3 title -->
<p>""".encode() + str(lunar_info['phase_spot_3_title']).encode() + """</p>
</div>""".encode()
phase_spot_4_title = """
<div class="col-3">
<!-- Phase Spot 4 title-->
<p>""".encode() + str(lunar_info['phase_spot_4_title']).encode() + """</p>
</div>""".encode()
end_relevant_phase_titles = """
<!-- END Relevant Phase Titles-->
</div>
""".encode()
# # # # # # # # # # # # #
# Grid 1 - Row 3
# n Relevant Moon phase dates
start_relevant_phase_dates = """
<!-- START Relevant Phase Dates -->
<div class="row">
""".encode()
phase_spot_1_date = """
<!-- Phase Spot 1 date -->
<p class="col-3">""".encode() + str(lunar_info['phase_spot_1_date']).encode() + """</p>
""".encode()
phase_spot_2_date = """
<!-- Phase Spot 2 date -->
<p class="col-3">""".encode() + str(lunar_info['phase_spot_2_date']).encode() + """</p>
""".encode()
phase_spot_3_date = """
<!-- Phase Spot 3 date -->
<p class="col-3">""".encode() + str(lunar_info['phase_spot_3_date']).encode() + """</p>
""".encode()
phase_spot_4_date = """
<!-- Phase Spot 4 date -->
<p class="col-3">""".encode() + str(lunar_info['phase_spot_4_date']).encode() + """</p>
""".encode()
end_relevant_phase_dates = """
<!-- END Relevant Phase Dates-->
</div>
""".encode()
end_relevant_phase_info = """
</div>
<!-- END Relevant Phase info -->
<div>
<hr style="border:0;border-bottom: 1px dashed #ccc"/>
</div>
""".encode()
# # # # # # # # # # # # # end of Grid 1
# # # # # # # # # # # #
# Grid 2 - row 1
# sunrise and sunset titles
start_sunset_sunrise_info = """
<!-- START Sunset / Sunrise Info-->
<div class='row'>
""".encode()
# # # # # # # # # # # # #
# Grid 2 - col 1
# sunrise for today
start_sunset_info = """
<!-- START Sunset Info-->
<div class="col-6">
<p>Sunset Today</p>
<p>""".encode() + str(lunar_info['pf_sunset']).encode() + """
</p>
</div>
""".encode()
# # # # # # # # # # # # #
# Grid 2 - col 2
# sunrise for today
start_sunrise_info = """
<!-- START Sunrise Info-->
<div class='col-6'>
<p>Sunrise Today</p>
<p>""".encode() + str(lunar_info['pf_sunrise']).encode() + """
</p>
""".encode()
end_sunset_sunrise_info = """
</div>
</div>
<!-- END Sunrise / Sunset info -->
""".encode()
# # # # # # # # # # # # # end of Grid 2
# # # # # # # # # # # # #
# Data Update info
data_update_info = """
<!-- Site last updated info -->
<p>Last updated """.encode() + str(lunar_info['pf_todaydate']).encode() + """</p>
""".encode()
# # # # # # # # # # # # #
# Footer
footer_info = """
<footer>
<p>Made by: <a href="http://jpromano.com">JPROMANO.COM</a></p>
</footer>
""".encode()
# # # # # # # # # # # # #
# File Closing tags
file_closing_tags = """
</div>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</div>
</html>
""".encode()
# # # # # # # # # # # # #
# CREATE HTML OUTPUT
# # # # # # # # # # # # #
#
# PAGE CREATION
output = doc_start + head + start_of_body + start_page_title
# HERO IMAGE
output += start_current_phase_symbol
output += start_moon_group + start_aura_group + start_current_phase + start_moon_craters + end_of_moon_group
# MOUNTAINS GROUP
output += start_mountains_image_group + end_of_symbol_group
# HEADLINE Title
output += start_lunar_card_title
# HEADLINE Phase Percent
output += start_relevant_phase_info
# GRID 1
output += start_relevant_phase_icons
# GRID 1: ROW 1 - Col 1, Col 2, Col 3, Col 4
output += phase_spot_1 + phase_spot_2 + phase_spot_3 + phase_spot_4
output += end_relevant_phase_icons
# GRID 1: ROW 2 - Col 1, Col 2, Col 3, Col 4
output += start_relevant_phase_titles + phase_spot_1_title + phase_spot_2_title + phase_spot_3_title + phase_spot_4_title
output += end_relevant_phase_titles
# GRID 1: ROW 3 - Col 1, Col 2, Col 3, Col 4
output += start_relevant_phase_dates + phase_spot_1_date + phase_spot_2_date + phase_spot_3_date + phase_spot_4_date
output += end_relevant_phase_dates + end_relevant_phase_info
# GRID 2: ROW 1 - Col 1, Col 2
output += start_sunset_sunrise_info + start_sunset_info
# GRID 2: ROW 2 - Col 1, Col 2
output += start_sunrise_info + end_sunset_sunrise_info
# SITE INFO
output += data_update_info
# FOOTER
output += footer_info
# Script Files
# CLOSING FILE TAGES
output += file_closing_tags
page_name = 'index'
file_type = '.html'
file_name = page_name + file_type
f = open(file_name,'wb')
f.write(output)
f.close()
webbrowser.open_new_tab(file_name)
And then send it out to Github.
import os
os.chdir('z:\\lunarphase')
os.popen('git add index.html')
os.popen('git commit -m today')
os.popen('git push')