Erweitere astronomische Umrechnungen um Koordinaten und Winkel
This commit is contained in:
@@ -55,6 +55,14 @@ REDSHIFT_ORDER = [
|
||||
"angularDiameterMpc",
|
||||
]
|
||||
TIME_ORDER = ["utc", "ut1", "tai", "jd", "mjd", "unix", "trueSolar", "sidereal"]
|
||||
COORDINATE_ORDER = ["ra", "dec", "galLon", "galLat"]
|
||||
ANGLE_ORDER = ["deg", "arcmin", "arcsec", "rad"]
|
||||
ANGLE_TO_DEGREES = {
|
||||
"deg": Decimal("1"),
|
||||
"arcmin": Decimal("0.01666666666666666666666666667"),
|
||||
"arcsec": Decimal("0.0002777777777777777777777777778"),
|
||||
"rad": DECIMAL_180 / DECIMAL_PI,
|
||||
}
|
||||
LEAP_SECONDS = [
|
||||
("1972-01-01T00:00:00Z", 10),
|
||||
("1972-07-01T00:00:00Z", 11),
|
||||
@@ -86,13 +94,26 @@ LEAP_SECONDS = [
|
||||
("2017-01-01T00:00:00Z", 37),
|
||||
]
|
||||
|
||||
EQ_TO_GAL_MATRIX = (
|
||||
(-0.0548755604162154, -0.8734370902348850, -0.4838350155487132),
|
||||
(0.4941094278755837, -0.4448296299600112, 0.7469822444972189),
|
||||
(-0.8676661490190047, -0.1980763734312015, 0.4559837761750669),
|
||||
)
|
||||
|
||||
|
||||
def parse_decimal_input(value: str) -> Decimal | None:
|
||||
text = str(value or "").strip()
|
||||
if text == "":
|
||||
return None
|
||||
|
||||
normalized = text.replace(" ", "").replace(".", "").replace(",", ".")
|
||||
normalized = text.replace(" ", "")
|
||||
if "," in normalized and "." in normalized:
|
||||
if normalized.rfind(",") > normalized.rfind("."):
|
||||
normalized = normalized.replace(".", "").replace(",", ".")
|
||||
else:
|
||||
normalized = normalized.replace(",", "")
|
||||
elif "," in normalized:
|
||||
normalized = normalized.replace(".", "").replace(",", ".")
|
||||
if normalized.count(".") > 1:
|
||||
return None
|
||||
|
||||
@@ -147,6 +168,26 @@ def format_clock(hours_value: float) -> str:
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
|
||||
|
||||
def normalize_angle_degrees(value: float) -> float:
|
||||
normalized = math.fmod(value, 360.0)
|
||||
if normalized < 0.0:
|
||||
normalized += 360.0
|
||||
return normalized
|
||||
|
||||
|
||||
def transpose_matrix(matrix: tuple[tuple[float, float, float], ...]) -> tuple[tuple[float, float, float], ...]:
|
||||
return tuple(tuple(matrix[row][column] for row in range(3)) for column in range(3))
|
||||
|
||||
|
||||
def multiply_matrix_vector(
|
||||
matrix: tuple[tuple[float, float, float], ...], vector: tuple[float, float, float]
|
||||
) -> tuple[float, float, float]:
|
||||
return tuple(sum(matrix_row[index] * vector[index] for index in range(3)) for matrix_row in matrix)
|
||||
|
||||
|
||||
GAL_TO_EQ_MATRIX = transpose_matrix(EQ_TO_GAL_MATRIX)
|
||||
|
||||
|
||||
def leap_seconds_for_utc(dt_utc: datetime) -> int:
|
||||
count = 0
|
||||
for effective_iso, total_offset in LEAP_SECONDS:
|
||||
@@ -244,6 +285,27 @@ def adaptive_simpson(fn, start: float, end: float, epsilon: float = 1e-10, max_d
|
||||
return recurse(start, end, epsilon, whole, left_value, middle_value, right_value, max_depth)
|
||||
|
||||
|
||||
def spherical_to_cartesian(longitude_deg: float, latitude_deg: float) -> tuple[float, float, float]:
|
||||
longitude_rad = math.radians(longitude_deg)
|
||||
latitude_rad = math.radians(latitude_deg)
|
||||
cos_latitude = math.cos(latitude_rad)
|
||||
return (
|
||||
cos_latitude * math.cos(longitude_rad),
|
||||
cos_latitude * math.sin(longitude_rad),
|
||||
math.sin(latitude_rad),
|
||||
)
|
||||
|
||||
|
||||
def cartesian_to_spherical(x: float, y: float, z: float) -> tuple[float, float]:
|
||||
radius = math.sqrt((x * x) + (y * y) + (z * z))
|
||||
if radius == 0.0:
|
||||
raise ValueError("Koordinatenvektor darf nicht null sein.")
|
||||
|
||||
longitude_deg = normalize_angle_degrees(math.degrees(math.atan2(y, x)))
|
||||
latitude_deg = math.degrees(math.asin(max(-1.0, min(1.0, z / radius))))
|
||||
return longitude_deg, latitude_deg
|
||||
|
||||
|
||||
def convert_distance(payload: dict) -> dict:
|
||||
source_unit = str(payload.get("sourceUnit") or "").strip().lower()
|
||||
source_value = parse_decimal_input(payload.get("sourceValue", ""))
|
||||
@@ -352,23 +414,26 @@ def convert_time(payload: dict) -> dict:
|
||||
"sidereal": "",
|
||||
}
|
||||
|
||||
leap_seconds = leap_seconds_for_utc(dt_utc)
|
||||
ut1_fallback = format_german_datetime(dt_utc) + " (nahezu UTC)"
|
||||
tai_fallback = format_german_datetime(dt_utc + timedelta(seconds=leap_seconds))
|
||||
|
||||
if ASTROPY_AVAILABLE:
|
||||
astropy_time = AstropyTime(dt_utc, scale="utc")
|
||||
try:
|
||||
ut1_datetime = astropy_time.ut1.to_datetime(timezone=timezone.utc)
|
||||
fields["ut1"] = format_german_datetime(ut1_datetime)
|
||||
except Exception:
|
||||
fields["ut1"] = "Nicht verfuegbar"
|
||||
fields["ut1"] = ut1_fallback
|
||||
|
||||
try:
|
||||
tai_datetime = astropy_time.tai.to_datetime(timezone=timezone.utc)
|
||||
fields["tai"] = format_german_datetime(tai_datetime)
|
||||
except Exception:
|
||||
fields["tai"] = "Nicht verfuegbar"
|
||||
fields["tai"] = tai_fallback
|
||||
else:
|
||||
leap_seconds = leap_seconds_for_utc(dt_utc)
|
||||
fields["ut1"] = format_german_datetime(dt_utc) + " (nahezu UTC)"
|
||||
fields["tai"] = format_german_datetime(dt_utc + timedelta(seconds=leap_seconds))
|
||||
fields["ut1"] = ut1_fallback
|
||||
fields["tai"] = tai_fallback
|
||||
|
||||
if longitude_value is not None and math.isfinite(longitude_value):
|
||||
observer = astronomy.Observer(0.0, longitude_value, 0.0)
|
||||
@@ -393,6 +458,57 @@ def convert_time(payload: dict) -> dict:
|
||||
return {"valid": True, "fields": fields}
|
||||
|
||||
|
||||
def convert_coordinates(payload: dict) -> dict:
|
||||
source_system = str(payload.get("sourceSystem") or "").strip().lower()
|
||||
first_value = parse_decimal_input(payload.get("value1", ""))
|
||||
second_value = parse_decimal_input(payload.get("value2", ""))
|
||||
|
||||
if first_value is None or second_value is None:
|
||||
return {"valid": False, "fields": {key: "" for key in COORDINATE_ORDER}}
|
||||
|
||||
longitude = float(first_value)
|
||||
latitude = float(second_value)
|
||||
if not math.isfinite(longitude) or not math.isfinite(latitude) or latitude < -90.0 or latitude > 90.0:
|
||||
return {"valid": False, "fields": {key: "" for key in COORDINATE_ORDER}}
|
||||
|
||||
if source_system == "equatorial":
|
||||
equatorial_vector = spherical_to_cartesian(normalize_angle_degrees(longitude), latitude)
|
||||
galactic_vector = multiply_matrix_vector(EQ_TO_GAL_MATRIX, equatorial_vector)
|
||||
ra_deg, dec_deg = cartesian_to_spherical(*equatorial_vector)
|
||||
gal_lon_deg, gal_lat_deg = cartesian_to_spherical(*galactic_vector)
|
||||
elif source_system == "galactic":
|
||||
galactic_vector = spherical_to_cartesian(normalize_angle_degrees(longitude), latitude)
|
||||
equatorial_vector = multiply_matrix_vector(GAL_TO_EQ_MATRIX, galactic_vector)
|
||||
gal_lon_deg, gal_lat_deg = cartesian_to_spherical(*galactic_vector)
|
||||
ra_deg, dec_deg = cartesian_to_spherical(*equatorial_vector)
|
||||
else:
|
||||
raise ValueError("Unbekanntes Koordinatensystem.")
|
||||
|
||||
fields = {
|
||||
"ra": format_decimal_german(ra_deg, max_fraction_digits=6),
|
||||
"dec": format_decimal_german(dec_deg, max_fraction_digits=6),
|
||||
"galLon": format_decimal_german(gal_lon_deg, max_fraction_digits=6),
|
||||
"galLat": format_decimal_german(gal_lat_deg, max_fraction_digits=6),
|
||||
}
|
||||
return {"valid": True, "fields": fields}
|
||||
|
||||
|
||||
def convert_angle(payload: dict) -> dict:
|
||||
source_unit = str(payload.get("sourceUnit") or "").strip().lower()
|
||||
source_value = parse_decimal_input(payload.get("sourceValue", ""))
|
||||
|
||||
if source_unit not in ANGLE_TO_DEGREES or source_value is None:
|
||||
return {"valid": False, "fields": {key: "" for key in ANGLE_ORDER}}
|
||||
|
||||
value_in_degrees = source_value * ANGLE_TO_DEGREES[source_unit]
|
||||
fields: dict[str, str] = {}
|
||||
for unit in ANGLE_ORDER:
|
||||
converted = value_in_degrees / ANGLE_TO_DEGREES[unit]
|
||||
fields[unit] = format_decimal_german(converted, max_fraction_digits=12)
|
||||
|
||||
return {"valid": True, "fields": fields}
|
||||
|
||||
|
||||
def handle_request(payload: dict) -> dict:
|
||||
kind = str(payload.get("kind") or "").strip().lower()
|
||||
if kind == "distance":
|
||||
@@ -401,6 +517,10 @@ def handle_request(payload: dict) -> dict:
|
||||
result = convert_redshift(payload)
|
||||
elif kind == "time":
|
||||
result = convert_time(payload)
|
||||
elif kind == "coordinates":
|
||||
result = convert_coordinates(payload)
|
||||
elif kind == "angle":
|
||||
result = convert_angle(payload)
|
||||
else:
|
||||
raise ValueError("Unbekannter Umrechnungsbereich.")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user