import os, datetime, math, urllib.request, ssl
import numpy as np
import customtkinter as ctk
from tkinter import messagebox
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from skyfield.api import Loader, wgs84
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def download_custom_file(filename, url):
filepath = os.path.join(BASE_DIR, filename)
if not os.path.exists(filepath):
try:
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req) as response, open(filepath, 'wb') as out_file:
out_file.write(response.read())
except Exception as e: print(f"Gagal mengunduh {filename}: {e}")
class Menu11App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Qiblah Direction & Times (Modul 11)")
self.geometry("900x600")
ctk.set_appearance_mode("Dark")
try: _create_unverified_https_context = ssl._create_unverified_context
except AttributeError: pass
else: ssl._create_default_https_context = _create_unverified_https_context
self.load_obj = Loader(BASE_DIR)
self.eph = self.load_obj('de421.bsp')
self.ts = self.load_obj.timescale()
self.setup_ui()
def setup_ui(self):
self.sidebar = ctk.CTkFrame(self, width=250)
self.sidebar.pack(side="left", fill="y", padx=10, pady=10)
now = datetime.datetime.now()
ctk.CTkLabel(self.sidebar, text="QIBLAH DIRECTION", font=("Segoe UI", 16, "bold")).pack(pady=20)
self.entry_y = ctk.CTkEntry(self.sidebar, placeholder_text="Tahun"); self.entry_y.insert(0, str(now.year)); self.entry_y.pack(pady=5)
self.entry_m = ctk.CTkEntry(self.sidebar, placeholder_text="Bulan"); self.entry_m.insert(0, str(now.month)); self.entry_m.pack(pady=5)
self.entry_d = ctk.CTkEntry(self.sidebar, placeholder_text="Tanggal"); self.entry_d.insert(0, str(now.day)); self.entry_d.pack(pady=5)
self.entry_lat = ctk.CTkEntry(self.sidebar, placeholder_text="Latitude"); self.entry_lat.insert(0, "-7.0667"); self.entry_lat.pack(pady=5)
self.entry_lon = ctk.CTkEntry(self.sidebar, placeholder_text="Longitude"); self.entry_lon.insert(0, "110.4100"); self.entry_lon.pack(pady=5)
self.entry_tz = ctk.CTkEntry(self.sidebar, placeholder_text="Timezone"); self.entry_tz.insert(0, "7.0"); self.entry_tz.pack(pady=5)
self.radio_var = ctk.StringVar(value="sun")
ctk.CTkRadioButton(self.sidebar, text="Sun is at Qiblah", variable=self.radio_var, value="sun").pack(pady=5)
ctk.CTkRadioButton(self.sidebar, text="Shadow is at Qiblah", variable=self.radio_var, value="shadow").pack(pady=5)
ctk.CTkButton(self.sidebar, text="▶ HITUNG WAKTU", command=self.hitung).pack(pady=10)
ctk.CTkButton(self.sidebar, text="🗺️ TAMPILKAN PETA", fg_color="#00695C", command=self.show_map).pack(pady=5)
self.textbox = ctk.CTkTextbox(self, font=("Consolas", 14))
self.textbox.pack(side="right", fill="both", expand=True, padx=10, pady=10)
def hitung(self):
self.textbox.delete("1.0", "end")
y, m, d = int(self.entry_y.get()), int(self.entry_m.get()), int(self.entry_d.get())
lat, lon, tz = float(self.entry_lat.get()), float(self.entry_lon.get()), float(self.entry_tz.get())
# Hitung Azimut Kiblat
phi_k, lam_k = math.radians(21.4225), math.radians(39.8262)
phi, lam = math.radians(lat), math.radians(lon)
y_q = math.sin(lam_k - lam)
x_q = math.cos(phi)*math.tan(phi_k) - math.sin(phi)*math.cos(lam_k-lam)
qiblah_angle = (math.degrees(math.atan2(y_q, x_q)) + 360.0) % 360.0
target_az = qiblah_angle if self.radio_var.get() == "sun" else (qiblah_angle + 180.0) % 360.0
t0 = self.ts.utc(y, m, d, -int(tz))
t1 = self.ts.utc(y, m, d, 24 - int(tz))
tt_array = np.linspace(t0.tt, t1.tt, 1440)
obs = (self.eph['earth'] + wgs84.latlon(lat, lon)).at(self.ts.tt_jd(tt_array)).observe(self.eph['sun']).apparent()
alt_arr, az_arr, _ = obs.altaz()
diffs = (az_arr.degrees - target_az + 180.0) % 360.0 - 180.0
crossings = []
for i in range(len(diffs) - 1):
if (diffs[i] <= 0 and diffs[i+1] > 0) or (diffs[i] >= 0 and diffs[i+1] < 0):
if abs(diffs[i] - diffs[i+1]) < 180.0 and alt_arr.degrees[i] > 0:
frac = abs(diffs[i]) / (abs(diffs[i]) + abs(diffs[i+1]) + 1e-9)
tt_res = tt_array[i] + frac * (tt_array[i+1] - tt_array[i])
crossings.append(self.ts.tt_jd(tt_res))
time_1, time_2 = "----", "----"
if len(crossings) > 0: time_1 = (crossings[0].utc_datetime() + datetime.timedelta(hours=tz)).strftime("%H:%M:%S")
if len(crossings) > 1: time_2 = (crossings[1].utc_datetime() + datetime.timedelta(hours=tz)).strftime("%H:%M:%S")
self.textbox.insert("end", f"QIBLAH DIRECTION & TIMES\n{'='*60}\n")
self.textbox.insert("end", f"Arah Kiblat (True North): {qiblah_angle:.2f}°\n")
self.textbox.insert("end", f"Mode Waktu: {'Matahari di Arah Kiblat' if self.radio_var.get()=='sun' else 'Bayangan di Arah Kiblat'}\n")
self.textbox.insert("end", f"{'-'*60}\nTanggal Waktu 1 Waktu 2\n")
self.textbox.insert("end", f"{d:02d}/{m:02d}/{y} {time_1} {time_2}\n")
def show_map(self):
try:
map_file = "map_topografi.jpg"
download_custom_file(map_file, "https://hisabmu.com/aifikih/berbagi/map_topografi.jpg")
lons = np.linspace(-180, 180, 360)
lats = np.linspace(-90, 90, 180)
LONS, LATS = np.meshgrid(lons, lats)
phi_k, lam_k = math.radians(21.4225), math.radians(39.8262)
phi, lam = np.radians(LATS), np.radians(LONS)
y = np.sin(lam_k - lam)
x = np.cos(phi) * np.tan(phi_k) - np.sin(phi) * np.cos(lam_k - lam)
Q = (np.degrees(np.arctan2(y, x)) + 360) % 360
fig, ax = plt.subplots(figsize=(10, 5))
if os.path.exists(map_file):
from PIL import Image
ax.imshow(Image.open(map_file), extent=[-180, 180, -90, 90], aspect='auto')
ax.contour(LONS, LATS, Q, levels=np.arange(0, 361, 10), colors='black', linewidths=0.6, alpha=0.8)
ax.scatter(39.8262, 21.4225, color='black', marker='*', s=200, label='Makkah (Kaaba)')
ax.set_title("Qiblah World Map")
ax.grid(True)
ax.legend()
plt.show()
except Exception as e:
messagebox.showerror("Error", str(e))
if __name__ == "__main__":
app = Menu11App()
app.mainloop()