Finding the time of the Sun’s elevation (Ephem, Astral, PvLib?)

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

Leave a Comment