Repository: pvlib/pvlib-python

Issue:
`pvlib.iotools.get_pvgis_hourly`'s `surface_azimuth` parameter doesn't use pvlib's azimuth convention
Nearly everything in pvlib represents azimuth angles as values in [0, 360) clockwise from north, except `pvlib.iotools.get_pvgis_hourly`:

https://github.com/pvlib/pvlib-python/blob/3def7e3375002ee3a5492b7bc609d3fb63a8edb1/pvlib/iotools/pvgis.py#L79-L81

This inconsistency is a shame.  However, I don't see any way to switch it to pvlib's convention without a hard break, which is also a shame.  I wonder how others view the cost/benefit analysis here.

See also https://github.com/pvlib/pvlib-python/pull/1395#discussion_r1181853794




Discussion:
Since `get_pvgis_hourly` is only using `surface_azimuth` to pass a value to a pvgis keyword `aspect`, we could add `aspect` with the South=0 convention and deprecate `surface_azimuth` as is. Then later, we can deprecate `aspect` and add `surface_azimuth` back with its pvlib meaning. Now that I write that out, seems better just to make the breaking change.


Patch:
diff --git a/pvlib/iotools/pvgis.py b/pvlib/iotools/pvgis.py
--- a/pvlib/iotools/pvgis.py
+++ b/pvlib/iotools/pvgis.py
@@ -45,7 +45,7 @@
 
 def get_pvgis_hourly(latitude, longitude, start=None, end=None,
                      raddatabase=None, components=True,
-                     surface_tilt=0, surface_azimuth=0,
+                     surface_tilt=0, surface_azimuth=180,
                      outputformat='json',
                      usehorizon=True, userhorizon=None,
                      pvcalculation=False,
@@ -76,9 +76,15 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
         Otherwise only global irradiance is returned.
     surface_tilt: float, default: 0
         Tilt angle from horizontal plane. Ignored for two-axis tracking.
-    surface_azimuth: float, default: 0
-        Orientation (azimuth angle) of the (fixed) plane. 0=south, 90=west,
-        -90: east. Ignored for tracking systems.
+    surface_azimuth: float, default: 180
+        Orientation (azimuth angle) of the (fixed) plane. Counter-clockwise
+        from north (north=0, south=180). This is offset 180 degrees from
+        the convention used by PVGIS. Ignored for tracking systems.
+
+        .. versionchanged:: 0.10.0
+           The `surface_azimuth` parameter now follows the pvlib convention, which
+           is counterclockwise from north. However, the convention used by the
+           PVGIS website and pvlib<=0.9.5 is offset by 180 degrees.
     usehorizon: bool, default: True
         Include effects of horizon
     userhorizon: list of float, default: None
@@ -144,6 +150,13 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
     time stamp convention, e.g., SARAH and SARAH2 provide instantaneous values,
     whereas values from ERA5 are averages for the hour.
 
+    Warning
+    -------
+    The azimuth orientation specified in the output metadata does not
+    correspond to the pvlib convention, but is offset 180 degrees. This is
+    despite the fact that the input parameter `surface_tilt` has to be
+    specified according to the pvlib convention.
+
     Notes
     -----
     data includes the following fields:
@@ -191,7 +204,7 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
     """  # noqa: E501
     # use requests to format the query string by passing params dictionary
     params = {'lat': latitude, 'lon': longitude, 'outputformat': outputformat,
-              'angle': surface_tilt, 'aspect': surface_azimuth,
+              'angle': surface_tilt, 'aspect': surface_azimuth-180,
               'pvcalculation': int(pvcalculation),
               'pvtechchoice': pvtechchoice, 'mountingplace': mountingplace,
               'trackingtype': trackingtype, 'components': int(components),
@@ -315,6 +328,11 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
     metadata : dict
         metadata
 
+    Warning
+    -------
+    The azimuth orientation specified in the output metadata does not
+    correspond to the pvlib convention, but is offset 180 degrees.
+
     Raises
     ------
     ValueError


Test patch:
diff --git a/pvlib/tests/iotools/test_pvgis.py b/pvlib/tests/iotools/test_pvgis.py
--- a/pvlib/tests/iotools/test_pvgis.py
+++ b/pvlib/tests/iotools/test_pvgis.py
@@ -206,14 +206,14 @@ def test_read_pvgis_hourly_bad_extension():
 
 
 args_radiation_csv = {
-    'surface_tilt': 30, 'surface_azimuth': 0, 'outputformat': 'csv',
+    'surface_tilt': 30, 'surface_azimuth': 180, 'outputformat': 'csv',
     'usehorizon': False, 'userhorizon': None, 'raddatabase': 'PVGIS-SARAH',
     'start': 2016, 'end': 2016, 'pvcalculation': False, 'components': True}
 
 url_hourly_radiation_csv = 'https://re.jrc.ec.europa.eu/api/seriescalc?lat=45&lon=8&outputformat=csv&angle=30&aspect=0&usehorizon=0&pvtechchoice=crystSi&mountingplace=free&trackingtype=0&components=1&raddatabase=PVGIS-SARAH&startyear=2016&endyear=2016'  # noqa: E501
 
 args_pv_json = {
-    'surface_tilt': 30, 'surface_azimuth': 0, 'outputformat': 'json',
+    'surface_tilt': 30, 'surface_azimuth': 180, 'outputformat': 'json',
     'usehorizon': True, 'userhorizon': None, 'raddatabase': 'PVGIS-SARAH2',
     'start': pd.Timestamp(2013, 1, 1), 'end': pd.Timestamp(2014, 5, 1),
     'pvcalculation': True, 'peakpower': 10, 'pvtechchoice': 'CIS', 'loss': 5,