Skip to content

solar

solar #

Solar position forecasting via the astral library.

Computes the sun's altitude and azimuth for a location and time using the astral library. The main entry point, solar_position, is vectorized so cg.Array arguments broadcast element-wise.

SolarPosition dataclass #

Solar position with altitude (elevation) and azimuth.

Attributes:

Name Type Description
altitude float

Sun elevation in degrees above the horizon (-90° to 90°; negative is below the horizon).

azimuth float

Sun azimuth in degrees clockwise from North (0°=N, 90°=E, 180°=S, 270°=W).

Source code in capturegraph-lib/capturegraph/scheduling/forecast/solar.py
@dataclass
class SolarPosition:
    """Solar position with altitude (elevation) and azimuth.

    Attributes:
        altitude: Sun elevation in degrees above the horizon (-90° to 90°;
            negative is below the horizon).
        azimuth: Sun azimuth in degrees clockwise from North (0°=N, 90°=E,
            180°=S, 270°=W).
    """

    altitude: float
    azimuth: float

    def __repr__(self) -> str:
        """Render as ``SolarPosition(alt=…°, az=…°)`` with one decimal place."""
        return f"SolarPosition(alt={self.altitude:.1f}°, az={self.azimuth:.1f}°)"

    def to_json(self) -> dict:
        """Convert to a dict with 'altitude' and 'azimuth' keys."""
        return {"altitude": self.altitude, "azimuth": self.azimuth}

    @classmethod
    def from_json(cls, obj: dict) -> SolarPosition:
        """Create a SolarPosition from a dict with 'altitude' and 'azimuth' keys."""
        return cls(altitude=obj["altitude"], azimuth=obj["azimuth"])

__repr__() #

Render as SolarPosition(alt=…°, az=…°) with one decimal place.

Source code in capturegraph-lib/capturegraph/scheduling/forecast/solar.py
def __repr__(self) -> str:
    """Render as ``SolarPosition(alt=…°, az=…°)`` with one decimal place."""
    return f"SolarPosition(alt={self.altitude:.1f}°, az={self.azimuth:.1f}°)"

from_json(obj) classmethod #

Create a SolarPosition from a dict with 'altitude' and 'azimuth' keys.

Source code in capturegraph-lib/capturegraph/scheduling/forecast/solar.py
@classmethod
def from_json(cls, obj: dict) -> SolarPosition:
    """Create a SolarPosition from a dict with 'altitude' and 'azimuth' keys."""
    return cls(altitude=obj["altitude"], azimuth=obj["azimuth"])

to_json() #

Convert to a dict with 'altitude' and 'azimuth' keys.

Source code in capturegraph-lib/capturegraph/scheduling/forecast/solar.py
def to_json(self) -> dict:
    """Convert to a dict with 'altitude' and 'azimuth' keys."""
    return {"altitude": self.altitude, "azimuth": self.azimuth}

solar_position(location, time) #

Calculate solar position for a single datetime and location.

Uses the astral library for accurate calculations. Vectorized: passing cg.Array arguments broadcasts element-wise.

Parameters:

Name Type Description Default
location Location

Location with latitude, longitude, and altitude.

required
time datetime

Date and time. Timezone-aware datetimes are converted to UTC; naive datetimes are interpreted as system-local time.

required

Returns:

Type Description
SolarPosition

SolarPosition with altitude and azimuth in degrees.

Raises:

Type Description
ImportError

If astral is not installed.

Source code in capturegraph-lib/capturegraph/scheduling/forecast/solar.py
@vectorize
def solar_position(
    location: Location,
    time: datetime,
) -> SolarPosition:
    """Calculate solar position for a single datetime and location.

    Uses the astral library for accurate calculations. Vectorized: passing
    cg.Array arguments broadcasts element-wise.

    Args:
        location: Location with latitude, longitude, and altitude.
        time: Date and time. Timezone-aware datetimes are converted to UTC;
            naive datetimes are interpreted as system-local time.

    Returns:
        SolarPosition with altitude and azimuth in degrees.

    Raises:
        ImportError: If astral is not installed.
    """
    try:
        from astral import Observer
        from astral.sun import azimuth, elevation
    except ImportError as e:
        raise ImportError("solar_position requires astral. Install with: pip install astral") from e

    observer = Observer(
        latitude=location.latitude,
        longitude=location.longitude,
        elevation=location.altitude_meters,
    )

    # check for numpy dt and convert to datetime
    if isinstance(time, np.datetime64):
        time = time.item()

    # normalize dt to UTC
    time = time.astimezone(UTC)

    alt = elevation(observer, time)
    az = azimuth(observer, time)

    return SolarPosition(altitude=alt, azimuth=az)