Is there a command in python’s ephem, astral, or pvlib packages to find the time of day that the sun is at a particular angle below the horizon (from a specifled lat/long and on a specified date) as a single command? Rather than have to iterate repetitively minute by minute to find the time when the angle is matched? Or any other package that might be able to do it? And is it possible to show a working example of this? Been unsuccessful at this for many days now. The iterative version works but takes a significant length of time… 1440 times more than it needs to (one calculation for every minute in the day).
Here is code that I have tried and it takes a long time to find all the values to then plot the graphic.
import tkinter as tk
from datetime import datetime, timedelta
import ephem, math
global UpTime, DnTime
# Function to calculate twilight colors
def get_twilight_color(altitude, TheTime):
global UpTime, DnTime
if altitude <= -1:
Output = "orange" # Civil twilight
if altitude <= -6:
Output = "magenta" # Nautical twilight
if UpTime != '':
if DnTime == '':
DnTime = TheTime
if altitude >= -6:
if UpTime == '':
UpTime = TheTime
if altitude <= -12:
Output = "purple" # Astronomical twilight
if altitude <= -18:
Output = "black" # Night
if altitude >= -1:
Output = "white" # Sunrise
if altitude >= 1:
Output = "blue" # Sunrise
return Output, UpTime, DnTime
def parse_altitude(altitude_str):
altitude_degrees = math.degrees(altitude_str)
return altitude_degrees
from datetime import datetime, timedelta
def is_dst(date):
# Determine if the given date is during daylight saving time in Melbourne
# DST in Melbourne starts on the first Sunday in October and ends on the first Sunday in April
start_date = datetime(date.year, 10, 1)
while start_date.weekday() != 6: # Find the first Sunday in October
start_date += timedelta(days=1)
end_date = datetime(date.year, 4, 1)
while end_date.weekday() != 6: # Find the first Sunday in April
end_date += timedelta(days=1)
print(f"Checking DST for {date}: Start Date = {start_date}, End Date = {end_date}")
return start_date <= date <= end_date
def convert_to_lmt(utc_time):
# Determine the Melbourne time offset (UTC+10 or UTC+11)
melbourne_offset = 11 if is_dst(utc_time) else 10
print (melbourne_offset)
# Apply the offset to the UTC time
lmt_time = utc_time + timedelta(hours=melbourne_offset)
return lmt_time
def convert_to_lmtHrs(utc_time):
# Determine the Melbourne time offset (UTC+10 or UTC+11)
melbourne_offset = 11 if is_dst(utc_time) else 10
# Apply the offset to the UTC time
lmt_time = utc_time + timedelta(hours=melbourne_offset)
return lmt_time.strftime("%H:%M")
def display_info(event):
x = event.x
column_index = x // 4 # Convert the x-coordinate to the corresponding column index
if 0 <= column_index < len(Cells):
info = Cells[column_index]
date_label.config(text=f"Date: {info[1]}")
up_time_label.config(text=f"Civil Twilight Start: {info[2]}")
dn_time_label.config(text=f"Civil Twilight End: {info[3]}")
else:
date_label.config(text="")
up_time_label.config(text="")
dn_time_label.config(text="")
# Create a tkinter window
root = tk.Tk()
root.title("Daylight Visualization")
# Canvas dimensions
canvas_width = 1461 # Width of each vertical stripe in pixels
canvas_height = 1440 # Height of the canvas (24 hours)
# Create a canvas to draw on
canvas = tk.Canvas(root, width=canvas_width, height=canvas_height/2, bg="white")
canvas.pack()
# Location of Melbourne, Australia
melbourne = ephem.Observer()
melbourne.lat = "-37.8136"
melbourne.lon = "144.9631"
melbourne.elev = 0
start_date = datetime(2023, 1, 1)
end_date = datetime(2023, 12, 31)
# List to store the date values
date_values = []
# Generate date values and append them to the list
current_date = start_date
Day = 0
LastColor=""
Cells = []
while current_date <= end_date:
date_values.append([current_date.year, current_date.month, current_date.day])
current_date += timedelta(days=1)
DSTcurrent_date = convert_to_lmt(current_date)
# Calculate twilight times for the specified date
melbourne.date = DSTcurrent_date.strftime("%Y/%m/%d")
local_midnight = DSTcurrent_date.replace(hour=14, minute=0, second=0, microsecond=0)
current_time = local_midnight
# Define time intervals for each pixel (3 minutes per pixel)
time_interval = timedelta(minutes=1)
# Calculate twilight colors and draw the vertical stripe
ColorStartY = 0
UpTime=""
DnTime=""
for y in range(canvas_height):
melbourne.date = current_time.strftime("%Y/%m/%d %H:%M:%S")
sun = ephem.Sun()
sun.compute(melbourne)
altitude = sun.alt
altitude = parse_altitude(altitude)
twilight_color, WhenUp, WhenDn = get_twilight_color(altitude, current_time)
if LastColor != twilight_color:
canvas.create_line(Day, ColorStartY, Day, y, width = 4, fill=LastColor)
LastColor = twilight_color
ColorStartY = y/2
current_time += time_interval
canvas.create_line(Day, ColorStartY, Day, canvas_height, width = 4, fill=LastColor)
CivTwiStart = convert_to_lmtHrs(WhenUp)
CivTwiEnd = convert_to_lmtHrs(WhenDn)
Cells.append([Day, DSTcurrent_date.strftime("%Y/%m/%d"), CivTwiStart, CivTwiEnd])
Day += 4
# Bind mouse events to display information
canvas.bind("<Motion>", display_info)
# Labels to display information
date_label = tk.Label(root, text="", font=("Helvetica", 24))
date_label.pack()
up_time_label = tk.Label(root, text="", font=("Helvetica", 24))
up_time_label.pack()
dn_time_label = tk.Label(root, text="", font=("Helvetica", 24))
dn_time_label.pack()
# Start the tkinter main loop
root.mainloop()
Don’t iterate minute by minute… A basic bisection will be much faster.
And the
ephem
module does it for you anyway. Always look at the doc, see rhodesmill.org/pyephem/quick.html#observer-horizon