Planeten Wechselt das Sternbild eingebaut
This commit is contained in:
+400
-2
@@ -273,6 +273,26 @@ def body_altitude_deg(
|
||||
return float(hor.altitude)
|
||||
|
||||
|
||||
def body_horizontal_coords(
|
||||
body: astronomy.Body,
|
||||
observer: astronomy.Observer,
|
||||
dt_utc: datetime,
|
||||
) -> tuple[float, float]:
|
||||
time_value = dt_to_time(dt_utc)
|
||||
eq = astronomy.Equator(body, time_value, observer, True, True)
|
||||
hor = astronomy.Horizon(time_value, observer, eq.ra, eq.dec, astronomy.Refraction.Normal)
|
||||
return float(hor.altitude), float(hor.azimuth)
|
||||
|
||||
|
||||
def body_constellation_info(
|
||||
body: astronomy.Body,
|
||||
dt_utc: datetime,
|
||||
) -> astronomy.ConstellationInfo:
|
||||
time_value = dt_to_time(dt_utc)
|
||||
equ = astronomy.EquatorFromVector(astronomy.GeoVector(body, time_value, True))
|
||||
return astronomy.Constellation(equ.ra, equ.dec)
|
||||
|
||||
|
||||
def serialize_body_position(
|
||||
key: str,
|
||||
label: str,
|
||||
@@ -419,7 +439,13 @@ def action_mercury_good_visibility_for_month(args: list[str]) -> dict:
|
||||
"local_iso": start_local.isoformat(),
|
||||
"local_date": start_local.strftime("%d.%m.%Y"),
|
||||
"local_time": start_local.strftime("%H:%M"),
|
||||
"end_local_iso": end_day[f"{period_key}_local"].isoformat() if end_day[f"{period_key}_local"] is not None else start_local.isoformat(),
|
||||
"end_local_time": end_day[f"{period_key}_local"].strftime("%H:%M") if end_day[f"{period_key}_local"] is not None else start_local.strftime("%H:%M"),
|
||||
"end_local_date": end_day["date"],
|
||||
"duration_minutes": max(
|
||||
1,
|
||||
int(round((((end_day[f"{period_key}_local"] or start_local) - start_local).total_seconds()) / 60.0)),
|
||||
),
|
||||
"duration_days": duration_days,
|
||||
})
|
||||
start_index = None
|
||||
@@ -446,6 +472,368 @@ def action_mercury_good_visibility_for_month(args: list[str]) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def action_planet_parades_for_month(args: list[str]) -> dict:
|
||||
if len(args) != 12:
|
||||
fail(
|
||||
"Aktion planet_parades_for_month erwartet 12 Argumente: latitude longitude elevation year month timezone sample_offset_minutes min_planet_alt_deg max_sun_alt_deg max_azimuth_span_deg min_planet_count min_consecutive_days",
|
||||
extra={"argv": args},
|
||||
)
|
||||
|
||||
latitude = parse_float(args[0], "Latitude")
|
||||
longitude = parse_float(args[1], "Longitude")
|
||||
elevation = parse_float(args[2], "Elevation")
|
||||
|
||||
try:
|
||||
year = int(args[3])
|
||||
month = int(args[4])
|
||||
sample_offset_minutes = int(args[6])
|
||||
min_planet_count = int(args[10])
|
||||
min_consecutive_days = int(args[11])
|
||||
except ValueError as exc:
|
||||
fail("Jahr, Monat oder Parade-Parameter sind ungueltig.", extra={"details": str(exc), "argv": args})
|
||||
|
||||
if month < 1 or month > 12:
|
||||
fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month})
|
||||
|
||||
timezone_name = args[5]
|
||||
min_planet_alt_deg = parse_float(args[7], "Minimale Planetenhoehe")
|
||||
max_sun_alt_deg = parse_float(args[8], "Maximale Sonnenhoehe")
|
||||
max_azimuth_span_deg = parse_float(args[9], "Maximale Azimutspanne")
|
||||
|
||||
try:
|
||||
tz = ZoneInfo(timezone_name)
|
||||
except Exception as exc:
|
||||
fail("Zeitzone ist ungueltig oder auf dem Server nicht verfuegbar.", extra={"details": str(exc)})
|
||||
|
||||
observer = astronomy.Observer(latitude, longitude, elevation)
|
||||
local_start = datetime(year, month, 1, 0, 0, 0, tzinfo=tz)
|
||||
if month == 12:
|
||||
local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz)
|
||||
else:
|
||||
local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz)
|
||||
|
||||
planet_defs = [
|
||||
("Merkur", astronomy.Body.Mercury, "mercury"),
|
||||
("Venus", astronomy.Body.Venus, "venus"),
|
||||
("Mars", astronomy.Body.Mars, "mars"),
|
||||
("Jupiter", astronomy.Body.Jupiter, "jupiter"),
|
||||
("Saturn", astronomy.Body.Saturn, "saturn"),
|
||||
]
|
||||
|
||||
daily_checks: list[dict] = []
|
||||
current_local = local_start
|
||||
while current_local < local_end:
|
||||
day_start_utc = current_local.astimezone(timezone.utc)
|
||||
next_day_utc = (current_local + timedelta(days=1)).astimezone(timezone.utc)
|
||||
start_time = dt_to_time(day_start_utc)
|
||||
|
||||
sunrise = search_event(astronomy.Body.Sun, astronomy.Direction.Rise, observer, start_time, next_day_utc)
|
||||
sunset = search_event(astronomy.Body.Sun, astronomy.Direction.Set, observer, start_time, next_day_utc)
|
||||
|
||||
def build_period(period_key: str, sample_local: datetime | None) -> dict:
|
||||
if sample_local is None:
|
||||
return {
|
||||
"ok": False,
|
||||
"sample_local": None,
|
||||
"sun_alt_deg": None,
|
||||
"visible_planets": [],
|
||||
"azimuth_span_deg": None,
|
||||
}
|
||||
|
||||
sample_utc = sample_local.astimezone(timezone.utc)
|
||||
sun_alt_deg = body_altitude_deg(astronomy.Body.Sun, observer, sample_utc)
|
||||
visible_planets = []
|
||||
for label, body, key in planet_defs:
|
||||
planet_alt_deg, planet_az_deg = body_horizontal_coords(body, observer, sample_utc)
|
||||
if planet_alt_deg >= min_planet_alt_deg:
|
||||
visible_planets.append({
|
||||
"key": key,
|
||||
"label": label,
|
||||
"altitude_deg": planet_alt_deg,
|
||||
"azimuth_deg": planet_az_deg,
|
||||
})
|
||||
|
||||
azimuth_span_deg = None
|
||||
if visible_planets:
|
||||
azimuths = sorted(planet["azimuth_deg"] for planet in visible_planets)
|
||||
if len(azimuths) == 1:
|
||||
azimuth_span_deg = 0.0
|
||||
else:
|
||||
wrap_gaps = [
|
||||
azimuths[index + 1] - azimuths[index]
|
||||
for index in range(len(azimuths) - 1)
|
||||
]
|
||||
wrap_gaps.append((azimuths[0] + 360.0) - azimuths[-1])
|
||||
azimuth_span_deg = 360.0 - max(wrap_gaps)
|
||||
|
||||
is_ok = (
|
||||
sun_alt_deg <= max_sun_alt_deg
|
||||
and len(visible_planets) >= min_planet_count
|
||||
and azimuth_span_deg is not None
|
||||
and azimuth_span_deg <= max_azimuth_span_deg
|
||||
)
|
||||
|
||||
return {
|
||||
"ok": is_ok,
|
||||
"sample_local": sample_local,
|
||||
"sun_alt_deg": sun_alt_deg,
|
||||
"visible_planets": visible_planets,
|
||||
"azimuth_span_deg": azimuth_span_deg,
|
||||
}
|
||||
|
||||
morning_local = None
|
||||
if sunrise is not None:
|
||||
sunrise_local = time_to_datetime(sunrise).astimezone(tz)
|
||||
morning_local = sunrise_local - timedelta(minutes=sample_offset_minutes)
|
||||
|
||||
evening_local = None
|
||||
if sunset is not None:
|
||||
sunset_local = time_to_datetime(sunset).astimezone(tz)
|
||||
evening_local = sunset_local + timedelta(minutes=sample_offset_minutes)
|
||||
|
||||
daily_checks.append({
|
||||
"date": current_local.strftime("%d.%m.%Y"),
|
||||
"morning": build_period("morning", morning_local),
|
||||
"evening": build_period("evening", evening_local),
|
||||
})
|
||||
|
||||
current_local += timedelta(days=1)
|
||||
|
||||
def build_windows(period_key: str, label_text: str) -> list[dict]:
|
||||
events = []
|
||||
start_index = None
|
||||
|
||||
for index, day in enumerate(daily_checks):
|
||||
period = day[period_key]
|
||||
is_ok = bool(period["ok"])
|
||||
if is_ok and start_index is None:
|
||||
start_index = index
|
||||
|
||||
is_last = index == len(daily_checks) - 1
|
||||
if start_index is not None and (not is_ok or is_last):
|
||||
end_index = index if (is_ok and is_last) else index - 1
|
||||
duration_days = end_index - start_index + 1
|
||||
if duration_days >= min_consecutive_days:
|
||||
window_days = daily_checks[start_index:end_index + 1]
|
||||
start_day = window_days[0]
|
||||
end_day = window_days[-1]
|
||||
start_period = start_day[period_key]
|
||||
start_local = start_period["sample_local"]
|
||||
|
||||
if start_local is not None:
|
||||
range_text = (
|
||||
start_day["date"]
|
||||
if start_index == end_index
|
||||
else f'{start_day["date"]} bis {end_day["date"]}'
|
||||
)
|
||||
|
||||
common_labels: set[str] | None = None
|
||||
max_visible_labels: list[str] = []
|
||||
max_visible_count = 0
|
||||
min_span_deg = None
|
||||
|
||||
for window_day in window_days:
|
||||
day_period = window_day[period_key]
|
||||
visible_labels = [planet["label"] for planet in day_period["visible_planets"]]
|
||||
visible_set = set(visible_labels)
|
||||
common_labels = visible_set if common_labels is None else (common_labels & visible_set)
|
||||
if len(visible_labels) > max_visible_count:
|
||||
max_visible_count = len(visible_labels)
|
||||
max_visible_labels = visible_labels
|
||||
span_value = day_period["azimuth_span_deg"]
|
||||
if span_value is not None:
|
||||
min_span_deg = span_value if min_span_deg is None else min(min_span_deg, span_value)
|
||||
|
||||
parade_labels = sorted(common_labels) if common_labels else max_visible_labels
|
||||
planet_text = ", ".join(parade_labels)
|
||||
count_text = f"{max_visible_count} Planeten"
|
||||
detail_parts = [count_text]
|
||||
if planet_text:
|
||||
detail_parts.append(planet_text)
|
||||
detail_parts.append(range_text)
|
||||
|
||||
events.append({
|
||||
"period": period_key,
|
||||
"label": f"Planetenparade am {label_text} ({'; '.join(detail_parts)})",
|
||||
"planet_count": max_visible_count,
|
||||
"planet_labels": parade_labels,
|
||||
"best_azimuth_span_deg": min_span_deg,
|
||||
"utc_iso": start_local.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"),
|
||||
"local_iso": start_local.isoformat(),
|
||||
"local_date": start_local.strftime("%d.%m.%Y"),
|
||||
"local_time": start_local.strftime("%H:%M"),
|
||||
"end_local_iso": end_day[period_key]["sample_local"].isoformat() if end_day[period_key]["sample_local"] is not None else start_local.isoformat(),
|
||||
"end_local_time": end_day[period_key]["sample_local"].strftime("%H:%M") if end_day[period_key]["sample_local"] is not None else start_local.strftime("%H:%M"),
|
||||
"end_local_date": end_day["date"],
|
||||
"duration_minutes": max(
|
||||
1,
|
||||
int(round((((end_day[period_key]["sample_local"] or start_local) - start_local).total_seconds()) / 60.0)),
|
||||
),
|
||||
"duration_days": duration_days,
|
||||
})
|
||||
start_index = None
|
||||
|
||||
return events
|
||||
|
||||
events = build_windows("morning", "Morgen")
|
||||
events.extend(build_windows("evening", "Abend"))
|
||||
events.sort(key=lambda item: item["local_iso"])
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "planet_parades_for_month",
|
||||
"selected": {
|
||||
"year": year,
|
||||
"month": month,
|
||||
"timezone": timezone_name,
|
||||
"sample_offset_minutes": sample_offset_minutes,
|
||||
"min_planet_alt_deg": min_planet_alt_deg,
|
||||
"max_sun_alt_deg": max_sun_alt_deg,
|
||||
"max_azimuth_span_deg": max_azimuth_span_deg,
|
||||
"min_planet_count": min_planet_count,
|
||||
"min_consecutive_days": min_consecutive_days,
|
||||
},
|
||||
"events": events,
|
||||
}
|
||||
|
||||
|
||||
def action_planet_constellation_changes_for_month(args: list[str]) -> dict:
|
||||
if len(args) != 3:
|
||||
fail(
|
||||
"Aktion planet_constellation_changes_for_month erwartet 3 Argumente: year month timezone",
|
||||
extra={"argv": args},
|
||||
)
|
||||
|
||||
try:
|
||||
year = int(args[0])
|
||||
month = int(args[1])
|
||||
except ValueError as exc:
|
||||
fail("Jahr oder Monat sind ungueltig.", extra={"details": str(exc), "argv": args})
|
||||
|
||||
if month < 1 or month > 12:
|
||||
fail("Monat muss zwischen 1 und 12 liegen.", extra={"month": month})
|
||||
|
||||
timezone_name = args[2]
|
||||
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 = datetime(year, month, 1, 0, 0, 0, tzinfo=tz)
|
||||
if month == 12:
|
||||
local_end = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=tz)
|
||||
utc_next_month = datetime(year + 1, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
||||
else:
|
||||
local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz)
|
||||
utc_next_month = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=timezone.utc)
|
||||
|
||||
utc_start = datetime(year, month, 1, 0, 0, 0, tzinfo=timezone.utc)
|
||||
utc_end = utc_next_month - timedelta(seconds=1)
|
||||
|
||||
constellation_names = {
|
||||
"Aries": "Widder",
|
||||
"Taurus": "Stier",
|
||||
"Gemini": "Zwillinge",
|
||||
"Cancer": "Krebs",
|
||||
"Leo": "Loewe",
|
||||
"Virgo": "Jungfrau",
|
||||
"Libra": "Waage",
|
||||
"Scorpius": "Skorpion",
|
||||
"Sagittarius": "Schuetze",
|
||||
"Capricornus": "Steinbock",
|
||||
"Aquarius": "Wassermann",
|
||||
"Pisces": "Fische",
|
||||
"Ophiuchus": "Schlangentraeger",
|
||||
"Cetus": "Walfisch",
|
||||
}
|
||||
|
||||
planet_defs = [
|
||||
("Merkur", astronomy.Body.Mercury, "mercury"),
|
||||
("Venus", astronomy.Body.Venus, "venus"),
|
||||
("Mars", astronomy.Body.Mars, "mars"),
|
||||
("Jupiter", astronomy.Body.Jupiter, "jupiter"),
|
||||
("Saturn", astronomy.Body.Saturn, "saturn"),
|
||||
("Uranus", astronomy.Body.Uranus, "uranus"),
|
||||
("Neptun", astronomy.Body.Neptune, "neptune"),
|
||||
]
|
||||
|
||||
def display_constellation_name(info: astronomy.ConstellationInfo) -> str:
|
||||
return constellation_names.get(info.name, info.name)
|
||||
|
||||
def refine_change_time(body: astronomy.Body, left_utc: datetime, right_utc: datetime, left_symbol: str) -> datetime:
|
||||
left = left_utc
|
||||
right = right_utc
|
||||
|
||||
for _ in range(40):
|
||||
mid = left + (right - left) / 2
|
||||
mid_symbol = body_constellation_info(body, mid).symbol
|
||||
if mid_symbol == left_symbol:
|
||||
left = mid
|
||||
else:
|
||||
right = mid
|
||||
|
||||
return right
|
||||
|
||||
events: list[dict] = []
|
||||
scan_step = timedelta(hours=6)
|
||||
|
||||
for label, body, key in planet_defs:
|
||||
left_utc = utc_start
|
||||
left_info = body_constellation_info(body, left_utc)
|
||||
current_utc = min(utc_end, left_utc + scan_step)
|
||||
|
||||
while current_utc <= utc_end:
|
||||
current_info = body_constellation_info(body, current_utc)
|
||||
if current_info.symbol != left_info.symbol:
|
||||
change_utc = refine_change_time(body, left_utc, current_utc, left_info.symbol)
|
||||
before_info = body_constellation_info(body, change_utc - timedelta(seconds=1))
|
||||
after_info = body_constellation_info(body, change_utc)
|
||||
local_dt = change_utc.astimezone(tz)
|
||||
|
||||
if local_start <= local_dt < local_end:
|
||||
from_name = display_constellation_name(before_info)
|
||||
to_name = display_constellation_name(after_info)
|
||||
events.append({
|
||||
"planet_key": key,
|
||||
"planet_label": label,
|
||||
"from_constellation_symbol": before_info.symbol,
|
||||
"from_constellation_name": from_name,
|
||||
"to_constellation_symbol": after_info.symbol,
|
||||
"to_constellation_name": to_name,
|
||||
"label": f"{label} wechselt von {from_name} nach {to_name}",
|
||||
"utc_iso": change_utc.isoformat().replace("+00:00", "Z"),
|
||||
"local_iso": local_dt.isoformat(),
|
||||
"local_date": local_dt.strftime("%d.%m.%Y"),
|
||||
"local_time": local_dt.strftime("%H:%M"),
|
||||
})
|
||||
|
||||
left_utc = change_utc + timedelta(seconds=1)
|
||||
left_info = body_constellation_info(body, left_utc)
|
||||
current_utc = min(utc_end, left_utc + scan_step)
|
||||
continue
|
||||
|
||||
left_utc = current_utc
|
||||
left_info = current_info
|
||||
current_utc = min(utc_end, current_utc + scan_step)
|
||||
if current_utc == left_utc:
|
||||
break
|
||||
|
||||
events.sort(key=lambda item: item["local_iso"])
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"action": "planet_constellation_changes_for_month",
|
||||
"selected": {
|
||||
"year": year,
|
||||
"month": month,
|
||||
"timezone": timezone_name,
|
||||
"utc_scan_start": utc_start.isoformat().replace("+00:00", "Z"),
|
||||
"utc_scan_end": utc_end.isoformat().replace("+00:00", "Z"),
|
||||
},
|
||||
"events": events,
|
||||
}
|
||||
|
||||
|
||||
def action_month_sky_context(args: list[str]) -> dict:
|
||||
if len(args) != 8:
|
||||
fail(
|
||||
@@ -3619,7 +4007,7 @@ def action_satellite_passes(args: list[str]) -> dict:
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 2:
|
||||
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "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", "moon_star_occultations_for_month"]})
|
||||
fail("Es wurde keine Aktion uebergeben.", extra={"available_actions": ["sun_moon_rise_set", "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"]})
|
||||
|
||||
action = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
@@ -3739,12 +4127,22 @@ def main() -> None:
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return
|
||||
|
||||
if action == "planet_parades_for_month":
|
||||
result = action_planet_parades_for_month(args)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return
|
||||
|
||||
if action == "planet_constellation_changes_for_month":
|
||||
result = action_planet_constellation_changes_for_month(args)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return
|
||||
|
||||
if action == "moon_star_occultations_for_month":
|
||||
result = action_moon_star_occultations_for_month(args)
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
return
|
||||
|
||||
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "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", "moon_star_occultations_for_month"]})
|
||||
fail("Unbekannte Aktion.", extra={"action": action, "available_actions": ["sun_moon_rise_set", "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"]})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user