Repository: pvlib/pvlib-python
Pull request #821 (2019-11-18 17:33:54)

Location object creation from epw metadata

 - [x] I am familiar with the [contributing guidelines](http://pvlib-python.readthedocs.io/en/latest/contributing.html).
 - [x] Fully tested. Added and/or modified tests to ensure correct behavior for all reasonable inputs. Tests (usually) must pass on the TravisCI and Appveyor testing services.
 - [x] Updates entries to `docs/sphinx/source/api.rst` for API changes.
 - [x] Adds description and name entries in the appropriate `docs/sphinx/source/whatsnew` file for all changes.
 - [x] Code quality and style is sufficient. Passes LGTM and SticklerCI checks.
 - [x] New code is fully documented. Includes sphinx/numpydoc compliant docstrings and comments in the code where necessary.
 - [x] Pull request is nearly complete and ready for detailed review.

Brief description of the problem and proposed solution (if not already fully described in the issue linked to above):

My PR is for allowing creation of a Location object from the metadata coming from epw weather file. My first thought was to create a new `from_epw()` which works as `from_tmy()`, but I rethought it and considered the option of changing `from_tmy()` in `from_tmy_or_epw()` for allowing it to accept epw files as well. I personally think that the second option is better since tmy files can be formatted in epw format, and thus it would simplify life for users.
I've let both options in my draft PR, give me your opinion on what is best according to you.


Patch:
diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst
index b9c7a82..f55faa5 100644
--- a/docs/sphinx/source/api.rst
+++ b/docs/sphinx/source/api.rst
@@ -376,6 +376,7 @@ in some files.
    :toctree: generated/
 
    location.Location.from_tmy
+   location.Location.from_epw
 
 
 TMY
diff --git a/docs/sphinx/source/whatsnew/v0.7.0.rst b/docs/sphinx/source/whatsnew/v0.7.0.rst
index 6de109c..c7d4fa5 100644
--- a/docs/sphinx/source/whatsnew/v0.7.0.rst
+++ b/docs/sphinx/source/whatsnew/v0.7.0.rst
@@ -140,6 +140,8 @@ Enhancements
   computing reductions in variability due to a spatially distributed plant.
 * Created one new incidence angle modifier (IAM) function for diffuse irradiance:
   :py:func:`pvlib.iam.martin_ruiz_diffuse`. (:issue:`751`)
+* Add :py:meth:`~pvlib.location.Location.from_epw`, a method to create a Location
+  object from epw metadata, typically coming from `pvlib.iotools.epw.read_epw`.
 
 Bug fixes
 ~~~~~~~~~
diff --git a/pvlib/iotools/tmy.py b/pvlib/iotools/tmy.py
index f17908f..767e18f 100644
--- a/pvlib/iotools/tmy.py
+++ b/pvlib/iotools/tmy.py
@@ -94,7 +94,7 @@ def read_tmy3(filename=None, coerce_year=None, recolumn=True):
     TMYData.ZenithlumUncertainty        Uncertainty based on random and bias error estimates                        see [1] section 2.10
     TMYData.TotCld                      Amount of sky dome covered by clouds or obscuring phenonema at time stamp, tenths of sky
     TMYData.TotCldSource                See [1], Table 1-5, 8760x1 cell array of strings
-    TMYData.TotCldUnertainty            See [1], Table 1-6
+    TMYData.TotCldUncertainty           See [1], Table 1-6
     TMYData.OpqCld                      Amount of sky dome covered by clouds or obscuring phenonema that prevent observing the sky at time stamp, tenths of sky
     TMYData.OpqCldSource                See [1], Table 1-5, 8760x1 cell array of strings
     TMYData.OpqCldUncertainty           See [1], Table 1-6
@@ -246,7 +246,7 @@ def _recolumn(tmy3_dataframe):
         'DNillum', 'DNillumSource', 'DNillumUncertainty', 'DHillum',
         'DHillumSource', 'DHillumUncertainty', 'Zenithlum',
         'ZenithlumSource', 'ZenithlumUncertainty', 'TotCld', 'TotCldSource',
-        'TotCldUnertainty', 'OpqCld', 'OpqCldSource', 'OpqCldUncertainty',
+        'TotCldUncertainty', 'OpqCld', 'OpqCldSource', 'OpqCldUncertainty',
         'DryBulb', 'DryBulbSource', 'DryBulbUncertainty', 'DewPoint',
         'DewPointSource', 'DewPointUncertainty', 'RHum', 'RHumSource',
         'RHumUncertainty', 'Pressure', 'PressureSource',
@@ -346,7 +346,7 @@ def read_tmy2(filename):
     ZenithlumUncertainty            See [1], Table 3-4
     TotCld                          Amount of sky dome covered by clouds or obscuring phenonema at time stamp, tenths of sky
     TotCldSource                    See [1], Table 3-5, 8760x1 cell array of strings
-    TotCldUnertainty                See [1], Table 3-6
+    TotCldUncertainty                See [1], Table 3-6
     OpqCld                          Amount of sky dome covered by clouds or obscuring phenonema that prevent observing the sky at time stamp, tenths of sky
     OpqCldSource                    See [1], Table 3-5, 8760x1 cell array of strings
     OpqCldUncertainty               See [1], Table 3-6
@@ -406,7 +406,7 @@ def read_tmy2(filename):
 
     # paste in the column info as one long line
     string = '%2d%2d%2d%2d%4d%4d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%4d%1s%1d%2d%1s%1d%2d%1s%1d%4d%1s%1d%4d%1s%1d%3d%1s%1d%4d%1s%1d%3d%1s%1d%3d%1s%1d%4d%1s%1d%5d%1s%1d%10d%3d%1s%1d%3d%1s%1d%3d%1s%1d%2d%1s%1d'  # noqa: E501
-    columns = 'year,month,day,hour,ETR,ETRN,GHI,GHISource,GHIUncertainty,DNI,DNISource,DNIUncertainty,DHI,DHISource,DHIUncertainty,GHillum,GHillumSource,GHillumUncertainty,DNillum,DNillumSource,DNillumUncertainty,DHillum,DHillumSource,DHillumUncertainty,Zenithlum,ZenithlumSource,ZenithlumUncertainty,TotCld,TotCldSource,TotCldUnertainty,OpqCld,OpqCldSource,OpqCldUncertainty,DryBulb,DryBulbSource,DryBulbUncertainty,DewPoint,DewPointSource,DewPointUncertainty,RHum,RHumSource,RHumUncertainty,Pressure,PressureSource,PressureUncertainty,Wdir,WdirSource,WdirUncertainty,Wspd,WspdSource,WspdUncertainty,Hvis,HvisSource,HvisUncertainty,CeilHgt,CeilHgtSource,CeilHgtUncertainty,PresentWeather,Pwat,PwatSource,PwatUncertainty,AOD,AODSource,AODUncertainty,SnowDepth,SnowDepthSource,SnowDepthUncertainty,LastSnowfall,LastSnowfallSource,LastSnowfallUncertaint'  # noqa: E501
+    columns = 'year,month,day,hour,ETR,ETRN,GHI,GHISource,GHIUncertainty,DNI,DNISource,DNIUncertainty,DHI,DHISource,DHIUncertainty,GHillum,GHillumSource,GHillumUncertainty,DNillum,DNillumSource,DNillumUncertainty,DHillum,DHillumSource,DHillumUncertainty,Zenithlum,ZenithlumSource,ZenithlumUncertainty,TotCld,TotCldSource,TotCldUncertainty,OpqCld,OpqCldSource,OpqCldUncertainty,DryBulb,DryBulbSource,DryBulbUncertainty,DewPoint,DewPointSource,DewPointUncertainty,RHum,RHumSource,RHumUncertainty,Pressure,PressureSource,PressureUncertainty,Wdir,WdirSource,WdirUncertainty,Wspd,WspdSource,WspdUncertainty,Hvis,HvisSource,HvisUncertainty,CeilHgt,CeilHgtSource,CeilHgtUncertainty,PresentWeather,Pwat,PwatSource,PwatUncertainty,AOD,AODSource,AODUncertainty,SnowDepth,SnowDepthSource,SnowDepthUncertainty,LastSnowfall,LastSnowfallSource,LastSnowfallUncertaint'  # noqa: E501
     hdr_columns = 'WBAN,City,State,TZ,latitude,longitude,altitude'
 
     tmy2, tmy2_meta = _read_tmy2(string, columns, hdr_columns, filename)
diff --git a/pvlib/location.py b/pvlib/location.py
index 68b2062..95029ca 100644
--- a/pvlib/location.py
+++ b/pvlib/location.py
@@ -125,6 +125,42 @@ class Location(object):
         # not sure if this should be assigned regardless of input.
         if tmy_data is not None:
             new_object.tmy_data = tmy_data
+            new_object.weather = tmy_data
+
+        return new_object
+
+    @classmethod
+    def from_epw(cls, metadata, data=None, **kwargs):
+        """
+        Create a Location object based on a metadata
+        dictionary from epw data readers.
+
+        Parameters
+        ----------
+        metadata : dict
+            Returned from epw.read_epw
+        data : None or DataFrame, default None
+            Optionally attach the epw data to this object.
+
+        Returns
+        -------
+        Location object (or the child class of Location that you
+        called this method from).
+        """
+
+        latitude = metadata['latitude']
+        longitude = metadata['longitude']
+
+        name = metadata['city']
+
+        tz = metadata['TZ']
+        altitude = metadata['altitude']
+
+        new_object = cls(latitude, longitude, tz=tz, altitude=altitude,
+                         name=name, **kwargs)
+
+        if data is not None:
+            new_object.weather = data
 
         return new_object
 


Test patch:
diff --git a/pvlib/test/test_location.py b/pvlib/test/test_location.py
index db32ac5..d9503ae 100644
--- a/pvlib/test/test_location.py
+++ b/pvlib/test/test_location.py
@@ -218,7 +218,7 @@ def test_from_tmy_3():
     assert loc.name is not None
     assert loc.altitude != 0
     assert loc.tz != 'UTC'
-    assert_frame_equal(loc.tmy_data, data)
+    assert_frame_equal(loc.weather, data)
 
 
 def test_from_tmy_2():
@@ -229,7 +229,18 @@ def test_from_tmy_2():
     assert loc.name is not None
     assert loc.altitude != 0
     assert loc.tz != 'UTC'
-    assert_frame_equal(loc.tmy_data, data)
+    assert_frame_equal(loc.weather, data)
+
+
+def test_from_epw():
+    from test_epw import epw_testfile
+    from pvlib.iotools import read_epw
+    data, meta = read_epw(epw_testfile)
+    loc = Location.from_epw(meta, data)
+    assert loc.name is not None
+    assert loc.altitude != 0
+    assert loc.tz != 'UTC'
+    assert_frame_equal(loc.weather, data)
 
 
 def test_get_solarposition(expected_solpos, golden_mst):