From 2746c32ee8398b39ea16a698b997b1da27b8cabe Mon Sep 17 00:00:00 2001 From: Nicolas Martinez Date: Mon, 11 Sep 2023 02:56:00 +0000 Subject: [PATCH 1/9] Adding altitude lookup for location class Set altitude automatically from built-in map when not specified. See https://github.com/pvlib/pvlib-python/pull/1547 --- pvlib/location.py | 17 +++++++++---- pvlib/tests/test_location.py | 46 +++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/pvlib/location.py b/pvlib/location.py index 50f98d375d..77d615d13e 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -4,7 +4,7 @@ # Will Holmgren, University of Arizona, 2014-2016. -import os +import pathlib import datetime import pandas as pd @@ -14,6 +14,7 @@ from pvlib import solarposition, clearsky, atmosphere, irradiance from pvlib.tools import _degrees_to_index + class Location: """ Location objects are convenient containers for latitude, longitude, @@ -44,8 +45,10 @@ class Location: pytz.timezone objects will be converted to strings. ints and floats must be in hours from UTC. - altitude : float, default 0. + altitude : None or float, default None Altitude from sea level in meters. + If None, the altitude will be fetched from + :py:func:`pvlib.location.lookup_altitude`. name : None or string, default None. Sets the name attribute of the Location object. @@ -55,7 +58,8 @@ class Location: pvlib.pvsystem.PVSystem """ - def __init__(self, latitude, longitude, tz='UTC', altitude=0, name=None): + def __init__(self, latitude, longitude, tz='UTC', altitude=None, + name=None): self.latitude = latitude self.longitude = longitude @@ -75,6 +79,9 @@ def __init__(self, latitude, longitude, tz='UTC', altitude=0, name=None): else: raise TypeError('Invalid tz specification') + if altitude is None: + altitude = lookup_altitude(latitude, longitude) + self.altitude = altitude self.name = name @@ -427,8 +434,8 @@ def lookup_altitude(latitude, longitude): """ - pvlib_path = os.path.dirname(os.path.abspath(__file__)) - filepath = os.path.join(pvlib_path, 'data', 'Altitude.h5') + pvlib_path = pathlib.Path(__file__).parent + filepath = pvlib_path / 'data' / 'Altitude.h5' latitude_index = _degrees_to_index(latitude, coordinate='latitude') longitude_index = _degrees_to_index(longitude, coordinate='longitude') diff --git a/pvlib/tests/test_location.py b/pvlib/tests/test_location.py index b818b2fd94..8d52f65a54 100644 --- a/pvlib/tests/test_location.py +++ b/pvlib/tests/test_location.py @@ -12,6 +12,7 @@ from pytz.exceptions import UnknownTimeZoneError import pvlib +from pvlib import location from pvlib.location import Location, lookup_altitude from pvlib.solarposition import declination_spencer71 from pvlib.solarposition import equation_of_time_spencer71 @@ -212,7 +213,7 @@ def test_get_clearsky_valueerror(times): def test_from_tmy_3(): from pvlib.tests.iotools.test_tmy import TMY3_TESTFILE from pvlib.iotools import read_tmy3 - data, meta = read_tmy3(TMY3_TESTFILE, map_variables=True) + data, meta = read_tmy3(TMY3_TESTFILE) loc = Location.from_tmy(meta, data) assert loc.name is not None assert loc.altitude != 0 @@ -328,21 +329,28 @@ def test_extra_kwargs(): Location(32.2, -111, arbitrary_kwarg='value') -def test_lookup_altitude(): - max_alt_error = 125 - # location name, latitude, longitude, altitude - test_locations = [ - ('Tucson, USA', 32.2540, -110.9742, 724), - ('Lusaka, Zambia', -15.3875, 28.3228, 1253), - ('Tokio, Japan', 35.6762, 139.6503, 40), - ('Canberra, Australia', -35.2802, 149.1310, 566), - ('Bogota, Colombia', 4.7110, -74.0721, 2555), - ('Dead Sea, West Bank', 31.525849, 35.449214, -415), - ('New Delhi, India', 28.6139, 77.2090, 214), - ('Null Island, Atlantic Ocean', 0, 0, 0), - ] - - for name, lat, lon, expected_alt in test_locations: - alt_found = lookup_altitude(lat, lon) - assert abs(alt_found - expected_alt) < max_alt_error, \ - f'Max error exceded for {name} - e: {expected_alt} f: {alt_found}' +@pytest.mark.parametrize('lat,lon,expected_alt', [ + pytest.param(32.2540, -110.9742, 724, id='Tucson, USA'), + pytest.param(-15.3875, 28.3228, 1253, id='Lusaka, Zambia'), + pytest.param(35.6762, 139.6503, 40, id='Tokyo, Japan'), + pytest.param(-35.2802, 149.1310, 566, id='Canberra, Australia'), + pytest.param(4.7110, -74.0721, 2555, id='Bogota, Colombia'), + pytest.param(31.525849, 35.449214, -415, id='Dead Sea, West Bank'), + pytest.param(28.6139, 77.2090, 214, id='New Delhi, India'), + pytest.param(0, 0, 0, id='Null Island, Atlantic Ocean'), +]) +def test_lookup_altitude(lat, lon, expected_alt): + alt_found = lookup_altitude(lat, lon) + assert alt_found == pytest.approx(expected_alt, abs=125) + + +def test_location_lookup_altitude(mocker): + mocker.spy(location, 'lookup_altitude') + tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson') + location.lookup_altitude.assert_not_called() + assert tus.altitude == 700 + location.lookup_altitude.reset_mock() + + tus = Location(32.2, -111, 'US/Arizona') + location.lookup_altitude.assert_called_once_with(32.2, -111) + assert tus.altitude == location.lookup_altitude(32.2, -111) From cb64c2d0da8bd5ec400f989fdc101f69ef44019a Mon Sep 17 00:00:00 2001 From: Nicolas Martinez Date: Mon, 11 Sep 2023 03:13:55 +0000 Subject: [PATCH 2/9] Readding change removed by accident by patch --- pvlib/tests/test_location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_location.py b/pvlib/tests/test_location.py index 8d52f65a54..907134bb70 100644 --- a/pvlib/tests/test_location.py +++ b/pvlib/tests/test_location.py @@ -213,7 +213,7 @@ def test_get_clearsky_valueerror(times): def test_from_tmy_3(): from pvlib.tests.iotools.test_tmy import TMY3_TESTFILE from pvlib.iotools import read_tmy3 - data, meta = read_tmy3(TMY3_TESTFILE) + data, meta = read_tmy3(TMY3_TESTFILE, map_variables=True) loc = Location.from_tmy(meta, data) assert loc.name is not None assert loc.altitude != 0 From 8785bceda947ecab5cfd25bf5edd0d156eeafbac Mon Sep 17 00:00:00 2001 From: Nicolas Martinez Date: Mon, 11 Sep 2023 13:59:09 +0000 Subject: [PATCH 3/9] Note about fallback to zero when there is no data --- pvlib/location.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/location.py b/pvlib/location.py index 77d615d13e..d75bb6a97c 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -49,6 +49,7 @@ class Location: Altitude from sea level in meters. If None, the altitude will be fetched from :py:func:`pvlib.location.lookup_altitude`. + Set to 0 if there is no data at the location. name : None or string, default None. Sets the name attribute of the Location object. From 7e4ed35f2a5d04731e9300df12d3b24e7f2ef1ad Mon Sep 17 00:00:00 2001 From: Nicolas Martinez Date: Mon, 11 Sep 2023 14:01:30 +0000 Subject: [PATCH 4/9] Adding description to "what's new" --- docs/sphinx/source/whatsnew/v0.10.2.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.10.2.rst b/docs/sphinx/source/whatsnew/v0.10.2.rst index 6288b077fc..873e2d3bd3 100644 --- a/docs/sphinx/source/whatsnew/v0.10.2.rst +++ b/docs/sphinx/source/whatsnew/v0.10.2.rst @@ -22,6 +22,8 @@ Enhancements * Added :py:func:`~pvlib.iam.interp` option as AOI losses model in :py:class:`pvlib.modelchain.ModelChain` and :py:class:`pvlib.pvsystem.PVSystem`. (:issue:`1742`, :pull:`1832`) +* Default altitude in :py:class:`pvlib.location.Location` + now comes from :py:func:`~pvlib.location.lookup_altitude` (:issue:`1516`, :pull:`1850`) Bug fixes ~~~~~~~~~ @@ -64,3 +66,4 @@ Contributors * Anton Driesse (:ghuser:`adriesse`) * Lukas Grossar (:ghuser:`tongpu`) * Areeba Turabi (:ghuser:`aturabi`) +* Nicolas Martinez (:ghuser:`nicomt`) From fb83bbf32e9ac9eee7ecd84bfc85c10d6d105f6b Mon Sep 17 00:00:00 2001 From: Nicolas Martinez Date: Mon, 11 Sep 2023 17:00:05 -0400 Subject: [PATCH 5/9] Update pvlib/location.py Better type definition Co-authored-by: Kevin Anderson --- pvlib/location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/location.py b/pvlib/location.py index d75bb6a97c..8211f4b3eb 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -45,7 +45,7 @@ class Location: pytz.timezone objects will be converted to strings. ints and floats must be in hours from UTC. - altitude : None or float, default None + altitude : None or float, optional Altitude from sea level in meters. If None, the altitude will be fetched from :py:func:`pvlib.location.lookup_altitude`. From 2c4de91ec2abd3b22d4453d3e5c0f441ae4db95f Mon Sep 17 00:00:00 2001 From: Nicolas Martinez Date: Mon, 11 Sep 2023 21:09:45 +0000 Subject: [PATCH 6/9] Fix wording for altitude param --- pvlib/location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/location.py b/pvlib/location.py index 8211f4b3eb..7636f74954 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -49,7 +49,7 @@ class Location: Altitude from sea level in meters. If None, the altitude will be fetched from :py:func:`pvlib.location.lookup_altitude`. - Set to 0 if there is no data at the location. + If no data is available for the location, the altitude is set to 0. name : None or string, default None. Sets the name attribute of the Location object. From 9142532d86a879b06151040f0c7c31c91b363fec Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 21 Jun 2024 11:30:02 -0400 Subject: [PATCH 7/9] move whatsnew entry to 0.11.0 --- docs/sphinx/source/whatsnew/v0.10.2.rst | 5 ----- docs/sphinx/source/whatsnew/v0.11.0.rst | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.2.rst b/docs/sphinx/source/whatsnew/v0.10.2.rst index 94d18fef4a..30614487cf 100644 --- a/docs/sphinx/source/whatsnew/v0.10.2.rst +++ b/docs/sphinx/source/whatsnew/v0.10.2.rst @@ -21,10 +21,6 @@ Enhancements * Added :py:func:`~pvlib.iam.interp` option as AOI losses model in :py:class:`pvlib.modelchain.ModelChain` and :py:class:`pvlib.pvsystem.PVSystem`. (:issue:`1742`, :pull:`1832`) -* Default altitude in :py:class:`pvlib.location.Location` - now comes from :py:func:`~pvlib.location.lookup_altitude` (:issue:`1516`, :pull:`1850`) - :py:class:`~pvlib.modelchain.ModelChain` and - :py:class:`~pvlib.pvsystem.PVSystem`. (:issue:`1742`, :pull:`1832`) * :py:class:`~pvlib.pvsystem.PVSystem` objects with a single :py:class:`~pvlib.pvsystem.Array` can now be created without wrapping the ``Array`` in a list first. (:issue:`1831`, :pull:`1854`) @@ -80,7 +76,6 @@ Contributors * Anton Driesse (:ghuser:`adriesse`) * Lukas Grossar (:ghuser:`tongpu`) * Areeba Turabi (:ghuser:`aturabi`) -* Nicolas Martinez (:ghuser:`nicomt`) * Saurabh Aneja (:ghuser:`spaneja`) * Miroslav Šedivý (:ghuser:`eumiro`) * kjsauer (:ghuser:`kjsauer`) diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 9cdd5e8680..01e993b447 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -64,6 +64,9 @@ Enhancements diffuse fraction of Photosynthetically Active Radiation (PAR) from the global diffuse fraction and the solar zenith. (:issue:`2047`, :pull:`2048`) +* Default altitude in :py:class:`pvlib.location.Location` + now comes from :py:func:`~pvlib.location.lookup_altitude` (:issue:`1516`, :pull:`1850`) + Bug fixes ~~~~~~~~~ @@ -90,3 +93,4 @@ Contributors * Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) * Mark Campanelli (:ghuser:`markcampanelli`) * Rajiv Daxini (:ghuser:`RDaxini`) +* Nicolas Martinez (:ghuser:`nicomt`) From 7c33337f3daddae02dc36114c7efffe0bfb50dc5 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 21 Jun 2024 11:30:14 -0400 Subject: [PATCH 8/9] docstring tweak --- pvlib/location.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/location.py b/pvlib/location.py index 394eabda3d..0ed914751b 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -45,9 +45,9 @@ class Location: pytz.timezone objects will be converted to strings. ints and floats must be in hours from UTC. - altitude : None or float, optional + altitude : float, optional Altitude from sea level in meters. - If None, the altitude will be fetched from + If not specified, the altitude will be fetched from :py:func:`pvlib.location.lookup_altitude`. If no data is available for the location, the altitude is set to 0. From d8df05a43bc9d0a0f59b2a751342b4827290a9af Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 21 Jun 2024 11:31:43 -0400 Subject: [PATCH 9/9] fix 0.10.2 whatsnew file --- docs/sphinx/source/whatsnew/v0.10.2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.2.rst b/docs/sphinx/source/whatsnew/v0.10.2.rst index 30614487cf..3b82d98613 100644 --- a/docs/sphinx/source/whatsnew/v0.10.2.rst +++ b/docs/sphinx/source/whatsnew/v0.10.2.rst @@ -19,8 +19,8 @@ Enhancements * Added a continuous version of the Erbs diffuse-fraction/decomposition model. :py:func:`pvlib.irradiance.erbs_driesse` (:issue:`1755`, :pull:`1834`) * Added :py:func:`~pvlib.iam.interp` option as AOI losses model in - :py:class:`pvlib.modelchain.ModelChain` and - :py:class:`pvlib.pvsystem.PVSystem`. (:issue:`1742`, :pull:`1832`) + :py:class:`~pvlib.modelchain.ModelChain` and + :py:class:`~pvlib.pvsystem.PVSystem`. (:issue:`1742`, :pull:`1832`) * :py:class:`~pvlib.pvsystem.PVSystem` objects with a single :py:class:`~pvlib.pvsystem.Array` can now be created without wrapping the ``Array`` in a list first. (:issue:`1831`, :pull:`1854`)