Skip to content

API Reference: Utils Module

utils

Utility functions for the ndastro_engine package.

This module provides: - get_app_data_dir: Get the application data directory for the given app name.

dd2dms(decimal_degrees)

Convert decimal degrees to degrees, minutes, and seconds.

Parameters:

Name Type Description Default
decimal_degrees float

The angle in decimal degrees.

required

Returns:

Type Description
tuple[int, int, float, int]

tuple[int, int, float, int]: A tuple containing degrees, minutes, seconds, and sign.

Source code in ndastro_engine/utils.py
def dd2dms(decimal_degrees: float) -> tuple[int, int, float, int]:
    """Convert decimal degrees to degrees, minutes, and seconds.

    Args:
        decimal_degrees (float): The angle in decimal degrees.

    Returns:
        tuple[int, int, float, int]: A tuple containing degrees, minutes, seconds, and sign.

    """
    sign = 1 if decimal_degrees >= 0 else -1
    abs_degrees = abs(decimal_degrees)
    degrees = int(abs_degrees)
    minutes_full = (abs_degrees - degrees) * 60
    minutes = int(minutes_full)
    seconds = (minutes_full - minutes) * 60
    return degrees, minutes, seconds, sign

dd2dmsstr(decimal_degrees)

Convert decimal degrees to a formatted DMS string.

Parameters:

Name Type Description Default
decimal_degrees float

The angle in decimal degrees.

required

Returns:

Name Type Description
str str

The angle in DMS format as a string.

Source code in ndastro_engine/utils.py
def dd2dmsstr(decimal_degrees: float) -> str:
    """Convert decimal degrees to a formatted DMS string.

    Args:
        decimal_degrees (float): The angle in decimal degrees.

    Returns:
        str: The angle in DMS format as a string.

    """
    degrees, minutes, seconds, sign = dd2dms(decimal_degrees)
    sign_str = "" if sign >= 0 else "-"
    return f"{sign_str}{degrees}° {minutes}' {seconds:.2f}\""

decimal_years_to_years_months_days_ghatis(decimal_years)

Convert decimal years to years, months, days, hours, minutes, seconds, and ghatis.

Parameters:

Name Type Description Default
decimal_years float

The time duration in decimal years.

required

Returns:

Type Description
tuple[int, int, int, int, int, int, int]

tuple[int, int, int, int, int, int, int]: A tuple containing years, months, days, hours, minutes, seconds, and ghatis.

Source code in ndastro_engine/utils.py
def decimal_years_to_years_months_days_ghatis(decimal_years: float) -> tuple[int, int, int, int, int, int, int]:
    """Convert decimal years to years, months, days, hours, minutes, seconds, and ghatis.

    Args:
        decimal_years (float): The time duration in decimal years.

    Returns:
        tuple[int, int, int, int, int, int, int]: A tuple containing years, months, days, hours, minutes, seconds, and ghatis.

    """
    years = int(decimal_years)
    remaining_years = decimal_years - years

    months = int(remaining_years * 12)
    remaining_months = (remaining_years * 12 - months) * AVERAGE_DAYS_IN_MONTH  # Average days in a month

    days = int(remaining_months)  # Average days in a month
    remaining_days = remaining_months - days

    hours = int(remaining_days * 24)  # 1 day = 24 hours
    remaining_hours = remaining_days * 24 - hours

    mins = int(remaining_hours * 60)  # 1 hour = 60 minutes
    remaining_mins = remaining_hours * 60 - mins

    secs = int(remaining_mins * 60)  # 1 minute = 60 seconds
    ghatis = int(hours * 60 / 24)  # 1 ghati = 24 minutes

    return years, months, days, hours, mins, secs, ghatis

dms2dd(degrees, minutes, seconds, sign=1)

Convert degrees, minutes, and seconds to decimal degrees.

Parameters:

Name Type Description Default
degrees int

The degrees component.

required
minutes int

The minutes component.

required
seconds float

The seconds component.

required
sign int

The sign of the angle. Defaults to 1 (positive).

1

Returns:

Name Type Description
float float

The angle in decimal degrees.

Source code in ndastro_engine/utils.py
def dms2dd(degrees: int, minutes: int, seconds: float, sign: int = 1) -> float:
    """Convert degrees, minutes, and seconds to decimal degrees.

    Args:
        degrees (int): The degrees component.
        minutes (int): The minutes component.
        seconds (float): The seconds component.
        sign (int, optional): The sign of the angle. Defaults to 1 (positive).

    Returns:
        float: The angle in decimal degrees.

    """
    decimal_degrees = abs(degrees) + minutes / 60 + seconds / 3600
    return sign * decimal_degrees

get_app_data_dir(appname)

Get the application data directory for the given app name.

Parameters

appname : str Name of the application.

Returns

Path Path to the application data directory.

Source code in ndastro_engine/utils.py
def get_app_data_dir(appname: str) -> Path:
    """Get the application data directory for the given app name.

    Parameters
    ----------
    appname : str
        Name of the application.

    Returns
    -------
    Path
        Path to the application data directory.

    """
    home = Path.home()
    if sys.platform == OS_WIN:
        return home / "AppData/Local" / appname

    if sys.platform == OS_MAC:
        return home / "Library/Application Support" / appname

    # Linux and other Unix-like systems (uses XDG spec fallback)
    data_home = os.getenv("XDG_DATA_HOME", "~/.local/share")
    return Path(data_home).expanduser() / appname

get_elevation_by_latlon(lat, lon, timeout=10.0) cached

Get elevation in meters for latitude and longitude.

Uses whatismyelevation.com API and falls back to 0.0 when lookup fails.

Parameters:

Name Type Description Default
lat float

Latitude in decimal degrees.

required
lon float

Longitude in decimal degrees.

required
timeout float

Request timeout in seconds. Defaults to 3.0.

10.0

Returns:

Name Type Description
float float

Elevation in meters, or 0.0 when unavailable.

Source code in ndastro_engine/utils.py
@lru_cache(maxsize=256)
def get_elevation_by_latlon(lat: float, lon: float, timeout: float = 10.0) -> float:
    """Get elevation in meters for latitude and longitude.

    Uses whatismyelevation.com API and falls back to 0.0 when lookup fails.

    Args:
        lat (float): Latitude in decimal degrees.
        lon (float): Longitude in decimal degrees.
        timeout (float, optional): Request timeout in seconds. Defaults to 3.0.

    Returns:
        float: Elevation in meters, or 0.0 when unavailable.

    """
    url = f"https://whatismyelevation.com/api/elevation?latitude={lat}&longitude={lon}"

    try:
        with urlopen(url, timeout=timeout) as response:  # noqa: S310
            payload = json.loads(response.read().decode("utf-8"))
            elevation = payload.get("elevation", 0.0)
            return float(elevation)
    except (URLError, TimeoutError, OSError, ValueError, TypeError, json.JSONDecodeError):
        return 0.0

normalize_degree(degree)

Normalize the degree to be within 0-360.

Parameters:

Name Type Description Default
degree float

The degree to normalize.

required

Returns:

Name Type Description
float float

The normalized degree.

Source code in ndastro_engine/utils.py
def normalize_degree(degree: float) -> float:
    """Normalize the degree to be within 0-360.

    Args:
        degree (float): The degree to normalize.

    Returns:
        float: The normalized degree.

    """
    return (degree % DEGREE_MAX + DEGREE_MAX) % DEGREE_MAX

parse_iso_datetime(datetime_str)

Parse an ISO datetime string.

If timezone information is missing, UTC is assumed.

Parameters:

Name Type Description Default
datetime_str str

ISO datetime string to parse.

required

Returns:

Name Type Description
datetime datetime

Parsed timezone-aware datetime.

Raises:

Type Description
ValueError

If the string is not a valid ISO datetime.

Source code in ndastro_engine/utils.py
def parse_iso_datetime(datetime_str: str) -> datetime:
    """Parse an ISO datetime string.

    If timezone information is missing, UTC is assumed.

    Args:
        datetime_str (str): ISO datetime string to parse.

    Returns:
        datetime: Parsed timezone-aware datetime.

    Raises:
        ValueError: If the string is not a valid ISO datetime.

    """
    normalized = datetime_str.strip()

    # Python datetime.fromisoformat does not accept a trailing 'Z'.
    if normalized.endswith("Z"):
        normalized = f"{normalized[:-1]}+00:00"

    try:
        parsed = datetime.fromisoformat(normalized)
    except ValueError as exc:
        msg = f"Invalid ISO datetime string: {datetime_str}"
        raise ValueError(msg) from exc

    if parsed.tzinfo is None:
        return parsed.replace(tzinfo=UTC)

    return parsed