Planetensichtbarkeit auf PY umgestellt
This commit is contained in:
+361
-1
@@ -396,6 +396,361 @@ PLANETS = [
|
||||
("Neptun", astronomy.Body.Neptune),
|
||||
]
|
||||
|
||||
PLANET_VISIBILITY_ROWS = [
|
||||
{
|
||||
"key": "mercury",
|
||||
"name": "MERKUR",
|
||||
"body": astronomy.Body.Mercury,
|
||||
"symbol": "Me",
|
||||
"color": "#b0b0c8",
|
||||
"glow": "rgba(176,176,200,.45)",
|
||||
"type": "planet",
|
||||
},
|
||||
{
|
||||
"key": "venus",
|
||||
"name": "VENUS",
|
||||
"body": astronomy.Body.Venus,
|
||||
"symbol": "Ve",
|
||||
"color": "#f5e090",
|
||||
"glow": "rgba(245,224,144,.55)",
|
||||
"type": "planet",
|
||||
},
|
||||
{
|
||||
"key": "mars",
|
||||
"name": "MARS",
|
||||
"body": astronomy.Body.Mars,
|
||||
"symbol": "Ma",
|
||||
"color": "#e8602a",
|
||||
"glow": "rgba(232,96,42,.5)",
|
||||
"type": "planet",
|
||||
},
|
||||
{
|
||||
"key": "jupiter",
|
||||
"name": "JUPITER",
|
||||
"body": astronomy.Body.Jupiter,
|
||||
"symbol": "Ju",
|
||||
"color": "#d4b880",
|
||||
"glow": "rgba(212,184,128,.5)",
|
||||
"type": "planet",
|
||||
},
|
||||
{
|
||||
"key": "saturn",
|
||||
"name": "SATURN",
|
||||
"body": astronomy.Body.Saturn,
|
||||
"symbol": "Sa",
|
||||
"color": "#e8d898",
|
||||
"glow": "rgba(232,216,152,.45)",
|
||||
"type": "planet",
|
||||
},
|
||||
{
|
||||
"key": "uranus",
|
||||
"name": "URANUS",
|
||||
"body": astronomy.Body.Uranus,
|
||||
"symbol": "Ur",
|
||||
"color": "#70e0e8",
|
||||
"glow": "rgba(112,224,232,.4)",
|
||||
"type": "planet",
|
||||
},
|
||||
{
|
||||
"key": "neptune",
|
||||
"name": "NEPTUN",
|
||||
"body": astronomy.Body.Neptune,
|
||||
"symbol": "Ne",
|
||||
"color": "#4070e0",
|
||||
"glow": "rgba(64,112,224,.4)",
|
||||
"type": "planet",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def hour_from_utc_start(event_time: astronomy.Time | None, utc_start: datetime) -> float | None:
|
||||
if event_time is None:
|
||||
return None
|
||||
return (time_to_datetime(event_time) - utc_start).total_seconds() / 3600.0
|
||||
|
||||
|
||||
def build_visibility_segments(
|
||||
body: astronomy.Body,
|
||||
observer: astronomy.Observer,
|
||||
utc_start: datetime,
|
||||
utc_end: datetime,
|
||||
) -> tuple[list[dict], float | None, float | None]:
|
||||
start_time = dt_to_time(utc_start)
|
||||
start_eq = astronomy.Equator(body, start_time, observer, True, True)
|
||||
start_hor = astronomy.Horizon(start_time, observer, start_eq.ra, start_eq.dec, astronomy.Refraction.Normal)
|
||||
is_up = float(start_hor.altitude) > 0.0
|
||||
|
||||
events: list[dict] = []
|
||||
|
||||
def gather(direction: astronomy.Direction) -> None:
|
||||
search_dt = utc_start - timedelta(minutes=15)
|
||||
while True:
|
||||
limit_days = max(0.01, min(3.0, (utc_end + timedelta(hours=2) - search_dt).total_seconds() / 86400.0))
|
||||
event_time = astronomy.SearchRiseSet(body, observer, direction, dt_to_time(search_dt), limit_days, 1.05)
|
||||
if event_time is None:
|
||||
break
|
||||
|
||||
hour_value = hour_from_utc_start(event_time, utc_start)
|
||||
if hour_value is None or hour_value > 24.1:
|
||||
break
|
||||
|
||||
events.append({
|
||||
"hour": hour_value,
|
||||
"type": "rise" if direction == astronomy.Direction.Rise else "set",
|
||||
})
|
||||
search_dt = time_to_datetime(event_time) + timedelta(minutes=1)
|
||||
|
||||
gather(astronomy.Direction.Rise)
|
||||
gather(astronomy.Direction.Set)
|
||||
events.sort(key=lambda item: item["hour"])
|
||||
|
||||
segments: list[dict] = []
|
||||
segment_start = 0.0 if is_up else None
|
||||
|
||||
for event in events:
|
||||
clamped_hour = max(-0.1, min(24.1, float(event["hour"])))
|
||||
if event["type"] == "rise" and not is_up:
|
||||
segment_start = max(0.0, clamped_hour)
|
||||
is_up = True
|
||||
elif event["type"] == "set" and is_up:
|
||||
segment_end = min(24.0, clamped_hour)
|
||||
if segment_start is not None and segment_end > segment_start:
|
||||
segments.append({"from": segment_start, "to": segment_end})
|
||||
segment_start = None
|
||||
is_up = False
|
||||
|
||||
if is_up and segment_start is not None:
|
||||
segments.append({"from": segment_start, "to": 24.0})
|
||||
|
||||
actual_rise_hour = segments[0]["from"] if segments else None
|
||||
actual_set_hour = segments[-1]["to"] if segments else None
|
||||
|
||||
if segments and segments[0]["from"] < 0.08:
|
||||
search_dt = utc_start - timedelta(hours=36)
|
||||
previous_rise: astronomy.Time | None = None
|
||||
while True:
|
||||
limit_days = max(0.01, min(4.0, (utc_start - search_dt).total_seconds() / 86400.0 + 1.0))
|
||||
rise_time = astronomy.SearchRiseSet(body, observer, astronomy.Direction.Rise, dt_to_time(search_dt), limit_days, 2.5)
|
||||
if rise_time is None:
|
||||
break
|
||||
rise_dt = time_to_datetime(rise_time)
|
||||
if rise_dt >= utc_start:
|
||||
break
|
||||
previous_rise = rise_time
|
||||
search_dt = rise_dt + timedelta(minutes=1)
|
||||
previous_rise_hour = hour_from_utc_start(previous_rise, utc_start)
|
||||
if previous_rise_hour is not None:
|
||||
actual_rise_hour = previous_rise_hour
|
||||
|
||||
if segments and segments[-1]["to"] > 23.95:
|
||||
limit_days = 2.0
|
||||
next_set = astronomy.SearchRiseSet(
|
||||
body,
|
||||
observer,
|
||||
astronomy.Direction.Set,
|
||||
dt_to_time(utc_start + timedelta(hours=23)),
|
||||
limit_days,
|
||||
2.0,
|
||||
)
|
||||
next_set_hour = hour_from_utc_start(next_set, utc_start)
|
||||
if next_set_hour is not None and next_set_hour > 23.95:
|
||||
actual_set_hour = next_set_hour
|
||||
|
||||
return segments, actual_rise_hour, actual_set_hour
|
||||
|
||||
|
||||
def compute_best_segments(
|
||||
segments: list[dict],
|
||||
astro_dawn_hour: float | None,
|
||||
astro_dusk_hour: float | None,
|
||||
) -> list[dict]:
|
||||
if astro_dawn_hour is None or astro_dusk_hour is None:
|
||||
return []
|
||||
|
||||
result: list[dict] = []
|
||||
for segment in segments:
|
||||
start_hour = float(segment["from"])
|
||||
end_hour = float(segment["to"])
|
||||
|
||||
if start_hour < astro_dawn_hour:
|
||||
best_end = min(end_hour, astro_dawn_hour)
|
||||
if best_end - start_hour > 0.05:
|
||||
result.append({"from": start_hour, "to": best_end})
|
||||
|
||||
if end_hour > astro_dusk_hour:
|
||||
best_start = max(start_hour, astro_dusk_hour)
|
||||
best_end = min(end_hour, 24.0)
|
||||
if best_end - best_start > 0.05:
|
||||
result.append({"from": best_start, "to": best_end})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def moon_phase_code(phase_angle: float) -> tuple[str, bool]:
|
||||
waning = phase_angle > 180.0
|
||||
if phase_angle < 22.5 or phase_angle >= 337.5:
|
||||
return "NM", waning
|
||||
if not waning and phase_angle < 67.5:
|
||||
return "ZS", waning
|
||||
if not waning and phase_angle < 112.5:
|
||||
return "ZH", waning
|
||||
if not waning and phase_angle < 157.5:
|
||||
return "ZG", waning
|
||||
if 157.5 <= phase_angle < 202.5:
|
||||
return "VM", waning
|
||||
if waning and phase_angle < 247.5:
|
||||
return "AG", waning
|
||||
if waning and phase_angle < 292.5:
|
||||
return "AH", waning
|
||||
return "AS", waning
|
||||
|
||||
|
||||
def action_planet_visibility_chart(args: list[str]) -> dict:
|
||||
if len(args) != 5:
|
||||
fail(
|
||||
"Aktion planet_visibility_chart erwartet 5 Argumente: latitude longitude elevation date timezone",
|
||||
extra={"argv": args},
|
||||
)
|
||||
|
||||
latitude = parse_float(args[0], "Latitude")
|
||||
longitude = parse_float(args[1], "Longitude")
|
||||
elevation = parse_float(args[2], "Elevation")
|
||||
date_text = args[3]
|
||||
timezone_name = args[4]
|
||||
|
||||
try:
|
||||
local_start = datetime.strptime(date_text, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
fail("Datum muss im Format YYYY-MM-DD uebergeben werden.", extra={"date": date_text})
|
||||
|
||||
try:
|
||||
tz = ZoneInfo(timezone_name)
|
||||
except Exception as exc:
|
||||
fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)})
|
||||
|
||||
local_start = local_start.replace(tzinfo=tz)
|
||||
local_noon = local_start + timedelta(hours=12)
|
||||
local_end = local_start + timedelta(days=1)
|
||||
utc_start = local_start.astimezone(timezone.utc)
|
||||
utc_end = local_end.astimezone(timezone.utc)
|
||||
observer = astronomy.Observer(latitude, longitude, elevation)
|
||||
start_time = dt_to_time(utc_start)
|
||||
|
||||
def search_altitude(direction: astronomy.Direction, altitude_deg: float) -> astronomy.Time | None:
|
||||
limit_days = (utc_end - utc_start).total_seconds() / 86400.0
|
||||
result = astronomy.SearchAltitude(astronomy.Body.Sun, observer, direction, start_time, limit_days, altitude_deg)
|
||||
if result is None:
|
||||
return None
|
||||
if time_to_datetime(result) >= utc_end:
|
||||
return None
|
||||
return result
|
||||
|
||||
sun_rise = search_event(astronomy.Body.Sun, astronomy.Direction.Rise, observer, start_time, utc_end)
|
||||
sun_set = search_event(astronomy.Body.Sun, astronomy.Direction.Set, observer, start_time, utc_end)
|
||||
civil_dawn = search_altitude(astronomy.Direction.Rise, -6.0)
|
||||
civil_dusk = search_altitude(astronomy.Direction.Set, -6.0)
|
||||
nautical_dawn = search_altitude(astronomy.Direction.Rise, -12.0)
|
||||
nautical_dusk = search_altitude(astronomy.Direction.Set, -12.0)
|
||||
astro_dawn = search_altitude(astronomy.Direction.Rise, -18.0)
|
||||
astro_dusk = search_altitude(astronomy.Direction.Set, -18.0)
|
||||
|
||||
twilight = {
|
||||
"sunRiseH": hour_from_utc_start(sun_rise, utc_start),
|
||||
"sunSetH": hour_from_utc_start(sun_set, utc_start),
|
||||
"civDawnH": hour_from_utc_start(civil_dawn, utc_start),
|
||||
"civDuskH": hour_from_utc_start(civil_dusk, utc_start),
|
||||
"nautDawnH": hour_from_utc_start(nautical_dawn, utc_start),
|
||||
"nautDuskH": hour_from_utc_start(nautical_dusk, utc_start),
|
||||
"astroDawnH": hour_from_utc_start(astro_dawn, utc_start),
|
||||
"astroDuskH": hour_from_utc_start(astro_dusk, utc_start),
|
||||
}
|
||||
|
||||
moon_time = dt_to_time(local_noon.astimezone(timezone.utc))
|
||||
moon_phase = float(astronomy.MoonPhase(moon_time))
|
||||
moon_illumination = astronomy.Illumination(astronomy.Body.Moon, moon_time)
|
||||
moon_label, moon_waning = moon_phase_code(moon_phase)
|
||||
moon_segments, moon_rise_hour, moon_set_hour = build_visibility_segments(
|
||||
astronomy.Body.Moon,
|
||||
observer,
|
||||
utc_start,
|
||||
utc_end,
|
||||
)
|
||||
moon_best_segments = compute_best_segments(
|
||||
moon_segments,
|
||||
twilight["astroDawnH"],
|
||||
twilight["astroDuskH"],
|
||||
)
|
||||
|
||||
rows: list[dict] = []
|
||||
for row_def in PLANET_VISIBILITY_ROWS:
|
||||
midday_time = dt_to_time(local_noon.astimezone(timezone.utc))
|
||||
illumination = astronomy.Illumination(row_def["body"], midday_time)
|
||||
segments, rise_hour, set_hour = build_visibility_segments(
|
||||
row_def["body"],
|
||||
observer,
|
||||
utc_start,
|
||||
utc_end,
|
||||
)
|
||||
rows.append({
|
||||
"key": row_def["key"],
|
||||
"type": row_def["type"],
|
||||
"name": row_def["name"],
|
||||
"symbol": row_def["symbol"],
|
||||
"color": row_def["color"],
|
||||
"glow": row_def["glow"],
|
||||
"segs": segments,
|
||||
"bestSegs": compute_best_segments(
|
||||
segments,
|
||||
twilight["astroDawnH"],
|
||||
twilight["astroDuskH"],
|
||||
),
|
||||
"actualRiseH": rise_hour,
|
||||
"actualSetH": set_hour,
|
||||
"magnitude": float(illumination.mag),
|
||||
})
|
||||
|
||||
rows.append({
|
||||
"key": "moon",
|
||||
"type": "moon",
|
||||
"name": "MOND",
|
||||
"symbol": moon_label,
|
||||
"color": "#d0d8f0",
|
||||
"glow": "rgba(208,216,240,.55)",
|
||||
"segs": moon_segments,
|
||||
"bestSegs": moon_best_segments,
|
||||
"actualRiseH": moon_rise_hour,
|
||||
"actualSetH": moon_set_hour,
|
||||
"illum": float(moon_illumination.phase_fraction),
|
||||
"waning": moon_waning,
|
||||
"phase": moon_phase,
|
||||
"label": moon_label,
|
||||
})
|
||||
|
||||
utc_offset_hours = local_noon.utcoffset().total_seconds() / 3600.0
|
||||
timezone_abbr = local_noon.tzname() or timezone_name
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "planet_visibility_chart",
|
||||
"date": date_text,
|
||||
"timezone": timezone_name,
|
||||
"timezone_abbr": timezone_abbr,
|
||||
"utc_offset_hours": utc_offset_hours,
|
||||
"location": {
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"elevation": elevation,
|
||||
},
|
||||
"twilight": twilight,
|
||||
"rows": rows,
|
||||
"moon": {
|
||||
"illum": float(moon_illumination.phase_fraction),
|
||||
"waning": moon_waning,
|
||||
"phase": moon_phase,
|
||||
"label": moon_label,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def action_planet_rise_set(args: list[str]) -> dict:
|
||||
if len(args) != 5:
|
||||
@@ -4259,6 +4614,11 @@ def main() -> None:
|
||||
print(json.dumps(result, ensure_ascii=True))
|
||||
return
|
||||
|
||||
if action == "planet_visibility_chart":
|
||||
result = action_planet_visibility_chart(args)
|
||||
print(json.dumps(result, ensure_ascii=True))
|
||||
return
|
||||
|
||||
if action == "month_sky_context":
|
||||
result = action_month_sky_context(args)
|
||||
print(json.dumps(result, ensure_ascii=True))
|
||||
@@ -4354,7 +4714,7 @@ def main() -> None:
|
||||
print(json.dumps(result, ensure_ascii=True))
|
||||
return
|
||||
|
||||
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})
|
||||
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "solar_longitude_to_datetime", "current_solar_longitude", "astronomical_conversions", "comet_brightnesses", "moon_star_occultations", "moon_phase_details", "satellite_passes", "planet_rise_set", "planet_visibility_chart", "month_sky_context", "moon_phases_for_month", "golden_handle_for_month", "season_changes_for_month", "time_changes_for_month", "moon_planet_approaches", "moon_deep_sky_approaches_for_month", "planet_bright_star_approaches_for_month", "planet_conjunctions_for_month", "eclipses_for_month", "moon_apsides_for_month", "sun_apsides_for_month", "inner_planet_elongations_for_month", "venus_peak_magnitude_for_month", "outer_planet_events_for_month", "outer_planet_stations_for_month", "jupiter_moons_one_side_for_month", "mercury_good_visibility_for_month", "planet_parades_for_month", "planet_constellation_changes_for_month", "moon_star_occultations_for_month"]})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user