elongasi-de441.py

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