Solar Position
PVGIS features API functions for each and every elementary solar positioning angle. To start we launch an interactive i/python session
Where ?¶
Define the geographic location and the positioning of our solar surface
Native objects for coordinates¶
We can also use PVGIS' native classes Longitude and Latitude for the coordinates. Actually, we should ! We can import them as every other Python module and create objects
inspect them
and use them as in the following example
>>> from pvgisprototype import Longitude
>>> Longitude(value=8.628)
>>> longitude = Longitude(value=8.628)
Let's see what is in the longitude variable
longitude=Longitude(data_source=None, equation=None, algorithm=None, unit=None, value=8.628, symbol='Λ', description="The angle between a point on the Earth's surface and the meridian plane, with its value ranging from 0° at the Prime Meridian in Greenwich, England, to 180° east or west.", label='Longitude', title='Longitude', supertitle='Longitude data', shortname='Longitude', name='Longitude', min_radians=-3.141592653589793, max_radians=3.141592653589793, min_degrees=-180, max_degrees=180)
Even better if we define the unit
>>> from pvgisprototype import Longitude
>>> Longitude(value=8.628)
>>> longitude = Longitude(value=8.628, unit='degrees')
Now the longitude value is unit-aware !
data_source=None equation=None algorithm=None unit='degrees' value=8.628 symbol='Λ' description="The angle between a point on the Earth's surface and the meridian plane, with its value ranging from 0° at the Prime Meridian in Greenwich, England, to 180° east or west." label='Longitude' title='Longitude' supertitle='Longitude data' shortname='Longitude' name='Longitude' min_radians=-3.141592653589793 max_radians=3.141592653589793 min_degrees=-180 max_degrees=180
We can even do
longitude.degrees=8.628
or
longitude.radians=0.15058700786207074
When ?¶
Prepate a series of timestamps as a Pandas DatetimeIndex, using our helper function generate_datetime_series
>>> from pvgisprototype.api.datetime.datetimeindex import generate_datetime_series
>>> timestamps = generate_datetime_series(start_time="2010-01-27 08:00:00", end_time="2010-01-27 18:00:00", frequency="h")
and the default UTC timezone as a ZoneInfo object
Let's confirm the generation of the timestamps and the timezone :
timestamps=DatetimeIndex(['2010-01-27 08:00:00', '2010-01-27 09:00:00', '2010-01-27 10:00:00', '2010-01-27 11:00:00', '2010-01-27 12:00:00', '2010-01-27 13:00:00', '2010-01-27 14:00:00', '2010-01-27 15:00:00', '2010-01-27 16:00:00', '2010-01-27 17:00:00', '2010-01-27 18:00:00'], dtype='datetime64[ns]', freq='h')
utc_zone=zoneinfo.ZoneInfo(key='UTC')
Solar altitude¶
>>> from pvgisprototype.api.position.altitude import calculate_solar_altitude_series
>>> calculate_solar_altitude_series
We can see the required arguments to run the command
Error Handling¶
Let's give it a first try with some reasonable input values for longitude, latitude and timestamps plus timezone
Failure
Traceback (most recent call last):
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/markdown_exec/_internal/formatters/python.py", line 71, in _run_python
exec_python(code, code_block_id, exec_globals)
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/markdown_exec/_internal/formatters/_exec_python.py", line 8, in exec_python
exec(compiled, exec_globals) # noqa: S102
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<code block: session solar-altitude; n2>", line 1, in <module>
calculate_solar_altitude_series(8, 45, '2001-01-01 10:00:00', 'UTC')
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/pvgisprototype/log.py", line 460, in wrapper
return function(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/pvgisprototype/api/position/altitude.py", line 225, in calculate_solar_altitude_series
solar_altitude_series = model_solar_altitude_series(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/pvgisprototype/log.py", line 460, in wrapper
return function(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/pvgisprototype/core/caching.py", line 215, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/pvgisprototype/validation/functions.py", line 93, in wrapper
validated_input = input_model(**input_data)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/builds/NikosAlexandris/pvgis-prototype/.venv/lib/python3.11/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 6 validation errors for ModelSolarAltitudeTimeSeriesInputModel
timestamps.is-instance[DatetimeIndex]
Input should be an instance of DatetimeIndex [type=is_instance_of, input_value='2001-01-01 10:00:00', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/is_instance_of
timestamps.is-instance[Timestamp]
Input should be an instance of Timestamp [type=is_instance_of, input_value='2001-01-01 10:00:00', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/is_instance_of
latitude.constrained-float
Input should be less than or equal to 1.5707963267948966 [type=less_than_equal, input_value=45, input_type=int]
For further information visit https://errors.pydantic.dev/2.12/v/less_than_equal
latitude.Latitude
Input should be a valid dictionary or instance of Latitude [type=model_type, input_value=45, input_type=int]
For further information visit https://errors.pydantic.dev/2.12/v/model_type
longitude.constrained-float
Input should be less than or equal to 3.141592653589793 [type=less_than_equal, input_value=8, input_type=int]
For further information visit https://errors.pydantic.dev/2.12/v/less_than_equal
longitude.Longitude
Input should be a valid dictionary or instance of Longitude [type=model_type, input_value=8, input_type=int]
For further information visit https://errors.pydantic.dev/2.12/v/model_type
PVGIS' API is indeed idiomatic and our reasonable inputs won't work ! However, the input arguments are validated via Pydantic and thus we receive informative error messages.
Speaking PVGIS' API language¶
Let's import the required modules
>>> from pandas import Timestamp
>>> from zoneinfo import ZoneInfo
>>> from pvgisprototype.api.utilities.conversions import convert_float_to_radians_if_requested
Setting the input parameters
>>> latitude=convert_float_to_radians_if_requested(8, 'radians')
>>> longitude=convert_float_to_radians_if_requested(45, 'radians')
>>> timestamps=Timestamp('2001-01-01 10:00:00+00:00')
>>> timezone=ZoneInfo('UTC')
And re-run the calculation
>>> solar_altitude = calculate_solar_altitude_series(longitude=longitude, latitude=latitude, timestamps=timestamps, timezone=timezone)
>>> print(f"Solar altitude from PVGIS' API : {solar_altitude}")
Solar altitude from PVGIS' API : {'noaa': OrderedDict([('Core', OrderedDict([('Name', 'Solar Altitude'), ('Title', 'Solar Altitude'), ('Description', 'Solar altitude data for a location and period in time'), ('Symbol', '⦩'), ('Altitude ⦩', array([0.97663325], dtype=float32)), ('refracted_value', array([], dtype=float64)), ('Unit', 'radians')])), ('Context', {}), ('Metadata', {}), ('Out-of-range', {}), ('Sources', {}), ('References', {}), ('Fingerprint', {})])}
Solar Azimuth¶
We can calculate solar azimuth angles for a specific geographic location and over a time series with the API function calculate_solar_azimuth_series().
Help¶
What exactly can we do with it ? We can use the Python built-in function help to find out
Or indeed, in an interactive ipython session, it suffices to use a ? right after the function name and without space
More Imports¶
>>> from pvgisprototype import Longitude, Latitude
>>> from pvgisprototype.api.datetime.datetimeindex import generate_datetime_series
>>> timestamps = generate_datetime_series(start_time='2010-01-27', end_time='2010-01-28')
>>> from zoneinfo import ZoneInfo
Calculate solar azimuth time series for the 27th January 2010
>>> longitude = Longitude(value=8.628, unit='degrees')
>>> latitude = Latitude(value=45.812, unit='degrees')
>>> calculate_solar_azimuth_series(
... longitude=longitude.radians,
... latitude=latitude.radians,
... timestamps=timestamps,
... timezone=ZoneInfo("UTC"),
... adjust_for_atmospheric_refraction=True
... )
The above function returns a Python dictionary with top-level key/s being the model or algorithm used for the calculation. Of course we can feed the result to a new variable and print or re-use it for further processing
>>> solar_azimuth_series = calculate_solar_azimuth_series(
... longitude=longitude.radians,
... latitude=latitude.radians,
... timestamps=timestamps,
... timezone=ZoneInfo("UTC"),
... adjust_for_atmospheric_refraction=True
... )
>>> print(f"{solar_azimuth_series=}")
solar_azimuth_series={'noaa': OrderedDict([('Core', OrderedDict([('Name', 'Solar Azimuth'), ('Title', 'Solar Azimuth'), ('Description', 'Solar azimuth angle data for a location and period in time'), ('Symbol', '\U000f19a5'), ('Azimuth \U000f19a5', array([0.20324755, 0.68162394, 1.0347457 , 1.2956891 , 1.5057368 , 1.6915469 , 1.8700204 , 2.0612502 , 2.250216 , 2.466558 , 2.7086406 , 2.9751859 , 3.2346225 , 3.50884 , 3.757659 , 3.9807858 , 4.1786404 , 4.3685203 , 4.547683 , 4.73073 , 4.933111 , 5.178298 , 5.503539 , 5.949113 , 0.19983101], dtype=float32)), ('Azimuth Origin \U000f19a5', 'North'), ('Unit', 'radians')])), ('Context', {}), ('Metadata', {}), ('Out-of-range', {}), ('Sources', {}), ('References', {}), ('Fingerprint', {})])}