This section documents the API.
Solar position¶
fractional_year
¶
solar_time
¶
Functions:
| Name | Description |
|---|---|
calculate_solar_time_series |
Calculates the solar time using all models and returns the results in a table. |
model_solar_time_series |
Calculates the solar time using the requested algorithm. |
calculate_solar_time_series
¶
calculate_solar_time_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_time_models: List[SolarTimeModel] = [noaa],
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = VERBOSE_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> Dict
Calculates the solar time using all models and returns the results in a table.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
longitude
|
Longitude
|
Observer longitude. |
required |
timestamps
|
DatetimeIndex
|
Timestamps for the evaluation grid. |
required |
timezone
|
ZoneInfo
|
Local timezone for solar time conversions. |
required |
solar_time_models
|
list of SolarTimeModel
|
Models to evaluate (default: NOAA only). |
[noaa]
|
dtype
|
str
|
Array dtype. |
DATA_TYPE_DEFAULT
|
array_backend
|
str
|
Array backend identifier. |
ARRAY_BACKEND_DEFAULT
|
verbose
|
int
|
Verbosity level. |
VERBOSE_LEVEL_DEFAULT
|
log
|
int
|
Logging level. |
VERBOSE_LEVEL_DEFAULT
|
validate_output
|
bool
|
Whether to validate model outputs. |
VALIDATE_OUTPUT_DEFAULT
|
Returns:
| Type | Description |
|---|---|
dict
|
Nested overview keyed by model name with timing algorithm, solar time series, and unit metadata. |
Source code in pvgis/api/position/solar_time.py
def calculate_solar_time_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_time_models: List[SolarTimeModel] = [SolarTimeModel.noaa],
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = VERBOSE_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> Dict:
"""Calculates the solar time using all models and returns the results in a table.
Parameters
----------
longitude : Longitude
Observer longitude.
timestamps : DatetimeIndex
Timestamps for the evaluation grid.
timezone : ZoneInfo
Local timezone for solar time conversions.
solar_time_models : list of SolarTimeModel, optional
Models to evaluate (default: NOAA only).
dtype : str, optional
Array dtype.
array_backend : str, optional
Array backend identifier.
verbose : int, optional
Verbosity level.
log : int, optional
Logging level.
validate_output : bool, optional
Whether to validate model outputs.
Returns
-------
dict
Nested overview keyed by model name with timing algorithm, solar time
series, and unit metadata.
"""
results = {}
# solar_time_models = select_models(SolarTimeModel, solar_time_model) # Using a callback fails!
for solar_time_model in solar_time_models:
if (
solar_time_model != SolarTimeModel.all
): # ignore 'all' in the enumeration
solar_time_series = model_solar_time_series(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
solar_time_model=solar_time_model,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_time_model_overview = {
solar_time_model.name: {
TIME_ALGORITHM_NAME: (
solar_time_model.value if solar_time_model else NOT_AVAILABLE
),
SOLAR_TIME_NAME: (
solar_time_series if solar_time_series else NOT_AVAILABLE
),
UNITS_COLUMN_NAME: MINUTES,
}
}
results = results | solar_time_model_overview
return results
model_solar_time_series
¶
model_solar_time_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_time_model: SolarTimeModel = noaa,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
)
Calculates the solar time using the requested algorithm.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
longitude
|
Longitude
|
Observer longitude. |
required |
timestamps
|
DatetimeIndex
|
UTC (or aligned) timestamps for the series. |
required |
timezone
|
ZoneInfo
|
Local timezone for solar time conversions. |
required |
solar_time_model
|
SolarTimeModel
|
Algorithm to use (default: NOAA). |
noaa
|
dtype
|
str
|
Array dtype. |
DATA_TYPE_DEFAULT
|
array_backend
|
str
|
Array backend identifier. |
ARRAY_BACKEND_DEFAULT
|
verbose
|
int
|
Verbosity level. |
VERBOSE_LEVEL_DEFAULT
|
log
|
int
|
Logging level. |
LOG_LEVEL_DEFAULT
|
validate_output
|
bool
|
Whether to validate model outputs. |
VALIDATE_OUTPUT_DEFAULT
|
Returns:
| Type | Description |
|---|---|
object
|
Time series object produced by the selected |
Source code in pvgis/api/position/solar_time.py
@log_function_call
@validate_with_pydantic(ModelSolarTimeTimeSeriesInputModel)
def model_solar_time_series(
longitude: Longitude,
# latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_time_model: SolarTimeModel = SolarTimeModel.noaa,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
):
"""Calculates the solar time using the requested _algorithm_.
Parameters
----------
longitude : Longitude
Observer longitude.
timestamps : DatetimeIndex
UTC (or aligned) timestamps for the series.
timezone : ZoneInfo
Local timezone for solar time conversions.
solar_time_model : SolarTimeModel, optional
Algorithm to use (default: NOAA).
dtype : str, optional
Array dtype.
array_backend : str, optional
Array backend identifier.
verbose : int, optional
Verbosity level.
log : int, optional
Logging level.
validate_output : bool, optional
Whether to validate model outputs.
Returns
-------
object
Time series object produced by the selected ``solar_time_model`` (minutes-based).
"""
solar_time_series = None
if solar_time_model.value == SolarTimeModel.milne:
pass
solar_time_series = calculate_apparent_solar_time_series_milne1921(
longitude=longitude,
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_time_model.value == SolarTimeModel.pvgis:
# Requires : time_offset_global, hour_offset
pass
if solar_time_model.value == SolarTimeModel.noaa:
solar_time_series = calculate_true_solar_time_series_noaa(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_time_model.value == SolarTimeModel.skyfield:
pass
# # vvv vvv vvv --------------------------------------- expects degrees!
# solar_time = calculate_solar_time_skyfield(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# timezone=timezone,
# verbose=verbose,
# )
# # ^^^ ^^^ ^^^ --------------------------------------- expects degrees!
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return solar_time_series
hour_angle
¶
Functions:
| Name | Description |
|---|---|
calculate_event_hour_angle_series |
Calculate the hour angle (ω) at sunrise and sunset |
calculate_solar_hour_angle_series |
Calculate the hour angle ω' |
model_solar_hour_angle_series |
|
calculate_event_hour_angle_series
¶
calculate_event_hour_angle_series(
latitude: Latitude,
surface_tilt: float = 0,
solar_declination: float = 0,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> HourAngleSunrise
Calculate the hour angle (ω) at sunrise and sunset
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
latitude
|
Latitude
|
Latitude (Φ) is the angle between the sun's rays and its projection on the horizontal surface measured in radians |
required |
surface_tilt
|
float
|
Surface tilt (or slope) (β) is the angle between the inclined surface (slope) and the horizontal plane. |
0
|
solar_declination
|
float
|
Solar declination (δ) is the angle between the equator and a line drawn from the centre of the Earth to the centre of the sun measured in radians. |
0
|
Returns:
| Type | Description |
|---|---|
Tuple(float, str)
|
Tuple containg (hour_angle, units). Hour angle (ω) is the angle at any instant through which the earth has to turn to bring the meridian of the observer directly in line with the sun's rays measured in radian. |
Notes
Hour angle = acos( -tan * ( Latitude Angle - Tilt Angle ) * tan( Declination Angle ) )
The hour angle (ω) at sunrise and sunset measures the angular distance between the sun at the local solar time and the sun at solar noon.
ω = acos(-tan(Φ-β)*tan(δ))
Source code in pvgis/api/position/hour_angle.py
@validate_with_pydantic(CalculateEventHourAngleInputModel)
def calculate_event_hour_angle_series(
latitude: Latitude,
surface_tilt: float = 0,
solar_declination: float = 0,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> HourAngleSunrise:
"""Calculate the hour angle (ω) at sunrise and sunset
Parameters
----------
latitude: float
Latitude (Φ) is the angle between the sun's rays and its projection on the
horizontal surface measured in radians
surface_tilt: float
Surface tilt (or slope) (β) is the angle between the inclined surface
(slope) and the horizontal plane.
solar_declination: float
Solar declination (δ) is the angle between the equator and a line drawn
from the centre of the Earth to the centre of the sun measured in
radians.
Returns
-------
Tuple(float, str)
Tuple containg (hour_angle, units). Hour angle (ω) is the angle at any
instant through which the earth has to turn to bring the meridian of the
observer directly in line with the sun's rays measured in radian.
Notes
-----
Hour angle = acos( -tan * ( Latitude Angle - Tilt Angle ) * tan( Declination Angle ) )
The hour angle (ω) at sunrise and sunset measures the angular distance
between the sun at the local solar time and the sun at solar noon.
ω = acos(-tan(Φ-β)*tan(δ))
"""
hour_angle_sunrise = acos(
-tan(latitude.radians - surface_tilt.radians) * tan(solar_declination.radians)
)
return HourAngleSunrise(
value=hour_angle_sunrise,
unit=RADIANS,
)
calculate_solar_hour_angle_series
¶
calculate_solar_hour_angle_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [
noaa
],
solar_time_model: SolarTimeModel = noaa,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> SolarHourAngle
Calculate the hour angle ω'
ω = (ST / 3600 - 12) * 15 * pi / 180
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
longitude
|
Longitude
|
Observer longitude. |
required |
timestamps
|
DatetimeIndex
|
Timestamps for the evaluation grid. |
required |
timezone
|
ZoneInfo
|
Local timezone for solar time conversions. |
required |
solar_position_models
|
list of SolarPositionModel
|
Solar geometry models to include (default: NOAA). |
[noaa]
|
solar_time_model
|
SolarTimeModel
|
Solar time algorithm feeding hour-angle calculations. |
noaa
|
angle_output_units
|
str
|
Units used when extracting hour-angle values from internal series. |
RADIANS
|
dtype
|
str
|
Array dtype. |
DATA_TYPE_DEFAULT
|
array_backend
|
str
|
Array backend identifier. |
ARRAY_BACKEND_DEFAULT
|
verbose
|
int
|
Verbosity level. |
VERBOSE_LEVEL_DEFAULT
|
log
|
int
|
Logging level. |
LOG_LEVEL_DEFAULT
|
validate_output
|
bool
|
Whether to validate model outputs. |
VALIDATE_OUTPUT_DEFAULT
|
fingerprint
|
bool
|
If true, add content hashes to the returned overview table. |
FINGERPRINT_FLAG_DEFAULT
|
Returns:
| Type | Description |
|---|---|
dict
|
Overview keyed by |
Notes
The hour angle ω (elsewhere symbolised with h) of a point on the earth’s
surface is defined as the angle through which the earth would turn to bring
the meridian of the point directly under the sun. The hour angle at local
solar noon is zero, with each 360/24 or 15° of longitude equivalent to 1 h,
afternoon hours being designated as positive. Expressed symbolically, the
hour angle in degrees is:
h = ±0.25 (Number of minutes from local solar noon)
where the plus sign applies to afternoon hours and the minus sign to morning hours.
The hour angle can also be obtained from the apparent solar time (AST); that is, the corrected local solar time:
h = (AST - 12) * 15
At local solar noon, AST = 12 and h = 0°. Therefore, from Eq <<(2.3)<<, the local solar time (LST, the time shown by our clocks at local solar noon) is:
LST = 12 - ET ∓ 4 * (SL - LL)
where:
ET is the Equation of Time
SL Standard Longitude
LL Local Longitude
Example 1
The equation for LST at local solar noon for Nicosia, Cyprus is:
LST = 12 - ET - 13.32 (minutes)
Example 2
Given the ET for March 10 (N = 69) is calculated from Eq (2.1), in which the factor B is obtained from Eq <<(2.2)<< as:
B = 360 / 364 * (N-81) = 360 / 364 * (69- 81) = -11.87
ET = 9.87 * sin(2*B) - 7.53 * cos(B) - 1.5 * sin(B) =
= 9.87 * sin(-2 * 11.87) - 7.53 * cos(-11.87) - 1.5 * sin(-11.87)
= -11.04min ∼ -11min
The standard meridian for Athens is 30°E longitude.
The apparent solar time on March 10 at 2:30 pm for the city of Athens, Greece (23°40′E longitude) is
AST = 14:30 - 4 * (30 - 23.66) - 0:11
= 14:30 - 0:25 - 0:11
= 13:54 or 1:54 pm
Additional notes:
Nomenclature from [1]_
α [°] solar altitude angle β [°] tilt angle δ [°] solar declination θ [°] solar incidence angle Φ [°] solar zenith angle h [°] hour angle L [°] local latitude N [-] day of the year z [°] solar azimuth angle ZS [°] surface azimuth angle AST Apparent Solar Time LST Local Standard Time ET Equation of Time SL Standard Longitude LL Local Longitude DS Daylight Saving
.. [1] Determination of Optimal Position of Solar Trough Collector. Available from: https://www.researchgate.net/publication/317826540_Determination_of_Optimal_Position_of_Solar_Trough_Collector [accessed Sep 06 2023].
In PVGIS : hour_angle = (solar_time / 3600 - 12) * 15 * 0.0175
which means:
- solar time is expected in seconds
- conversion to radians `* 0.0175` replaced by `pi / 180`
In this function:
Source code in pvgis/api/position/hour_angle.py
def calculate_solar_hour_angle_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [SolarPositionModel.noaa],
solar_time_model: SolarTimeModel = SolarTimeModel.noaa,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> SolarHourAngle:
"""Calculate the hour angle ω'
ω = (ST / 3600 - 12) * 15 * pi / 180
Parameters
----------
longitude : Longitude
Observer longitude.
timestamps : DatetimeIndex
Timestamps for the evaluation grid.
timezone : ZoneInfo
Local timezone for solar time conversions.
solar_position_models : list of SolarPositionModel, optional
Solar geometry models to include (default: NOAA).
solar_time_model : SolarTimeModel, optional
Solar time algorithm feeding hour-angle calculations.
angle_output_units : str, optional
Units used when extracting hour-angle values from internal series.
dtype : str, optional
Array dtype.
array_backend : str, optional
Array backend identifier.
verbose : int, optional
Verbosity level.
log : int, optional
Logging level.
validate_output : bool, optional
Whether to validate model outputs.
fingerprint : bool, optional
If true, add content hashes to the returned overview table.
Returns
-------
dict
Overview keyed by ``solar_position_model.name`` with algorithm metadata,
timing model, hour-angle values, units, and optional fingerprints.
Notes
-----
The hour angle ω (elsewhere symbolised with `h`) of a point on the earth’s
surface is defined as the angle through which the earth would turn to bring
the meridian of the point directly under the sun. The hour angle at local
solar noon is zero, with each 360/24 or 15° of longitude equivalent to 1 h,
afternoon hours being designated as positive. Expressed symbolically, the
hour angle in degrees is:
h = ±0.25 (Number of minutes from local solar noon)
where the plus sign applies to afternoon hours and the minus sign to
morning hours.
The hour angle can also be obtained from the apparent solar time (AST);
that is, the corrected local solar time:
h = (AST - 12) * 15
At local solar noon, AST = 12 and h = 0°. Therefore, from Eq <<(2.3)<<, the
local solar time (LST, the time shown by our clocks at local solar noon)
is:
LST = 12 - ET ∓ 4 * (SL - LL)
where:
ET is the Equation of Time
SL Standard Longitude
LL Local Longitude
Example 1
The equation for LST at local solar noon for Nicosia, Cyprus is:
LST = 12 - ET - 13.32 (minutes)
Example 2
Given the ET for March 10 (N = 69) is calculated from Eq (2.1), in which
the factor B is obtained from Eq <<(2.2)<< as:
B = 360 / 364 * (N-81) = 360 / 364 * (69- 81) = -11.87
ET = 9.87 * sin(2*B) - 7.53 * cos(B) - 1.5 * sin(B) =
= 9.87 * sin(-2 * 11.87) - 7.53 * cos(-11.87) - 1.5 * sin(-11.87)
= -11.04min ∼ -11min
The standard meridian for Athens is 30°E longitude.
The apparent solar time on March 10 at 2:30 pm for the city of Athens,
Greece (23°40′E longitude) is
AST = 14:30 - 4 * (30 - 23.66) - 0:11
= 14:30 - 0:25 - 0:11
= 13:54 or 1:54 pm
Additional notes:
Nomenclature from [1]_
α [°] solar altitude angle
β [°] tilt angle
δ [°] solar declination
θ [°] solar incidence angle
Φ [°] solar zenith angle
h [°] hour angle
L [°] local latitude
N [-] day of the year
z [°] solar azimuth angle
ZS [°] surface azimuth angle
AST Apparent Solar Time
LST Local Standard Time
ET Equation of Time
SL Standard Longitude
LL Local Longitude
DS Daylight Saving
.. [1] Determination of Optimal Position of Solar Trough Collector. Available from: https://www.researchgate.net/publication/317826540_Determination_of_Optimal_Position_of_Solar_Trough_Collector [accessed Sep 06 2023].
In PVGIS :
hour_angle = (solar_time / 3600 - 12) * 15 * 0.0175
which means:
- solar time is expected in seconds
- conversion to radians `* 0.0175` replaced by `pi / 180`
In this function:
"""
results = {}
for solar_position_model in solar_position_models:
# for the time being! ------------------------------------------------
if solar_position_model != SolarPositionModel.noaa:
logger.warning(
f"Solar geometry overview series is not implemented for the requested solar position model: {solar_position_model}!"
)
# --------------------------------------------------------------------
if (
solar_position_model != SolarPositionModel.all
): # ignore 'all' in the enumeration
solar_hour_angle_series = model_solar_hour_angle_series(
longitude=longitude,
# latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_time_model=solar_time_model,
solar_position_model=solar_position_model,
dtype=dtype,
array_backend=array_backend,
validate_output=validate_output,
verbose=verbose,
log=log,
)
hour_angle_values = (
getattr(solar_hour_angle_series, angle_output_units, NOT_AVAILABLE)
if solar_hour_angle_series
else NOT_AVAILABLE
)
model_entry = {
POSITION_ALGORITHM_NAME: solar_position_model.value,
TIME_ALGORITHM_NAME: (
solar_hour_angle_series.timing_algorithm
if solar_hour_angle_series
else NOT_AVAILABLE
),
HOUR_ANGLE_NAME: hour_angle_values,
UNITS_COLUMN_NAME: angle_output_units,
}
if fingerprint and solar_hour_angle_series is not None:
model_entry[FINGERPRINT_COLUMN_NAME] = generate_hash(hour_angle_values)
results = results | {solar_position_model.name: model_entry}
return results
model_solar_hour_angle_series
¶
model_solar_hour_angle_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_model: SolarPositionModel = noaa,
solar_time_model: SolarTimeModel = milne,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarHourAngle
Source code in pvgis/api/position/hour_angle.py
@log_function_call
@custom_cached
# @validate_with_pydantic(CalculateSolarHourAngleTimeSeriesNOAAInput)
def model_solar_hour_angle_series(
longitude: Longitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa,
solar_time_model: SolarTimeModel = SolarTimeModel.milne,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarHourAngle:
""" """
solar_hour_angle_series = None
if solar_position_model.value == SolarPositionModel.noaa:
solar_hour_angle_series = calculate_solar_hour_angle_series_noaa(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_position_model.value == SolarPositionModel.skyfield:
pass
if solar_position_model.value == SolarPositionModel.jenco:
pass
if solar_position_model.value == SolarPositionModel.hofierka:
solar_hour_angle_series = calculate_solar_hour_angle_series_hofierka(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.pvlib:
solar_hour_angle_series = calculate_solar_hour_angle_series_pvlib(
longitude=longitude,
timestamps=timestamps,
# timezone=timezone,
# dtype=dtype,
# array_backend=array_backend,
# verbose=verbose,
# log=log,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return solar_hour_angle_series
declination
¶
Functions:
| Name | Description |
|---|---|
calculate_solar_declination_series |
Calculate the solar declination angle |
model_solar_declination_series |
|
calculate_solar_declination_series
¶
calculate_solar_declination_series(
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_declination_models: List[
SolarDeclinationModel
] = [pvgis],
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict
Calculate the solar declination angle
The solar declination is the angle between the rays of the sun and the equator of the earth. It is used to calculate the solar elevation and azimuth angles.
Source code in pvgis/api/position/declination.py
def calculate_solar_declination_series(
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_declination_models: List[SolarDeclinationModel] = [
SolarDeclinationModel.pvgis
],
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict:
"""Calculate the solar declination angle
The solar declination is the angle between the rays of the sun and the
equator of the earth. It is used to calculate the solar elevation and
azimuth angles.
"""
results = {}
for solar_declination_model in solar_declination_models:
if (
solar_declination_model != SolarDeclinationModel.all
): # ignore 'all' in the enumeration
solar_declination_series = model_solar_declination_series(
timestamps=timestamps,
timezone=timezone,
solar_declination_model=solar_declination_model,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
array_backend=array_backend,
dtype=dtype,
verbose=verbose,
log=log,
validate_output=validate_output,
)
declination_values = getattr(
solar_declination_series, angle_output_units, NOT_AVAILABLE
)
model_entry = {
POSITION_ALGORITHM_NAME: solar_declination_model.value,
DECLINATION_NAME: declination_values,
UNITS_COLUMN_NAME: angle_output_units,
}
if fingerprint:
model_entry[FINGERPRINT_COLUMN_NAME] = generate_hash(
declination_values
)
results = results | {solar_declination_model.name: model_entry}
return results
model_solar_declination_series
¶
model_solar_declination_series(
timestamps: DatetimeIndex,
timezone: ZoneInfo = ZoneInfo("UTC"),
solar_declination_model: SolarDeclinationModel = pvgis,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = VERBOSE_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarDeclination
Source code in pvgis/api/position/declination.py
@log_function_call
@custom_cached
def model_solar_declination_series(
timestamps: DatetimeIndex,
timezone: ZoneInfo = ZoneInfo("UTC"),
solar_declination_model: SolarDeclinationModel = SolarDeclinationModel.pvgis,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = VERBOSE_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarDeclination:
""" """
solar_declination_series = None
if solar_declination_model.value == SolarDeclinationModel.noaa:
solar_declination_series = calculate_solar_declination_series_noaa(
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_declination_series = convert_series_to_degrees_if_requested(
solar_declination_series,
angle_output_units,
)
if solar_declination_model.value == SolarDeclinationModel.pvgis:
# if solar_position_model.value == SolarPositionModel.hofierka:
solar_declination_series = calculate_solar_declination_series_hofierka(
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_declination_model.value == SolarDeclinationModel.hargreaves:
pass
if solar_declination_model.value == SolarDeclinationModel.pvlib:
solar_declination_series = calculate_solar_declination_series_pvlib(
timestamps=timestamps,
# dtype=dtype,
# array_backend=array_backend,
# verbose=verbose,
# log=log,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
log_data_fingerprint(
data=solar_declination_series.value,
log_level=log,
hash_after_this_verbosity_level=HASH_AFTER_THIS_VERBOSITY_LEVEL,
)
return solar_declination_series
zenith
¶
Functions:
| Name | Description |
|---|---|
calculate_solar_zenith_series |
Calculates the solar position using the requested models and returns the |
model_solar_zenith_series |
Notes |
calculate_solar_zenith_series
¶
calculate_solar_zenith_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [
noaa
],
apply_atmospheric_refraction: bool = True,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict
Calculates the solar position using the requested models and returns the results in a dictionary.
Source code in pvgis/api/position/zenith.py
@log_function_call
def calculate_solar_zenith_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [SolarPositionModel.noaa],
# solar_time_model: SolarTimeModel = SolarTimeModel.noaa,
apply_atmospheric_refraction: bool = True,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict:
"""Calculates the solar position using the requested models and returns the
results in a dictionary.
"""
results = {}
for solar_position_model in solar_position_models:
if (
solar_position_model != SolarPositionModel.all
): # ignore 'all' in the enumeration
solar_zenith_series = model_solar_zenith_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_position_model=solar_position_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
zenith_values = (
getattr(solar_zenith_series, angle_output_units, NOT_AVAILABLE)
if solar_zenith_series
else NOT_AVAILABLE
)
model_entry = {
TIME_ALGORITHM_NAME: (
solar_zenith_series.timing_algorithm
if solar_zenith_series
else NOT_AVAILABLE
),
POSITION_ALGORITHM_NAME: solar_position_model.value,
ZENITH_NAME: zenith_values,
UNITS_COLUMN_NAME: None,
}
if fingerprint and solar_zenith_series is not None:
model_entry[FINGERPRINT_COLUMN_NAME] = generate_hash(zenith_values)
results = results | {solar_position_model.name: model_entry}
return results
model_solar_zenith_series
¶
model_solar_zenith_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_model: SolarPositionModel = noaa,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarZenith
Notes
The solar altitude angle measures from the horizon up towards the zenith (positive, and down towards the nadir (negative)). The altitude is zero all along the great circle between zenith and nadir.
- All solar calculation functions return floating angular measurements in radians.
pysolar :
From https://pysolar.readthedocs.io:
-
Altitude is reckoned with zero at the horizon. The altitude is positive when the sun is above the horizon.
-
The result is returned with units.
Source code in pvgis/api/position/zenith.py
@log_function_call
@custom_cached
@validate_with_pydantic(ModelSolarAltitudeTimeSeriesInputModel)
def model_solar_zenith_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarZenith:
"""
Notes
-----
The solar altitude angle measures from the horizon up towards the zenith
(positive, and down towards the nadir (negative)). The altitude is zero all
along the great circle between zenith and nadir.
- All solar calculation functions return floating angular measurements in
radians.
pysolar :
From https://pysolar.readthedocs.io:
- Altitude is reckoned with zero at the horizon. The altitude is positive
when the sun is above the horizon.
- The result is returned with units.
"""
solar_zenith_series = None
solar_altitude_series = None
if solar_position_model.value == SolarPositionModel.noaa:
solar_zenith_series = calculate_solar_zenith_series_noaa(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_position_model.value == SolarPositionModel.skyfield:
pass
# if solar_position_model.value == SolarPositionModel.skyfield:
# solar_zenith, solar_azimuth = calculate_solar_zenith_azimuth_skyfield(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# )
if solar_position_model.value == SolarPositionModel.suncalc:
pass
# if solar_position_model.value == SolarPositionModel.suncalc:
# # note : first azimuth, then altitude
# solar_azimuth_south_radians_convention, solar_zenith = suncalc.get_position(
# date=timestamp, # this comes first here!
# lng=longitude.degrees,
# lat=latitude.degrees,
# ).values() # zero points to south
# solar_zenith = SolarAltitude(
# value=solar_zenith,
# unit=RADIANS,
# position_algorithm='suncalc',
# timing_algorithm='suncalc',
# )
# if (
# not isfinite(solar_zenith.degrees)
# or not solar_zenith.min_degrees <= solar_zenith.degrees <= solar_zenith.max_degrees
# ):
# raise ValueError(
# f"The calculated solar altitude angle {solar_zenith.degrees} is out of the expected range\
# [{solar_zenith.min_degrees}, {solar_zenith.max_degrees}] degrees"
# )
if solar_position_model.value == SolarPositionModel.pysolar:
pass
# if solar_position_model.value == SolarPositionModel.pysolar:
# timestamp = attach_timezone(timestamp, timezone)
# solar_zenith = pysolar.solar.get_altitude(
# latitude_deg=latitude.degrees, # this comes first
# longitude_deg=longitude.degrees,
# when=timestamp,
# ) # returns degrees by default
# # required by output function
# solar_zenith = SolarAltitude(
# value=solar_zenith,
# unit=DEGREES,
# position_algorithm='pysolar',
# timing_algorithm='pysolar',
# )
# if (
# not isfinite(solar_zenith.degrees)
# or not solar_zenith.min_degrees <= solar_zenith.degrees <= solar_zenith.max_degrees
# ):
# raise ValueError(
# f"The calculated solar altitude angle {solar_zenith.degrees} is out of the expected range\
# [{solar_zenith.min_degrees}, {solar_zenith.max_degrees}] degrees"
# )
if solar_position_model.value == SolarPositionModel.jenco:
solar_altitude_series = calculate_solar_altitude_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.hofierka:
solar_altitude_series = calculate_solar_altitude_series_hofierka(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.pvlib:
pass
# if solar_position_model.value == SolarPositionModel.pvlib:
# solar_zenith = calculate_solar_zenith_pvlib(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# timezone=timezone,
# verbose=verbose,
# )
if isinstance(solar_altitude_series, SolarAltitude):
solar_zenith_series = SolarZenith(
value=solar_altitude_series.radians - (np.pi / 2),
unit=RADIANS,
position_algorithm=solar_altitude_series.position_algorithm,
timing_algorithm=solar_altitude_series.timing_algorithm,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return solar_zenith_series
altitude
¶
Functions:
| Name | Description |
|---|---|
calculate_solar_altitude_series |
Calculates the solar position using the requested models and returns the |
model_solar_altitude_series |
Notes |
calculate_solar_altitude_series
¶
calculate_solar_altitude_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [
noaa
],
solar_time_model: SolarTimeModel = noaa,
apply_atmospheric_refraction: bool = True,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict
Calculates the solar position using the requested models and returns the results in a dictionary.
Source code in pvgis/api/position/altitude.py
@log_function_call
def calculate_solar_altitude_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [SolarPositionModel.noaa],
solar_time_model: SolarTimeModel = SolarTimeModel.noaa,
apply_atmospheric_refraction: bool = True,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict:
"""Calculates the solar position using the requested models and returns the
results in a dictionary.
"""
results = {}
for solar_position_model in solar_position_models:
if (
solar_position_model != SolarPositionModel.all
): # ignore 'all' in the enumeration
solar_altitude_series = model_solar_altitude_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_position_model=solar_position_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
altitude_values = (
getattr(solar_altitude_series, angle_output_units, NOT_AVAILABLE)
if solar_altitude_series
else NOT_AVAILABLE
)
model_entry = {
SolarPositionParameter.timing: (
solar_altitude_series.timing_algorithm
if solar_altitude_series
else NOT_AVAILABLE
),
SolarPositionParameter.positioning: solar_position_model.value,
SolarPositionParameter.altitude: altitude_values,
UNITS_COLUMN_NAME: None,
}
if fingerprint and solar_altitude_series is not None:
model_entry[FINGERPRINT_COLUMN_NAME] = generate_hash(altitude_values)
results = results | {solar_position_model.name: model_entry}
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return results
model_solar_altitude_series
¶
model_solar_altitude_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex | Timestamp | None,
timezone: ZoneInfo,
solar_position_model: SolarPositionModel = noaa,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarAltitude
Notes
The solar altitude angle measures from the horizon up towards the zenith (positive, and down towards the nadir (negative)). The altitude is zero all along the great circle between zenith and nadir.
- All solar calculation functions return floating angular measurements in radians.
pysolar :
From https://pysolar.readthedocs.io:
-
Altitude is reckoned with zero at the horizon. The altitude is positive when the sun is above the horizon.
-
The result is returned with units.
Source code in pvgis/api/position/altitude.py
@log_function_call
@custom_cached
@validate_with_pydantic(ModelSolarAltitudeTimeSeriesInputModel)
def model_solar_altitude_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex | Timestamp | None,
timezone: ZoneInfo,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarAltitude:
"""
Notes
-----
The solar altitude angle measures from the horizon up towards the zenith
(positive, and down towards the nadir (negative)). The altitude is zero all
along the great circle between zenith and nadir.
- All solar calculation functions return floating angular measurements in
radians.
pysolar :
From https://pysolar.readthedocs.io:
- Altitude is reckoned with zero at the horizon. The altitude is positive
when the sun is above the horizon.
- The result is returned with units.
"""
logger.debug(
f"Executing solar positioning modelling function model_solar_altitude_series() for\n{timestamps}",
alt=f"Executing [underline]solar positioning modelling[/underline] function model_solar_altitude_series() for\n{timestamps}",
)
solar_altitude_series = None
if solar_position_model.value == SolarPositionModel.noaa:
solar_altitude_series = calculate_solar_altitude_series_noaa(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_position_model.value == SolarPositionModel.skyfield:
pass
# if solar_position_model.value == SolarPositionModel.skyfield:
# solar_altitude, solar_azimuth = calculate_solar_altitude_azimuth_skyfield(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# )
if solar_position_model.value == SolarPositionModel.suncalc:
pass
# if solar_position_model.value == SolarPositionModel.suncalc:
# # note : first azimuth, then altitude
# solar_azimuth_south_radians_convention, solar_altitude = suncalc.get_position(
# date=timestamp, # this comes first here!
# lng=longitude.degrees,
# lat=latitude.degrees,
# ).values() # zero points to south
# solar_altitude = SolarAltitude(
# value=solar_altitude,
# unit=RADIANS,
# position_algorithm='suncalc',
# timing_algorithm='suncalc',
# )
# if (
# not isfinite(solar_altitude.degrees)
# or not solar_altitude.min_degrees <= solar_altitude.degrees <= solar_altitude.max_degrees
# ):
# raise ValueError(
# f"The calculated solar altitude angle {solar_altitude.degrees} is out of the expected range\
# [{solar_altitude.min_degrees}, {solar_altitude.max_degrees}] degrees"
# )
if solar_position_model.value == SolarPositionModel.pysolar:
pass
# if solar_position_model.value == SolarPositionModel.pysolar:
# timestamp = attach_timezone(timestamp, timezone)
# solar_altitude = pysolar.solar.get_altitude(
# latitude_deg=latitude.degrees, # this comes first
# longitude_deg=longitude.degrees,
# when=timestamp,
# ) # returns degrees by default
# # required by output function
# solar_altitude = SolarAltitude(
# value=solar_altitude,
# unit=DEGREES,
# position_algorithm='pysolar',
# timing_algorithm='pysolar',
# )
# if (
# not isfinite(solar_altitude.degrees)
# or not solar_altitude.min_degrees <= solar_altitude.degrees <= solar_altitude.max_degrees
# ):
# raise ValueError(
# f"The calculated solar altitude angle {solar_altitude.degrees} is out of the expected range\
# [{solar_altitude.min_degrees}, {solar_altitude.max_degrees}] degrees"
# )
if solar_position_model.value == SolarPositionModel.jenco:
solar_altitude_series = calculate_solar_altitude_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.hofierka:
solar_altitude_series = calculate_solar_altitude_series_hofierka(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.pvlib:
solar_altitude_series = calculate_solar_altitude_series_pvlib(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
logger.debug(
f"Returning solar altitude time series :\n{solar_altitude_series}",
alt=f"Returning [yellow]solar altitude[/yellow] time series :\n{solar_altitude_series}",
)
return solar_altitude_series
azimuth
¶
An overview of conventions and conversions from a North-based system to either East- or South-based systems is:
┌─────────────┐ ┌────────────┐ ┌────────────┐
│ N=0 │ │ N │ │ N │
│ ▲ │ │ ▲ │ │ ▲ │
Origin │ W ◄┼► E │ │ W ◄┼► E=0 │ │ W ◄┼► E │
│ ▼ │ │ ▼ │ │ ▼ │
│ S │ │ S │ │ S=0 │
└─────────────┘ └────────────┘ └────────────┘
┌─────────────┐ ┌────────────┐ ┌────────────┐
│ │ │ │ │ │
│ │ │ │ │ │
Input South │ 180 │ │ 90 │ │ 0 │ (IS) │ │ │ │ │ │ │ │ │ │ │ │ └─────────────┘ └────────────┘ └────────────┘ ┌─────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ │ │ │ Internal │ │ │ │ │ │ │ = │ │ IS - 90 │ │ IS - 180 │ Conversion │ │ │ │ │ │ │ │ │ │ │ │ └─────────────┘ └────────────┘ └────────────┘
Functions:
| Name | Description |
|---|---|
calculate_solar_azimuth_series |
Calculates the solar position using all models and returns the results in a table. |
model_solar_azimuth_series |
The solar azimuth angle measures horizontally around the horizon from north |
calculate_solar_azimuth_series
¶
calculate_solar_azimuth_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [
noaa
],
solar_time_model: SolarTimeModel = noaa,
apply_atmospheric_refraction: bool = True,
refracted_solar_zenith: RefractedSolarZenith
| None = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
verbose: int = VERBOSE_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict
Calculates the solar position using all models and returns the results in a table.
Source code in pvgis/api/position/azimuth.py
def calculate_solar_azimuth_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
solar_position_models: List[SolarPositionModel] = [SolarPositionModel.noaa],
solar_time_model: SolarTimeModel = SolarTimeModel.noaa,
apply_atmospheric_refraction: bool = True,
refracted_solar_zenith: (
RefractedSolarZenith | None
) = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT, # radians
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
verbose: int = VERBOSE_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict:
"""
Calculates the solar position using all models and returns the results in a table.
"""
results = {}
for solar_position_model in solar_position_models:
if (
solar_position_model != SolarPositionModel.all
): # ignore 'all' in the enumeration
solar_azimuth_series = model_solar_azimuth_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_position_model=solar_position_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
refracted_solar_zenith=refracted_solar_zenith,
solar_time_model=solar_time_model,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
verbose=verbose,
validate_output=validate_output,
)
azimuth_values = (
getattr(solar_azimuth_series, angle_output_units, NOT_AVAILABLE)
if solar_azimuth_series
else NOT_AVAILABLE
)
model_entry = {
TIME_ALGORITHM_NAME: (
solar_azimuth_series.timing_algorithm
if solar_azimuth_series
else NOT_AVAILABLE
),
POSITION_ALGORITHM_NAME: (
solar_azimuth_series.position_algorithm
if solar_azimuth_series
else NOT_AVAILABLE
),
AZIMUTH_NAME: azimuth_values,
AZIMUTH_ORIGIN_NAME: (
solar_azimuth_series.origin
if solar_azimuth_series
else NOT_AVAILABLE
),
UNITS_COLUMN_NAME: angle_output_units,
}
if fingerprint and solar_azimuth_series is not None:
model_entry[FINGERPRINT_COLUMN_NAME] = generate_hash(azimuth_values)
results = results | {solar_position_model.name: model_entry}
return results
model_solar_azimuth_series
¶
model_solar_azimuth_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex | None,
timezone: ZoneInfo | None,
solar_position_model: SolarPositionModel = noaa,
apply_atmospheric_refraction: bool = True,
refracted_solar_zenith: RefractedSolarZenith
| None = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
solar_time_model: SolarTimeModel = milne,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = 0,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarAzimuth
The solar azimuth angle measures horizontally around the horizon from north through east, south, and west.
Notes
- All solar calculation functions return floating angular measurements in radians.
pysolar :
From https://pysolar.readthedocs.io:
-
Azimuth is reckoned with zero corresponding to north. Positive azimuth estimates correspond to estimates east of north; negative estimates, or estimates larger than 180 are west of north. In the northern hemisphere, if we speak in terms of (altitude, azimuth), the sun comes up around (0, 90), reaches (70, 180) around noon, and sets around (0, 270).
-
The result is returned with units.
Source code in pvgis/api/position/azimuth.py
@log_function_call
@custom_cached
@validate_with_pydantic(ModelSolarAzimuthTimeSeriesInputModel)
def model_solar_azimuth_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex | None,
timezone: ZoneInfo | None,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa,
apply_atmospheric_refraction: bool = True,
refracted_solar_zenith: (
RefractedSolarZenith | None
) = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT, # radians
solar_time_model: SolarTimeModel = SolarTimeModel.milne,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = 0,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> SolarAzimuth:
"""
The solar azimuth angle measures horizontally around the horizon from north
through east, south, and west.
Notes
-----
- All solar calculation functions return floating angular measurements in
radians.
pysolar :
From https://pysolar.readthedocs.io:
- Azimuth is reckoned with zero corresponding to north. Positive azimuth
estimates correspond to estimates east of north; negative estimates, or
estimates larger than 180 are west of north. In the northern hemisphere,
if we speak in terms of (altitude, azimuth), the sun comes up around
(0, 90), reaches (70, 180) around noon, and sets around (0, 270).
- The result is returned with units.
"""
logger.debug(
f"Executing solar positioning modelling function model_solar_azimuth_series() for\n{timestamps}",
alt=f"Executing [underline]solar positioning modelling[/underline] function model_solar_azimuth_series() for\n{timestamps}",
)
solar_azimuth_series = None
if solar_position_model.value == SolarPositionModel.noaa:
solar_azimuth_series = calculate_solar_azimuth_series_noaa(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_position_model.value == SolarPositionModel.jenco:
solar_azimuth_series = calculate_solar_azimuth_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.skyfield:
pass
# if solar_position_model.value == SolarPositionModel.skyfield:
# solar_altitude, solar_azimuth = calculate_solar_altitude_azimuth_skyfield(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# )
if solar_position_model.value == SolarPositionModel.suncalc:
pass
# if solar_position_model.value == SolarPositionModel.suncalc:
# # note : first azimuth, then altitude
# solar_azimuth_south_radians_convention, solar_altitude = suncalc.get_position(
# date=timestamp, # this comes first here!
# lng=longitude.degrees,
# lat=latitude.degrees,
# ).values() # zero points to south
# solar_azimuth = convert_south_to_north_radians_convention(
# solar_azimuth_south_radians_convention
# )
# solar_azimuth = SolarAzimuth(
# value=solar_azimuth,
# unit=RADIANS,
# position_algorithm='suncalc',
# timing_algorithm='suncalc',
# )
if solar_position_model.value == SolarPositionModel.pysolar:
pass
# timestamp = attach_timezone(timestamp, timezone)
# solar_azimuth = pysolar.solar.get_azimuth(
# latitude_deg=longitude.degrees, # this comes first
# longitude_deg=latitude.degrees,
# when=timestamp,
# ) # returns degrees by default
# # required by output function
# solar_azimuth = SolarAzimuth(
# value=solar_azimuth,
# unit=DEGREES,
# position_algorithm='pysolar',
# timing_algorithm='pysolar',
# )
if solar_position_model.value == SolarPositionModel.hofierka:
pass
# solar_azimuth = calculate_solar_azimuth_pvgis(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# timezone=timezone,
# solar_time_model=solar_time_model,
# )
if solar_position_model.value == SolarPositionModel.pvlib:
solar_azimuth_series = calculate_solar_azimuth_series_pvlib(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
# log_data_fingerprint(
# data=solar_azimuth_series.value,
# log_level=log,
# hash_after_this_verbosity_level=HASH_AFTER_THIS_VERBOSITY_LEVEL,
# )
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
logger.debug(
f"Returning solar azimuth time series :\n{solar_azimuth_series}",
alt=f"Returning [yellow]solar azimuth[/yellow] time series :\n{solar_azimuth_series}",
)
return solar_azimuth_series
overview
¶
Functions:
| Name | Description |
|---|---|
calculate_solar_position_overview_series |
Calculate an overview of solar position parameters for a time series. |
model_solar_position_overview_series |
Model solar position parameters for a position and moment in time. |
calculate_solar_position_overview_series
¶
calculate_solar_position_overview_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
event: List[SolarEvent | None] = [None],
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
solar_position_models: List[SolarPositionModel] = [
noaa
],
sun_horizon_position: List[
SunHorizonPositionModel
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: SolarIncidenceModel = iqbal,
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = pvgis,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
solar_time_model: SolarTimeModel = noaa,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = VERBOSE_LEVEL_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict
Calculate an overview of solar position parameters for a time series.
Calculate an overview of solar position parameters for a solar surface orientation and tilt at a given geographic position for a time series and for the user-requested solar position models (as in positioning algorithms) and one solar time model (as in solar timing algorithm).
Notes
While it is straightforward to report the solar position parameters for a series of solar position models (positioning algorithms), offering the option for multiple solar time models (timing algorithms), would mean to carefully craft the combinations for each solar time model and solar position models. Not impossible, yet something for expert users that would like to assess different combinations of algorithms to explore and assess solar position parameters.
Source code in pvgis/api/position/overview.py
def calculate_solar_position_overview_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
event: List[SolarEvent | None] = [None],
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
solar_position_models: List[SolarPositionModel] = [SolarPositionModel.noaa],
sun_horizon_position: List[SunHorizonPositionModel] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: SolarIncidenceModel = SolarIncidenceModel.iqbal,
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = ShadingModel.pvgis,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
solar_time_model: SolarTimeModel = SolarTimeModel.noaa,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = RADIANS,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = VERBOSE_LEVEL_DEFAULT,
fingerprint: bool = FINGERPRINT_FLAG_DEFAULT,
) -> Dict:
"""Calculate an overview of solar position parameters for a time series.
Calculate an overview of solar position parameters for a solar surface
orientation and tilt at a given geographic position for a time series and
for the user-requested solar position models (as in positioning algorithms)
and one solar time model (as in solar timing algorithm).
Notes
-----
While it is straightforward to report the solar position parameters for a
series of solar position models (positioning algorithms), offering the
option for multiple solar time models (timing algorithms), would mean to
carefully craft the combinations for each solar time model and solar
position models. Not impossible, yet something for expert users that would
like to assess different combinations of algorithms to explore and assess
solar position parameters.
"""
results = {}
for solar_position_model in solar_position_models:
# for the time being! ------------------------------------------------
if solar_position_model != SolarPositionModel.noaa:
logger.warning(
f"Solar geometry overview series is not implemented for the requested solar position model: {solar_position_model}!"
)
# --------------------------------------------------------------------
if (
solar_position_model != SolarPositionModel.all
): # ignore 'all' in the enumeration
(
solar_declination_series,
solar_hour_angle_series,
solar_zenith_series,
solar_altitude_series,
solar_azimuth_series,
surface_orientation,
surface_tilt,
solar_incidence_series,
sun_horizon_position_series,
surface_in_shade_series,
solar_event_type_series,
solar_event_time_series,
) = model_solar_position_overview_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
event=event,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
solar_time_model=solar_time_model,
solar_position_model=solar_position_model,
sun_horizon_position=sun_horizon_position,
horizon_profile=horizon_profile,
shading_model=shading_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
# solar_incidence_model=solar_incidence_model,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_position_model_overview = {
solar_position_model.name: {
SolarPositionParameter.timing: (
solar_hour_angle_series.timing_algorithm
if solar_hour_angle_series
else NOT_AVAILABLE
),
SolarPositionParameter.declination: (
getattr(
solar_declination_series, angle_output_units, NOT_AVAILABLE
)
if solar_declination_series
else NOT_AVAILABLE
),
SolarPositionParameter.hour_angle: (
getattr(
solar_hour_angle_series, angle_output_units, NOT_AVAILABLE
)
if solar_hour_angle_series
else NOT_AVAILABLE
),
SolarPositionParameter.positioning: solar_position_model.value,
SolarPositionParameter.zenith: (
getattr(solar_zenith_series, angle_output_units, NOT_AVAILABLE)
if solar_zenith_series
else NOT_AVAILABLE
),
SolarPositionParameter.altitude: (
getattr(
solar_altitude_series, angle_output_units, NOT_AVAILABLE
)
if solar_altitude_series
else NOT_AVAILABLE
),
SolarPositionParameter.azimuth: (
getattr(solar_azimuth_series, angle_output_units, NOT_AVAILABLE)
if solar_azimuth_series
else NOT_AVAILABLE
),
AZIMUTH_ORIGIN_NAME: ( # Not meant as a column name !
solar_azimuth_series.origin
if solar_azimuth_series
else NOT_AVAILABLE
),
SURFACE_ORIENTATION_NAME: (
getattr(surface_orientation, angle_output_units, NOT_AVAILABLE)
if surface_orientation
else None
),
SURFACE_TILT_NAME: (
getattr(surface_tilt, angle_output_units, NOT_AVAILABLE)
if surface_tilt
else None
),
SolarPositionParameter.incidence: (
getattr(
solar_incidence_series, angle_output_units, NOT_AVAILABLE
)
if solar_incidence_series
else NOT_AVAILABLE
),
INCIDENCE_ALGORITHM_NAME: (
solar_incidence_series.incidence_algorithm
if solar_incidence_series
else NOT_AVAILABLE
),
INCIDENCE_DEFINITION: (
solar_incidence_series.definition
if solar_incidence_series
else NOT_AVAILABLE
),
SUN_HORIZON_POSITIONS_NAME: ( # Requested positions
sun_horizon_position if sun_horizon_position else NOT_AVAILABLE
),
SolarPositionParameter.sun_horizon: ( # Series of sun-horizon position
sun_horizon_position_series.tolist()
if sun_horizon_position_series is not None
else NOT_AVAILABLE
),
**generate_dictionary_of_surface_in_shade_series(
surface_in_shade_series,
angle_output_units,
), # This is the horizon height profile !
SOLAR_EVENTS_NAME: ( # Requested solar events
event if event else NOT_AVAILABLE
),
**(
{SolarPositionParameter.event_type: solar_event_type_series}
if solar_event_type_series is not None
and not (
isinstance(solar_event_type_series, (list, numpy.ndarray))
and all(x is None for x in solar_event_type_series)
)
else {}
),
**(
{SolarPositionParameter.event_time: solar_event_time_series}
if solar_event_time_series is not None
and not (
isinstance(solar_event_time_series, DatetimeIndex)
and all(isna(x) for x in solar_event_time_series)
)
else {}
),
UNITS_COLUMN_NAME: angle_output_units,
FINGERPRINT_COLUMN_NAME: (
generate_hash(solar_incidence_series)
if fingerprint
else NOT_AVAILABLE
),
}
}
results = results | solar_position_model_overview
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return results
model_solar_position_overview_series
¶
model_solar_position_overview_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
event: List[SolarEvent | None] = [None],
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
solar_time_model: SolarTimeModel = milne,
solar_position_model: SolarPositionModel = noaa,
sun_horizon_position: List[
SunHorizonPositionModel
] = SUN_HORIZON_POSITION_DEFAULT,
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = pvgis,
apply_atmospheric_refraction: bool = True,
refracted_solar_zenith: float
| None = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> Tuple
Model solar position parameters for a position and moment in time.
Model essential solar position parameters for a solar surface
orientation and tilt at a given geographic position for a time series based
on a given solar position model (as in positioning algorithm, see class
SolarPositionModel) and solar time model (as in solar timing algorithm,
see class SolarTimeModel) :
- solar declination
- solar hour angle
- solar zenith
- solar altitude
- solar azimuth
- solar incidence
- behind horizon
Notes
The solar altitude angle measures from the horizon up towards the zenith (positive, and down towards the nadir (negative)). The altitude is zero all along the great circle between zenith and nadir.
The solar azimuth angle measures horizontally around the horizon from north through east, south, and west.
In order to avoid confusion, the solar incidence angle series are derived
using specific functions for each "algorithm". For example, the NOAA solar
positioning set of equations are "bound" to the
calculate_solar_incidence_series_iqbal(). However, thinking of more
flexibility, for example to facilitate a cross-comparison
between different implementations of the Equation of Time and their impact
on different solar incidence angle definitions, we can refactor the source
code to allow for combinations of different "blocks" of solar timing and
positioning algorithms.
The following combinations are currently the default ones to derive the solar incidence angle :
-
SolarPositionModel.noaa + SolarIncidenceModel.iqbal
-
SolarPositionModel.jenco + SolarIncidenceModel.jenco
-
SolarPositionModel.hofierka + SolarIncidenceModel.hofierka
Source code in pvgis/api/position/overview.py
@log_function_call
@validate_with_pydantic(ModelSolarPositionOverviewSeriesInputModel)
def model_solar_position_overview_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
event: List[SolarEvent | None] = [None],
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
solar_time_model: SolarTimeModel = SolarTimeModel.milne,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa,
sun_horizon_position: List[SunHorizonPositionModel] = SUN_HORIZON_POSITION_DEFAULT,
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = ShadingModel.pvgis,
apply_atmospheric_refraction: bool = True,
refracted_solar_zenith: float | None = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
# solar_incidence_model: SolarIncidenceModel = SolarIncidenceModel.iqbal,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
) -> Tuple:
"""Model solar position parameters for a position and moment in time.
Model essential solar position parameters for a solar surface
orientation and tilt at a given geographic position for a time series based
on a given solar position model (as in positioning algorithm, see class
`SolarPositionModel`) and solar time model (as in solar timing algorithm,
see class `SolarTimeModel`) :
- solar declination
- solar hour angle
- solar zenith
- solar altitude
- solar azimuth
- solar incidence
- behind horizon
Notes
-----
The solar altitude angle measures from the horizon up towards the zenith
(positive, and down towards the nadir (negative)). The altitude is zero all
along the great circle between zenith and nadir.
The solar azimuth angle measures horizontally around the horizon from north
through east, south, and west.
In order to avoid confusion, the solar incidence angle series are derived
using specific functions for each "algorithm". For example, the NOAA solar
positioning set of equations are "bound" to the
`calculate_solar_incidence_series_iqbal()`. However, thinking of more
flexibility, for example to facilitate a cross-comparison
between different implementations of the Equation of Time and their impact
on different solar incidence angle definitions, we can refactor the source
code to allow for combinations of different "blocks" of solar timing and
positioning algorithms.
The following combinations are currently the default ones to derive the
solar incidence angle :
- SolarPositionModel.noaa + SolarIncidenceModel.iqbal
- SolarPositionModel.jenco + SolarIncidenceModel.jenco
- SolarPositionModel.hofierka + SolarIncidenceModel.hofierka
"""
solar_declination_series = None # updated if applicable
solar_hour_angle_series = None
solar_zenith_series = None # updated if applicable
solar_altitude_series = None
solar_azimuth_series = None
solar_incidence_series = None
sun_horizon_position_series = None
surface_in_shade_series = model_surface_in_shade_series(
horizon_profile=horizon_profile,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_time_model=solar_time_model,
solar_position_model=solar_position_model,
shading_model=shading_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
refracted_solar_zenith=refracted_solar_zenith,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_event_series = model_solar_event_time_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
event=event,
timezone=timezone,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_event_type_series = solar_event_series.event
solar_event_time_series = solar_event_series.value
if solar_position_model.value == SolarPositionModel.noaa:
solar_declination_series = calculate_solar_declination_series_noaa(
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_hour_angle_series = calculate_solar_hour_angle_series_noaa(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_zenith_series = calculate_solar_zenith_series_noaa(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_altitude_series = calculate_solar_altitude_series_noaa(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
solar_azimuth_series = calculate_solar_azimuth_series_noaa(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
# Iqbal (1983) measures azimuthal angles from South !
surface_orientation_south_convention = SurfaceOrientation(
value=convert_north_to_south_radians_convention(
north_based_angle=surface_orientation
),
unit=RADIANS,
)
solar_incidence_series = calculate_solar_incidence_series_iqbal(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
surface_orientation=surface_orientation_south_convention,
surface_tilt=surface_tilt,
apply_atmospheric_refraction=apply_atmospheric_refraction,
sun_horizon_position=sun_horizon_position,
surface_in_shade_series=surface_in_shade_series,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_position_model.value == SolarPositionModel.skyfield:
pass
# solar_hour_angle, solar_declination = calculate_solar_hour_angle_declination_skyfield(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# timezone=timezone,
# )
# solar_altitude, solar_azimuth = calculate_solar_altitude_azimuth_skyfield(
# longitude=longitude,
# latitude=latitude,
# timestamp=timestamp,
# )
# solar_zenith = SolarZenith(
# value = 90 - solar_altitude.degrees,
# unit = DEGREES,
# position_algorithm=solar_azimuth.position_algorithm,
# timing_algorithm=solar_azimuth.timing_algorithm,
# )
if solar_position_model.value == SolarPositionModel.suncalc:
pass
# # note : first azimuth, then altitude
# solar_azimuth_south_radians_convention, solar_altitude = suncalc.get_position(
# date=timestamp, # this comes first here!
# lng=longitude.degrees,
# lat=latitude.degrees,
# ).values() # zero points to south
# solar_azimuth = convert_south_to_north_radians_convention(
# solar_azimuth_south_radians_convention
# )
# solar_azimuth = SolarAzimuth(
# value=solar_azimuth,
# unit=RADIANS,
# position_algorithm='suncalc',
# timing_algorithm='suncalc',
# )
# solar_altitude = SolarAltitude(
# value=solar_altitude,
# unit=RADIANS,
# position_algorithm='suncalc',
# timing_algorithm='suncalc',
# )
# solar_zenith = SolarZenith(
# value = 90 - solar_altitude.degrees,
# unit = DEGREES,
# position_algorithm='suncalc',
# timing_algorithm='suncalc',
# )
if solar_position_model.value == SolarPositionModel.pysolar:
pass
# timestamp = attach_timezone(timestamp, timezone)
# solar_altitude = pysolar.solar.get_altitude(
# latitude_deg=latitude.degrees, # this comes first
# longitude_deg=longitude.degrees,
# when=timestamp,
# ) # returns degrees by default
# # required by output function
# solar_altitude = SolarAltitude(
# value=solar_altitude,
# unit=DEGREES,
# position_algorithm='pysolar',
# timing_algorithm='pysolar',
# )
# solar_zenith = SolarZenith(
# value = 90 - solar_altitude.degrees,
# unit = DEGREES,
# position_algorithm=solar_altitude.position_algorithm,
# timing_algorithm=solar_altitude.timing_algorithm,
# )
# solar_azimuth = pysolar.solar.get_azimuth(
# latitude_deg=latitude.degrees, # this comes first
# longitude_deg=longitude.degrees,
# when=timestamp,
# ) # returns degrees by default
# # required by output function
# solar_azimuth = SolarAzimuth(
# value=solar_azimuth,
# unit=DEGREES,
# position_algorithm='pysolar',
# timing_algorithm='pysolar',
# )
if solar_position_model.value == SolarPositionModel.jenco:
solar_declination_series = calculate_solar_declination_series_jenco(
timestamps=timestamps,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_altitude_series = calculate_solar_altitude_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=0,
log=log,
)
solar_azimuth_series = calculate_solar_azimuth_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=0,
log=log,
) # North = 0
surface_orientation_south_convention = SurfaceOrientation(
value=convert_north_to_south_radians_convention(
north_based_angle=surface_orientation
),
unit=RADIANS,
)
# And apparently, defined the complementary surface tilt angle too!
# from math import pi
# surface_tilt = SurfaceTilt(
# value=(pi/2 - surface_tilt.radians),
# unit=RADIANS,
# )
# ---------------------------------------------------------- Update-Me
solar_incidence_series = calculate_solar_incidence_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
# surface_orientation=surface_orientation,
# surface_orientation=surface_orientation_east_convention,
surface_orientation=surface_orientation_south_convention,
surface_tilt=surface_tilt,
apply_atmospheric_refraction=apply_atmospheric_refraction,
surface_in_shade_series=surface_in_shade_series,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.hofierka:
solar_declination_series = calculate_solar_declination_series_hofierka(
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_hour_angle_series = calculate_solar_hour_angle_series_hofierka(
longitude=longitude,
timestamps=timestamps,
timezone=timezone,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_altitude_series = calculate_solar_altitude_series_hofierka(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_azimuth_series = calculate_solar_azimuth_series_hofierka(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
# apply_atmospheric_refraction=apply_atmospheric_refraction,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
) # East = 0 !
surface_orientation_south_convention = SurfaceOrientation(
value=convert_north_to_south_radians_convention(
north_based_angle=surface_orientation
),
unit=RADIANS,
)
solar_incidence_series = calculate_solar_incidence_series_hofierka(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
# surface_orientation=surface_orientation,
surface_orientation=surface_orientation_south_convention,
surface_tilt=surface_tilt,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_position_model.value == SolarPositionModel.pvlib:
solar_declination_series = calculate_solar_declination_series_pvlib(
timestamps=timestamps,
# dtype=dtype,
# array_backend=array_backend,
# verbose=verbose,
# log=log,
)
solar_hour_angle_series = calculate_solar_hour_angle_series_pvlib(
longitude=longitude,
timestamps=timestamps,
# timezone=timezone,
# dtype=dtype,
# array_backend=array_backend,
# verbose=verbose,
# log=log,
)
solar_zenith_series = calculate_solar_zenith_series_pvlib(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_altitude_series = calculate_solar_altitude_series_pvlib(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_azimuth_series = calculate_solar_azimuth_series_pvlib(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
solar_incidence_series = calculate_solar_incidence_series_pvlib(
longitude=longitude,
latitude=latitude,
surface_tilt=surface_tilt,
surface_orientation=surface_orientation,
timestamps=timestamps,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_incidence_series.sun_horizon_position is not None:
sun_horizon_position_series = solar_incidence_series.sun_horizon_position
else:
sun_horizon_position = NOT_AVAILABLE
if shading_model:
surface_in_shade_series = model_surface_in_shade_series(
horizon_profile=horizon_profile,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_time_model=solar_time_model,
solar_position_model=solar_position_model,
shading_model=shading_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
refracted_solar_zenith=refracted_solar_zenith,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
position_series = (
solar_declination_series if solar_declination_series is not None else None,
solar_hour_angle_series if solar_hour_angle_series is not None else None,
solar_zenith_series if solar_zenith_series is not None else None,
solar_altitude_series if solar_altitude_series is not None else None,
solar_azimuth_series if solar_azimuth_series is not None else None,
surface_orientation if surface_orientation is not None else None,
surface_tilt if surface_tilt is not None else None,
solar_incidence_series if solar_incidence_series is not None else None,
(
sun_horizon_position_series
if sun_horizon_position_series is not None
else None
),
surface_in_shade_series if surface_in_shade_series is not None else None,
solar_event_type_series if solar_event_series.event is not None else None,
solar_event_time_series if solar_event_series.value is not None else None,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return position_series
incidence
¶
API modules to calculate the solar incidence angle between the direction of the sun-to-surface vector and either the direction of the normal-to-surface vector or the direction of the surface-plane vector.
Attention is required im handling the rotational solar azimuth and surface orientation (also referred to as surface azimuth) anngles. The origin of measuring azimuthal angles will obvisouly impact the direction of the calculated angles. See also the API azimuth.py module.
Functions:
| Name | Description |
|---|---|
calculate_solar_incidence_series |
Calculates the solar Incidence angle for the selected models and returns the results in a table |
model_solar_incidence_series |
|
calculate_solar_incidence_series
¶
calculate_solar_incidence_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
solar_position_model: SolarPositionModel = noaa,
sun_horizon_position: List[
SunHorizonPositionModel
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_models: List[SolarIncidenceModel] = [
iqbal
],
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = pvgis,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = ANGLE_OUTPUT_UNITS_DEFAULT,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
) -> Dict
Calculates the solar Incidence angle for the selected models and returns the results in a table
Source code in pvgis/api/position/incidence.py
@log_function_call
def calculate_solar_incidence_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo,
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
# solar_time_model: SolarTimeModel = SolarTimeModel.milne,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa, # Only one !
sun_horizon_position: List[SunHorizonPositionModel] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_models: List[SolarIncidenceModel] = [SolarIncidenceModel.iqbal],
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = ShadingModel.pvgis,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
angle_output_units: str = ANGLE_OUTPUT_UNITS_DEFAULT,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
) -> Dict:
"""Calculates the solar Incidence angle for the selected models and returns the results in a table"""
results = {}
for solar_incidence_model in solar_incidence_models:
if (
solar_incidence_model != SolarIncidenceModel.all
): # ignore 'all' in the enumeration
solar_incidence_series = model_solar_incidence_series(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
surface_orientation=surface_orientation,
surface_tilt=surface_tilt,
solar_position_model=solar_position_model,
sun_horizon_position=sun_horizon_position,
# solar_time_model=solar_time_model,
solar_incidence_model=solar_incidence_model,
horizon_profile=horizon_profile,
shading_model=shading_model,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
validate_output=validate_output,
verbose=verbose,
log=log,
)
solar_incidence_model_series = {
solar_incidence_model.name: {
TIME_ALGORITHM_NAME: (
solar_incidence_series.timing_algorithm
if solar_incidence_series
else NOT_AVAILABLE
),
POSITION_ALGORITHM_NAME: (
solar_incidence_series.position_algorithm
if solar_incidence_series
else NOT_AVAILABLE
),
# ALTITUDE_NAME: getattr(solar_altitude_series, angle_output_units, NOT_AVAILABLE) if solar_altitude_series else NOT_AVAILABLE,
# AZIMUTH_NAME: getattr(solar_azimuth_series, angle_output_units, NOT_AVAILABLE) if solar_azimuth_series else NOT_AVAILABLE,
AZIMUTH_ORIGIN_NAME: (
solar_incidence_series.azimuth_origin
if solar_incidence_series
else NOT_AVAILABLE
),
# SURFACE_ORIENTATION_NAME: getattr(surface_orientation, angle_output_units, NOT_AVAILABLE) if surface_orientation else None,
# SURFACE_TILT_NAME: getattr(surface_tilt, angle_output_units, NOT_AVAILABLE) if surface_tilt else None,
INCIDENCE_ALGORITHM_NAME: (
solar_incidence_series.incidence_algorithm
if solar_incidence_series
else NOT_AVAILABLE
),
INCIDENCE_NAME: (
getattr(
solar_incidence_series, angle_output_units, NOT_AVAILABLE
)
if solar_incidence_series
else NOT_AVAILABLE
),
INCIDENCE_DEFINITION: (
solar_incidence_series.definition
if solar_incidence_series
else NOT_AVAILABLE
),
UNITS_COLUMN_NAME: angle_output_units,
}
}
results = results | solar_incidence_model_series
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
return results
model_solar_incidence_series
¶
model_solar_incidence_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo | None = None,
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: float
| None = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT,
solar_time_model: SolarTimeModel = milne,
solar_position_model: SolarPositionModel = noaa,
sun_horizon_position: List[
SunHorizonPositionModel
] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: SolarIncidenceModel = iqbal,
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = pvgis,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
) -> SolarIncidence
Source code in pvgis/api/position/incidence.py
@log_function_call
@custom_cached
@validate_with_pydantic(ModelSolarIncidenceTimeSeriesInputModel)
def model_solar_incidence_series(
longitude: Longitude,
latitude: Latitude,
timestamps: DatetimeIndex,
timezone: ZoneInfo | None = None,
surface_orientation: SurfaceOrientation = SURFACE_ORIENTATION_DEFAULT,
surface_tilt: SurfaceTilt = SURFACE_TILT_DEFAULT,
apply_atmospheric_refraction: bool = ATMOSPHERIC_REFRACTION_FLAG_DEFAULT,
refracted_solar_zenith: (
float | None
) = REFRACTED_SOLAR_ZENITH_ANGLE_DEFAULT, # radians
solar_time_model: SolarTimeModel = SolarTimeModel.milne,
solar_position_model: SolarPositionModel = SolarPositionModel.noaa,
sun_horizon_position: List[SunHorizonPositionModel] = SUN_HORIZON_POSITION_DEFAULT,
solar_incidence_model: SolarIncidenceModel = SolarIncidenceModel.iqbal,
horizon_profile: DataArray | None = None,
shading_model: ShadingModel = ShadingModel.pvgis,
complementary_incidence_angle: bool = COMPLEMENTARY_INCIDENCE_ANGLE_DEFAULT,
zero_negative_solar_incidence_angle: bool = ZERO_NEGATIVE_INCIDENCE_ANGLE_DEFAULT,
perigee_offset: float = PERIGEE_OFFSET,
eccentricity_correction_factor: float = ECCENTRICITY_CORRECTION_FACTOR,
dtype: str = DATA_TYPE_DEFAULT,
array_backend: str = ARRAY_BACKEND_DEFAULT,
validate_output: bool = VALIDATE_OUTPUT_DEFAULT,
verbose: int = VERBOSE_LEVEL_DEFAULT,
log: int = LOG_LEVEL_DEFAULT,
) -> SolarIncidence:
""" """
logger.debug(
f"Executing solar positioning modelling function model_solar_incidence_series() for\n{timestamps}",
alt=f"Executing [underline]solar positioning modelling[/underline] function model_solar_incidence_series() for\n{timestamps}",
)
solar_incidence_series = None
surface_in_shade_series = model_surface_in_shade_series(
horizon_profile=horizon_profile,
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
solar_time_model=solar_time_model,
solar_position_model=solar_position_model,
shading_model=shading_model,
apply_atmospheric_refraction=apply_atmospheric_refraction,
refracted_solar_zenith=refracted_solar_zenith,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
validate_output=validate_output,
)
if solar_incidence_model.value == SolarIncidenceModel.jenco:
# Update-Me ----------------------------------------------------------
# Hofierka (2002) measures azimuth angles from East !
# Convert the user-defined North-based surface orientation angle to East-based
# surface_orientation_east_convention = SurfaceOrientation(
# value=convert_north_to_east_radians_convention(
# north_based_angle=surface_orientation
# ),
# unit=RADIANS,
# )
surface_orientation_south_convention = SurfaceOrientation(
value=convert_north_to_south_radians_convention(
north_based_angle=surface_orientation
),
unit=RADIANS,
)
# And apparently, defined the complementary surface tilt angle too!
# from math import pi
# surface_tilt = SurfaceTilt(
# value=(pi/2 - surface_tilt.radians),
# unit=RADIANS,
# )
# ---------------------------------------------------------- Update-Me
solar_incidence_series = calculate_solar_incidence_series_jenco(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
# surface_orientation=surface_orientation,
# surface_orientation=surface_orientation_east_convention,
surface_orientation=surface_orientation_south_convention,
surface_tilt=surface_tilt,
apply_atmospheric_refraction=apply_atmospheric_refraction,
surface_in_shade_series=surface_in_shade_series,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
perigee_offset=perigee_offset,
eccentricity_correction_factor=eccentricity_correction_factor,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_incidence_model.value == SolarIncidenceModel.iqbal:
# Iqbal (1983) measures azimuthal angles from South !
surface_orientation_south_convention = SurfaceOrientation(
value=convert_north_to_south_radians_convention(
north_based_angle=surface_orientation
),
unit=RADIANS,
)
solar_incidence_series = calculate_solar_incidence_series_iqbal(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
surface_orientation=surface_orientation_south_convention,
surface_tilt=surface_tilt,
apply_atmospheric_refraction=apply_atmospheric_refraction,
sun_horizon_position=sun_horizon_position,
surface_in_shade_series=surface_in_shade_series,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
dtype=dtype,
array_backend=array_backend,
validate_output=validate_output,
verbose=verbose,
log=log,
)
if solar_incidence_model.value == SolarIncidenceModel.hofierka:
surface_orientation_south_convention = SurfaceOrientation(
value=convert_north_to_south_radians_convention(
north_based_angle=surface_orientation
),
unit=RADIANS,
)
solar_incidence_series = calculate_solar_incidence_series_hofierka(
longitude=longitude,
latitude=latitude,
timestamps=timestamps,
timezone=timezone,
# surface_orientation=surface_orientation,
surface_orientation=surface_orientation_south_convention,
surface_tilt=surface_tilt,
apply_atmospheric_refraction=apply_atmospheric_refraction,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if solar_incidence_model.value == SolarIncidenceModel.pvlib:
solar_incidence_series = calculate_solar_incidence_series_pvlib(
longitude=longitude,
latitude=latitude,
surface_tilt=surface_tilt,
surface_orientation=surface_orientation,
timestamps=timestamps,
complementary_incidence_angle=complementary_incidence_angle,
zero_negative_solar_incidence_angle=zero_negative_solar_incidence_angle,
dtype=dtype,
array_backend=array_backend,
verbose=verbose,
log=log,
)
if verbose > DEBUG_AFTER_THIS_VERBOSITY_LEVEL:
debug(locals())
logger.debug(
f"Returning solar incidence time series :\n{solar_incidence_series}",
alt=f"Returning [yellow]solar incidence[/yellow] time series :\n{solar_incidence_series}",
)
return solar_incidence_series
models
¶
Functions:
| Name | Description |
|---|---|
select_models |
Select models from an enum list. |
validate_model |
Check that one and only one model from an Enum class is selected |
select_models
¶
Select models from an enum list.
validate_model
¶
Check that one and only one model from an Enum class is selected
Source code in pvgis/api/position/models.py
def validate_model(enum_type: Type[Enum], model: List[Enum]) -> Enum:
"""Check that one and only one model from an Enum class is selected"""
if model == enum_type.all: # or len(model) > 1: will not work! -- ReviewMe
raise typer.BadParameter(
"You can select only one model for [code]solar_time_model[/code]. Multiple or all are not a meaningful option."
)
return model