Skip to content

Lookup altitude2 #1547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
16 changes: 11 additions & 5 deletions pvlib/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Will Holmgren, University of Arizona, 2014-2016.

import os
import pathlib
import datetime
import warnings

Expand Down Expand Up @@ -45,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.
Expand All @@ -56,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
Expand All @@ -76,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
Expand Down Expand Up @@ -427,8 +433,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')
Expand Down
110 changes: 75 additions & 35 deletions pvlib/solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import warnings
import datetime

from pvlib import atmosphere
from pvlib import atmosphere, location
from pvlib.tools import datetime_to_djd, djd_to_datetime


Expand Down Expand Up @@ -52,12 +52,13 @@ def get_solarposition(time, latitude, longitude,
negative to west.

altitude : None or float, default None
If None, computed from pressure. Assumed to be 0 m
if pressure is also None.
Altitude from sea level in meters.
If None, the altitude will be fetched from
:py:func:`pvlib.location.lookup_altitude`.

pressure : None or float, default None
If None, computed from altitude. Assumed to be 101325 Pa
if altitude is also None.
Air pressure in Pascals.
If None, computed from altitude.

method : string, default 'nrel_numpy'
'nrel_numpy' uses an implementation of the NREL SPA algorithm
Expand Down Expand Up @@ -92,12 +93,10 @@ def get_solarposition(time, latitude, longitude,
.. [3] NREL SPA code: http://rredc.nrel.gov/solar/codesandalgorithms/spa/
"""

if altitude is None and pressure is None:
altitude = 0.
pressure = 101325.
elif altitude is None:
altitude = atmosphere.pres2alt(pressure)
elif pressure is None:
if altitude is None:
altitude = location.lookup_altitude(latitude, longitude)

if pressure is None:
pressure = atmosphere.alt2pres(altitude)

method = method.lower()
Expand Down Expand Up @@ -129,7 +128,7 @@ def get_solarposition(time, latitude, longitude,
return ephem_df


def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
def spa_c(time, latitude, longitude, pressure=None, altitude=None,
temperature=12, delta_t=67.0,
raw_spa_output=False):
"""
Expand All @@ -153,10 +152,13 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
longitude : float
Longitude in decimal degrees. Positive east of prime meridian,
negative to west.
pressure : float, default 101325
Pressure in Pascals
altitude : float, default 0
Height above sea level. [m]
pressure : None or float, default None
Air pressure in Pascals.
If None, computed from altitude.
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`.
temperature : float, default 12
Temperature in C
delta_t : float, default 67.0
Expand Down Expand Up @@ -194,6 +196,11 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
pyephem, spa_python, ephemeris
"""

if altitude is None:
altitude = location.lookup_altitude(latitude, longitude)
if pressure is None:
pressure = atmosphere.alt2pres(altitude)

# Added by Rob Andrews (@Calama-Consulting), Calama Consulting, 2014
# Edited by Will Holmgren (@wholmgren), University of Arizona, 2014
# Edited by Tony Lorenzo (@alorenzo175), University of Arizona, 2015
Expand Down Expand Up @@ -275,7 +282,7 @@ def _spa_python_import(how):


def spa_python(time, latitude, longitude,
altitude=0, pressure=101325, temperature=12, delta_t=67.0,
altitude=None, pressure=None, temperature=12, delta_t=67.0,
atmos_refract=None, how='numpy', numthreads=4):
"""
Calculate the solar position using a python implementation of the
Expand All @@ -298,10 +305,13 @@ def spa_python(time, latitude, longitude,
longitude : float
Longitude in decimal degrees. Positive east of prime meridian,
negative to west.
altitude : float, default 0
Distance above sea level.
pressure : int or float, optional, default 101325
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`.
pressure : int or float, optional, default None
avg. yearly air pressure in Pascals.
If None, computed from altitude.
temperature : int or float, optional, default 12
avg. yearly air temperature in degrees C.
delta_t : float, optional, default 67.0
Expand Down Expand Up @@ -351,6 +361,11 @@ def spa_python(time, latitude, longitude,
pyephem, spa_c, ephemeris
"""

if altitude is None:
altitude = location.lookup_altitude(latitude, longitude)
if pressure is None:
pressure = atmosphere.alt2pres(altitude)

# Added by Tony Lorenzo (@alorenzo175), University of Arizona, 2015

lat = latitude
Expand Down Expand Up @@ -504,8 +519,8 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature,

def sun_rise_set_transit_ephem(times, latitude, longitude,
next_or_previous='next',
altitude=0,
pressure=101325,
altitude=None,
pressure=None,
temperature=12, horizon='0:00'):
"""
Calculate the next sunrise and sunset times using the PyEphem package.
Expand All @@ -520,10 +535,13 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
Longitude in degrees, positive east of prime meridian, negative to west
next_or_previous : str
'next' or 'previous' sunrise and sunset relative to time
altitude : float, default 0
distance above sea level in meters.
pressure : int or float, optional, default 101325
air pressure in Pascals.
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`.
pressure : None or float, default None
Air pressure in Pascals.
If None, computed from altitude.
temperature : int or float, optional, default 12
air temperature in degrees C.
horizon : string, format +/-X:YY
Expand Down Expand Up @@ -555,6 +573,11 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
else:
raise ValueError('times must be localized')

if altitude is None:
altitude = location.lookup_altitude(latitude, longitude)
if pressure is None:
pressure = atmosphere.alt2pres(altitude)

obs, sun = _ephem_setup(latitude, longitude, altitude,
pressure, temperature, horizon)
# create lists of sunrise and sunset time localized to time.tz
Expand Down Expand Up @@ -588,7 +611,7 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
'transit': trans})


def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
def pyephem(time, latitude, longitude, altitude=None, pressure=None,
temperature=12, horizon='+0:00'):
"""
Calculate the solar position using the PyEphem package.
Expand All @@ -603,10 +626,13 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
longitude : float
Longitude in decimal degrees. Positive east of prime meridian,
negative to west.
altitude : float, default 0
Height above sea level in meters. [m]
pressure : int or float, optional, default 101325
air pressure in Pascals.
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`.
pressure : None or float, default None
Air pressure in Pascals.
If None, computed from altitude.
temperature : int or float, optional, default 12
air temperature in degrees C.
horizon : string, optional, default '+0:00'
Expand Down Expand Up @@ -642,6 +668,11 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325,
except TypeError:
time_utc = time

if altitude is None:
altitude = location.lookup_altitude(latitude, longitude)
if pressure is None:
pressure = atmosphere.alt2pres(altitude)

sun_coords = pd.DataFrame(index=time)

obs, sun = _ephem_setup(latitude, longitude, altitude,
Expand Down Expand Up @@ -862,7 +893,7 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):


def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
altitude=0, pressure=101325, temperature=12, horizon='+0:00',
altitude=None, pressure=None, temperature=12, horizon='+0:00',
xtol=1.0e-12):
"""
Calculate the time between lower_bound and upper_bound
Expand All @@ -885,11 +916,14 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
and 'az' (which must be given in radians).
value : int or float
The value of the attribute to solve for
altitude : float, default 0
Distance above sea level.
pressure : int or float, optional, default 101325
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`.
pressure : int or float, optional, default None
Air pressure in Pascals. Set to 0 for no
atmospheric correction.
If None, computed from altitude.
temperature : int or float, optional, default 12
Air temperature in degrees C.
horizon : string, optional, default '+0:00'
Expand All @@ -913,6 +947,12 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value,
If the given attribute is not an attribute of a
PyEphem.Sun object.
"""

if altitude is None:
altitude = location.lookup_altitude(latitude, longitude)
if pressure is None:
pressure = atmosphere.alt2pres(altitude)

obs, sun = _ephem_setup(latitude, longitude, altitude,
pressure, temperature, horizon)

Expand Down
44 changes: 26 additions & 18 deletions pvlib/tests/test_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion pvlib/tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1678,7 +1678,7 @@ def test_PVSystem_multiple_array_get_aoi():
def solar_pos():
times = pd.date_range(start='20160101 1200-0700',
end='20160101 1800-0700', freq='6H')
location = Location(latitude=32, longitude=-111)
location = Location(latitude=32, longitude=-111, altitude=0)
return location.get_solarposition(times)


Expand Down
Loading