import customtkinter as ctk
from skyfield.api import load, wgs84
from skyfield import almanac
from datetime import datetime
import pytz
import tkinter.messagebox as messagebox
# Konfigurasi Tema UI
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
class KalkulatorAstronomi(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Kalkulator Tinggi Hilal, Elongasi & Iluminasi - Ephemeris DE441")
self.geometry("650x650") # Diperbesar untuk menampung baris iluminasi
self.resizable(False, False)
# Inisialisasi Skyfield
try:
self.ts = load.timescale()
self.eph = load('de441-gabungan.bsp')
self.bumi = self.eph['earth']
self.matahari = self.eph['sun']
self.bulan = self.eph['moon']
except Exception as e:
messagebox.showerror("Error Ephemeris", f"Gagal memuat de441-gabungan.bsp\nPastikan file ada di direktori yang sama.\nError: {e}")
self.setup_ui()
self.set_default_values()
# Mulai siklus waktu Live
self.update_live_time()
def setup_ui(self):
main_frame = ctk.CTkFrame(self)
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
# --- Judul ---
lbl_title = ctk.CTkLabel(main_frame, text="Parameter Perhitungan (WIB)", font=ctk.CTkFont(size=16, weight="bold"))
lbl_title.grid(row=0, column=0, columnspan=3, pady=(10, 20), sticky="w")
# --- Input Lokasi ---
ctk.CTkLabel(main_frame, text="Lintang (Desimal):").grid(row=1, column=0, padx=10, pady=10, sticky="w")
self.ent_lintang = ctk.CTkEntry(main_frame, width=200)
self.ent_lintang.grid(row=1, column=1, padx=10, pady=10)
ctk.CTkLabel(main_frame, text="Bujur (Desimal):").grid(row=2, column=0, padx=10, pady=10, sticky="w")
self.ent_bujur = ctk.CTkEntry(main_frame, width=200)
self.ent_bujur.grid(row=2, column=1, padx=10, pady=10)
# --- Input Waktu ---
ctk.CTkLabel(main_frame, text="Tanggal (YYYY-MM-DD):").grid(row=3, column=0, padx=10, pady=10, sticky="w")
self.ent_tanggal = ctk.CTkEntry(main_frame, width=200)
self.ent_tanggal.grid(row=3, column=1, padx=10, pady=10)
ctk.CTkLabel(main_frame, text="Waktu (HH:MM:SS):").grid(row=4, column=0, padx=10, pady=10, sticky="w")
self.ent_waktu = ctk.CTkEntry(main_frame, width=200)
self.ent_waktu.grid(row=4, column=1, padx=10, pady=10)
# --- Switch Waktu Live ---
self.switch_live = ctk.CTkSwitch(main_frame, text="Waktu LIVE (Real-time)")
self.switch_live.grid(row=4, column=2, padx=10, pady=10, sticky="w")
self.switch_live.select() # Nyalakan Live secara default
# --- Event Binding: Otomatis matikan Live saat user mengetik/klik ---
self.ent_tanggal.bind("<Key>", self.hentikan_live)
self.ent_tanggal.bind("<Button-1>", self.hentikan_live)
self.ent_waktu.bind("<Key>", self.hentikan_live)
self.ent_waktu.bind("<Button-1>", self.hentikan_live)
# --- Tombol Hitung ---
btn_hitung = ctk.CTkButton(main_frame, text="Hitung Posisi (Manual)", command=self.hitung_posisi, font=ctk.CTkFont(weight="bold"))
btn_hitung.grid(row=5, column=0, columnspan=3, pady=20)
# --- Output Hasil ---
self.frm_hasil = ctk.CTkFrame(main_frame, fg_color="transparent")
self.frm_hasil.grid(row=6, column=0, columnspan=3, sticky="ew")
self.lbl_hasil_matahari = ctk.CTkLabel(self.frm_hasil, text="Tinggi Matahari: -", font=ctk.CTkFont(size=15))
self.lbl_hasil_matahari.pack(pady=5, anchor="w")
self.lbl_hasil_bulan = ctk.CTkLabel(self.frm_hasil, text="Tinggi Hilal (Bulan): -", font=ctk.CTkFont(size=15))
self.lbl_hasil_bulan.pack(pady=5, anchor="w")
self.lbl_hasil_elongasi = ctk.CTkLabel(self.frm_hasil, text="Elongasi (Sudut Pisah): -", font=ctk.CTkFont(size=15, weight="bold"))
self.lbl_hasil_elongasi.pack(pady=5, anchor="w")
# Label Baru untuk Iluminasi
self.lbl_hasil_iluminasi = ctk.CTkLabel(self.frm_hasil, text="Iluminasi Bulan (Fase): -", font=ctk.CTkFont(size=15, weight="bold"), text_color="#2FA572")
self.lbl_hasil_iluminasi.pack(pady=5, anchor="w")
def set_default_values(self):
# Setup Default: Semarang
self.ent_lintang.insert(0, "-6.9932")
self.ent_bujur.insert(0, "110.4203")
def hentikan_live(self, event=None):
# Mematikan sakelar jika pengguna mencoba memasukkan nilai manual
if self.switch_live.get() == 1:
self.switch_live.deselect()
def update_live_time(self):
# Jika sakelar Live menyala, update kolom teks dan lakukan komputasi
if self.switch_live.get() == 1:
tz = pytz.timezone('Asia/Jakarta')
now = datetime.now(tz)
self.ent_tanggal.delete(0, 'end')
self.ent_tanggal.insert(0, now.strftime("%Y-%m-%d"))
self.ent_waktu.delete(0, 'end')
self.ent_waktu.insert(0, now.strftime("%H:%M:%S"))
# Hitung otomatis setiap detik saat Live menyala
self.hitung_posisi(mode_live=True)
# Ulangi fungsi ini setiap 1000ms (1 detik)
self.after(1000, self.update_live_time)
def hitung_posisi(self, mode_live=False):
try:
lat = float(self.ent_lintang.get())
lon = float(self.ent_bujur.get())
tgl_str = self.ent_tanggal.get()
waktu_str = self.ent_waktu.get()
tz = pytz.timezone('Asia/Jakarta')
dt_str = f"{tgl_str} {waktu_str}"
dt_obj = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S")
dt_aware = tz.localize(dt_obj)
t = self.ts.from_datetime(dt_aware)
pengamat = self.bumi + wgs84.latlon(lat, lon)
# Objek apparent Matahari dan Bulan
astro_matahari = pengamat.at(t).observe(self.matahari).apparent()
astro_bulan = pengamat.at(t).observe(self.bulan).apparent()
# Hitung Alt & Azimuth
alt_matahari, az_matahari, jarak_m = astro_matahari.altaz()
alt_bulan, az_bulan, jarak_b = astro_bulan.altaz()
# Hitung Elongasi (Sudut Pisah Geosentris/Toposentris)
elongasi = astro_matahari.separation_from(astro_bulan)
# Hitung Iluminasi Bulan (Fraksi Pencahayaan)
fraksi_iluminasi = almanac.fraction_illuminated(self.eph, 'moon', t)
persentase_iluminasi = fraksi_iluminasi * 100
# Update Label Hasil
self.lbl_hasil_matahari.configure(
text=f"Tinggi Matahari: {alt_matahari.degrees:.4f}° | Azimuth: {az_matahari.degrees:.4f}°"
)
self.lbl_hasil_bulan.configure(
text=f"Tinggi Hilal (Bulan): {alt_bulan.degrees:.4f}° | Azimuth: {az_bulan.degrees:.4f}°"
)
self.lbl_hasil_elongasi.configure(
text=f"Elongasi (Sudut Pisah): {elongasi.degrees:.4f}°"
)
self.lbl_hasil_iluminasi.configure(
text=f"Iluminasi Bulan (Fase): {persentase_iluminasi:.4f}%"
)
except ValueError:
# Cegah popup error muncul terus-menerus saat user sedang mengetik manual dan Live belum mati sepenuhnya
if not mode_live:
messagebox.showwarning("Input Error", "Pastikan format angka, tanggal (YYYY-MM-DD), dan waktu (HH:MM:SS) sudah benar.")
except Exception as e:
if not mode_live:
messagebox.showerror("Error Perhitungan", f"Terjadi kesalahan: {str(e)}")
if __name__ == "__main__":
app = KalkulatorAstronomi()
app.mainloop()