diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 7cdab9cb6b..e35cdcdc32 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,4 +93,5 @@ Contributors * Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) * Mark Campanelli (:ghuser:`markcampanelli`) * Rajiv Daxini (:ghuser:`RDaxini`) +* Nicolas Martinez (:ghuser:`nicomt`) * :ghuser:`PhilBrk8` diff --git a/pvlib/location.py b/pvlib/location.py index 9b64780570..0ed914751b 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,11 @@ class Location: pytz.timezone objects will be converted to strings. ints and floats must be in hours from UTC. - altitude : float, default 0. + altitude : float, optional Altitude from sea level in meters. + 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. name : string, optional Sets the name attribute of the Location object. @@ -55,7 +59,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 +80,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 +435,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 53ba7b51cf..e04b10ab4c 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 @@ -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)