Source code for pysolorie.observer

# Copyright 2023 Alireza Aghamohammadi

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
from typing import Optional

from .exceptions import InvalidObserverLatitudeError, MissingObserverLatitudeError
from .sun_position import SunPosition


[docs] class Observer: r""" A class to model an observer based on horizontal and equatorial pictures of the sun-earth geometry. """ def __init__( self, observer_latitude: Optional[float] = None, observer_longitude: Optional[float] = None, ): """ To instantiate the ``Observer`` class, provide the following parameter. :param observer_latitude: The latitude of the observer in degrees (optional). :type observer_latitude: Optional[float] :param observer_longitude: The longitude of the observer in degrees (optional). :type observer_longitude: Optional[float] """ self.observer_latitude = observer_latitude self.observer_longitude = observer_longitude self.sun_position = SunPosition()
[docs] def calculate_zenith_angle(self, day_of_year: int, solar_time: float) -> float: r""" Calculate the solar zenith angle. The solar zenith angle is calculated using the formula: .. math:: \cos(\theta_z) = \sin(\phi) \times \sin(\delta) + \cos(\phi) \times \cos(\delta) \times \cos(\omega) | - :math:`\theta_z` is the solar zenith angle | - :math:`\phi` is the latitude of the observer | - :math:`\delta` is the solar declination | - :math:`\omega` is the hour angle. :param day_of_year: The day of the year. :type day_of_year: int :param solar_time: The solar time in seconds. :type solar_time: float :return: The zenith angle in radians. :rtype: float """ observer_latitude = self._ensure_latitude_provided() solar_declination = self.sun_position.solar_declination(day_of_year) hour_angle = self.sun_position.hour_angle(solar_time) return math.acos( math.sin(observer_latitude) * math.sin(solar_declination) + math.cos(observer_latitude) * math.cos(solar_declination) * math.cos(hour_angle) )
[docs] def calculate_sunrise_sunset(self, day_of_year: int) -> tuple: r""" Calculate the hour angle at sunrise and sunset. The hour angle at sunrise and sunset is calculated using the formula: .. math:: \cos(\omega) = -\tan(\phi) \times \tan(\delta) | - :math:`\omega` is the hour angle | - :math:`\phi` is the latitude of the observer | - :math:`\delta` is the solar declination. :param day_of_year: The day of the year. :type day_of_year: int :return: The hour angle at sunrise and sunset in radians. :rtype: tuple """ observer_latitude = self._ensure_latitude_provided() solar_declination = self.sun_position.solar_declination(day_of_year) tan_product = -math.tan(observer_latitude) * math.tan(solar_declination) if tan_product > 1: return 0, 0 if tan_product < -1: return -math.pi, math.pi hour_angle = math.acos(tan_product) sunrise = -hour_angle sunset = hour_angle return sunrise, sunset
def _ensure_latitude_provided(self) -> float: if self.observer_latitude is None: raise MissingObserverLatitudeError() if not math.radians(-88) <= self.observer_latitude <= math.radians(88): raise InvalidObserverLatitudeError() return self.observer_latitude _observer_latitude: Optional[float] @property def observer_latitude(self) -> Optional[float]: """ Getter for the observer's latitude. :return: The observer's latitude in radians. :rtype: Optional[float] """ return self._observer_latitude @observer_latitude.setter def observer_latitude(self, value: Optional[float]): """ Setter for the observer's latitude. Converts the value to radians if not None. :param value: The observer's latitude in degrees. :type value: Optional[float] """ self._observer_latitude = math.radians(value) if value is not None else None _observer_longitude: Optional[float] @property def observer_longitude(self) -> Optional[float]: """ Getter for the observer's longitude. :return: The observer's longitude in radians. :rtype: Optional[float] """ return self._observer_longitude @observer_longitude.setter def observer_longitude(self, value: Optional[float]): """ Setter for the observer's longitude. Converts the value to radians if not None. :param value: The observer's longitude in degrees. :type value: Optional[float] """ self._observer_longitude = math.radians(value) if value is not None else None