diff --git a/pysrc/target/calc_alt.py b/modules/lithium.pytarget/target/calc_alt.py similarity index 61% rename from pysrc/target/calc_alt.py rename to modules/lithium.pytarget/target/calc_alt.py index 61f2b2f3..fa05b9b2 100644 --- a/pysrc/target/calc_alt.py +++ b/modules/lithium.pytarget/target/calc_alt.py @@ -1,31 +1,36 @@ -from astropy.coordinates import EarthLocation, AltAz, SkyCoord import datetime +from astropy.coordinates import EarthLocation, AltAz, SkyCoord from astroplan import Observer, FixedTarget, moon_illumination from astropy.time import Time import astropy.units as u import numpy as np from dataclasses import dataclass from loguru import logger +import argparse +from typing import List, Tuple, Dict, Optional @dataclass class ObservationResult: - altitude: float - azimuth: float - highest_altitude: float | None - available_shoot_time: float | None + altitude: Optional[float] + azimuth: Optional[float] + highest_altitude: Optional[float] + available_shoot_time: Optional[float] is_above_horizon: bool def calculate_current_alt(observation_start_time: datetime.datetime, observer_location: EarthLocation, ra: float, dec: float) -> ObservationResult: """ - Calculate the current altitude and azimuth of a celestial object. + Calculate the current altitude and azimuth of a celestial object given its RA and Dec. - :param observation_start_time: The start time of the observation. - :param observer_location: The location of the observer on Earth. - :param ra: Right Ascension of the celestial object in degrees. - :param dec: Declination of the celestial object in degrees. - :return: An ObservationResult containing the altitude, azimuth, highest altitude, available shoot time, and whether the object is above the horizon. + Parameters: + observation_start_time (datetime.datetime): The start time of the observation. + observer_location (EarthLocation): The location of the observer. + ra (float): Right Ascension of the celestial object in degrees. + dec (float): Declination of the celestial object in degrees. + + Returns: + ObservationResult: A dataclass containing the altitude, azimuth, highest altitude, available shoot time, and whether the object is above the horizon. """ logger.debug( f"Calculating current altitude and azimuth for RA: {ra}, Dec: {dec} at {observation_start_time}") @@ -61,39 +66,44 @@ def calculate_current_alt(observation_start_time: datetime.datetime, observer_lo return ObservationResult(altitude=None, azimuth=None, highest_altitude=None, available_shoot_time=None, is_above_horizon=False) -def calculate_highest_alt(observation_start_time: datetime.datetime, observer_location: EarthLocation, ra: float, dec: float) -> float | None: +def calculate_highest_alt(observation_start_time: datetime.datetime, observer_location: EarthLocation, ra: float, dec: float) -> Optional[float]: """ Calculate the highest altitude of a celestial object during the night. - :param observation_start_time: The start time of the observation. - :param observer_location: The location of the observer on Earth. - :param ra: Right Ascension of the celestial object in degrees. - :param dec: Declination of the celestial object in degrees. - :return: The highest altitude in degrees, or None if the object is not observable. + Parameters: + observation_start_time (datetime.datetime): The start time of the observation. + observer_location (EarthLocation): The location of the observer. + ra (float): Right Ascension of the celestial object in degrees. + dec (float): Declination of the celestial object in degrees. + + Returns: + Optional[float]: The highest altitude of the celestial object in degrees, or None if not observable. """ logger.debug( f"Calculating highest altitude for RA: {ra}, Dec: {dec} during the night of {observation_start_time}") try: + # Create a FixedTarget object for the celestial object target = FixedTarget(coord=SkyCoord( ra, dec, unit='deg', frame='icrs'), name="Target") observer = Observer(location=observer_location) observation_time = Time(observation_start_time) - # Calculate the beginning and end of the night + # Calculate the start and end of the night begin_night = observer.twilight_evening_astronomical( observation_time, which='nearest') end_night = observer.twilight_morning_astronomical( observation_time, which='next') if begin_night < end_night: - # Calculate the time of the object's meridian transit (highest point in the sky) + # Calculate the time of the object's meridian transit midnight = observer.target_meridian_transit_time( observation_time, target, which='nearest') if begin_night < midnight < end_night: max_altitude = observer.altaz(midnight, target).alt else: + # Calculate the altitudes at the start and end of the night nighttime_altitudes = [observer.altaz(time, target).alt for time in [ begin_night, end_night]] max_altitude = max(nighttime_altitudes) @@ -107,20 +117,24 @@ def calculate_highest_alt(observation_start_time: datetime.datetime, observer_lo return None -def calculate_available_shoot_time(observation_start_time: datetime.datetime, observer_location: EarthLocation, ra: float, dec: float) -> float | None: +def calculate_available_shoot_time(observation_start_time: datetime.datetime, observer_location: EarthLocation, ra: float, dec: float) -> Optional[float]: """ Calculate the available shooting time for a celestial object during the night. - :param observation_start_time: The start time of the observation. - :param observer_location: The location of the observer on Earth. - :param ra: Right Ascension of the celestial object in degrees. - :param dec: Declination of the celestial object in degrees. - :return: The available shooting time in hours, or None if the object is not observable. + Parameters: + observation_start_time (datetime.datetime): The start time of the observation. + observer_location (EarthLocation): The location of the observer. + ra (float): Right Ascension of the celestial object in degrees. + dec (float): Declination of the celestial object in degrees. + + Returns: + Optional[float]: The available shooting time in hours, or None if not observable. """ logger.debug( f"Calculating available shooting time for RA: {ra}, Dec: {dec} during the night of {observation_start_time}") try: + # Create a FixedTarget object for the celestial object target = FixedTarget(coord=SkyCoord( ra, dec, unit=u.deg), name="Target") observer = Observer(location=observer_location) @@ -137,6 +151,7 @@ def calculate_available_shoot_time(observation_start_time: datetime.datetime, ob "Invalid time period: Begin of night is after the end of night.") return None + # Generate time intervals throughout the night times = begin_of_night + delta_t * \ np.arange(0, 1, step_size * 60.0 / delta_t.sec) altitudes = observer.altaz(times, target).alt @@ -145,6 +160,7 @@ def calculate_available_shoot_time(observation_start_time: datetime.datetime, ob observable_time_indices = altitudes > 50 * u.deg observable_times = times[observable_time_indices] + # Estimate the total observable time in hours observable_time_estimate = len(observable_times) * step_size / 60 logger.info( f"Calculated available shooting time: {observable_time_estimate} hours") @@ -158,8 +174,11 @@ def calculate_moon_phase(observation_time: datetime.datetime) -> float: """ Calculate the moon phase as a percentage of illumination. - :param observation_time: The time of the observation. - :return: The moon phase as a percentage, or -1 if an error occurs. + Parameters: + observation_time (datetime.datetime): The time of the observation. + + Returns: + float: The moon phase as a percentage of illumination. """ logger.debug(f"Calculating moon phase for {observation_time}") @@ -172,15 +191,18 @@ def calculate_moon_phase(observation_time: datetime.datetime) -> float: return -1 -def evaluate_observation_conditions(observer_location: EarthLocation, observation_time: datetime.datetime, ra: float, dec: float) -> dict: +def evaluate_observation_conditions(observer_location: EarthLocation, observation_time: datetime.datetime, ra: float, dec: float) -> Dict[str, Optional[float]]: """ Evaluate the observation conditions for a celestial object. - :param observer_location: The location of the observer on Earth. - :param observation_time: The time of the observation. - :param ra: Right Ascension of the celestial object in degrees. - :param dec: Declination of the celestial object in degrees. - :return: A dictionary containing the current altitude, azimuth, highest altitude, available shoot time, whether the object is above the horizon, moon phase, and an overall score. + Parameters: + observer_location (EarthLocation): The location of the observer. + observation_time (datetime.datetime): The time of the observation. + ra (float): Right Ascension of the celestial object in degrees. + dec (float): Declination of the celestial object in degrees. + + Returns: + Dict[str, Optional[float]]: A dictionary containing the current altitude, azimuth, highest altitude, available shoot time, whether the object is above the horizon, moon phase, and an overall score. """ logger.debug( f"Evaluating observation conditions for RA: {ra}, Dec: {dec} at {observation_time}") @@ -190,18 +212,15 @@ def evaluate_observation_conditions(observer_location: EarthLocation, observatio observation_time, observer_location, ra, dec) moon_phase_percent = calculate_moon_phase(observation_time) - # Initialize score score = 100 - # Adjust score based on altitude + # Adjust score based on altitude and moon phase if result.altitude is not None and result.altitude < 30: - score -= 20 # Lower score if altitude is less than 30 degrees + score -= 20 - # Adjust score based on moon phase if moon_phase_percent > 50: - score -= 30 # Lower score if the moon is more than 50% illuminated + score -= 30 - # Ensure the score is not negative score = max(score, 0) logger.info(f"Evaluation complete with score: {score}") @@ -219,15 +238,18 @@ def evaluate_observation_conditions(observer_location: EarthLocation, observatio return {} -def plan_multiple_observations(observer_location: EarthLocation, start_time: datetime.datetime, ra_dec_list: list, days: int = 1) -> dict: +def plan_multiple_observations(observer_location: EarthLocation, start_time: datetime.datetime, ra_dec_list: List[Tuple[float, float]], days: int = 1) -> Dict[str, Dict[str, Optional[float]]]: """ Plan observations for multiple celestial objects over a specified number of days. - :param observer_location: The location of the observer on Earth. - :param start_time: The start time of the observation period. - :param ra_dec_list: A list of tuples containing the Right Ascension and Declination of the celestial objects. - :param days: The number of days over which to plan the observations. - :return: A dictionary containing the observation schedule. + Parameters: + observer_location (EarthLocation): The location of the observer. + start_time (datetime.datetime): The start time of the observations. + ra_dec_list (List[Tuple[float, float]]): A list of tuples containing the RA and Dec of the celestial objects. + days (int): The number of days to plan observations for. + + Returns: + Dict[str, Dict[str, Optional[float]]]: A dictionary containing the observation conditions for each object on each day. """ logger.debug( f"Planning observations for multiple objects over {days} days starting from {start_time}") @@ -247,25 +269,46 @@ def plan_multiple_observations(observer_location: EarthLocation, start_time: dat return {} -# Example usage: +def main(): + """ + Main function to parse command line arguments and calculate observation conditions. + """ + parser = argparse.ArgumentParser( + description="Calculate celestial object observation conditions.") + parser.add_argument("--lat", type=float, required=True, + help="Latitude of the observer location.") + parser.add_argument("--lon", type=float, required=True, + help="Longitude of the observer location.") + parser.add_argument("--height", type=float, default=0, + help="Height of the observer location in meters.") + parser.add_argument("--ra", type=float, required=True, + help="Right Ascension of the celestial object in degrees.") + parser.add_argument("--dec", type=float, required=True, + help="Declination of the celestial object in degrees.") + parser.add_argument("--days", type=int, default=1, + help="Number of days to plan observations for.") + args = parser.parse_args() + + # Create an EarthLocation object for the observer's location + location = EarthLocation(lat=args.lat * u.deg, + lon=args.lon * u.deg, height=args.height * u.m) + observation_time = datetime.datetime.now() + + # Calculate the current altitude and azimuth + result = calculate_current_alt( + observation_time, location, args.ra, args.dec) + print( + f"Current Altitude: {result.altitude} degrees, Azimuth: {result.azimuth} degrees") + print(f"Highest Altitude: {result.highest_altitude} degrees") + print(f"Available Shooting Time: {result.available_shoot_time} hours") + print(f"Is Above Horizon: {result.is_above_horizon}") + + # Plan observations for multiple days + ra_dec_list = [(args.ra, args.dec)] + schedule = plan_multiple_observations( + location, observation_time, ra_dec_list, days=args.days) + print(schedule) + + if __name__ == "__main__": - try: - location = EarthLocation( - lat=34.0522*u.deg, lon=-118.2437*u.deg, height=71*u.m) - observation_time = datetime.datetime.now() - ra, dec = 10.684, 41.269 # Example coordinates (RA, Dec in degrees) - - result = calculate_current_alt(observation_time, location, ra, dec) - print( - f"Current Altitude: {result.altitude} degrees, Azimuth: {result.azimuth} degrees") - print(f"Highest Altitude: {result.highest_altitude} degrees") - print(f"Available Shooting Time: {result.available_shoot_time} hours") - print(f"Is Above Horizon: {result.is_above_horizon}") - - # Plan observations for the next 3 days - ra_dec_list = [(10.684, 41.269), (83.822, -5.391)] - schedule = plan_multiple_observations( - location, observation_time, ra_dec_list, days=3) - print(schedule) - except Exception as e: - logger.error(f"Error in main execution: {e}") + main() diff --git a/pysrc/target/calc_azalt.py b/modules/lithium.pytarget/target/calc_azalt.py similarity index 100% rename from pysrc/target/calc_azalt.py rename to modules/lithium.pytarget/target/calc_azalt.py diff --git a/pysrc/target/calc_fov.py b/modules/lithium.pytarget/target/calc_fov.py similarity index 69% rename from pysrc/target/calc_fov.py rename to modules/lithium.pytarget/target/calc_fov.py index ff251b75..0e1abc78 100644 --- a/pysrc/target/calc_fov.py +++ b/modules/lithium.pytarget/target/calc_fov.py @@ -1,5 +1,7 @@ import numpy as np from loguru import logger +import argparse +from typing import Tuple def setup_logging(): @@ -32,7 +34,7 @@ def ra_dec2fixed_xyz(ra: float, dec: float) -> np.ndarray: return np.array([x, y, z]) -def fixed_xyz2ra_dec(vector: np.ndarray) -> tuple[float, float]: +def fixed_xyz2ra_dec(vector: np.ndarray) -> Tuple[float, float]: """ Convert fixed XYZ coordinates to RA/DEC. @@ -57,12 +59,12 @@ def fixed_xyz2ra_dec(vector: np.ndarray) -> tuple[float, float]: def calc_fov_points(x_pixels: int, x_pixel_size: float, y_pixels: int, y_pixel_size: float, focal_length: int, - target_ra: float, target_dec: float, camera_rotation: float) -> tuple[tuple[float, float], - tuple[float, + target_ra: float, target_dec: float, camera_rotation: float) -> Tuple[Tuple[float, float], + Tuple[float, float], - tuple[float, + Tuple[float, float], - tuple[float, float]]: + Tuple[float, float]]: """ Calculate the field of view (FOV) points in RA/DEC coordinates for a camera given its parameters. @@ -142,12 +144,41 @@ def calc_fov_points(x_pixels: int, x_pixel_size: float, y_pixels: int, y_pixel_s return corners -# Example usage: -if __name__ == "__main__": +def main(): + parser = argparse.ArgumentParser( + description="Calculate the field of view (FOV) points in RA/DEC coordinates for a camera.") + parser.add_argument('--x_pixels', type=int, required=True, + help='Number of pixels in the x direction') + parser.add_argument('--x_pixel_size', type=float, required=True, + help='Size of each pixel in the x direction in micrometers') + parser.add_argument('--y_pixels', type=int, required=True, + help='Number of pixels in the y direction') + parser.add_argument('--y_pixel_size', type=float, required=True, + help='Size of each pixel in the y direction in micrometers') + parser.add_argument('--focal_length', type=int, required=True, + help='Focal length of the camera in millimeters') + parser.add_argument('--target_ra', type=float, required=True, + help='Target right ascension in degrees') + parser.add_argument('--target_dec', type=float, + required=True, help='Target declination in degrees') + parser.add_argument('--camera_rotation', type=float, + default=0.0, help='Rotation of the camera in degrees') + + args = parser.parse_args() + # Set up logging setup_logging() - fov_points = calc_fov_points(x_pixels=4000, x_pixel_size=3.75, y_pixels=3000, y_pixel_size=3.75, focal_length=500, - target_ra=180.0, target_dec=45.0, camera_rotation=0.0) - for idx, point in enumerate(fov_points, start=1): - print(f"Corner {idx}: RA = {point[0]:.6f}°, DEC = {point[1]:.6f}°") + try: + fov_points = calc_fov_points(x_pixels=args.x_pixels, x_pixel_size=args.x_pixel_size, y_pixels=args.y_pixels, + y_pixel_size=args.y_pixel_size, focal_length=args.focal_length, + target_ra=args.target_ra, target_dec=args.target_dec, camera_rotation=args.camera_rotation) + for idx, point in enumerate(fov_points, start=1): + print(f"Corner {idx}: RA = {point[0]:.6f}°, DEC = {point[1]:.6f}°") + except Exception as e: + logger.error(f"Error calculating FOV points: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/pysrc/target/calc_twilight.py b/modules/lithium.pytarget/target/calc_twilight.py similarity index 76% rename from pysrc/target/calc_twilight.py rename to modules/lithium.pytarget/target/calc_twilight.py index a76d4c1b..ec9bb65e 100644 --- a/pysrc/target/calc_twilight.py +++ b/modules/lithium.pytarget/target/calc_twilight.py @@ -1,10 +1,19 @@ +import sys from astroplan import Observer from astropy.time import Time -from astropy.coordinates import Angle +from astropy.coordinates import Angle, EarthLocation from datetime import datetime, timedelta from zoneinfo import ZoneInfo from typing import Dict, Union, Optional from loguru import logger +import argparse + + +def setup_logging(): + """Set up the loguru logging configuration to log both to console and to a file.""" + logger.add("twilight_calculation.log", level="DEBUG", + format="{time} {level} {message}", rotation="10 MB") + logger.info("Logging setup complete.") def calculate_twilight(observer: Observer, time: Time, time_offset: ZoneInfo) -> Dict[str, Dict[str, str]]: @@ -176,29 +185,52 @@ def format_time(t): return ( return {} -# Example usage: +def main(): + parser = argparse.ArgumentParser( + description="Calculate twilight and golden hour times for a given location and time.") + parser.add_argument('--lat', type=float, required=True, + help='Latitude of the observer in degrees') + parser.add_argument('--lon', type=float, required=True, + help='Longitude of the observer in degrees') + parser.add_argument('--height', type=float, default=0, + help='Height of the observer above sea level in meters') + parser.add_argument('--date', type=str, required=True, + help='Date and time of the observation (format: YYYY-MM-DD HH:MM:SS)') + parser.add_argument('--timezone', type=str, required=True, + help='Timezone of the observation (e.g., America/Los_Angeles)') + + args = parser.parse_args() + + # Set up logging + setup_logging() + + try: + observer_location = EarthLocation( + lat=args.lat * u.deg, lon=args.lon * u.deg, height=args.height * u.m) + observer = Observer(location=observer_location) + observation_time = Time(datetime.strptime( + args.date, '%Y-%m-%d %H:%M:%S')) + time_offset = ZoneInfo(args.timezone) + + # Calculate twilight times + twilight_times = calculate_twilight( + observer, observation_time, time_offset) + print("Twilight times:", twilight_times) + + # Find when the Sun reaches a specific altitude (-6 degrees is often used for civil twilight) + sun_altitude_time = find_sun_altitude_time( + observer, -6, observation_time, time_offset) + print("Sun altitude time (-6 degrees):", sun_altitude_time) + + # Calculate golden hour times + golden_hour_times = calculate_golden_hour( + observer, observation_time, time_offset) + print("Golden hour times:", golden_hour_times) + + except Exception as e: + logger.error(f"Error in main execution: {e}") + sys.exit(1) + + if __name__ == "__main__": - from astropy.coordinates import EarthLocation - import astropy.units as u - - # Observer location example - observer_location = EarthLocation( - lat=34.0522*u.deg, lon=-118.2437*u.deg, height=71*u.m) - observer = Observer(location=observer_location) - observation_time = Time(datetime.now()) - time_offset = ZoneInfo("America/Los_Angeles") - - # Calculate twilight times - twilight_times = calculate_twilight( - observer, observation_time, time_offset) - print("Twilight times:", twilight_times) - - # Find when the Sun reaches a specific altitude (-6 degrees is often used for civil twilight) - sun_altitude_time = find_sun_altitude_time( - observer, -6, observation_time, time_offset) - print("Sun altitude time (-6 degrees):", sun_altitude_time) - - # Calculate golden hour times - golden_hour_times = calculate_golden_hour( - observer, observation_time, time_offset) - print("Golden hour times:", golden_hour_times) + main() diff --git a/pysrc/target/find_all.py b/modules/lithium.pytarget/target/find_all.py similarity index 68% rename from pysrc/target/find_all.py rename to modules/lithium.pytarget/target/find_all.py index 1ac0b72a..a8344d2d 100644 --- a/pysrc/target/find_all.py +++ b/modules/lithium.pytarget/target/find_all.py @@ -1,14 +1,14 @@ -from astropy.coordinates import EarthLocation +from astropy.coordinates import EarthLocation, AltAz, SkyCoord +from astropy.time import Time import pandas as pd from pathlib import Path import numpy as np -from astropy.coordinates import AltAz, SkyCoord -from astropy.time import Time import datetime import re from pydantic import BaseModel, field_validator from typing import List, Optional, Dict, Tuple from loguru import logger +import argparse from calc_alt import calculate_star_info @@ -24,10 +24,10 @@ class DSO(BaseModel): """Data model for Deep Sky Object (DSO).""" name: str - ra: Optional[float] = None # 赤经 (0 <= ra <= 360) - dec: Optional[float] = None # 赤纬 (-90 <= dec <= 90) + ra: Optional[float] = None # Right Ascension (0 <= ra <= 360) + dec: Optional[float] = None # Declination (-90 <= dec <= 90) alias: Optional[str] = None - magnitude: Optional[float] = None # 星等 + magnitude: Optional[float] = None # Magnitude altitude_curve: Optional[List[float]] = None azimuth_curve: Optional[List[float]] = None @@ -49,9 +49,7 @@ def validate_dec(cls, v, info): def setup_logging(): - """ - Set up the loguru logging configuration to log both to console and to a file. - """ + """Set up the loguru logging configuration to log both to console and to a file.""" logger.add("dso_search.log", level="DEBUG", format="{time} {level} {message}", rotation="10 MB") logger.info("Logging setup complete.") @@ -112,11 +110,11 @@ def search_DSO(to_search_name: str, observer_location: EarthLocation, date_time_ results = results[(results['magnitude'] >= magnitude_range[0]) & ( results['magnitude'] <= magnitude_range[1])] if ra_range: - results = results[(results['ra'] >= ra_range[0]) & ( - results['ra'] <= ra_range[1])] + results = results[(results['ra'] >= ra_range[0]) + & (results['ra'] <= ra_range[1])] if dec_range: - results = results[(results['dec'] >= dec_range[0]) & ( - results['dec'] <= dec_range[1])] + results = results[(results['dec'] >= dec_range[0]) + & (results['dec'] <= dec_range[1])] if alias: results = results[results['alias'].str.contains( alias, case=False, na=False)] @@ -184,37 +182,57 @@ def batch_search_DSO(names: List[str], observer_location: EarthLocation, date_ti return results -# 调用示例 -if __name__ == "__main__": - # 设置日志 +def main(): + parser = argparse.ArgumentParser( + description="Search for Deep Sky Objects (DSOs) and calculate their altitude and azimuth curves.") + parser.add_argument('--lat', type=float, required=True, + help='Latitude of the observer in degrees') + parser.add_argument('--lon', type=float, required=True, + help='Longitude of the observer in degrees') + parser.add_argument('--height', type=float, default=0, + help='Height of the observer above sea level in meters') + parser.add_argument('--date', type=str, required=True, + help='Date and time of the observation (format: YYYY-MM-DD HH:MM:SS)') + parser.add_argument('--names', type=str, nargs='+', + required=True, help='Names of the DSOs to search for') + parser.add_argument('--magnitude_range', type=float, + nargs=2, help='Magnitude range for filtering DSOs') + parser.add_argument('--ra_range', type=float, nargs=2, + help='Right Ascension range for filtering DSOs') + parser.add_argument('--dec_range', type=float, nargs=2, + help='Declination range for filtering DSOs') + parser.add_argument('--alias', type=str, help='Alias for filtering DSOs') + + args = parser.parse_args() + + # Set up logging setup_logging() - # 定义观测者位置和时间 - observer_location = EarthLocation( - lat=34.0522, lon=-118.2437, height=100) # 例如洛杉矶 - date_time_string = datetime.datetime( - 2024, 8, 29, 22, 0, 0) # 2024年8月29日22点 - - # 单一DSO搜索 - dso_name = "M42" - dso_results = search_DSO(dso_name, observer_location, date_time_string, - magnitude_range=(0, 10), - ra_range=(0, 360), - dec_range=(-90, 90), - alias="Orion") - for dso in dso_results: - print( - f"Name: {dso.name}, Altitude Curve: {dso.altitude_curve}, Azimuth Curve: {dso.azimuth_curve}") - - # 批量DSO搜索 - dso_names = ["M42", "NGC224"] - batch_results = batch_search_DSO(dso_names, observer_location, date_time_string, - magnitude_range=(0, 10), - ra_range=(0, 360), - dec_range=(-90, 90), - alias="Andromeda") - for name, results in batch_results.items(): - print(f"Results for {name}:") - for dso in results: - print( - f"Name: {dso.name}, Altitude Curve: {dso.altitude_curve}, Azimuth Curve: {dso.azimuth_curve}") + try: + observer_location = EarthLocation( + lat=args.lat * u.deg, lon=args.lon * u.deg, height=args.height * u.m) + date_time_string = datetime.datetime.strptime( + args.date, '%Y-%m-%d %H:%M:%S') + + # Batch search DSOs + batch_results = batch_search_DSO(args.names, observer_location, date_time_string, + magnitude_range=tuple( + args.magnitude_range) if args.magnitude_range else None, + ra_range=tuple( + args.ra_range) if args.ra_range else None, + dec_range=tuple( + args.dec_range) if args.dec_range else None, + alias=args.alias) + for name, results in batch_results.items(): + print(f"Results for {name}:") + for dso in results: + print( + f"Name: {dso.name}, Altitude Curve: {dso.altitude_curve}, Azimuth Curve: {dso.azimuth_curve}") + + except Exception as e: + logger.error(f"Error in main execution: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/pysrc/target/find_named.py b/modules/lithium.pytarget/target/find_named.py similarity index 77% rename from pysrc/target/find_named.py rename to modules/lithium.pytarget/target/find_named.py index d843952e..93173ecf 100644 --- a/pysrc/target/find_named.py +++ b/modules/lithium.pytarget/target/find_named.py @@ -8,6 +8,7 @@ from dataclasses import dataclass from enum import Enum from loguru import logger +import argparse this_file_path = Path(__file__) star_file = this_file_path.parent / 'NamedStars.csv' @@ -157,31 +158,45 @@ def get_light_star_list(observation_location: EarthLocation, observation_time: d return filtered_star_list -# Example Usage -if __name__ == "__main__": +def main(): + parser = argparse.ArgumentParser( + description="Find named stars visible from a given location and time.") + parser.add_argument('--lat', type=float, required=True, + help='Latitude of the observer in degrees') + parser.add_argument('--lon', type=float, required=True, + help='Longitude of the observer in degrees') + parser.add_argument('--height', type=float, default=0, + help='Height of the observer above sea level in meters') + parser.add_argument('--date', type=str, required=True, + help='Date and time of the observation (format: YYYY-MM-DD HH:MM:SS)') + parser.add_argument('--max_magnitude', type=int, default=2, + help='Maximum magnitude of stars to include') + parser.add_argument('--range_filter', type=str, nargs='*', + help='Cardinal directions to filter the stars') + parser.add_argument('--constellation_filter', type=str, + help='Constellation to filter the stars') + parser.add_argument('--constellation_zh_filter', type=str, + help='Constellation Chinese name to filter the stars') + parser.add_argument('--name_filter', type=str, + help='Star name to filter the stars') + parser.add_argument('--show_name_filter', type=str, + help='Star Chinese name to filter the stars') + + args = parser.parse_args() + # Set up logging setup_logging() - # Example location: New York City, USA - observation_location = EarthLocation( - lat=40.7128*u.deg, lon=-74.0060*u.deg, height=10*u.m) - - # Observation time: Current time in UTC - observation_time = datetime.datetime.now().astimezone(ZoneInfo("UTC")) - - # Maximum magnitude of stars to display - max_magnitude = 2 - - # Range filter: Only show stars in the north and east skies - range_filter = ['north', 'east'] - - # Constellation filter: Only show stars in the Orion constellation - constellation_filter = 'Ori' - - # Call the function to get the list of visible stars try: + observation_location = EarthLocation( + lat=args.lat * u.deg, lon=args.lon * u.deg, height=args.height * u.m) + observation_time = datetime.datetime.strptime( + args.date, '%Y-%m-%d %H:%M:%S').astimezone(ZoneInfo("UTC")) + + # Get the list of visible stars visible_stars = get_light_star_list( - observation_location, observation_time, max_magnitude, range_filter, constellation_filter) + observation_location, observation_time, args.max_magnitude, args.range_filter, + args.constellation_filter, args.constellation_zh_filter, args.name_filter, args.show_name_filter) # Print the results if visible_stars: @@ -197,3 +212,7 @@ def get_light_star_list(observation_location: EarthLocation, observation_time: d except Exception as e: logger.error(f"An error occurred: {e}") print(f"An error occurred: {e}") + + +if __name__ == "__main__": + main() diff --git a/pysrc/target/planet.py b/modules/lithium.pytarget/target/planet.py similarity index 100% rename from pysrc/target/planet.py rename to modules/lithium.pytarget/target/planet.py diff --git a/modules/lithium.pytarget/tests/test_calc_alt.py b/modules/lithium.pytarget/tests/test_calc_alt.py new file mode 100644 index 00000000..f6b9f4e7 --- /dev/null +++ b/modules/lithium.pytarget/tests/test_calc_alt.py @@ -0,0 +1,67 @@ +import pytest +import datetime +from astropy.coordinates import EarthLocation +from modules.lithium.pytarget.target.calc_alt import calculate_current_alt, ObservationResult + +# FILE: modules/lithium.pytarget/target/test_calc_alt.py + + +@pytest.fixture +def observer_location(): + return EarthLocation(lat=34.0522, lon=-118.2437, height=0) # Los Angeles, CA + +@pytest.fixture +def observation_start_time(): + return datetime.datetime(2023, 10, 1, 22, 0, 0) # 10 PM on October 1, 2023 + +def test_calculate_current_alt_valid(observer_location, observation_start_time): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + + result = calculate_current_alt(observation_start_time, observer_location, ra, dec) + + assert isinstance(result, ObservationResult) + assert result.altitude is not None + assert result.azimuth is not None + assert result.highest_altitude is not None + assert result.available_shoot_time is not None + assert isinstance(result.is_above_horizon, bool) + +def test_calculate_current_alt_below_horizon(observer_location, observation_start_time): + ra = 180.0 # Right Ascension in degrees + dec = -90.0 # Declination in degrees (below horizon) + + result = calculate_current_alt(observation_start_time, observer_location, ra, dec) + + assert isinstance(result, ObservationResult) + assert result.altitude is not None + assert result.azimuth is not None + assert result.highest_altitude is None + assert result.available_shoot_time is None + assert result.is_above_horizon is False + +def test_calculate_current_alt_invalid_ra_dec(observer_location, observation_start_time): + ra = 400.0 # Invalid Right Ascension + dec = 100.0 # Invalid Declination + + result = calculate_current_alt(observation_start_time, observer_location, ra, dec) + + assert isinstance(result, ObservationResult) + assert result.altitude is None + assert result.azimuth is None + assert result.highest_altitude is None + assert result.available_shoot_time is None + assert result.is_above_horizon is False + +def test_calculate_current_alt_edge_case(observer_location, observation_start_time): + ra = 0.0 # Right Ascension at edge + dec = 0.0 # Declination at edge + + result = calculate_current_alt(observation_start_time, observer_location, ra, dec) + + assert isinstance(result, ObservationResult) + assert result.altitude is not None + assert result.azimuth is not None + assert result.highest_altitude is not None + assert result.available_shoot_time is not None + assert isinstance(result.is_above_horizon, bool) \ No newline at end of file diff --git a/modules/lithium.pytarget/tests/test_calc_azalt.py b/modules/lithium.pytarget/tests/test_calc_azalt.py new file mode 100644 index 00000000..48f6b9b9 --- /dev/null +++ b/modules/lithium.pytarget/tests/test_calc_azalt.py @@ -0,0 +1,75 @@ +import pytest +import datetime +from astropy.coordinates import EarthLocation +from modules.lithium.pytarget.target.calc_azalt import calculate_azimuth_altitude, calculate_star_info, calculate_rise_set_times + +# FILE: modules/lithium.pytarget/target/test_calc_azalt.py + + +@pytest.fixture +def observer_location(): + return EarthLocation(lat=34.0522, lon=-118.2437, height=0) # Los Angeles, CA + +@pytest.fixture +def observation_datetime(): + return datetime.datetime(2023, 10, 1, 22, 0, 0, tzinfo=datetime.timezone.utc) # 10 PM UTC on October 1, 2023 + +def test_calculate_azimuth_altitude_valid(observer_location, observation_datetime): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + + azimuth, altitude = calculate_azimuth_altitude(observation_datetime, observer_location, ra, dec) + + assert azimuth is not None + assert altitude is not None + +def test_calculate_azimuth_altitude_invalid_datetime(observer_location): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + invalid_datetime = datetime.datetime(2023, 10, 1, 22, 0, 0) # No timezone info + + azimuth, altitude = calculate_azimuth_altitude(invalid_datetime, observer_location, ra, dec) + + assert azimuth is None + assert altitude is None + +def test_calculate_star_info_valid(observer_location, observation_datetime): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + + star_info = calculate_star_info(observation_datetime, observer_location, ra, dec) + + assert len(star_info) > 0 + for entry in star_info: + assert len(entry) == 3 + assert isinstance(entry[0], str) + assert isinstance(entry[1], float) + assert isinstance(entry[2], float) + +def test_calculate_star_info_invalid_datetime(observer_location): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + invalid_datetime = datetime.datetime(2023, 10, 1, 22, 0, 0) # No timezone info + + star_info = calculate_star_info(invalid_datetime, observer_location, ra, dec) + + assert len(star_info) == 0 + +def test_calculate_rise_set_times_valid(observer_location, observation_datetime): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + + rise_time, set_time = calculate_rise_set_times(observer_location, ra, dec, observation_datetime) + + assert rise_time is not None + assert set_time is not None + +def test_calculate_rise_set_times_invalid_datetime(observer_location): + ra = 10.684 # Right Ascension in degrees + dec = 41.269 # Declination in degrees + invalid_datetime = datetime.datetime(2023, 10, 1, 22, 0, 0) # No timezone info + + rise_time, set_time = calculate_rise_set_times(observer_location, ra, dec, invalid_datetime) + + assert rise_time is None + assert set_time is None \ No newline at end of file diff --git a/modules/lithium.pytarget/tests/test_calc_fov.py b/modules/lithium.pytarget/tests/test_calc_fov.py new file mode 100644 index 00000000..1a283d3e --- /dev/null +++ b/modules/lithium.pytarget/tests/test_calc_fov.py @@ -0,0 +1,92 @@ +# FILE: modules/lithium.pytarget/target/test_calc_fov.py + + +def test_calc_fov_points_valid(): + x_pixels = 4000 + x_pixel_size = 5.0 # micrometers + y_pixels = 3000 + y_pixel_size = 5.0 # micrometers + focal_length = 1000 # millimeters + target_ra = 10.0 # degrees + target_dec = 20.0 # degrees + camera_rotation = 0.0 # degrees + + corners = calc_fov_points(x_pixels, x_pixel_size, y_pixels, y_pixel_size, + focal_length, target_ra, target_dec, camera_rotation) + + assert len(corners) == 4 + for corner in corners: + assert isinstance(corner, tuple) + assert len(corner) == 2 + assert isinstance(corner[0], float) + assert isinstance(corner[1], float) + + +def test_calc_fov_points_negative_focal_length(): + x_pixels = 4000 + x_pixel_size = 5.0 # micrometers + y_pixels = 3000 + y_pixel_size = 5.0 # micrometers + focal_length = -1000 # millimeters (invalid) + target_ra = 10.0 # degrees + target_dec = 20.0 # degrees + camera_rotation = 0.0 # degrees + + with pytest.raises(ValueError, match="Focal length must be a positive value."): + calc_fov_points(x_pixels, x_pixel_size, y_pixels, y_pixel_size, + focal_length, target_ra, target_dec, camera_rotation) + + +def test_calc_fov_points_zero_focal_length(): + x_pixels = 4000 + x_pixel_size = 5.0 # micrometers + y_pixels = 3000 + y_pixel_size = 5.0 # micrometers + focal_length = 0 # millimeters (invalid) + target_ra = 10.0 # degrees + target_dec = 20.0 # degrees + camera_rotation = 0.0 # degrees + + with pytest.raises(ValueError, match="Focal length must be a positive value."): + calc_fov_points(x_pixels, x_pixel_size, y_pixels, y_pixel_size, + focal_length, target_ra, target_dec, camera_rotation) + + +def test_calc_fov_points_edge_case(): + x_pixels = 1 + x_pixel_size = 1.0 # micrometers + y_pixels = 1 + y_pixel_size = 1.0 # micrometers + focal_length = 1 # millimeters + target_ra = 0.0 # degrees + target_dec = 0.0 # degrees + camera_rotation = 0.0 # degrees + + corners = calc_fov_points(x_pixels, x_pixel_size, y_pixels, y_pixel_size, + focal_length, target_ra, target_dec, camera_rotation) + + assert len(corners) == 4 + for corner in corners: + assert isinstance(corner, tuple) + assert len(corner) == 2 + assert isinstance(corner[0], float) + assert isinstance(corner[1], float) + + +def test_calc_fov_points_with_rotation(): + x_pixels = 4000 + x_pixel_size = 5.0 # micrometers + y_pixels = 3000 + y_pixel_size = 5.0 # micrometers + focal_length = 1000 # millimeters + target_ra = 10.0 # degrees + target_dec = 20.0 # degrees + camera_rotation = 45.0 # degrees + + corners = calc_fov_points(x_pixels, x_pixel_size, y_pixels, y_pixel_size, + focal_length, target_ra, target_dec, camera_rotation) + + assert len(corners) == 4 + for corner in corners: + assert isinstance(corner, tuple) + assert len(corner) == 2 diff --git a/modules/lithium.pytarget/tests/test_calc_twilight.py b/modules/lithium.pytarget/tests/test_calc_twilight.py new file mode 100644 index 00000000..b95f306d --- /dev/null +++ b/modules/lithium.pytarget/tests/test_calc_twilight.py @@ -0,0 +1,61 @@ +import pytest +import datetime +from astroplan import Observer +from astropy.time import Time +from astropy.coordinates import EarthLocation +from zoneinfo import ZoneInfo +from modules.lithium.pytarget.target.calc_twilight import calculate_twilight + +# FILE: modules/lithium.pytarget/target/test_calc_twilight.py + + +@pytest.fixture +def observer_location(): + return EarthLocation(lat=34.0522, lon=-118.2437, height=0) # Los Angeles, CA + +@pytest.fixture +def observation_time(): + return Time(datetime.datetime(2023, 10, 1, 22, 0, 0, tzinfo=datetime.timezone.utc)) # 10 PM UTC on October 1, 2023 + +@pytest.fixture +def time_offset(): + return ZoneInfo("America/Los_Angeles") + +@pytest.fixture +def observer(observer_location): + return Observer(location=observer_location) + +def test_calculate_twilight_valid(observer, observation_time, time_offset): + twilight_times = calculate_twilight(observer, observation_time, time_offset) + + assert 'evening' in twilight_times + assert 'morning' in twilight_times + assert 'sun_set_time' in twilight_times['evening'] + assert 'evening_civil_time' in twilight_times['evening'] + assert 'evening_nautical_time' in twilight_times['evening'] + assert 'evening_astro_time' in twilight_times['evening'] + assert 'sun_rise_time' in twilight_times['morning'] + assert 'morning_civil_time' in twilight_times['morning'] + assert 'morning_nautical_time' in twilight_times['morning'] + assert 'morning_astro_time' in twilight_times['morning'] + +def test_calculate_twilight_invalid_time(observer, time_offset): + invalid_time = Time(datetime.datetime(2023, 10, 1, 22, 0, 0)) # No timezone info + + twilight_times = calculate_twilight(observer, invalid_time, time_offset) + + assert twilight_times == {} + +def test_calculate_twilight_invalid_observer(observation_time, time_offset): + invalid_observer = None + + twilight_times = calculate_twilight(invalid_observer, observation_time, time_offset) + + assert twilight_times == {} + +def test_calculate_twilight_invalid_time_offset(observer, observation_time): + invalid_time_offset = None + + twilight_times = calculate_twilight(observer, observation_time, invalid_time_offset) + + assert twilight_times == {} \ No newline at end of file diff --git a/modules/lithium.pytarget/tests/test_find_all.py b/modules/lithium.pytarget/tests/test_find_all.py new file mode 100644 index 00000000..3b5d0129 --- /dev/null +++ b/modules/lithium.pytarget/tests/test_find_all.py @@ -0,0 +1,79 @@ +import pytest +import datetime +from astropy.coordinates import EarthLocation +from modules.lithium.pytarget.target.find_all import search_DSO, batch_search_DSO, DSO + +# FILE: modules/lithium.pytarget/target/test_find_all.py + + +@pytest.fixture +def observer_location(): + return EarthLocation(lat=34.0522, lon=-118.2437, height=0) # Los Angeles, CA + +@pytest.fixture +def date_time_string(): + return datetime.datetime(2023, 10, 1, 22, 0, 0) # 10 PM on October 1, 2023 + +def test_search_DSO_valid(observer_location, date_time_string): + to_search_name = "M31" # Andromeda Galaxy + results = search_DSO(to_search_name, observer_location, date_time_string) + + assert isinstance(results, list) + assert all(isinstance(dso, DSO) for dso in results) + assert len(results) > 0 + +def test_search_DSO_no_results(observer_location, date_time_string): + to_search_name = "NonExistentObject" + results = search_DSO(to_search_name, observer_location, date_time_string) + + assert isinstance(results, list) + assert len(results) == 0 + +def test_search_DSO_with_filters(observer_location, date_time_string): + to_search_name = "M31" # Andromeda Galaxy + magnitude_range = (3.0, 5.0) + ra_range = (0.0, 50.0) + dec_range = (30.0, 50.0) + alias = "Andromeda" + + results = search_DSO(to_search_name, observer_location, date_time_string, + magnitude_range=magnitude_range, ra_range=ra_range, dec_range=dec_range, alias=alias) + + assert isinstance(results, list) + assert all(isinstance(dso, DSO) for dso in results) + assert len(results) > 0 + +def test_batch_search_DSO_valid(observer_location, date_time_string): + names = ["M31", "M42"] # Andromeda Galaxy and Orion Nebula + results = batch_search_DSO(names, observer_location, date_time_string) + + assert isinstance(results, dict) + assert all(isinstance(name, str) for name in results.keys()) + assert all(isinstance(dsos, list) for dsos in results.values()) + assert all(all(isinstance(dso, DSO) for dso in dsos) for dsos in results.values()) + +def test_batch_search_DSO_no_results(observer_location, date_time_string): + names = ["NonExistentObject1", "NonExistentObject2"] + results = batch_search_DSO(names, observer_location, date_time_string) + + assert isinstance(results, dict) + assert all(isinstance(name, str) for name in results.keys()) + assert all(isinstance(dsos, list) for dsos in results.values()) + assert all(len(dsos) == 0 for dsos in results.values()) + +def test_batch_search_DSO_with_filters(observer_location, date_time_string): + names = ["M31", "M42"] # Andromeda Galaxy and Orion Nebula + magnitude_range = (3.0, 5.0) + ra_range = (0.0, 50.0) + dec_range = (30.0, 50.0) + alias = "Andromeda" + + results = batch_search_DSO(names, observer_location, date_time_string, + magnitude_range=magnitude_range, ra_range=ra_range, dec_range=dec_range, alias=alias) + + assert isinstance(results, dict) + assert all(isinstance(name, str) for name in results.keys()) + assert all(isinstance(dsos, list) for dsos in results.values()) + assert all(all(isinstance(dso, DSO) for dso in dsos) for dsos in results.values()) + assert len(results["M31"]) > 0 + assert len(results["M42"]) > 0 \ No newline at end of file diff --git a/modules/lithium.pytarget/tests/test_find_named.py b/modules/lithium.pytarget/tests/test_find_named.py new file mode 100644 index 00000000..3db320c2 --- /dev/null +++ b/modules/lithium.pytarget/tests/test_find_named.py @@ -0,0 +1,73 @@ +import pytest +import datetime +from astropy.coordinates import EarthLocation +from zoneinfo import ZoneInfo +from modules.lithium.pytarget.target.find_named import get_light_star_list, SimpleLightStarInfo, CardinalDirection + +# FILE: modules/lithium.pytarget/target/test_find_named.py + + +@pytest.fixture +def observer_location(): + return EarthLocation(lat=34.0522, lon=-118.2437, height=0) # Los Angeles, CA + +@pytest.fixture +def observation_time(): + return datetime.datetime(2023, 10, 1, 22, 0, 0, tzinfo=ZoneInfo("UTC")) # 10 PM UTC on October 1, 2023 + +def test_get_light_star_list_valid(observer_location, observation_time): + max_magnitude = 2 + result = get_light_star_list(observer_location, observation_time, max_magnitude) + + assert isinstance(result, list) + assert all(isinstance(star, dict) for star in result) + assert len(result) > 0 + +def test_get_light_star_list_no_results(observer_location, observation_time): + max_magnitude = 0 # No stars should match this magnitude + result = get_light_star_list(observer_location, observation_time, max_magnitude) + + assert isinstance(result, list) + assert len(result) == 0 + +def test_get_light_star_list_with_filters(observer_location, observation_time): + max_magnitude = 2 + range_filter = [CardinalDirection.NORTH.value] + constellation_filter = "And" + constellation_zh_filter = "仙女座" + name_filter = "Andromeda" + show_name_filter = "仙女座" + + result = get_light_star_list(observer_location, observation_time, max_magnitude, range_filter, + constellation_filter, constellation_zh_filter, name_filter, show_name_filter) + + assert isinstance(result, list) + assert all(isinstance(star, dict) for star in result) + assert len(result) > 0 + +def test_get_light_star_list_invalid_range_filter(observer_location, observation_time): + max_magnitude = 2 + range_filter = ["invalid_direction"] + + result = get_light_star_list(observer_location, observation_time, max_magnitude, range_filter) + + assert isinstance(result, list) + assert len(result) > 0 # Should ignore the invalid filter and return results + +def test_get_light_star_list_edge_case(observer_location, observation_time): + max_magnitude = 4 # Edge case for maximum magnitude + result = get_light_star_list(observer_location, observation_time, max_magnitude) + + assert isinstance(result, list) + assert all(isinstance(star, dict) for star in result) + assert len(result) > 0 + +def test_calculate_light_star_info(observer_location, observation_time): + star = SimpleLightStarInfo( + name="Test Star", show_name="测试星", ra=10.684, dec=41.269, constellation="And", constellation_zh="仙女座", magnitude=2.0 + ) + star.update_alt_az(45.0, 90.0) + + assert star.alt == 45.0 + assert star.az == 90.0 + assert star.sky == "east" \ No newline at end of file diff --git a/modules/lithium.pytarget/tests/test_planet.py b/modules/lithium.pytarget/tests/test_planet.py new file mode 100644 index 00000000..94e5dcf8 --- /dev/null +++ b/modules/lithium.pytarget/tests/test_planet.py @@ -0,0 +1,99 @@ +import re +import pytest +import datetime +from modules.lithium.pytarget.target.planet import Moon + +# FILE: modules/lithium.pytarget/target/test_planet.py + + +@pytest.fixture +def moon(): + # San Francisco, CA + return Moon(lat="37.7749", lon="-122.4194", elevation=10) + + +def test_get_next_full(moon): + next_full = moon.get_next_full + assert isinstance(next_full, datetime.date) + + +def test_get_next_new(moon): + next_new = moon.get_next_new + assert isinstance(next_new, datetime.date) + + +def test_get_previous_full(moon): + previous_full = moon.get_previous_full + assert isinstance(previous_full, datetime.date) + + +def test_get_previous_new(moon): + previous_new = moon.get_previous_new + assert isinstance(previous_new, datetime.date) + + +def test_get_next_last_quarter(moon): + next_last_quarter = moon.get_next_last_quarter + assert isinstance(next_last_quarter, datetime.date) + + +def test_get_next_first_quarter(moon): + next_first_quarter = moon.get_next_first_quarter + assert isinstance(next_first_quarter, datetime.date) + + +def test_get_previous_last_quarter(moon): + previous_last_quarter = moon.get_previous_last_quarter + assert isinstance(previous_last_quarter, datetime.date) + + +def test_get_previous_first_quarter(moon): + previous_first_quarter = moon.get_previous_first_quarter + assert isinstance(previous_first_quarter, datetime.date) + + +def test_get_moon_phase(moon): + moon_phase = moon.get_moon_phase() + assert isinstance(moon_phase, str) + assert moon_phase in ["Full", "New", "First Quarter", "Last Quarter", + "Waxing Crescent", "Waxing Gibbous", "Waning Gibbous", "Waning Crescent"] + + +def test_get_moon_ra(moon): + moon_ra = moon.get_moon_ra() + assert isinstance(moon_ra, str) + + +def test_get_moon_dec(moon): + moon_dec = moon.get_moon_dec() + assert isinstance(moon_dec, str) + + +def test_get_moon_az(moon): + moon_az = moon.get_moon_az() + assert isinstance(moon_az, str) + assert moon_az.endswith("°") + + +def test_get_moon_alt(moon): + moon_alt = moon.get_moon_alt() + assert isinstance(moon_alt, str) + assert moon_alt.endswith("°") + + +def test_get_moon_rise(moon): + moon_rise = moon.get_moon_rise() + assert isinstance(moon_rise, str) + assert re.match(r"\d{2}:\d{2}:\d{2}", moon_rise) or moon_rise == "-" + + +def test_get_moon_set(moon): + moon_set = moon.get_moon_set() + assert isinstance(moon_set, str) + assert re.match(r"\d{2}:\d{2}:\d{2}", moon_set) or moon_set == "-" + + +def test_get_moon_transit(moon): + moon_transit = moon.get_moon_transit() + assert isinstance(moon_transit, str) + assert re.match(r"\d{2}:\d{2}:\d{2}", moon_transit) or moon_transit == "-" diff --git a/pysrc/addon/__init__.py b/pysrc/addon/__init__.py deleted file mode 100644 index e69de29b..00000000