Repository: geocryology/globsim (language: python)

Issue:
globsim_scale.py does not gracefully handle old output files
If globsim_scale.py is run on a directory where a valid output file (scaled/scaled_era5_1.0h.nc) that contains the same output kernels already exists, it will open that file instead of overwriting. The use of the same kernels will lead to a name collision and result in a crash with an unclear message. 

Desired behaviour would be to overwrite, create backup of existing output file and then overwrite, or refuse to run if file already exists.

Sample output below:

(globsim)$ python3 globsim_scale.py -d ERA5 -f ~/ldg/ldg.globsim_scale
PRESS_Pa_pl
Traceback (most recent call last):
  File "globsim_scale.py", line 48, in <module>
    GlobsimScale(sfile, ERAI=ERAI, ERA5=ERA5, JRA=JRA, MERRA=MERRA)
  File "/home/luis/git_down/globsim/globsim_main.py", line 117, in GlobsimScale
    ERA5sc.process()
  File "/home/luis/git_down/globsim/era5.py", line 1319, in process
    getattr(self, kernel_name)()
  File "/home/luis/git_down/globsim/era5.py", line 1354, in PRESS_Pa_pl
    var           = self.rg.createVariable(vn,'f4',('time','station'))    
  File "netCDF4/_netCDF4.pyx", line 2736, in netCDF4._netCDF4.Dataset.createVariable
  File "netCDF4/_netCDF4.pyx", line 3758, in netCDF4._netCDF4.Variable.__init__
  File "netCDF4/_netCDF4.pyx", line 1857, in netCDF4._netCDF4._ensure_nc_success
RuntimeError: NetCDF: String match to name in use


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
new file mode 100644
index 0000000..9dc2878
--- /dev/null
+++ b/.github/workflows/python-publish.yml
@@ -0,0 +1,31 @@
+# This workflows will upload a Python Package using Twine when a release is created
+# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
+
+name: Upload Python Package
+
+on:
+  release:
+    types: [created]
+
+jobs:
+  deploy:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python
+      uses: actions/setup-python@v2
+      with:
+        python-version: '3.x'
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install setuptools wheel twine
+    - name: Build and publish
+      env:
+        TWINE_USERNAME: ${{ secrets.PYPI_USR }}
+        TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
+      run: |
+        python setup.py sdist bdist_wheel
+        twine upload dist/*
diff --git a/.gitignore b/.gitignore
index 8622f8c..875f3b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
 
 docs/build/*
 /docs/build/*
+/projects
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..73f69e0
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/deployment.xml b/.idea/deployment.xml
new file mode 100644
index 0000000..908d428
--- /dev/null
+++ b/.idea/deployment.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PublishConfigData" autoUpload="On explicit save action" exclude=".svn;.cvs;.idea;.DS_Store;.git;.hg;*.hprof;*.pyc,docker" autoUploadExternalChanges="true">
+    <serverData>
+      <paths name="hma000">
+        <serverdata>
+          <mappings>
+            <mapping deploy="/globsim" local="$PROJECT_DIR$" web="/" />
+            <mapping deploy="/projects" local="$PROJECT_DIR$/../projects" web="/" />
+          </mappings>
+        </serverdata>
+      </paths>
+    </serverData>
+    <option name="myAutoUpload" value="ON_EXPLICIT_SAVE" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/globsim.iml b/.idea/globsim.iml
new file mode 100644
index 0000000..ceeb4ac
--- /dev/null
+++ b/.idea/globsim.iml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="format" value="PLAIN" />
+    <option name="myDocStringFormat" value="Plain" />
+  </component>
+  <component name="TemplatesService">
+    <option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
+    <option name="TEMPLATE_FOLDERS">
+      <list>
+        <option value="$MODULE_DIR$/.pycharm_helpers/pydev/pydev_tests_python/flask1/templates" />
+      </list>
+    </option>
+  </component>
+  <component name="TestRunnerService">
+    <option name="PROJECT_TEST_RUNNER" value="pytest" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..d597ea3
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,12 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="ignoredIdentifiers">
+        <list>
+          <option value="netCDF4" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..5b3d0f1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7" project-jdk-type="Python SDK" />
+  <component name="PythonCompatibilityInspectionAdvertiser">
+    <option name="version" value="3" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..c78e8e4
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/globsim.iml" filepath="$PROJECT_DIR$/.idea/globsim.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/webServers.xml b/.idea/webServers.xml
new file mode 100644
index 0000000..7b03dd2
--- /dev/null
+++ b/.idea/webServers.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="WebServers">
+    <option name="servers">
+      <webServer id="6154d3ec-a719-4221-b7d6-50685ab59a19" name="hma000">
+        <fileTransfer rootFolder="/home/hma000" accessType="SFTP" host="206.12.93.23" port="22" sshConfigId="8146239a-9358-43bb-ae29-8fcd4ce3aa5a" sshConfig="hma000@206.12.93.23:22 key" keyPair="true">
+          <advancedOptions>
+            <advancedOptions dataProtectionLevel="Private" passiveMode="true" shareSSLContext="true" />
+          </advancedOptions>
+        </fileTransfer>
+      </webServer>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/docker/Dockerfile b/Dockerfile
similarity index 80%
rename from docker/Dockerfile
rename to Dockerfile
index 57caa9d..c8e5ff1 100644
--- a/docker/Dockerfile
+++ b/Dockerfile
@@ -1,14 +1,8 @@
-FROM ubuntu:latest
-
-COPY esmf-python.tar.gz /opt
-COPY credential/ /root
-
-RUN tar xvfz /opt/esmf-python.tar.gz -C /opt
-RUN rm /opt/esmf-python.tar.gz
+FROM ubuntu:18.04
+RUN apt-get update
 
 ENV PYTHONPATH=/opt/python/lib/python3.6/site-packages
 
-RUN apt-get update
 RUN apt-get install -y dialog
 RUN apt-get install -y apt-utils
 RUN apt-get install -y python3
@@ -30,8 +24,10 @@ RUN pip3 install pyproj
 RUN pip3 install cdsapi
 RUN pip3 install requests
 RUN pip3 install lxml
+RUN pip3 install 'cftime==1.0.3.4'
 
 RUN pip3 install pygrib==2.0.1
+RUN pip3 install tomlkit
 
 RUN pip3 install https://software.ecmwf.int/wiki/download/attachments/56664858/ecmwf-api-client-python.tgz
 
@@ -39,8 +35,10 @@ RUN apt-get install -y netcdf-bin
 RUN apt-get install -y libnetcdf-dev
 RUN apt-get install -y libnetcdff-dev
 RUN apt-get install -y nano
+RUN apt-get install -y git-lfs
 
-RUN git clone https://github.com/geocryology/globsim --branch v1.0.6 --depth 1 /opt/globsim
+RUN git lfs clone https://github.com/geocryology/globsim --depth 1 /opt/globsim
+RUN tar xvfz /opt/globsim/lib/esmf-python.tar.gz -C /opt
 
 RUN echo /opt >/usr/lib/python3/dist-packages/globsim.pth
 RUN ln -s /opt/esmf-install/ /opt/esmf
diff --git a/README.md b/README.md
index ee09311..61b8927 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
+![Tests](https://github.com/geocryology/globsim/workflows/Tests/badge.svg)
 # Globsim
 
-Welcome to Globsim, a tool for automating the downloading, interpolation and scaling of different reanalyses to produce meteorological time series for user-defined point locations.
+Welcome to Globsim, a tool for automating the downloading, interpolation and scaling of different reanalyses to produce meteorological time series for user-defined point locations. Full desciption available in this publication: https://gmd.copernicus.org/articles/12/4661/2019/
 
 ## Installation
 Details for installation and usage for Globsim can be found on our [ReadTheDocs page](https://globsim.readthedocs.io/en/latest/?).
diff --git a/data_check.py b/data_check.py
deleted file mode 100644
index 9e0c6ce..0000000
--- a/data_check.py
+++ /dev/null
@@ -1,257 +0,0 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-#
-# Copyright Xiaojing Quan & Stephan Gruber
-#==============================================================================
-# a script for checking the continuity of Time with given the chunk for downloaded netCDF files from ERA_Interim and MERRA-2,
-# 
-# -- Step 1: read in downloaded netCDF files with a wildcard expression;
-# -- Step 2: check the time coverage of varialbes to be continous; 
-# -- Step 3: if there was any missing one, to list the missing series of time 
-#
-#===============================================================================
-from __future__        import print_function
-from datetime          import datetime, timedelta, date
-from os                import path
-from netCDF4           import Dataset, MFDataset
-from generic           import ParameterIO, StationListRead
-
-
-import numpy as np
-import csv
-import netCDF4 as nc
-import math
-import time as tc
-import sys
-
-
-class MERRA2DataCheck(object):
-    """
-        To check  the continuity of the time coverage of from all the downloaded 
-        netCDF files with a wildward expression
-          
-        Args:
-        ncfile_in: Full path to an MERRA-2 derived netCDF file. This can
-        contain wildcards to point to multiple files if temporal
-      
-    """
-
-    def __init__(self, ifile):
-        #read parameter file
-        self.ifile = ifile
-        par = ParameterIO(self.ifile)
-        self.dir_inp = path.join(par.project_directory,'merra2') 
-        self.dir_out = path.join(par.project_directory,'station')
-        self.variables = par.variables
-        self.list_name = par.list_name
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-        
-        # time bounds
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end}
-                      
-                      
-    def DataReadin(self, file_in):    
-        """
-        To read in all the downloaded netCDF files with a wildward expression
-          
-        Args:
-            ncfile_in: Full path to an MERRA-2 derived netCDF file. This can
-                        contain wildcards to point to multiple files if temporal
-                        chunking was used.
-              
-            ncfile_out: Full path to the output netCDF file to write.  
-          """
-
-        # open netcdf file handle, can be one file of several with wildcards
-        ncf = nc.MFDataset(file_in, 'r', aggdim ='time')
-                                                                 
-        #get variable 'time' 
-        nctime = ncf.variables['time'][:]
-        t_unit = ncf.variables['time'].units 
-        try :
-            t_cal = ncf.variables['time'].calendar
-        except AttributeError : # Attribute doesn't exist
-            t_cal = u"gregorian" # or standard
- 
-        # check the continourity of the time coverage
-        if file_in == path.join(self.dir_inp,'merra_pl_*.nc'):
-             missingIndex = list(set(xrange(nctime[0], nctime[-1] + 1, 6)) - set(nctime))       
-             if missingIndex == []:
-                print("NO MISSING TIME FOR", file_in)
-             else:    
-                 missingIndex.sort()
-                 missingTime = nc.num2date(missingIndex, units = t_unit, calendar = t_cal)
-                 
-                 print('FOR', file_in)
-                 
-                 print("Number of Missing Indices of Time : ", len(missingIndex))
-                 
-                 print("Unit of Time: ", t_unit)
-                 
-                 print('Missing Time List:', list(missingTime))
-        
-            
-        else:
-             missingIndex = list(set(xrange(nctime[0], nctime[-1] + 1)) - set(nctime))      
-             if missingIndex == []:
-                print("NO MISSING TIME FOR", file_in)
-             else:    
-                 missingIndex.sort()
-                 missingTime = nc.num2date(missingIndex, units = t_unit, calendar = t_cal)
-
-                 print('FOR', file_in)
-
-                 print("Number of Missing Indices of Time : ", len(missingIndex))
-                 
-                 print("Unit of Time: ", t_unit)
-
-                 print('Missing Time List:', list(missingTime)              )
-
-    def process(self):
-        """
-        combine the specific type of single or mutiple netCDF files
-        """                       
-
-        # === Surface  Data ===    
-        #                
-        self.DataReadin(path.join(self.dir_inp,'merra_sa_*.nc'))          
-
-        # === Pressure-level Data ===    
-        # 
-        #                
-        self.DataReadin(path.join(self.dir_inp,'merra_pl_*.nc'))          
-
-        # === radiation Data ===    
-        # 
-        #                
-        self.DataReadin(path.join(self.dir_inp,'merra_sr_*.nc'))          
-
-
-class ERADataCheck(object):
-    """
-        To check  the continourity of the time coverage of from all the downloaded 
-        netCDF files with a wildward expression
-          
-        Args:
-        ncfile_in: Full path to an ERA_Interim derived netCDF file. This can
-        contain wildcards to point to multiple files if temporal
-      
-    """
-
-    def __init__(self, ifile):
-        #read parameter file
-        self.ifile = ifile
-        par = ParameterIO(self.ifile)
-        self.dir_inp = path.join(par.project_directory,'eraint') 
-        self.dir_out = path.join(par.project_directory,'station')
-        self.variables = par.variables
-        self.list_name = par.list_name
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-        
-        # time bounds
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end}
-                      
-                      
-    def DataReadin(self, file_in):    
-        """
-        To read in all the downloaded netCDF files with a wildward expression
-          
-        Args:
-            ncfile_in: Full path to an MERRA-2 derived netCDF file. This can
-                        contain wildcards to point to multiple files if temporal
-                        chunking was used.
-              
-            ncfile_out: Full path to the output netCDF file to write.  
-          """
-    
-        # open netcdf file handle, can be one file of several with wildcards
-        ncf = nc.MFDataset(file_in, 'r', aggdim ='time') 
-                  
-        #get variable 'time' 
-        nctime = ncf.variables['time'][:]
-        t_unit = ncf.variables['time'].units 
-        try :
-            t_cal = ncf.variables['time'].calendar
-        except AttributeError : # Attribute doesn't exist
-            t_cal = u"gregorian" # or standard
- 
-        # check the continourity of the time coverage
-        if file_in == path.join(self.dir_inp,'era_sf_*.nc'):
-             missingIndex = list(set(xrange(nctime[0], nctime[-1] + 1, 3)) - set(nctime))       
-             if missingIndex == []:
-                print("NO MISSING TIME FOR", file_in)
-             else:    
-                 missingIndex.sort()
-                 missingTime = nc.num2date(missingIndex, units = t_unit, calendar = t_cal)
-                 
-                 print('FOR', file_in)
-                 
-                 print("Number of Missing Indices of Time : ", len(missingIndex))
-                 
-                 print("Unit of Time: ", t_unit)
-                 
-                 print('Missing Time List:', list(missingTime)              )
-        
-            
-        else:
-             missingIndex = list(set(xrange(nctime[0], nctime[-1] + 1, 6)) - set(nctime))      
-             if missingIndex == []:
-                print("NO MISSING TIME FOR", file_in)
-             else:    
-                 missingIndex.sort()
-                 missingTime = nc.num2date(missingIndex, units = t_unit, calendar = t_cal)
-
-                 print('FOR', file_in)
-
-                 print("Number of Missing Indices of Time : ", len(missingIndex))
-                 
-                 print("Unit of Time: ", t_unit)
-
-                 print('Missing Time List:', list(missingTime)              )
-
-    def process(self):
-        """
-        combine the specific type of single or mutiple netCDF files
-        """                       
-
-        # === Surface  Data ===    
-        #                
-        self.DataReadin(path.join(self.dir_inp,'era_sa_*.nc'))          
-
-        # === Pressure-level Data ===    
-        # 
-        #                
-        self.DataReadin(path.join(self.dir_inp,'era_pl_*.nc'))          
-
-        # === radiation Data ===    
-        # 
-        #                
-        self.DataReadin(path.join(self.dir_inp,'era_sf_*.nc'))          
-
-                
-# ===========For Run DataCheck ================================================ 
-# Args:
-# ifile : Full path to a Globsim Interpolate Paramter file under operating latap or virtual machine
-  
-ifile = '/home/xquan/src/globsim/examples/par/examples.globsim_interpolate'
-
-# to check merra2 data 
-MERRA2check = MERRA2DataCheck(ifile)
-
-MERRA2check.process()
-
-# # to check eraint data
-# ERAcheck = ERADataCheck(ifile)
-# 
-# ERAcheck.process()
-
diff --git a/docker/esmf-python.tar.gz b/docker/esmf-python.tar.gz
deleted file mode 100644
index ce06047..0000000
--- a/docker/esmf-python.tar.gz
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a53694382f446dd00e5314534b5216f822ba6158ba0153dfc927e9feb17f27c7
-size 62628030
diff --git a/docs/source/Docker.rst b/docs/source/Docker.rst
index f73f9a6..aa9fa8c 100644
--- a/docs/source/Docker.rst
+++ b/docs/source/Docker.rst
@@ -14,7 +14,7 @@ Open GlobSim Docker container
 -----------------------------
 Next, open a docker command-line window and download the globsim docker image::
 
-    docker pull geocryology/globsim:latest
+    docker pull geocryology/globsim:v1_0_5_GMDD
     
 Then, start the docker container! You'll want to attach it to a folder on your computer using the `-v` flag, replacing <directory> with a directory on your computer::
 
diff --git a/docs/source/Examples.rst b/docs/source/Examples.rst
index 4466252..5b9c82e 100644
--- a/docs/source/Examples.rst
+++ b/docs/source/Examples.rst
@@ -15,25 +15,25 @@ Tutorial
 --------
 Navigate to the GlobSim folder::
 
-    cd /opt/globsim
+    cd /opt/GlobSim/globsim
    
 Run the download script::
 
-    python3 globsim_download.py -d ERA5 -f ./examples/Example1/par/examples.globsim_download
+    python3 globsim.globsim_download.py -d ERA5 -f ./examples/Example1/par/example_config.toml
     
 This downloads meteorological variables from ERA5 for a small region in the NWT. It should take a few minutes to complete (although depending on how busy the servers are it may take longer), and will download files into the Example1/era5 directory. Once it has finished, you can interpolate the gridded data to points::
 
-    python3 globsim_interpolate.py -d ERA5 -f ./examples/Example1/par/examples.globsim_interpolate
+    python3 globsim.globsim_interpolate.py -d ERA5 -f ./examples/Example1/par/example_config.toml
     
 This creates three new files per reanalysis in the /Example1/stations files. Finally, run the final scaling operation::
 
-    python3 globsim_scale.py -d ERA5 -f ./examples/Example1/par/examples.globsim_scale
+    python3 globsim.globsim_scale.py -d ERA5 -f ./examples/Example1/par/example_config.toml
 
 This creates a single file in the /Example1/scale folder with scaled meteorological variables at each of the stations in the /Example1/par/siteslist.csv.
 
 You can now try with another reanalysis::
 
-    python3 globsim_download.py -d JRA -f ./examples/Example1/par/examples.globsim_download
-    python3 globsim_interpolate.py -d JRA -f ./examples/Example1/par/examples.globsim_interpolate
-    python3 globsim_scale.py -d JRA -f ./examples/Example1/par/examples.globsim_scale
+    python3 globsim.globsim_download.py -d JRA -f ./examples/Example1/par/example_config.toml
+    python3 globsim.globsim_interpolate.py -d JRA -f ./examples/Example1/par/example_config.toml
+    python3 globsim.globsim_scale.py -d JRA -f ./examples/Example1/par/example_config.toml
 
diff --git a/docs/source/Installation.rst b/docs/source/Installation.rst
index d95e42b..1bc069e 100644
--- a/docs/source/Installation.rst
+++ b/docs/source/Installation.rst
@@ -8,7 +8,7 @@ Requirements
 
 GlobSim source files can be obtained in one of two ways
 
-Using pip (not yet!!)::
+Using pip (does not work yet!!)::
 
     pip3 install globsim 
 
@@ -73,7 +73,7 @@ The following setup was used to install on Ubuntu 16.04::
     pip3 install https://software.ecmwf.int/wiki/download/attachments/56664858/ecmwf-api-client-python.tgz 
 
     ## Netcdf - C
-    apt install libnetcdf-bin
+    apt install netcdf-bin
     apt install libnetcdf-dev
 
     ## Netcdf - Fortran
diff --git a/docs/source/Operation.rst b/docs/source/Operation.rst
index 7ca3fd8..74e9044 100644
--- a/docs/source/Operation.rst
+++ b/docs/source/Operation.rst
@@ -4,7 +4,7 @@ Operation
 
 Parameter files
 ---------------
-Three parameter files are used to control GLOBSIM. There is one for each step in the procedure (download, interpolate, scale). The parameter files should all be in the /par subdirectory of the project directory. 
+Parameter files are used to control GLOBSIM. The parameter files follow the TOML standard (https://toml.io/en/). Each step in the procedure (download, interpolate, scale) can be split into its own file, or combined. The important thing is that they always have the appropriate headings (e.g. [download], [interpolate], or [scale]). The parameter files should be in the /par subdirectory of the project directory. 
 
 
 Downloading
@@ -55,11 +55,80 @@ Rescaling
 **project_directory**             This is the full path to the project directory which stores the downloaded files and the control files. It should include a subdirectory called /par which contains parameter files (control files) as well as a csv describing the sites to which data are scaled.
 **station_list**                  The filename (without path) of csv containing site information such as *sitelist.csv* (note that this must match the interpolation parameter file)
 **output_file**                   Path to output netCDF to be created. 
-**overwrite**                     Either *True* or *False*. Whether or not to overwrite the `output_file` if it exists.
+**overwrite**                     Either *true* or *false*. Whether or not to overwrite the `output_file` if it exists.
 **time_step**                     The desired output time step in hours
 **kernels**                       Which processing kernels should be used. Missing or misspelled kernels will be ignored by globsim.
 =========================         ===============
 
+Example Parameter File
+^^^^^^^^^^^^^^^^^^^^^^
+Here is an example of a TOML parameter file with all three sections (download, interpolate, scale) combined into one section.
+
+::
+
+    title = "Globsim Control File"
+
+    [download]
+    # logistics
+    project_directory = "/opt/globsim/examples/Example1"
+    credentials_directory = "/root"
+
+    # chunk size for splitting files and download [days]
+    chunk_size = 2
+
+    # area bounding box [decimal degrees]
+    bbN = 66
+    bbS = 62
+    bbW = -112
+    bbE = -108
+
+    # ground elevation range within area [m]
+    ele_min = 0
+    ele_max = 2500
+
+    # time slice [YYYY/MM/DD]
+    beg = "2017/07/01"
+    end = "2017/07/05"
+
+    # variables to download [CF Standard Name Table]
+    variables = ["air_temperature", "relative_humidity", "wind_speed", "wind_from_direction", "precipitation_amount", "downwelling_shortwave_flux_in_air", "downwelling_longwave_flux_in_air", "downwelling_shortwave_flux_in_air_assuming_clear_sky", "downwelling_longwave_flux_in_air_assuming_clear_sky"]
+
+    [interpolate]
+    # Path to the parent directory of /par - It should match the download and scale files
+    project_directory = "/opt/globsim/examples/Example1"
+
+    # Filename (without path) of csv containing site information (must match scaling control file)
+    station_list = "siteslist.csv"
+
+    # How many time steps to interpolate at once? This helps memory management.
+    # Keep small for large area files and small memory computer, make larger to get 
+    # speed on big machines and when working with small area files.
+    # for a small area, we suggest values up to 2000, but consider the memory limit of your computer
+    chunk_size = 2000
+
+    # time slice [YYYY/MM/DD] assuming 00:00 hours
+    beg = "2017/07/01"
+    end = "2017/07/05"
+
+    # variables to interpolate [CF Standard Name Table]
+    variables = ["air_temperature", "relative_humidity", "wind_speed", "wind_from_direction", "precipitation_amount", "downwelling_shortwave_flux_in_air", "downwelling_longwave_flux_in_air", "downwelling_shortwave_flux_in_air_assuming_clear_sky", "downwelling_longwave_flux_in_air_assuming_clear_sky"]
+
+    [scale]
+    # Path to the parent directory of /par - It should match the download and interpolate files
+    project_directory = "/opt/globsim/examples/Example1"
+
+    # Filename (without path) of csv containing site information (must match interpolation control file)
+    station_list = "siteslist.csv"
+
+    # processing kernels to be used.  Unavailable kernels will be ignored
+    kernels = ["PRESS_Pa_pl", "AIRT_C_pl", "AIRT_C_sur", "PREC_mm_sur", "RH_per_sur", "WIND_sur", "SW_Wm2_sur", "LW_Wm2_sur", "SH_kgkg_sur"]
+
+    # desired time step for output data [hours]
+    time_step = 1
+
+    # Should the output file be overwritten if it exists?
+    overwrite = true
+
 Station list for interpolation
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 This is an example of a Globsim station list file. The resulting netCDF file will use the station numbers as identifiers. Use extension like this: 'my_stations.csv'::
@@ -77,7 +146,7 @@ The **project directory** is the location to which data is downloaded and where
      project_a/par/          (parameter files for data download and interpolation)
      project_a/jra-55/       (JRA-55 data)
      project_a/eraint/       (ERA-Interim data)
-     project_a/era5/         (ERA-5 data)
+     project_a/era5/         (ERA5 data)
      project_a/merra2/       (MERRA 2 data)
      project_a/station/      (data interpolated to stations)
      project_a/scale/        (final scaled files)
\ No newline at end of file
diff --git a/docs/source/images/flowchart.png b/docs/source/images/flowchart.png
old mode 100644
new mode 100755
index 9e3b5be..30bee8b
Binary files a/docs/source/images/flowchart.png and b/docs/source/images/flowchart.png differ
diff --git a/era5.py b/era5.py
deleted file mode 100644
index 800256e..0000000
--- a/era5.py
+++ /dev/null
@@ -1,1568 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Methods for downloading ERA5 data from the ECMWF server for limited
-# areas and limited times.
-#
-#
-# (C) Copyright Stephan Gruber (2013–2017)
-#         
-#     This program is free software: you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation, either version 3 of the License, or
-#     (at your option) any later version.
-#
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-#
-#     You should have received a copy of the GNU General Public License
-#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
-# 
-#  OVERALL WORKFLOW
-# 
-#  See: https://github.com/geocryology/globsim/wiki/Globsim
-#       and the examples directory on github
-#
-# ECMWF and netCDF information:
-# https://software.ecmwf.int/wiki/display/WEBAPI/Python+ERA-interim+examples
-#
-# For variable codes and units, see: 
-#     http://www.ecmwf.int/publications/manuals/d/gribapi/param/
-#
-# Check ECMWF job status: http://apps.ecmwf.int/webmars/joblist/
-#
-#  CF-Convention: this is how you check netcdf file conformity: 
-#  http://pumatest.nerc.ac.uk/cgi-bin/cf-checker-dev.pl 
-#
-#===============================================================================
-from __future__ import print_function
-
-import re
-
-import numpy   as np
-import netCDF4 as nc
-import cdsapi
-
-from datetime import datetime, timedelta
-from math     import exp, floor, atan2, pi
-from os       import path, listdir, makedirs
-from fnmatch  import filter
-from ecmwfapi.api import ECMWFDataServer
-from scipy.interpolate import interp1d
-
-import urllib3
-urllib3.disable_warnings()
-
-from globsim.generic  import ParameterIO, StationListRead, ScaledFileOpen, series_interpolate, variables_skip, spec_hum_kgkg, str_encode
-
-try:
-    from nco import Nco
-except ImportError:
-    print("*** NCO not imported, netCDF appending not possible. ***")
-    pass 
-
-try:
-    import ESMF
-    # Check ESMF version.  7.0.1 behaves differently than 7.1.0r 
-    ESMFv = int(re.sub("[^0-9]", "", ESMF.__version__))
-    ESMFnew = ESMFv > 701
-    
-except ImportError:
-    print("*** ESMF not imported, interpolation not possible. ***")
-    pass   
-            
-
-
-
-class ERA5generic(object):
-    """
-    Parent class for other ERA5 classes.
-    
-    """           
-    CDS_DICT =  {'130.128' : 'temperature',
-                     '157.128' : 'relative_humidity',
-                     '131.128' : 'u_component_of_wind',
-                     '132.128' : 'v_component_of_wind',
-                     '129.128' : 'geopotential',
-                     '167.128' : '2m_temperature',
-                     '168.128' : '2m_dewpoint_temperature',
-                     '206.128' : 'total_column_ozone',        # also consider surface_solar_radiation_downward_clear_sky
-                     '137.128' : 'total_column_water_vapour', # also consider surface_solar_radiation_downward_clear_sky
-                     '165.128' : '10m_u_component_of_wind',
-                     '166.128' : '10m_v_component_of_wind',
-                     '228.128' : 'total_precipitation',
-                     '169.128' : 'surface_solar_radiation_downwards',
-                     '175.128' : 'surface_thermal_radiation_downwards',
-                     '172.128' : 'land_sea_mask'
-                     }
-                     
-    def areaString(self, area):
-        """Converts numerical coordinates into string: North/West/South/East"""
-        res  = str(round(area['north'],2)) + "/"
-        res += str(round(area['west'], 2)) + "/"
-        res += str(round(area['south'],2)) + "/"
-        res += str(round(area['east'], 2))        
-        return(res)
-        
-    def dateString(self, date):
-        """Converts datetime objects into string"""
-        res  = (date['beg'].strftime("%Y-%m-%d") + "/to/" +
-                date['end'].strftime("%Y-%m-%d"))       
-        return(res)    
-        
-    def getPressure(self, elevation):
-        """Convert elevation into air pressure using barometric formula"""
-        g  = 9.80665   #Gravitational acceleration [m/s2]
-        R  = 8.31432   #Universal gas constant for air [N·m /(mol·K)]    
-        M  = 0.0289644 #Molar mass of Earth's air [kg/mol]
-        P0 = 101325    #Pressure at sea level [Pa]
-        T0 = 288.15    #Temperature at sea level [K]
-        #http://en.wikipedia.org/wiki/Barometric_formula
-        return P0 * exp((-g * M * elevation) / (R * T0)) / 100 #[hPa] or [bar]
-    
-    def getPressureLevels(self, elevation):
-        """Restrict list of ERA5 pressure levels to be downloaded"""
-        Pmax = self.getPressure(elevation['min']) + 55
-        Pmin = self.getPressure(elevation['max']) - 55 
-        levs = np.array([300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 775, 
-                         800, 825, 850, 875, 900, 925, 950, 975, 1000])
-        mask = (levs >= Pmin) * (levs <= Pmax) #select
-        levs = '/'.join(map(str, levs[mask]))
-        return levs
-
-    def getDictionaryGen(self, area, date):
-        """
-        Makes dictionary of generic variables for a server call
-        """
-        dictionary_gen = {
-          'area'    : self.areaString(area),
-          'date'    : self.dateString(date),
-          'dataset' : "era5",
-          'stream'  : "oper",
-          'class'   : "ea",
-          'format'  : "netcdf",
-          'grid'    : "0.25/0.25"}
-        return dictionary_gen
- 
-    def getDstring(self):
-        return ('_' + self.date['beg'].strftime("%Y%m%d") + "_to_" +
-                      self.date['end'].strftime("%Y%m%d"))
-    
-    def download(self, api, storage):
-        if api == 'ecmwf':
-            server = ECMWFDataServer()
-            print(server.trace('=== ERA5 ({}API): START ACCESS ON {} ===='.format('ECMWF', storage.upper())))
-            server.retrieve(self.getDictionary())
-            print(server.trace('=== ERA5 ({}API): END ACCESS ON {} ===='.format('ECMWF', storage.upper())))
-        
-        elif api == 'cds':
-            self.download_cds(storage)
-    
-    def download_cds(self, storage='cds'):
-        ''' 'patched' download function to allow download using cdsapi from MARS or CDS servers  '''
-        server = cdsapi.Client()
-        
-        query = self.getDictionary()
-        target = query.pop('target')
-        levtype = query.pop('levtype')
-        
-        # select dataset
-        if storage == 'ecmwf':
-            dataset = 'reanalysis-era5-complete'
-        elif storage == 'cds':
-            dataset = {'pl' : 'reanalysis-era5-pressure-levels',
-                       'sfc': 'reanalysis-era5-single-levels'
-                      }[levtype]
-            query = self.ECM2CDS(query)
-        
-        # launch download request
-        print(server.info('=== ERA5 ({}API): START ACCESS ON {} ===='.format("CDS", storage.upper())))
-        if path.isfile(target):
-             print("WARNING: File '{}' already exists and was skipped".format(target))
-        else:
-            server.retrieve(dataset, query, target)
-        print(server.info('=== ERA5 ({}API): END ACCESS ON {} ===='.format("CDS", storage.upper())))
-    
-    
-    def ECM2CDS(self, query):
-        ''' convert ECMWF query to CDS format '''
-
-        # remove unnecessary keys
-        for key in ['class', 'dataset', 'stream', 'type']:
-            query.pop(key)
-        
-        # replace key('param'):val(str)  string with key('variables'):val(list)
-        query['variable'] = [self.CDS_DICT[L] for L in query.pop('param').split('/')]
-        
-        # replace levellist if in dictionary (also string '100/200/300' to list ['100', '200', '300'])
-        if 'levellist' in query:
-            query['pressure_level'] = [L for L in query.pop('levellist').split('/')]
-            
-        # reformat date range (start/to/end --> start/end)
-        query['date'] = re.sub("/to", "", query['date'])
-        
-        # reformat time string into list
-        query['time'] = [t[0:5] for t in query['time'].split('/')]
-        
-        # add product type (one of Reanalysis, Ensemble members, Ensemble mean, Ensemble spread)
-        query['product_type'] = 'reanalysis'
-        
-        return(query)
-        
-    def TranslateCF2ERA(self, variables, dpar):
-        """
-        Translate CF Standard Names into ERA5 code numbers.
-        """
-        self.param = ''
-        for var in variables:
-            try:
-                self.param += dpar.get(var)+'/'
-                
-            except TypeError:
-                pass    
-        self.param = self.param.rstrip('/') #fix last                                        
-                
-    def __str__(self):
-        string = ("List of generic variables to query ECMWF server for "
-                  "ERA5 data: {0}")
-        return string.format(self.getDictionary) 
-    
-    def netCDF_empty(self, ncfile_out, stations, nc_in):
-        #TODO change date type from f4 to f8 for lat and lon
-        '''
-        Creates an empty station file to hold interpolated reults. The number of 
-        stations is defined by the variable stations, variables are determined by 
-        the variable list passed from the gridded original netCDF.
-        
-        ncfile_out: full name of the file to be created
-        stations:   station list read with generic.StationListRead() 
-        variables:  variables read from netCDF handle
-        lev:        list of pressure levels, empty is [] (default)
-        '''
-        
-        #Build the netCDF file
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4_CLASSIC')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'ERA5, interpolated bilinearly to stations'
-        rootgrp.featureType = "timeSeries"
-                                                
-        # dimensions
-        station = rootgrp.createDimension('station', len(stations))
-        time    = rootgrp.createDimension('time', None)
-                
-        # base variables
-        time           = rootgrp.createVariable('time', 'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1900-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station', 'i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude', 'f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east' 
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-        
-        # assign station characteristics            
-        station[:]   = list(stations['station_number'])
-        latitude[:]  = list(stations['latitude_dd'])
-        longitude[:] = list(stations['longitude_dd'])
-        height[:]    = list(stations['elevation_m'])
-        
-        # extra treatment for pressure level files
-        try:
-            lev = nc_in.variables['level'][:]
-            print("== 3D: file has pressure levels")
-            level = rootgrp.createDimension('level', len(lev))
-            level           = rootgrp.createVariable('level','i4',('level'))
-            level.long_name = 'pressure_level'
-            level.units     = 'hPa'  
-            level[:] = lev 
-        except:
-            print("== 2D: file without pressure levels")
-            lev = []
-                    
-        # create and assign variables based on input file
-        for n, var in enumerate(nc_in.variables):
-            if variables_skip(var):
-                continue                 
-            print("VAR: ", str_encode(var))
-            # extra treatment for pressure level files           
-            if len(lev):
-                tmp = rootgrp.createVariable(var,'f4',('time', 'level', 'station'))
-            else:
-                tmp = rootgrp.createVariable(var,'f4',('time', 'station'))     
-            tmp.long_name = nc_in.variables[var].long_name.encode('UTF8') 
-            tmp.units     = nc_in.variables[var].units.encode('UTF8')  
-                    
-        #close the file
-        rootgrp.close()
-
-        
-class ERA5pl(ERA5generic):
-    """Returns an object for ERA5 data that has methods for querying the
-    ECMWF server.
-       
-    Args:
-        date: A dictionary specifying the time period desired with a begin 
-              and an end date given as a datetime.datetime object.
-              
-        area: A dictionary delimiting the area to be queried with the latitudes
-              north and south, and the longitudes west and east [decimal deg].  
-              
-        elevation: A dictionary specifying the min/max elevation of the area of
-                   interest. This is used to determine the pressure levels 
-                   needed. Unit: [m].
-        
-        variables:  List of variable(s) to download based on CF Standard Names.
-        
-        directory: Directory to hold output files
-              
-    Example:
-        from datetime import datetime
-        date  = {'beg' : datetime(1994, 1, 1),
-                 'end' : datetime(2013, 1, 1)}
-        area  = {'north' :  40.0,
-                 'south' :  15.0,
-                 'west'  :  60.0,
-                 'east'  : 105.0}
-        elevation = {'min' :    0, 
-                     'max' : 8850}
-        variables  = ['air_temperature', 'relative_humidity']             
-        directory = '/Users/stgruber/Desktop'             
-        ERA5pl = ERA5pl(date, area, elevation, variables, directory) 
-        ERA5pl.download()
-    """
-    def __init__(self, date, area, elevation, variables, directory):
-        self.date       = date
-        self.area       = area
-        self.elevation  = elevation
-        self.directory  = directory
-        outfile = 'era5_pl' + self.getDstring() + '.nc'
-        self.file_ncdf  = path.join(self.directory, outfile)
- 
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable names. 
-        dpar = {'air_temperature'   : '130.128',           # [K]
-                'relative_humidity' : '157.128',           # [%]
-                'wind_speed'        : '131.128/132.128'}   # [m s-1]
-                
-        # translate variables into those present in ERA pl data        
-        self.TranslateCF2ERA(variables, dpar)
-        self.param += '/129.128' # geopotential always needed [m2 s-2]
-        self.time = ("00:00:00/01:00:00/02:00:00/03:00:00/"
-                     "04:00:00/05:00:00/06:00:00/07:00:00/"
-                     "08:00:00/09:00:00/10:00:00/11:00:00/"
-                     "12:00:00/13:00:00/14:00:00/15:00:00/"
-                     "16:00:00/17:00:00/18:00:00/19:00:00/"
-                     "20:00:00/21:00:00/22:00:00/23:00:00")
-    
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "pl",
-           'levellist': self.getPressureLevels(self.elevation),
-           'time'     : self.time,
-           'step'     : "0",
-           'type'     : "an",
-           'param'    : self.param,
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary    
-        
-    def __str__(self):
-        string = ("List of variables to query ECMWF server for "
-                  "ERA5 data: {0}")
-        return string.format(self.getDictionary) 
-
-                                
-class ERA5sa(ERA5generic):
-    """
-    Returns an object for ERA5 data that has methods for querying the
-    ECMWF server for surface analysis variables (airt2,dewp2, ozone, vapor,
-    wind10).
-       
-    Args:
-        
-        target:    File name of the netcdf file to be created.
-        
-        variable:  List of variable(s) to download based on CF Standard Names 
-              
-    Example:
-        from datetime import datetime
-        date  = {'beg' : datetime(1994, 1, 1),
-                 'end' : datetime(2013, 1, 1)}
-        area  = {'north' :  40.0,
-                 'south' :  15.0,
-                 'west'  :  60.0,
-                 'east'  : 105.0}
-        variables  = ['air_temperature', 'relative_humidity']             
-        directory = '/Users/stgruber/Desktop'             
-        ERA5sa = ERA5sa(date, area, variables, directory) 
-        ERA5sa.download()      
-    """
-    def __init__(self, date, area, variables, directory):
-        self.date       = date
-        self.area       = area
-        self.directory  = directory
-        outfile = 'era5_sa' + self.getDstring() + '.nc'
-        self.file_ncdf  = path.join(self.directory, outfile)
-        
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable names. 
-        dpar = {'air_temperature'   : '167.128',  # [K] 2m values
-                'relative_humidity' : '168.128',  # [K] 2m values
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky' : 
-                    '206.128/137.128',  # [kg m-2] Total column ozone 
-                                        # [kg m-2] Total column W vapor                                                             
-                'wind_speed' : '165.128/166.128'}   # [m s-1] 10m values
-                
-        # translate variables into those present in ERA pl data        
-        self.TranslateCF2ERA(variables, dpar)
-        
-    
-    def getTime(self):
-        
-        times = np.arange(0, 24)
-        times = [str(t).zfill(2) for t in times]
-        times = [t+':00:00' for t in times]
-        times = '/'.join(times)
-        
-        return times
-
-
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "sfc",
-           'time'     : self.getTime(),
-           'step'     : "0",
-           'type'     : "an",
-           'param'    : self.param,
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary  
-        
-    def __str__(self):
-        string = ("Class for ERA5 surface analysis data: {0}")
-        return string.format(self.getDictionary)         
-
-
-class ERA5sf(ERA5generic):
-    """
-    Returns an object for ERA5 data that has methods for querying the
-    ECMWF server for surface forecast variables (prec, swin, lwin).
-       
-    Args:        
-        target:    File name of the netcdf file to be created.
-        
-        variables:  List of variable(s) to download that can include one, several
-                   , or all of these: ['airt', 'rh', 'geop', 'wind'].
-        
-        ERAgen:    ERA5generic() object with generic information on the area and
-                   time span to be
-              
-    Example:
-        from datetime import datetime
-        date  = {'beg' : datetime(1994, 1, 1),
-                 'end' : datetime(1994, 1, 1)}
-        area  = {'north' :  40.0,
-                 'south' :  40.0,
-                 'west'  :  60.0,
-                 'east'  :  60.0}
-        variables  = ['prec','swin','lwin']             
-        directory = '/Users/stgruber/Desktop'             
-        ERA5sf = ERA5sf(date, area, variables, directory) 
-        ERA5sf.download()   
-    """
-    def __init__(self, date, area, variables, directory):
-        self.date       = date
-        self.area       = area
-        self.directory  = directory
-        outfile = 'era5_sf' + self.getDstring() + '.nc'
-        self.file_ncdf  = path.join(self.directory, outfile)
-
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable names. 
-        # [m] total precipitation
-        # [J m-2] short-wave downward
-        # [J m-2] long-wave downward
-        dpar = {'precipitation_amount'              : '228.128', #[kg m**-2 s**-1]
-                'downwelling_shortwave_flux_in_air' : '169.128', #[J m**-2]
-                'downwelling_longwave_flux_in_air'  : '175.128'} #[J m**-2]
-                
-        # translate variables into those present in ERA pl data        
-        self.TranslateCF2ERA(variables, dpar)
-
-        
-    def getStep(self):
-        steps = np.arange(1, 13)
-        steps = [str(s) for s in steps]
-        steps = '/'.join(steps)
-        
-        return steps
-
-    def getDictionary(self):
-    
-        self.dictionary = {
-           'levtype'  : "sfc",
-           'time'     : ("00:00:00/01:00:00/02:00:00/03:00:00/"
-                     "04:00:00/05:00:00/06:00:00/07:00:00/"
-                     "08:00:00/09:00:00/10:00:00/11:00:00/"
-                     "12:00:00/13:00:00/14:00:00/15:00:00/"
-                     "16:00:00/17:00:00/18:00:00/19:00:00/"
-                     "20:00:00/21:00:00/22:00:00/23:00:00"),
-           'step'     : self.getStep(),
-           'type'     : "fc",
-           'param'    : self.param,
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary  
-        
-    def __str__(self):
-        string = ("List of variables to query ECMWF server for "
-                  "ERA5 air tenperature data: {0}")
-        return string.format(self.getDictionary)      
-                
-class ERA5to(ERA5generic):
-    """
-    Returns an object for downloading and handling ERA5
-    topography (invariant).
-       
-    Args:      
-              
-    Example:
-        area  = {'north' :  40.0,
-                 'south' :  45.0,
-                 'west'  :  60.0,
-                 'east'  :  65.0}            
-        directory = '/Users/stgruber/Desktop'             
-        ERA5to = ERA5to(area, directory) 
-        ERA5to.download()       
-    """
-    def __init__(self, area, directory):
-        self.area       = area
-        self.date       = {'beg' : datetime(2017, 1, 1),
-                           'end' : datetime(2017, 1, 1)}
-        self.directory  = directory
-        self.file_ncdf  = path.join(self.directory,'era5_to.nc')
-
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "sfc",
-           'time'     : "00:00:00",
-           'step'     : "0",
-           'type'     : "an",
-           'param'    : "129.128/172.128", # geopotential and land-sea mask
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary   
-        
-    def __str__(self):
-        string = ("List of variables to query ECMWF server for "
-                  "ERA5 air tenperature data: {0}")
-        return string.format(self.getDictionary) 
-
-
-class ERA5download(object):
-    """
-    Class for ERA5 data that has methods for querying 
-    the ECMWF server, returning all variables usually needed.
-       
-    Args:
-        pfile: Full path to a Globsim Download Parameter file. 
-        api  : Which API to use. Either 'cds' (default) or 'ecmwf' (deprecated'
-        storage : Which server to access data from. Either 'cds'  (default) or 'ecmwf' (deprecated). Note
-                 that you can use the cds api to access the ecmwf storage (MARS)
-    Example:          
-        ERAd = ERA5download(pfile) 
-        ERAd.retrieve()
-    """
-        
-    def __init__(self, pfile, api='cds', storage='cds'):
-        # read parameter file
-        self.pfile = pfile
-        par = ParameterIO(self.pfile)
-        
-        # assign bounding box
-        self.area  = {'north':  par.bbN,
-                      'south':  par.bbS,
-                      'west' :  par.bbW,
-                      'east' :  par.bbE}
-        
-        # sanity check to make sure area is good
-        if (par.bbN < par.bbS) or (par.bbE < par.bbW):
-            raise Exception("Bounding box is invalid: {}".format(self.area))
-            
-        if (np.abs(par.bbN-par.bbS) < 1.5) or (np.abs(par.bbE-par.bbW) < 1.5):
-            raise Exception("Download area is too small to conduct interpolation.")
-        
-        # time bounds
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end}
-
-        # elevation
-        self.elevation = {'min' : par.ele_min, 
-                          'max' : par.ele_max}
-        
-        # data directory for ERA5
-        self.directory = path.join(par.project_directory, "era5") 
-        if path.isdir(self.directory) == False:
-            makedirs(self.directory)   
-     
-        # variables
-        self.variables = par.variables
-            
-        # chunk size for downloading and storing data [days]        
-        self.chunk_size = par.chunk_size   
-
-        # set api and download server/storage
-        self.api = api
-        self.storage = storage
-
-                           
-    def retrieve(self):
-        """
-        Retrieve all required ERA5 data from MARS/CDS server.
-        """        
-        # prepare time loop
-        date_i = {}
-        slices = floor(float((self.date['end'] - self.date['beg']).days)/
-                       self.chunk_size)+1
-                                                 
-        # topography
-        if path.isfile(path.join(self.directory,'era5_to.nc')):
-            print("WARNING: File 'era5_to.nc' already exists. Skipping.")
-        else: 
-            top = ERA5to(self.area, self.directory)
-            top.download(self.api, self.storage)
-        
-        for ind in range (0, int(slices)): 
-            #prepare time slices   
-            date_i['beg'] = self.date['beg'] + timedelta(days = 
-                            self.chunk_size * ind)
-            date_i['end'] = self.date['beg'] + timedelta(days = 
-                            self.chunk_size * (ind+1) - 1)
-            if ind == (slices-1):
-                date_i['end'] = self.date['end']
-            
-            #actual functions                                                                           
-            pl = ERA5pl(date_i, self.area, self.elevation, 
-                       self.variables, self.directory) 
-            sa = ERA5sa(date_i, self.area, self.variables, self.directory) 
-            sf = ERA5sf(date_i, self.area, self.variables, self.directory) 
-        
-            #download from ECMWF server convert to netCDF  
-            ERAli = [pl, sa, sf]
-            for era in ERAli:
-                era.download(self.api, self.storage)          
-        
-        # report inventory
-        self.inventory()  
-        
-                                                                                                                                                                                                           
-    def inventory(self):
-        """
-        Report on data avaialbe in directory: time slice, variables, area 
-        """
-        print("\n\n\n")
-        print("=== INVENTORY FOR GLOBSIM ERA5 DATA ===\n")
-        print("Download parameter file: \n" + self.pfile + "\n")
-        # loop over filetypes, read, report
-        file_type = ['era5_pl_*.nc', 'era5_sa_*.nc', 
-                     'era5_sf_*.nc', 'era5_t*.nc']
-        for ft in file_type:
-            infile = path.join(self.directory, ft)
-            nf = len(filter(listdir(self.directory), ft))
-            print(str(nf) + " FILE(S): " + infile)
-            
-            if nf > 0:
-                # open dataset
-                ncf = nc.MFDataset(infile, 'r', aggdim='time')
-                
-                # list variables
-                keylist = [str_encode(x) for x in ncf.variables.keys()]
-                print("    VARIABLES:")
-                print("        " + str(len(keylist)) + 
-                      " variables, including dimensions")
-                for key in keylist:
-                    print("        " + ncf.variables[key].long_name)
-                
-                # time slice
-                time = ncf.variables['time']
-                tmin = '{:%Y/%m/%d}'.format(nc.num2date(min(time[:]), 
-                                     time.units, calendar=time.calendar))
-                tmax = '{:%Y/%m/%d}'.format(nc.num2date(max(time[:]), 
-                                     time.units, calendar=time.calendar))
-                print("    TIME SLICE")                     
-                print("        " + str(len(time[:])) + " time steps")
-                print("        " + tmin + " to " + tmax)
-                      
-                # area
-                lon = ncf.variables['longitude']
-                lat = ncf.variables['latitude']
-                nlat = str(len(lat))
-                nlon = str(len(lon))
-                ncel = str(len(lat) * len(lon))
-                print("    BOUNDING BOX / AREA")
-                print("        " + ncel + " cells, " + nlon + 
-                      " W-E and " + nlat + " S-N")
-                print("        N: " + str(max(lat)))
-                print("        S: " + str(min(lat)))
-                print("        W: " + str(min(lon)))
-                print("        E: " + str(max(lon)))
-                            
-                ncf.close()
-                   
-    def __str__(self):
-        return "Object for ERA5 data download and conversion"
-		
-
-class ERA5interpolate(object):
-    """
-    Collection of methods to interpolate ERA5 netCDF files to station
-    coordinates. All variables retain their original units and time stepping.
-    """
-        
-    def __init__(self, ifile):
-        #read parameter file
-        self.ifile = ifile
-        par = ParameterIO(self.ifile)
-        self.dir_inp = path.join(par.project_directory,'era5') 
-        self.dir_out = self.makeOutDir(par)
-        self.variables = par.variables
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        self.list_name = par.station_list.split(path.extsep)[0]
-        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-        #convert longitude to ERA notation if using negative numbers  
-        self.stations['longitude_dd'] = self.stations['longitude_dd'] % 360             
-        
-        # time bounds, add one day to par.end to include entire last day
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end + timedelta(days=1)}
-    
-        # chunk size: how many time steps to interpolate at the same time?
-        # A small chunk size keeps memory usage down but is slow.
-        self.cs  = int(par.chunk_size)
-    
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirIntp = path.join(par.project_directory, 'interpolated')
-        
-        if not (path.isdir(dirIntp)):
-            makedirs(dirIntp)
-            
-        return dirIntp
-
-    def ERAinterp2D(self, ncfile_in, ncf_in, points, tmask_chunk,
-                        variables=None, date=None):    
-        """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
-        to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all Era5 files).
-          
-        Args:
-            ncfile_in: Full path to an Era5 derived netCDF file. This can
-                       contain wildcards to point to multiple files if temporal
-                       chunking was used.
-              
-            ncf_in: A netCDF4.MFDataset derived from reading in ERA5 
-                    multiple files (def ERA2station())
-            
-            points: A dictionary of locations. See method StationListRead in
-                    generic.py for more details.
-        
-            variables:  List of variable(s) to interpolate such as 
-                        ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
-                        Defaults to using all variables available.
-        
-            date: Directory to specify begin and end time for the derived time 
-                  series. Defaluts to using all times available in ncfile_in.
-              
-        Example:
-            from datetime import datetime
-            date  = {'beg' : datetime(2008, 1, 1),
-                      'end' : datetime(2008,12,31)}
-            variables  = ['t','u', 'v']       
-            stations = StationListRead("points.csv")      
-            ERA2station('era5_sa.nc', 'era5_sa_inter.nc', stations, 
-                        variables=variables, date=date)        
-        """   
-        
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
-
-        # get spatial dimensions
-        if pl: # only for pressure level files
-            lev  = ncf_in.variables['level'][:]
-            nlev = len(lev)
-              
-        # test if time steps to interpolate remain
-        nt = sum(tmask_chunk)
-        if nt == 0:
-            raise ValueError('No time steps from netCDF file selected.')
-    
-        # get variables
-        varlist = [str_encode(x) for x in ncf_in.variables.keys()]
-        varlist.remove('time')
-        varlist.remove('latitude')
-        varlist.remove('longitude')
-        if pl: #only for pressure level files
-            varlist.remove('level')
-    
-        #list variables that should be interpolated
-        if variables is None:
-            variables = varlist
-        #test is variables given are available in file
-        if (set(variables) < set(varlist) == 0):
-            raise ValueError('One or more variables not in netCDF file.')           
-
-        # Create source grid from a SCRIP formatted file. As ESMF needs one
-        # file rather than an MFDataset, give first file in directory.
-        flist = np.sort(filter(listdir(self.dir_inp), 
-                               path.basename(ncfile_in)))
-        ncsingle = path.join(self.dir_inp, flist[0])
-        sgrid = ESMF.Grid(filename=ncsingle, filetype=ESMF.FileFormat.GRIDSPEC)
-
-        # create source field on source grid
-        if pl: #only for pressure level files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt, nlev])
-        else: # 2D files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt])
-
-        # assign data from ncdf: (variable, time, latitude, longitude) 
-        for n, var in enumerate(variables):
-            
-            if pl: # only for pressure level files
-                if ESMFnew:
-                    sfield.data[:,:,n,:,:] = ncf_in.variables[var][tmask_chunk,:,:,:].transpose((3,2,0,1)) 
-                else:
-                    sfield.data[n,:,:,:,:] = ncf_in.variables[var][tmask_chunk,:,:,:].transpose((0,1,3,2)) # original
-
-            else:
-                if ESMFnew:
-                    sfield.data[:,:,n,:] = ncf_in.variables[var][tmask_chunk,:,:].transpose((2,1,0))
-                else:
-                    sfield.data[n,:,:,:] = ncf_in.variables[var][tmask_chunk,:,:].transpose((0,2,1)) # original
-                
-
-        # create locstream, CANNOT have third dimension!!!
-        locstream = ESMF.LocStream(len(self.stations), 
-                                   coord_sys=ESMF.CoordSys.SPH_DEG)
-        locstream["ESMF:Lon"] = list(self.stations['longitude_dd'])
-        locstream["ESMF:Lat"] = list(self.stations['latitude_dd'])
-
-        # create destination field
-        if pl: # only for pressure level files
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt, nlev])
-        else:
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt])
-        
-
-        # regridding function, consider ESMF.UnmappedAction.ERROR
-        regrid2D = ESMF.Regrid(sfield, dfield,
-                                regrid_method=ESMF.RegridMethod.BILINEAR,
-                                unmapped_action=ESMF.UnmappedAction.IGNORE,
-                                dst_mask_values=None)
-                  
-        # regrid operation, create destination field (variables, times, points)
-        dfield = regrid2D(sfield, dfield)        
-        sfield.destroy() #free memory                  
-		    
-        return dfield, variables
-
-    def ERA2station(self, ncfile_in, ncfile_out, points,
-                    variables = None, date = None):
-        
-        """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
-        to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all ERA5 files). The type 
-        of variable and file structure are determined from the input.
-        
-        This function creates an empty of netCDF file to hold the interpolated 
-        results, by calling ERA5generic().netCDF_empty. Then, data is 
-        interpolated in temporal chunks and appended. The temporal chunking can 
-        be set in the interpolation parameter file.
-        
-        Args:
-        ncfile_in: Full path to an ERA5 derived netCDF file. This can
-                   contain wildcards to point to multiple files if temporal
-                  chunking was used.
-            
-        ncfile_out: Full path to the output netCDF file to write.     
-        
-        points: A dictionary of locations. See method StationListRead in
-                generic.py for more details.
-    
-        variables:  List of variable(s) to interpolate such as 
-                    ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
-                    Defaults to using all variables available.
-    
-        date: Directory to specify begin and end time for the derived time 
-                series. Defaluts to using all times available in ncfile_in.
-        
-        cs: chunk size, i.e. how many time steps to interpolate at once. This 
-            helps to manage overall memory usage (small cs is slower but less
-            memory intense).          
-        """
-                
-        # read in one type of mutiple netcdf files       
-        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim ='time')
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
-
-        # build the output of empty netCDF file
-        ERA5generic().netCDF_empty(ncfile_out, self.stations, ncf_in) 
-                                     
-        # open the output netCDF file, set it to be appendable ('a')
-        ncf_out = nc.Dataset(ncfile_out, 'a')
-
-        # get time and convert to datetime object
-        nctime = ncf_in.variables['time'][:]
-        #"hours since 1900-01-01 00:00:0.0"
-        t_unit = ncf_in.variables['time'].units 
-        try :
-            t_cal = ncf_in.variables['time'].calendar
-        except AttributeError :  # attribute doesn't exist
-            t_cal = u"gregorian" # standard
-        time = nc.num2date(nctime, units = t_unit, calendar = t_cal)
-        
-        # detect invariant files (topography etc.)
-        if len(time) == 1:
-            invariant=True
-        else:
-            invariant=False                                                                         
-    
-        # restrict to date/time range if given
-        if date is None:
-            tmask = time < datetime(3000, 1, 1)
-        else:
-            tmask = (time < date['end']) * (time >= date['beg'])
-                          
-        # get time vector for output
-        time_in = nctime[tmask]     
-
-        # ensure that chunk sizes cover entire period even if
-        # len(time_in) is not an integer multiple of cs
-        niter  = len(time_in)//self.cs
-        niter += ((len(time_in) % self.cs) > 0)
-
-        # loop over chunks
-        for n in range(niter):
-            # indices (relative to index of the output file)
-            beg = n * self.cs
-            # restrict last chunk to lenght of tmask plus one (to get last time)
-            end = min(n*self.cs + self.cs, len(time_in))-1
-            
-            # time to make tmask for chunk 
-            beg_time = nc.num2date(time_in[beg], units=t_unit, calendar=t_cal)
-            if invariant:
-                # allow topography to work in same code, len(nctime) = 1
-                end_time = nc.num2date(nctime[0], units=t_unit, calendar=t_cal)
-                #end = 1
-            else:
-                end_time = nc.num2date(time_in[end], units=t_unit, calendar=t_cal)
-                
-            #'<= end_time', would damage appending
-            tmask_chunk = (time <= end_time) * (time >= beg_time)
-            if invariant:
-                # allow topography to work in same code
-                tmask_chunk = [True]
-                 
-	    # get the interpolated variables
-            dfield, variables = self.ERAinterp2D(ncfile_in, ncf_in, 
-                                                 self.stations, tmask_chunk,
-                                                 variables=None, date=None) 
-            # append time
-            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:], 
-                                                     time_in[beg:end+1])
-                                  
-            #append variables
-            for i, var in enumerate(variables):
-                if variables_skip(var):
-                    continue
-                                                              
-                if pl:
-                    if ESMFnew:
-                        # dfield has dimensions (station, variables, time, pressure levels
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:].transpose((1,2,0))
-                    else:
-                        # dimension: time, level, station (pressure level files)
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[i,:,:,:]        
-                else:   
-                    if ESMFnew:
-                        # dfield has dimensions (station, variables, time)
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:].transpose((1,0))
-                    else:
-                        # dfield has dimensions time, station (2D files)
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[i,:,:]
-      		                                                                 	    
-                                     
-        #close the file
-        ncf_in.close()
-        ncf_out.close()
-                         
-       
-    def levels2elevation(self, ncfile_in, ncfile_out):    
-        """
-        Linear 1D interpolation of pressure level data available for individual
-        stations to station elevation. Where and when stations are below the 
-        lowest pressure level, they are assigned the value of the lowest 
-        pressure level.
-        
-        """
-        # open file 
-        #TODO check the aggdim does not work
-        ncf = nc.MFDataset(ncfile_in, 'r', aggdim='time')
-        height = ncf.variables['height'][:]
-        nt = len(ncf.variables['time'][:])
-        nl = len(ncf.variables['level'][:])
-        
-        # list variables
-        varlist = [str_encode(x) for x in ncf.variables.keys()]
-        varlist.remove('time')
-        varlist.remove('station')
-        varlist.remove('latitude')
-        varlist.remove('longitude')
-        varlist.remove('level')
-        varlist.remove('height')
-        varlist.remove('z')
-
-        # === open and prepare output netCDF file ==============================
-        # dimensions: station, time
-        # variables: latitude(station), longitude(station), elevation(station)
-        #            others: ...(time, station)
-        # stations are integer numbers
-        # create a file (Dataset object, also the root group).
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'ERA5, interpolated (bi)linearly to stations'
-        rootgrp.featureType = "timeSeries"
-
-        # dimensions
-        station = rootgrp.createDimension('station', len(height))
-        time    = rootgrp.createDimension('time', nt)
-
-        # base variables
-        time           = rootgrp.createVariable('time',     'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1900-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station','i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude','f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east'  
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-       
-        # assign base variables
-        time[:]      = ncf.variables['time'][:]
-        station[:]   = ncf.variables['station'][:]
-        latitude[:]  = ncf.variables['latitude'][:]
-        longitude[:] = ncf.variables['longitude'][:]
-        height[:]    = ncf.variables['height'][:]
-        
-        # create and assign variables from input file
-        for var in varlist:
-            tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
-            tmp.long_name = ncf.variables[var].long_name.encode('UTF8')
-            tmp.units     = ncf.variables[var].units.encode('UTF8')  
-
-        # add air pressure as new variable
-        var = 'air_pressure'
-        varlist.append(var)
-        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
-        tmp.long_name = var.encode('UTF8')
-        tmp.units     = 'hPa'.encode('UTF8') 
-        # end file prepation ===================================================
-                                                                                                
-        # loop over stations
-        for n, h in enumerate(height): 
-            # convert geopotential [mbar] to height [m], shape: (time, level)
-            ele = ncf.variables['z'][:,:,n] / 9.80665
-            # TODO: check if height of stations in data range
-            
-            # difference in elevation, level directly above will be >= 0
-            dele = ele - h
-            # vector of level indices that fall directly above station. 
-            # Apply after ravel() of data.
-            va = np.argmin(dele + (dele < 0) * 100000, axis=1) 
-            # mask for situations where station is below lowest level
-            mask = va < (nl-1)
-            va += np.arange(ele.shape[0]) * ele.shape[1]
-            
-            # Vector level indices that fall directly below station.
-            # Apply after ravel() of data.
-            vb = va + mask # +1 when OK, +0 when below lowest level
-            
-            # weights
-            wa = np.absolute(dele.ravel()[vb]) 
-            wb = np.absolute(dele.ravel()[va])
-            wt = wa + wb
-            wa /= wt # Apply after ravel() of data.
-            wb /= wt # Apply after ravel() of data.
-            
-            #loop over variables and apply interpolation weights
-            for v, var in enumerate(varlist):
-                if var == 'air_pressure':
-                    # pressure [Pa] variable from levels, shape: (time, level)
-                    data = np.repeat([ncf.variables['level'][:]],
-                                      len(time),axis=0).ravel() 
-                else:    
-                    #read data from netCDF
-                    data = ncf.variables[var][:,:,n].ravel()
-                    
-                ipol = data[va]*wa + data[vb]*wb   # interpolated value                    
-                rootgrp.variables[var][:,n] = ipol # assign to file   
-    
-        rootgrp.close()
-        ncf.close()
-        # closed file ==========================================================    
-
-
-    def TranslateCF2short(self, dpar):
-        """
-        Map CF Standard Names into short codes used in ERA5 netCDF files.
-        """
-        varlist = [] 
-        for var in self.variables:
-            varlist.append(dpar.get(var))
-        # drop none
-        varlist = [item for item in varlist if item is not None]      
-        # flatten
-        varlist = [item for sublist in varlist for item in sublist]         
-        return(varlist) 
-    
-    def process(self):
-        """
-        Interpolate point time series from downloaded data. Provides access to 
-        the more generically ERA-like interpolation functions.
-        """                       
-
-        # 2D Interpolation for Invariant Data      
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable keys.            
-        dummy_date  = {'beg' : datetime(1979, 1, 1, 12, 0),
-                       'end' : datetime(1979, 1, 1, 12, 0)}     
-        self.ERA2station(path.join(self.dir_inp,'era5_to.nc'), 
-                         path.join(self.dir_out,'era5_to_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   ['z', 'lsm'], date = dummy_date)  
-                                  
-        # === 2D Interpolation for Surface Analysis Data ===    
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable keys. 
-        dpar = {'air_temperature'   : ['t2m'],  # [K] 2m values
-                'relative_humidity' : ['d2m'],  # [K] 2m values
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky' : 
-                    ['tco3', 'tcwv'],   # [kg m-2] Total column ozone 
-                                        # [kg m-2] Total column W vapor                                                             
-                'wind_speed' : ['u10', 'v10']}   # [m s-1] 10m values   
-        varlist = self.TranslateCF2short(dpar)                      
-        self.ERA2station(path.join(self.dir_inp,'era5_sa_*.nc'), 
-                         path.join(self.dir_out,'era5_sa_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   varlist, date = self.date)          
-        
-        # 2D Interpolation for Surface Forecast Data    'tp', 'strd', 'ssrd' 
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable keys.       
-        # [m] total precipitation
-        # [J m-2] short-wave downward
-        # [J m-2] long-wave downward
-        dpar = {'precipitation_amount'              : ['tp'],   
-                'downwelling_shortwave_flux_in_air' : ['ssrd'], 
-                'downwelling_longwave_flux_in_air'  : ['strd']} 
-        varlist = self.TranslateCF2short(dpar)                           
-        self.ERA2station(path.join(self.dir_inp,'era5_sf_*.nc'), 
-                         path.join(self.dir_out,'era5_sf_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   varlist, date = self.date)          
-                         
-        
-        # === 2D Interpolation for Pressure Level Data ===
-        # dictionary to translate CF Standard Names into ERA5
-        # pressure level variable keys. 
-        dpar = {'air_temperature'   : ['t'],           # [K]
-                'relative_humidity' : ['r'],           # [%]
-                'wind_speed'        : ['u', 'v']}      # [m s-1]
-        varlist = self.TranslateCF2short(dpar).append('z')
-        self.ERA2station(path.join(self.dir_inp,'era5_pl_*.nc'), 
-                         path.join(self.dir_out,'era5_pl_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   varlist, date = self.date)  
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
-        # 1D Interpolation for Pressure Level Data
-        self.levels2elevation(path.join(self.dir_out,'era5_pl_' + 
-                                        self.list_name + '.nc'), 
-                              path.join(self.dir_out,'era5_pl_' + 
-                                        self.list_name + '_surface.nc'))                 
-
-                                                        
-class ERA5scale(object):
-    """
-    Class for ERA5 data that has methods for scaling station data to
-    better resemble near-surface fluxes.
-    
-    Processing kernels have names in UPPER CASE.
-       
-    Args:
-        sfile: Full path to a Globsim Scaling Parameter file. 
-              
-    Example:          
-        ERAd = ERA5scale(sfile) 
-        ERAd.process()
-    """
-        
-    def __init__(self, sfile):
-        # read parameter file
-        self.sfile = sfile
-        par = ParameterIO(self.sfile)
-        self.intpdir = path.join(par.project_directory, 'interpolated')
-        self.scdir = self.makeOutDir(par)
-        self.list_name = par.station_list.split(path.extsep)[0]
-        
-        # read kernels
-        self.kernels = par.kernels
-        if not isinstance(self.kernels, list):
-            self.kernels = [self.kernels]
-
-        # get the station file and list_name from it
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        self.list_name = par.station_list.split(path.extsep)[0]
-        
-        # input file handles
-        self.nc_pl = nc.Dataset(path.join(self.intpdir, 'era5_pl_' + 
-                                self.list_name + '_surface.nc'), 'r')
-        self.nc_sa = nc.Dataset(path.join(self.intpdir, 'era5_sa_' + 
-                                self.list_name + '.nc'), 'r')
-        self.nc_sf = nc.Dataset(path.join(self.intpdir, 'era5_sf_' + 
-                                self.list_name + '.nc'), 'r')
-        self.nc_to = nc.Dataset(path.join(self.intpdir, 'era5_to_' + 
-                                self.list_name + '.nc'), 'r')
-        self.nstation = len(self.nc_to.variables['station'][:])                     
-                              
-        # output file 
-        self.output_file = self.getOutNCF(par, 'era5')
-        
-        # time vector for output data
-        # get time and convert to datetime object
-        nctime = self.nc_pl.variables['time'][:]
-        self.t_unit = self.nc_pl.variables['time'].units #"hours since 1900-01-01 00:00:0.0"
-        self.t_cal  = self.nc_pl.variables['time'].calendar
-        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
-        
-        # interpolation scale factor
-        self.time_step = par.time_step * 3600    # [s] scaled file
-        self.interval_in = (time[1]-time[0]).seconds #interval in seconds
-        self.interpN = floor(self.interval_in/self.time_step)
-        
-        #number of time steps for output, include last value
-        self.nt = int(floor((max(time) - min(time)).total_seconds()
-                            / 3600 / par.time_step)) + 1 
-
-        # vector of output time steps as datetime object
-        # 'seconds since 1900-01-01 00:00:0.0'
-        mt = min(time)
-        self.times_out = [mt + timedelta(seconds = (x*self.time_step)) 
-                          for x in range(0, self.nt)]                                                                   
-                                      
-        # vector of output time steps as written in ncdf file [s]
-        self.scaled_t_units = 'seconds since 1900-01-01 00:00:0.0'
-        self.times_out_nc = nc.date2num(self.times_out, 
-                                        units = self.scaled_t_units, 
-                                        calendar = self.t_cal) 
-                                        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)
-        
-    def process(self):
-        """
-        Run all relevant processes and save data. Each kernel processes one 
-        variable and adds it to the netCDF file.
-        """    
-        self.rg = ScaledFileOpen(self.output_file, 
-                                 self.nc_pl, self.times_out_nc,
-                                 t_unit = self.scaled_t_units, 
-                                 station_names = self.stations['station_name'])
-        
-        
-        # iterate thorugh kernels and start process
-        for kernel_name in self.kernels:
-            if hasattr(self, kernel_name):
-                print(kernel_name)
-                getattr(self, kernel_name)()
-            
-        # close netCDF files   
-        self.rg.close()
-        self.nc_pl.close()
-        self.nc_sf.close()
-        self.nc_sa.close()
-        self.nc_to.close()
-        
-    def getOutNCF(self, par, src, scaleDir = 'scale'):
-        '''make out file name'''
-        
-        timestep = str(par.time_step) + 'h'
-        src = '_'.join(['scaled', src, timestep])
-        src = src + '.nc'
-        fname = path.join(self.scdir, src)
-        
-        return fname
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirSC = path.join(par.project_directory, 'scaled')
-        
-        if not (path.isdir(dirSC)):
-            makedirs(dirSC)
-            
-        return dirSC
-        
-    def PRESS_Pa_pl(self):
-        """
-        Surface air pressure from pressure levels.
-        """        
-        # add variable to ncdf file
-        vn = 'PRESS_ERA5_Pa_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_pressure ERA-5 pressure levels only'
-        var.units     = 'Pa' 
-        var.standard_name = 'surface_air_pressure'
-        
-        # interpolate station by station
-        time_in = self.nc_pl.variables['time'][:].astype(np.int64)  
-        values  = self.nc_pl.variables['air_pressure'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            #scale from hPa to Pa 
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                        time_in*3600, values[:, n]) * 100          
-
-    def AIRT_C_pl(self):
-        """
-        Air temperature derived from pressure levels, exclusively.
-        """        
-        # add variable to ncdf file
-        vn = 'AIRT_ERA5_C_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_temperature ERA-5 pressure levels only'
-        var.units     = 'degrees_C' 
-        var.standard_name = 'air_temperature'
-        
-        # interpolate station by station
-        time_in = self.nc_pl.variables['time'][:].astype(np.int64)  
-        values  = self.nc_pl.variables['t'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                        time_in*3600, values[:, n]-273.15)          
-
-    def AIRT_C_sur(self):
-        """
-        Air temperature derived from surface data, exclusively.
-        """
-        # add variable to ncdf file
-        vn = 'AIRT_ERA5_C_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '2_metre_temperature ERA-5 surface only'
-        var.units     = self.nc_sa.variables['t2m'].units.encode('UTF8')  
-        
-        # interpolate station by station
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)      
-        values  = self.nc_sa.variables['t2m'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                                    time_in*3600, 
-                                                    values[:, n]-273.15)           
-        
-    def AIRT_redcapp(self):
-        """
-        Air temperature derived from surface data and pressure level data as
-        shown by the method REDCAPP.
-        """       
-        print("AIRT_ERA5_redcapp")            
-
-    def PREC_mm_sur(self):
-        """
-        Precipitation sum in mm for the time step given.
-        """   
-        # add variable to ncdf file
-        vn  = 'PREC_ERA5_mm_sur' # variable name
-        var = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Total precipitation ERA-5 surface only'
-        var.units     = 'kg m-2 s-1'
-        var.standard_name = 'precipitation_amount'
-        
-        # interpolation scale factor
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
-        
-        # total prec [mm] in 1 second  
-        values  = self.nc_sf.variables['tp'][:]*1000/self.interval_in
-        
-        # interpolate station by station
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            f = interp1d(time_in*3600, values[:, n], kind='linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step          
-            
-    def RH_per_sur(self):
-        """
-        Relative humdity derived from surface data, exclusively. Clipped to
-        range [0.1,99.9]. Kernel AIRT_ERA5_C_sur must be run before.
-        """         
-        # temporary variable,  interpolate station by station
-        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['d2m'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            dewp[:, n] = series_interpolate(self.times_out_nc, 
-                                            time_in*3600, values[:, n]-273.15) 
-                                                    
-        # add variable to ncdf file
-        vn = 'RH_ERA5_per_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Relative humidity ERA-5 surface only'
-        var.units     = 'percent'
-        var.standard_name = 'relative_humidity'
-        
-        # simple: https://doi.org/10.1175/BAMS-86-2-225
-        RH = 100 - 5 * (self.rg.variables['AIRT_ERA5_C_sur'][:, :]-dewp[:, :])
-        self.rg.variables[vn][:, :] = RH.clip(min=0.1, max=99.9)    
-        
-        
-    def WIND_sur(self):
-        """
-        Wind speed and direction temperature derived from surface data, 
-        exclusively.
-        """    
-        # temporary variable, interpolate station by station
-        U = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['u10'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            U[:, n] = series_interpolate(self.times_out_nc, 
-                                         time_in*3600, values[:, n]) 
-        
-        # temporary variable, interpolate station by station
-        V = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['v10'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            V[:, n] = series_interpolate(self.times_out_nc, 
-                                         time_in*3600, values[:, n]) 
-
-        # wind speed, add variable to ncdf file, convert
-        vn = 'WSPD_ERA5_ms_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 wind speed ERA-5 surface only'
-        var.units     = 'm s-1'  
-        var.standard_name = 'wind_speed'
-                
-        # wind direction, add variable to ncdf file, convert, relative to North 
-        vn = 'WDIR_ERA5_deg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 wind direction ERA-5 surface only'
-        var.units     = 'degree'
-        var.standard_name = 'wind_from_direction'
-
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            WS = np.sqrt(np.power(V,2) + np.power(U,2))
-            WD = [atan2(V[i, n], U[i, n])*(180/pi) + 
-                  180 for i in np.arange(V.shape[0])]
-            self.rg.variables['WSPD_ERA5_ms_sur'][:, n] = WS
-            self.rg.variables['WDIR_ERA5_deg_sur'][:,n] = WD                                          
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
-    def SW_Wm2_sur(self):
-        """
-        Short-wave downwelling radiation derived from surface data, exclusively.  
-        This kernel only interpolates in time.
-        """   
-        
-        # add variable to ncdf file
-        vn = 'SW_ERA5_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface solar radiation downwards ERA-5 surface only'
-        var.units     = 'W m-2'
-        var.standard_name = 'surface_downwelling_shortwave_flux'
-
-        # interpolate station by station
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sf.variables['ssrd'][:]/3600/self.interval_in#[w/m2/s]
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            f = interp1d(time_in*3600, values[:, n], kind='linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step
-
-
-    def LW_Wm2_sur(self):
-        """
-        Long-wave downwelling radiation derived from surface data, exclusively.  
-        This kernel only interpolates in time.
-        """   
-        
-        # add variable to ncdf file
-        vn = 'LW_ERA5_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface thermal radiation downwards ERA-5 surface only'
-        var.standard_name = 'surface_downwelling_longwave_flux'
-        var.units     = 'W m-2'
-        
-        # interpolate station by station
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sf.variables['strd'][:]/3600/self.interval_in #[w m-2 s-1]
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            f = interp1d(time_in*3600, values[:, n], kind='linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step   
-                                          
-                                                  
-    def SH_kgkg_sur(self):
-        '''
-        Specific humidity [kg/kg]
-        https://crudata.uea.ac.uk/cru/pubs/thesis/2007-willett/2INTRO.pdf
-        '''
-        
-        # temporary variable,  interpolate station by station
-        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['d2m'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            dewp[:, n] = series_interpolate(self.times_out_nc, 
-                                            time_in*3600, values[:, n]-273.15) 
-
-        # compute
-        SH = spec_hum_kgkg(dewp[:, :], 
-                           self.rg.variables['PRESS_ERA5_Pa_pl'][:, :])  
-        
-        # add variable to ncdf file
-        vn = 'SH_ERA5_kgkg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Specific humidity ERA-5 surface only'
-        var.units     = '1'  
-        var.standard_name = 'specific_humidity'
-        self.rg.variables[vn][:, :] = SH                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
-
diff --git a/era5_example.py b/era5_example.py
deleted file mode 100755
index 4591f68..0000000
--- a/era5_example.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# ==== DOWNLOAD ===============================================================
-
-from era5 import ERAdownload, ERAscale, ERAinterpolate
-
-pfile = "/home/nbrown/globsim/era5test/par/examples.globsim_download"
-ifile = "/home/nbrown/globsim/era5test/par/examples.globsim_interpolate"
-sfile =  "/home/nbrown/globsim/era5test/par/examples.globsim_scale"
-#ERAd = ERAdownload(pfile)
-#ERAd.retrieve()
-#ERAd.inventory()
-
-# ERAint = ERAinterpolate(ifile)
-# ERAint.process()
-
-ERAsc = ERAscale(sfile)
-ERAsc.process()
\ No newline at end of file
diff --git a/examples/Example1/par/example_config.toml b/examples/Example1/par/example_config.toml
new file mode 100644
index 0000000..089c058
--- /dev/null
+++ b/examples/Example1/par/example_config.toml
@@ -0,0 +1,64 @@
+# This is a TOML document
+
+title = "Globsim Control File"
+
+[download]
+# logistics
+project_directory = "/opt/globsim/examples/Example1"
+credentials_directory = "/root"
+
+# chunk size for splitting files and download [days]
+chunk_size = 2
+
+# area bounding box [decimal degrees]
+bbN = 66
+bbS = 62
+bbW = -112
+bbE = -108
+
+# ground elevation range within area [m]
+ele_min = 0
+ele_max = 2500
+
+# time slice [YYYY/MM/DD]
+beg = "2017/07/01"
+end = "2017/07/05"
+
+# variables to download [CF Standard Name Table]
+variables = ["air_temperature", "relative_humidity", "wind_speed", "wind_from_direction", "precipitation_amount", "downwelling_shortwave_flux_in_air", "downwelling_longwave_flux_in_air", "downwelling_shortwave_flux_in_air_assuming_clear_sky", "downwelling_longwave_flux_in_air_assuming_clear_sky"]
+
+[interpolate]
+# Path to the parent directory of /par - It should match the download and scale files
+project_directory = "/opt/globsim/examples/Example1"
+
+# Filename (without path) of csv containing site information (must match scaling control file)
+station_list = "siteslist.csv"
+
+# How many time steps to interpolate at once? This helps memory management.
+# Keep small for large area files and small memory computer, make larger to get 
+# speed on big machines and when working with small area files.
+# for a small area, we suggest values up to 2000, but consider the memory limit of your computer
+chunk_size = 2000
+
+# time slice [YYYY/MM/DD] assuming 00:00 hours
+beg = "2017/07/01"
+end = "2017/07/05"
+
+# variables to interpolate [CF Standard Name Table]
+variables = ["air_temperature", "relative_humidity", "wind_speed", "wind_from_direction", "precipitation_amount", "downwelling_shortwave_flux_in_air", "downwelling_longwave_flux_in_air", "downwelling_shortwave_flux_in_air_assuming_clear_sky", "downwelling_longwave_flux_in_air_assuming_clear_sky"]
+
+[scale]
+# Path to the parent directory of /par - It should match the download and interpolate files
+project_directory = "/opt/globsim/examples/Example1"
+
+# Filename (without path) of csv containing site information (must match interpolation control file)
+station_list = "siteslist.csv"
+
+# processing kernels to be used.  Unavailable kernels will be ignored
+kernels = ["PRESS_Pa_pl", "AIRT_C_pl", "AIRT_C_sur", "PREC_mm_sur", "RH_per_sur", "WIND_sur", "SW_Wm2_sur", "LW_Wm2_sur", "SH_kgkg_sur"]
+
+# desired time step for output data [hours]
+time_step = 1
+
+# Should the output file be overwritten if it exists?
+overwrite = true
\ No newline at end of file
diff --git a/generic.py b/generic.py
deleted file mode 100755
index 7225ebd..0000000
--- a/generic.py
+++ /dev/null
@@ -1,758 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Generic classes, methods, functions used for more than one reanalysis.
-#
-#
-# (C) Copyright Stephan Gruber (2017)
-#         
-#     This program is free software: you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation, either version 3 of the License, or
-#     (at your option) any later version.
-#
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-#
-#     You should have received a copy of the GNU General Public License
-#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-#===============================================================================
-from __future__  import print_function
-
-from datetime    import datetime
-from csv         import QUOTE_NONE
-from os          import mkdir, path
-from math        import floor
-
-import pandas  as pd
-import netCDF4 as nc
-import numpy as np
-
-# handle python 3 string types
-try:
-    basestring
-except:
-    basestring = str
-
-class ParameterIO(object):
-    """
-    Reads generic parameter files and makes values available as dictionary.
-    
-    # read file
-    par = ParameterIO('examples/par/examples.globsim_download')
-    
-    # access first reanalysis variable
-    par.variables[0]
-    
-    # access data_directory
-    par.data_directory
-    
-    # access north end of bounding box
-    par.bbN
-    """
-
-    def __init__(self, pfile):
-        """
-        Instantiate a new object and set conventions.
-        """
-        self.fmt_date = "%Y/%m/%d"
-        self.pfile    = pfile
-        self.comment  = "#"
-        self.assign   = "="
-        self.file_read()
-        
-    def __getitem__(self, item):
-        attr = getattr(self, item)
-        return(attr)
-
-    def file_read(self):
-        """
-        Read parameter file into a list of strings (each line is one entry) and
-        parse content into a dictionary.
-        """
-        # read file
-        with open(self.pfile, "r") as myfile:
-            inpts_str = myfile.readlines()
-
-        # parse content
-        for line in inpts_str:
-            d = self.line2dict(line)
-            if d is not None:
-                self.__dict__[list(d.keys())[0]] = list(d.values())[0]
-
-    def __is_only_comment(self, lin):
-        # checks whether line contains nothing but comment
-        for c in lin:
-            if c != " ":
-                if c == self.comment:
-                    return True
-                else:
-                    return False
-
-    def __string2datetime(self, valu):
-        # checks if value is a date string. If true, a datetime object is
-        # returned. If false, value is returned unchanged.
-        if not isinstance(valu, basestring):
-            return valu
-
-        # see if time conversion is possible
-        try:
-            valu = datetime.strptime(valu, self.fmt_date)
-        except ValueError:
-            pass
-        return valu
-        
-    def __string2logical(self, valu):
-        # checks if value is a true / false. If true, a logical object is
-        # returned. If false, value is returned unchanged.
-        if not isinstance(valu, basestring):
-            return valu
-
-        # see if time conversion is possible
-        if valu.lower() == "false":
-            valu = False
-        if valu.lower() == "true":
-            valu = True
-        return valu
-
-    def __string2datetime_list(self, dates):
-        # convert list of date strings to datetime
-        return [self.__string2datetime(date) for date in dates]
-
-    def line2dict(self, lin):
-        """
-        Converts one line of a parameter file into a dictionary. Comments
-        are recognised and ignored, float vectors are preserved, datetime
-        converted from string.
-        """
-        # Check if this is valid
-        if self.__is_only_comment(lin):
-            return None
-
-        # Remove possible trailing comment form line
-        lin = lin.split(self.comment)[0]
-
-        # Discard lines without value assignment
-        if len(lin.split(self.assign)) != 2:
-            return None
-
-        # Extract name and value, strip of leading/trailling blanks
-        name = lin.split(self.assign)[0].strip()
-        valu = lin.split(self.assign)[1].strip()
-
-        # Make a vector is commas are found
-        if valu.find(",") > 0:
-            # Convert to float or list of float if numeric
-            try:
-                valu = list(map(float, valu.split(",")))
-            except ValueError:
-                valu = list(valu.split(","))
-                valu = [v.strip() for v in valu]
-        else:
-            try:
-                valu = float(valu)
-            except ValueError:
-                pass
-                    
-        # Convert to datetime if it is datetime
-        valu = self.__string2datetime(valu)
-        
-        # Convert to logical if logical
-        valu = self.__string2logical(valu)
-        
-        # Make dictionary and return
-        return {name: valu}
-    
-
-def variables_skip(variable_name):
-        '''
-        Which variable names to use? Drop the ones that are dimensions.  
-        '''
-        skip = 0
-        dims = ('time', 'level', 'latitude', 'longitude', 'station', 'height')
-        if variable_name in dims:
-            skip = 1      
-        return skip 
-
-def StationListRead(sfile):  
-    '''
-    Reads ASCII station list and returns a pandas dataframe.
-    
-    # read station list
-    stations = StationListRead('examples/par/examples_list1.globsim_interpolate')
-    print(stations['station_number'])
-    '''
-    # read file
-    raw = pd.read_csv(sfile)    
-    raw = raw.rename(columns=lambda x: x.strip())
-    return(raw)
-
-
-def ScaledFileOpen(ncfile_out, nc_interpol, times_out, t_unit, station_names=None):
-    '''
-    Open netCDF file for scaled results (same for all reanalyses) or create it 
-    if it does not exist. Returns the file object so that kernel functions can 
-    successively write variables to it.
-    
-    '''
-    
-    try:
-        # read file if it exists
-        rootgrp = nc.Dataset(ncfile_out, 'a')
-        
-        #TODO: make sure the times and stations match if file exists
-    
-    except: 
-        # make netCDF outfile, variables are written in kernels
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'Reanalysis data interpolated and scaled to stations'
-        rootgrp.featureType = "timeSeries"
-        
-        name = ncfile_out[-9:-3]
-
-        # dimensions
-        station = rootgrp.createDimension('station', 
-                     len(nc_interpol.variables['station'][:]))
-        time    = rootgrp.createDimension('time', len(times_out))
-
-        # base variables
-        time           = rootgrp.createVariable('time', 'i8',('time'))
-        time.long_name = 'time'
-        time.axis      = 'T'
-        
-        time.units = t_unit
-        time.calendar  = 'gregorian'
-        
-        station             = rootgrp.createVariable('station', 'i4',('station'))
-        station.long_name   = 'station index for time series data'
-        station.units       = ''
-        station.standard_name = 'platform_id'
-        
-        latitude            = rootgrp.createVariable('latitude', 'f4',('station'))
-        latitude.standard_name  = 'latitude'
-        latitude.units      = 'degrees_N'
-        latitude.long_name  = 'WGS84 latitude'
-        latitude.axis       = 'Y'
-        
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.standard_name = 'longitude'
-        longitude.long_name = 'WGS84 longitude'
-        longitude.units     = 'degrees_E' 
-        longitude.axis      = 'X'
-        
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.standard_name = 'height_above_reference_ellipsoid'
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm' 
-        height.axis      = 'Z'
-        height.positive  = 'up'
-        
-        crs           = rootgrp.createVariable('crs','i4')
-        crs.long_name = 'coordinate system'
-        crs.grid_mapping_name = 'latitude_longitude'
-        crs.longitude_of_prime_meridian = 0.0
-        crs.semi_major_axis = 6378137
-        crs.inverse_flattening = 298.2572236
-        crs.wkt = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]'
-        
-        # assign base variables
-        time[:]      = times_out
-        station[:]   = nc_interpol.variables['station'][:]
-        latitude[:]  = nc_interpol.variables['latitude'][:]
-        longitude[:] = nc_interpol.variables['longitude'][:]
-        height[:]    = nc_interpol.variables['height'][:]
-        
-        # add station names to netcdf
-        if station_names is not None:
-            # first convert to character array
-            names_out = nc.stringtochar(np.array(station_names, 'S32'))
-            
-            # create space in the netcdf
-            nchar        = rootgrp.createDimension('name_strlen', 32) 
-            st           = rootgrp.createVariable('station_name', "S1", ('station', 'name_strlen'))
-            st.standard_name = 'platform_name'
-            st.units     = ''
-            
-            # add data
-            st[:] = names_out
-            
-    return rootgrp
-    
-
-def convert_cummulative(data):
-    """
-    Convert values that are serially cummulative, such as precipitation or 
-    radiation, into a cummulative series from start to finish that can be 
-    interpolated on for sacling. 
-    data: 1-dimensional time series 
-    """                       
-    # get increment per time step
-    diff = np.diff(data)
-    diff = np.concatenate(([data[0]], diff))
-    
-    # where new forecast starts, the increment will be smaller than 0
-    # and the actual value is used
-    mask = diff < 0
-    diff[mask] = data[mask]
-    
-    #get full cummulative sum
-    return np.cumsum(diff, dtype=np.float64)
-
-def cummulative2total(data, time):
-    """
-    Convert values that are serially cummulative, such as precipitation or 
-    radiation, into a cummulative series from start to finish that can be 
-    interpolated on for sacling. 
-    data: 1-dimensional time series 
-    """                       
-    # get increment per time step
-    diff = np.diff(data)
-    diff = np.concatenate(([data[0]], diff))
-    
-    # where new forecast starts, the increment will be smaller than 0
-    # and the actual value is used
-    
-    mask = [timei.hour in [3,15] for timei in time]
-    diff[mask] = data[mask]
-    
-    mask = diff < 0
-    diff[mask] = 0
-    
-    return diff
-    
-
-def series_interpolate(time_out, time_in, value_in, cum=False):
-    '''
-    Interpolate single time series. Convenience function for usage in scaling 
-    kernels.
-    time_out: Array of times [s] for which output is desired. Integer. 
-    time_in:  Array of times [s] for which value_in is given. Integer. 
-    value_in: Value time series. Must have same length as time_in.
-    cum:      Is valiable serially cummulative like LWin? Default: False.
-    '''
-    time_step_sec = time_out[1]-time_out[0]
-    
-    # convert to continuous cummulative, if values are serially cummulative
-    if cum:
-        value_in = convert_cummulative(value_in)
-
-    # interpolate            
-    vi = np.interp(time_out, time_in, value_in)
- 
-    # convert from cummulative to normal time series if needed
-    if cum:
-        vi = np.diff(vi) / time_step_sec
-        vi = np.float32(np.concatenate(([vi[0]], vi)))
-            
-    return vi        
-
-def globsimScaled2Pandas(ncdf_in, station_nr):
-    '''
-    Read a scaled (or interpolated) globsim netCDF file and return all values
-    for one station as a Pandas data frame.
-    
-    ncdf_in: full path to a globsim netCDF (by station)
-    
-    station_nr: station_number, as given in the stations .csv file to identify 
-                the station.
-    
-    '''
-    # open file                                     
-    ncf = nc.Dataset(ncdf_in, 'r')
-    
-    # station mask
-    sm = ncf.variables['station'][:] == int(station_nr)                                                               
-    # list variables
-    varlist = [x.encode('UTF8') for x in ncf.variables.keys()]                                                                                                                                                                                             
-    
-    # get and convert time
-    time = ncf.variables['time'][:]
-    t_unit = ncf.variables['time'].units 
-    t_cal = ncf.variables['time'].calendar   
-    time = nc.num2date(time, units = t_unit, calendar = t_cal)
-                
-    # make data frame with time   
-    df = pd.DataFrame(data=time,columns=['time'])    
-    # add variables
-    for var in varlist:
-        if variables_skip(var):
-            continue
-        data = ncf.variables[var][:,sm]                                                                                                                                                                                                                                                                                                                                                                                               
-        df = pd.concat([df,pd.DataFrame(data=data,columns=[var])],axis=1) 
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
-    return df          
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
-              
-def globsim2GEOtop(ncdf_globsim, txt_geotop):
-    """
-    Convert globsim scaled netCDF to GEOtop meteo file.
-    """
-        
-    outfile = '/Users/stgruber/Supervision/MSc/Mary_Pascale_Laurentian/reanalysis/station/Meteo_0001.txt'
-    
-    #read time object        
-    time = self.rg.variables['time']        
-    date = self.rg.variables['time'][:]
-    
-    #read all other values
-    columns = ['Date','AIRT_ERA_C_pl','AIRT_ERA_C_sur','PREC_ERA_mm_sur','RH_ERA_per_sur','SW_ERA_Wm2_sur','LW_ERA_Wm2_sur','WSPD_ERA_ms_sur','WDIR_ERA_deg_sur']
-    metdata = np.zeros((len(date),len(columns)))
-    metdata[:,0] = date
-    for n, vn in enumerate(columns[1:]):
-        metdata[:,n+1] = self.rg.variables[vn][:, 0]
-    
-    #make data frame
-    data = pd.DataFrame(metdata, columns=columns)
-    data[['Date']] = nc.num2date(date, time.units, calendar=time.calendar)
-    
-    # round
-    decimals = pd.Series([2,1,1,1,1,1,1,1], index=columns[1:])
-    data.round(decimals)
-
-    #export to file
-    fmt_date = "%d/%m/%Y %H:%M"
-    data.to_csv(outfile, date_format=fmt_date, index=False, float_format='%.2f')
-        
-                   
-def globsim2CLASS(ncdf_globsim, met_class, station_nr):
-    """
-    Convert globsim scaled netCDF to CLASS-CTEM .met file.
-    
-    ncdf_globsim: full path to a globsim scaled netCDF (by station)
-    
-    met_class: full path to the CLASS-CTEM met file to write.
-    
-    station_nr: station_number, as given in the stations .csv file to identify 
-                the station.
-    
-    The columns in CLASS-CTEM MET files are:            
-    1)  Hour
-    2)  Minute
-    3)  Day of year
-    4)  Year YYYY
-    5)  Shortwave Radiation (W/m2)
-    6)  Longwave Radiation (W/m2)
-    7)  Precip (mm/s)
-    8)  Temp.(°C)
-    9)  Specific Humidity (Kg/Kg)
-    10) Wind Speed (m/s)
-    11) Pressure (Pa)
-    
-    """
-    
-    # columns to export
-    columns = ['time','SW_ERA_Wm2_sur', 'LW_ERA_Wm2_sur', 'PREC_ERA_mmsec_sur',
-               'AIRT_ERA_C_sur','SH_ERA_kgkg_sur', 'WSPD_ERA_ms_sur',
-               'AIRT_PRESS_Pa_pl']
-    
-    # output ASCII formatting
-    formatters={"time":             "  {:%H %M  %j  %Y}".format,
-                "SW_ERA_Wm2_sur":   "{:8.2f}".format,
-                "LW_ERA_Wm2_sur":   "{:8.2f}".format,
-                "PREC_ERA_mmsec_sur":  "{:13.4E}".format,
-                "AIRT_ERA_C_sur":   "{:8.2f}".format,
-                "SH_ERA_kgkg_sur":  "{:11.3E}".format,
-                "WSPD_ERA_ms_sur":  "{:7.2f}".format,
-                "AIRT_PRESS_Pa_pl": "{:11.2f}".format}
-    
-    # get data
-    df = globsimScaled2Pandas(ncdf_globsim, station_nr)                   
-    
-    # convery precipitation
-    df["PREC_ERA_mmsec_sur"] = df["PREC_ERA_mm_sur"]/1800.
-    
-    # write FORTRAN formatted ASCII file
-    with open(met_class, 'w') as f:
-        f.write(' ')
-        f.write(df.to_string(columns = columns,
-                formatters=formatters, 
-                header=False, index=False))
-    f.close()    
-     
-     
-def satvapp_kPa_fT(T):
-    '''
-    Saturation water vapour pressure [kPa] following the Tetens formula, Eq 4.2
-    in Stull, Practical Meteorology.
-    
-    T: Temperature [C]
-    '''                          
-    e0 = 0.6113  # [kPa]
-    b  = 17.2694 # fitting constant
-    T1 = 273.15  # [K]
-    T2 = 35.86   # [K]
-    T += T1
-    return e0 * np.exp((b*(T-T1))/(T-T2))
-                                                                                                                       
-
-def vapp_kPa_fTd(Td): 
-    '''
-    Water vapour pressure [hPa] derived from dewpoint temperature [C]. Taken 
-    from www.eol.ucar.edu/projects/ceop/dm/documents/refdata_report/eqns.html
-    where it is attributed to (Bolton 1980) 
-    https://doi.org/10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2
-    
-    Td: Dew point temperature [C] 
-    
-    '''
-    #(Bolton 1980) 
-    #https://doi.org/10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2
-    return 6.112 * np.exp((17.67 * Td)/(Td + 243.5)) 
-    
-    #https://www.weather.gov/media/epz/wxcalc/vaporPressure.pdf
-    #return 6.112 * np.power(10,(7.5 * Td)/(237.3 + Td))
-                    
-def spec_hum_kgkg(Td, Pr):
-    '''
-    Specific humidity [Kg/Kg]. Eq 4.7 in Stull, Practical Meteorology.
-    Td: Dewpoint temperature [C]
-    Pr:  Air pressure [Pa]
-    '''
-    E = 0.622 # density of vater vapour / density of dry air
-    e  = vapp_kPa_fTd(Td) / 10
-    P = Pr/1000. # from Pa to kPa 
-    spec_hum = E * e / (P - e * (1 - E)) 
-    return spec_hum   
-   
-def water_vap_pressure(RH,T):
-    '''
-    water vapour pressure [unit:1], Eq C9,C10 in Fiddes and Gruber (2014)
-    RH: relative humidity (%)
-    Tair: air temperature (kelvin)
-    '''   
-    es0 = 6.11 # reference saturation vapour pressure at 0ºC 
-    T0 = 273.15 # Kelvin     
-    lv = 2.5 * 1000000 #latent heat of vaporization of water 
-    Rv = 461.5 # gas constant for water vapour 
-    es = es0 * np.exp((lv)/Rv * (1/T0 - 1/T)) 
-    pv = (RH * es)/100  
-    return pv
-  
-def emissivity_clear_sky(RH,T): 
-    '''
-    clear sky emissivity, Eq(1) in Fiddes and Gruber (2014)
-    pv: water vapour pressure (1)
-    T: air temperature (kelvin)
-    '''  
-    pv = water_vap_pressure(RH, T)
-    x1 = 0.43 
-    x2 = 5.7 
-    e_clear = 0.23 + x1*(pv/T)**(1/x2)  
-    return e_clear
-
-
-def str_encode(value, encoding = "UTF8"):
-    '''
-    handles encoding to allow compatibility between python 2 and 3
-    specifically with regards to netCDF variables.   Python 2 imports 
-    variable names as unicode, whereas python 3 imports them as str.
-    '''
-    if type(value) == str:
-        return(value)
-    else:
-        return(value.encode(encoding))
-        
-def LW_downward(RH,T,N):
-    '''
-    incoming longware radiation [W/m2], Eq(14) in Fiddes and Gruber (2014)
-    e_clear: clear sky emissivity
-    N: cloud cover 
-    T: air temperature 
-    ''' 
-    e_clear = emissivity_clear_sky(RH,T)
-    p1 = 6
-    p2 = 4
-    e_as = 0.979
-    con = 5.67 * 10**(-8) # J/s/m/K4 Stefan-Boltzmann constant 
-    lw = e_clear*(1-N**p1)+(e_as*(N**p2))*con*T**4
-    return lw
-
-def create_globsim_directory(target_dir, name):
-    """
-    creates globsim directory
-    """
-    # create top-level
-    TL = path.join(target_dir, name)
-    mkdir(TL)
-    
-    # create subdirectories
-    mkdir(path.join(TL, "eraint"))
-    mkdir(path.join(TL, "Grib"))
-    mkdir(path.join(TL, "jra55"))
-    mkdir(path.join(TL, "merra2"))
-    mkdir(path.join(TL, "par"))
-    mkdir(path.join(TL, "scale"))
-    mkdir(path.join(TL, "station"))
-    mkdir(path.join(TL, "era5"))
-    
-    return(True)    
-    
-def nc_to_clsmet(ncd, out_dir, src, start=None, end=None):
-    """
-    @args
-    ncd: netcdf dataset
-    src: data source ("ERAI","ERA5" "MERRA2", "JRA55")
-    """
-    #open netcdf if string provided
-    if type(ncd) is str:
-        n = nc.Dataset(ncd)
-    
-    # find number of stations
-    nstn = len(n['station'][:])
-    
-    # get date / time columns
-    time = nc.num2date(n['time'][:], 
-                        units=n['time'].units, 
-                        calendar=n['time'].calendar)
-    time_step = (time[1]-time[0]).seconds # in second
-    
-    
-    HH =   [x.timetuple().tm_hour for x in time]
-    MM =   [x.timetuple().tm_min  for x in time]
-    DDD =  [x.timetuple().tm_yday for x in time]
-    YYYY = [x.timetuple().tm_year for x in time]
-    
-    TIME = np.stack((HH, MM, DDD, YYYY))
-    
-    # get shortwave
-    SW = "SW_{}_Wm2_sur".format(src)
-    SW = n[SW][:]
-    
-    # get longwave
-    LW = "LW_{}_Wm2_sur".format(src)
-    LW = n[LW][:]
-    
-    # get precip
-    PREC = "PREC_{}_mm_sur".format(src)
-    PREC = n[PREC][:] 
-    PREC /= time_step # [mm/0.5h] to [mm/s]
-    
-    # get temp
-    AIRT = "AIRT_{}_C_sur".format(src)
-    AIRT = n[AIRT][:]
-    
-    # get specific humidity
-    SH = "SH_{}_kgkg_sur".format(src)
-    SH = n[SH][:]
-    
-    # get wind speed
-    WSPD = "WSPD_{}_ms_sur".format(src)
-    WSPD = n[WSPD][:]
-    
-    # get pressure
-    PRESS = "PRESS_{}_Pa_pl".format(src)
-    PRESS = n[PRESS][:]
-    
-    # get site names
-    NAMES = nc.chartostring(n['station_name'][:])
-    
-    data = np.stack((SW, LW, PREC, AIRT, SH, WSPD, PRESS))
-    
-    # write output files
-    for i in range(nstn):
-        
-        # massage data into the right shape
-        out_array = data[:,:,i]
-        out_array = np.concatenate((TIME, out_array), 0)
-        out_array = np.transpose(out_array)
-        
-        #get station name 
-        st_name = NAMES[i]
-        
-        filename = "{}_{}_{}.MET".format(i, st_name, src)
-        savepath = path.join(out_dir, filename)
-        
-        # create file
-        np.savetxt(savepath, out_array, 
-                fmt = [" %02u", "%02u", " %03u", " %2u", "%8.2f", "%8.2f", 
-                        "%13.4E", "%8.2f", "%11.3E", "%7.2f", "%11.2f" ])
-                        
-                        
-def nc_to_gtmet(ncd, out_dir, src, start=None, end=None):
-    """
-    @args
-    ncd: netcdf dataset
-    src: data source ("ERAI","ERA5" "MERRA2", "JRA55")
-    """
-    #open netcdf if string provided
-    if type(ncd) is str:
-        n = nc.Dataset(ncd)
-    
-    # find number of stations
-    nstn = len(n['station'][:])
-    
-    # get date / time column
-    time = nc.num2date(n['time'][:], 
-                        units=n['time'].units, 
-                        calendar=n['time'].calendar)
-    
-    time = [x.strftime('%d/%m/%Y %H:%M') for x in time]
-    time = pd.DataFrame(time)
-    
-    # get precip
-    PREC = "PREC_{}_mm_sur".format(src)
-    PREC = n[PREC][:]
-    
-    # get wind velocity
-    WSPD = "WSPD_{}_ms_sur".format(src)
-    WSPD = n[WSPD][:]
-    
-    # get wind direction
-    WDIR = "WDIR_{}_deg_sur".format(src)
-    WDIR = n[WDIR][:]
-    
-    # get windx and windy
-    
-    # get RH 
-    RH = "RH_{}_per_sur".format(src)
-    RH = n[RH][:]
-        
-    # get air temp
-    AIRT = "AIRT_{}_C_sur".format(src)
-    AIRT = n[AIRT][:]
-    
-    # get dew temp (missing?)
-    
-    # get air pressure
-    PRESS = "PRESS_{}_Pa_pl".format(src)
-    PRESS = n[PRESS][:]
-    PRESS *= 1e-5      # convert to bar for geotop
-   
-    # get shortwave solar global (direct / diffuse missing?)
-    SW = "SW_{}_Wm2_sur".format(src)
-    SW = n[SW][:]
-    
-    # get longwave incoming
-    LW = "LW_{}_Wm2_sur".format(src)
-    LW = n[LW][:]
-
-    # get site names
-    NAMES = nc.chartostring(n['station_name'][:])
-
-    # combine data variables into array
-    data = np.stack((PREC, WSPD, WDIR, RH, AIRT, PRESS, SW, LW))
-    
-    # write output files
-    for i in range(nstn):
-        
-        # massage data into the right shape
-        out_df = pd.DataFrame(np.transpose(data[:,:,i]))
-        out_df = pd.concat([time, out_df], axis=1)
-        out_df.columns = ["Date", "IPrec", "WindVelocity", "WindDirection", "RH",
-                            "AirTemp", "AirPress", "SWglobal", "LWin"]
-        
-
-        #get station name 
-        st_name = NAMES[i]
-        
-        # prepare paths
-        filename = "{}-{}_{}_Forcing_0001.txt".format(i, st_name, src)
-        savepath = path.join(out_dir, filename)
-        
-        # create file
-        out_df.to_csv(savepath, index=False, float_format="%10.5f")
\ No newline at end of file
diff --git a/globsim-runner.py b/globsim-runner.py
new file mode 100644
index 0000000..e69de29
diff --git a/JRA.py b/globsim/JRA.py
old mode 100644
new mode 100755
similarity index 54%
rename from JRA.py
rename to globsim/JRA.py
index 6bdaa74..0572afe
--- a/JRA.py
+++ b/globsim/JRA.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*- 
-from __future__      import print_function
+# -*- coding: utf-8 -*-
+from __future__ import print_function
 
 import urllib.request
 import urllib.error
@@ -10,72 +10,66 @@
 import tarfile
 import time
 import sys
-import re
 
-import netCDF4       as nc
-import numpy         as np
+import netCDF4 as nc
+import numpy as np
 
-from datetime        import datetime, timedelta
-from globsim.generic import ParameterIO, StationListRead, ScaledFileOpen, str_encode, series_interpolate, variables_skip, LW_downward
-from os              import path, listdir, remove, makedirs
-from math            import exp, floor, atan2, pi
-from fnmatch         import filter
+from datetime import datetime, timedelta
+from os import path, remove, makedirs
+from math import floor, atan2, pi
 from scipy.interpolate import interp1d
 
-try:
-    import ESMF
-    
-    # Check ESMF version.  7.0.1 behaves differently than 7.1.0r 
-    ESMFv = int(re.sub("[^0-9]", "", ESMF.__version__))
-    ESMFnew = ESMFv > 701  
-except ImportError:
-    print("*** ESMF not imported, interpolation not possible. ***")
-    pass 
+from globsim.common_utils import str_encode, series_interpolate, variables_skip, GenericDownload, GenericScale, GenericInterpolate
+from globsim.nc_elements import netcdf_base, new_interpolated_netcdf, new_scaled_netcdf
+from globsim.meteorology import LW_downward, pressure_from_elevation
 
 
+def get_userinfo():
+    return None, None
+
 
 class RDA(object):
-    
+
     def __init__(self, username, password):
-        '''Return an object for RDA data sets, to submit subset requests 
-        on select gridded data sets, and to check on the processing status of 
+        '''Return an object for RDA data sets, to submit subset requests
+        on select gridded data sets, and to check on the processing status of
         any subset requests. Details could be found:
             https://www2.cisl.ucar.edu/data-portals/research-data-archive/command-line-subset-requests-%E2%80%93-rdams'''
-        
+
         self.base = 'https://rda.ucar.edu/apps/'
         self.loginurl = 'https://rda.ucar.edu/cgi-bin/login'
         self.cookie_file = 'auth.rda_ucar_edu'
         self.username = username
         self.password = password
-        
+
     def makeOpener(self, theurl):
         '''make the opener based on username and password'''
-        
+
         passman = urllib.request.HTTPPasswordMgrWithDefaultRealm()
         passman.add_password(None, theurl, self.username, self.password)
         authhandler = urllib.request.HTTPBasicAuthHandler(passman)
         opener = urllib.request.build_opener(authhandler)
         urllib.request.install_opener(opener)
-        
+
         return opener
-    
+
     def tarExtract(self, tarf, directory):
         '''extract the tar file'''
-        
+
         filename, file_extension = path.splitext(tarf)
-        
+
         if file_extension == '.tar':
             tar = tarfile.open(tarf)
-            tar.extractall(path = directory)
+            tar.extractall(path=directory)
             tar.close()
             remove(tarf)
-    
+
     def urlOpen(self, theurl):
         '''open the url'''
-        
+
         opener = self.makeOpener(theurl)
         request = urllib.request.Request(theurl)
-        
+
         try:
             url = opener.open(request)
         except urllib.error.HTTPError as e:
@@ -87,97 +81,95 @@ def urlOpen(self, theurl):
                 except urllib.error.HTTPError as e:
                     if e.code == 401:
                         print(
-                            'RDA username and password invalid, or you are'\
+                            'RDA username and password invalid, or you are'
                             'not authorized to access this dataset.\n')
-                        print('Please verify your login information at'\
+                        print('Please verify your login information at'
                               'http://rda.ucar.edu\n.')
                         sys.exit()
-        
+
         return url
-    
+
     def add_http_cookie(self, url, authstring):
         '''add_http_cookie(url,authstring): Get and add authentication cookie
-        to http file download handler'''     
-        
+        to http file download handler'''
+
         cj = http.cookiejar.MozillaCookieJar(self.cookie_file)
         openrf = urllib.request.build_opener(
             urllib.request.HTTPCookieProcessor(cj))
         frequest = urllib.request.Request(url, authstring)
         cj.add_cookie_header(frequest)
-        response = openrf.open(frequest)
+        _ = openrf.open(frequest)
         openerf = urllib.request.build_opener(
             urllib.request.HTTPCookieProcessor(cj))
         urllib.request.install_opener(openerf)
-    
+
     def getHelp(self):
         '''get the help information'''
-        
+
         theurl = self.base + 'help'
         url = self.urlOpen(theurl)
-        
+
         print(url.read().decode())
-        
-    
-    def getSummary(self, dsID = None):
+
+    def getSummary(self, dsID=None):
         '''get the summary of given dataset'''
-        
-        print('\nGetting summary information. Please wait as this may take'\
-              'awhile.\n')
-        
+
+        print('\nGetting summary information. Please wait as this may take'
+              'a while.\n')
+
         theurl = theurl = self.base + 'summary/' + dsID
         url = self.urlOpen(theurl)
-        
+
         print(url.read().decode())
-        
+
     def getMeta(self, dsID):
         '''get the metadata of gieven dataset'''
-        
+
         theurl = self.base + 'metadata/' + dsID
         url = self.urlOpen(theurl)
-        
+
         print(url.read().decode())
-        
+
     def getParaSummary(self, dsID):
         '''submit a subset request control file.
-           Subset request control files are built from the parameters dumped 
+           Subset request control files are built from the parameters dumped
            out by the '-get_metadata <dsnnn.n>' option.'''
-        
-        print('\nGetting parameter summary. Please wait as this may take'\
+
+        print('\nGetting parameter summary. Please wait as this may take'
               'awhile.\n')
-        
+
         theurl = self.base + 'paramsummary/' + dsID
         url = self.urlOpen(theurl)
-        
+
         print(url.read().decode())
-        
+
     def getStatus(self):
         '''get the status of requests'''
-        
+
         theurl = self.base + 'request'
         url = self.urlOpen(theurl)
-        
+
         print(url.read().decode())
-        
-        
-    def submit(self, controlparms):      
+
+    def submit(self, controlparms):
         '''submit JRA55 dataset request based on predescribed control
         parameters'''
-        
+
         theurl = self.base + 'request'
-        
+
         jsondata = '{'
         for k in list(controlparms.keys()):
             jsondata += '"' + k + '"' + ":" + '"' + controlparms[k] + '",'
         jsondata = jsondata[:-1]
         jsondata += '}'
         print('\n ====== Submitting Request ====\n')
-        
+
         if len(jsondata) > 1:
             request = urllib.request.Request(
                 theurl, jsondata.encode(), {'Content-type':'application/json'})
         else:
             request = urllib.request.Request(theurl)
-        
+
         opener = self.makeOpener(theurl)
         try:
             url = opener.open(request)
@@ -191,27 +183,27 @@ def submit(self, controlparms):
                 except urllib.error.HTTPError as e:
                     if e.code == 401:
                         print(
-                            'RDA username and password invalid, or you are'\
+                            'RDA username and password invalid, or you are'
                             'not authorized to access this dataset.\n')
-                        print('Please verify your login information at'\
+                        print('Please verify your login information at'
                               'http://rda.ucar.edu\n.')
                         sys.exit()
-        
+
         print(url.read().decode())
-    
+
     def downloadSinglefile(self, remfile, outfile):
         '''download_file(remfile,outfile) : download a file from a remote
          server (remfile) to a local location (outfile)'''
-        
-        frequest = urllib.request.Request(remfile)
+
+        _ = urllib.request.Request(remfile)
         fresponse = urllib.request.urlopen(remfile)
         with open(outfile, 'wb') as handle:
             handle.write(fresponse.read())
-    
+
     def downloadFile(self, filelist, directory):
-        '''download_files(filelist,directory): Download multiple files from the 
+        '''download_files(filelist,directory): Download multiple files from the
         rda server and save them to a local directory'''
-        
+
         backslash = '/'
         filecount = 0
         percentcomplete = 0
@@ -232,15 +224,13 @@ def downloadFile(self, filelist, directory):
             elif(not path.isfile(outpath)):
                 self.downloadSinglefile(key, outpath)
             self.tarExtract(outpath, directory)
-                
-    
+
             filecount = filecount + 1
             percentcomplete = (float(filecount) / float(length))
         self.update_progress(percentcomplete, directory)
-        
-    
+
     def update_progress(self, progress, outdir):
-        
+
         barLength = 20  # Modify this to change the length of the progress bar
         status = ""
         if isinstance(progress, int):
@@ -258,71 +248,70 @@ def update_progress(self, progress, outdir):
         text = "\r ====== Downloading Request ======\n "
         sys.stdout.write(text)
         sys.stdout.flush()
-        
+
     def getDSindex(self):
         '''get the index of submitted dataset index'''
-        
+
         theurl = self.base + 'request/'
         url = self.urlOpen(theurl)
-        
-        authdata = 'email='+self.username+'&password='+self.password+'&action=login'
+
+        authdata = 'email=' + self.username + '&password=' + self.password + '&action=login'
         authdata = authdata.encode()
-        
+
         responses = url.read().decode()
         responses = responses.split('\n')
         responses = [item for item in responses if item.startswith("RequestIndex")]
-        
+
         status = [item.split('- ')[1] for item in responses]
         status = np.where(np.asarray(status) == 'Online')[0]
         dsIndex = [item.split(':  ')[1] for item in responses]
         dsIndex = [item.split(', ')[0] for item in dsIndex]
         dsIndex = np.asarray(dsIndex)[status]
-        
+
         return dsIndex
 
-    
     def download(self, directory, ds):
         '''download all the data completed from the NCAR server'''
-        
+
         if not isinstance(ds, str):
             ds = str(ds)
-            
+
         theurl = self.base + 'request/' + ds + '/filelist'
         url = self.urlOpen(theurl)
-        
-        authdata = 'email='+self.username+'&password='+self.password+'&action=login'
+
+        authdata = 'email=' + self.username + '&password=' + self.password + '&action=login'
         authdata = authdata.encode()
-        
+
         jsonfilelist = url.read().decode()
 
         if not jsonfilelist[0] != "{":
-            
+
             filelist = json.loads(jsonfilelist)
 
             # get cookie required to download data files
             self.add_http_cookie(self.loginurl, authdata)
-        
+
             print("\n\nStarting Download.\n\n")
-        
+
             self.downloadFile(filelist, directory)
-                
+
     def purge(self, dsIndex):
         '''delete dataset from NCAR server based on given dsIndex'''
-        
+
         for ds in dsIndex:
             if not isinstance(ds, str):
                 ds = str(ds)
             theurl = self.base + 'request/' + ds
-            
+
             opener = self.makeOpener(theurl)
             request = urllib.request.Request(theurl)
             request.get_method = lambda: 'DELETE'
-            
+
             try:
                 url = opener.open(request)
             except urllib.error.HTTPError as e:
                 if e.code == 401:
-                    print('RDA username and password invalid.  Please try again\n')
+                    print('RDA username and password invalid. Please try again\n')
                     opener = self.makeOpener(theurl)
                     try:
                         url = opener.open(request)
@@ -332,97 +321,87 @@ def purge(self, dsIndex):
                                 'RDA username and password invalid, or you are not authorized to access this dataset.\n')
                             print('Please verify your login information at http://rda.ucar.edu\n.')
                             sys.exit()
-            
-            print(url.read().decode())   
+
+            print(url.read().decode())
+
 
 class JRApl(object):
-    
+
     def __init__(self, date, area, elevation, variables, rda):
         '''Returns an object for JRA55 data that has methods for querying the
         NCAR server for pressure level variables (prec, swin, lwin). '''
-        
+
         self.date = date
         self.area = area
         self.elevation = elevation
-        #self.directory = directory
-        
+
         dpar = {'air_temperature'   : ['Temperature'],
                 'relative_humidity' : ['Relative humidity'],
-                'wind_speed'        : ['u-component of wind', 
+                'wind_speed'        : ['u-component of wind',
                                        'v-component of wind']}
-        
+
         self.param = self.getParam(dpar, variables)
-        
+
     def getParam(self, dpar, variables):
-        
-        varlist = [] 
+
+        varlist = []
         for var in variables:
             varlist.append(dpar.get(var))
 
         varlist = [item for item in varlist if item is not None]
         varlist = [item for sublist in varlist for item in sublist]
-        
+
         if len(varlist) > 0:
             varlist += ['Geopotential height', 'level', 'pressure']
-        
+
         return varlist
-    
+
     def makeDate(self):
         '''convert data format to NCAR RDA request'''
-        
+
         beg = self.date['beg'].strftime('%Y%m%d%H%M')
         end = self.date['end'].strftime('%Y%m%d%H%M')
         dateRange = beg + '/to/' + end
-        
+
         return dateRange
-    
-    def getPressure(self, elevation):
-        g  = 9.80665   #Gravitational acceleration [m/s2]
-        R  = 8.31432   #Universal gas constant for air [N.m /(mol.K)]    
-        M  = 0.0289644 #Molar mass of Earth's air [kg/mol]
-        P0 = 101325    #Pressure at sea level [Pa]
-        T0 = 288.15    #Temperature at sea level [K]
-        #http://en.wikipedia.org/wiki/Barometric_formula
-        return P0 * exp((-g * M * elevation) / (R * T0)) / 100 #[hPa] or [bar]
-    
+
     def getPressureLevels(self):
-        total_ele37 = [100, 125, 150, 175, 200, 225, 250, 300, 350, 400, 450, 500, 550, 
-                       600, 650, 700, 750, 775, 800, 825, 850, 875, 900, 
+        total_ele37 = [100, 125, 150, 175, 200, 225, 250, 300, 350, 400, 450, 500, 550,
+                       600, 650, 700, 750, 775, 800, 825, 850, 875, 900,
                        925, 950, 975, 1000]
-        
+
         # flip max and min because 1000 is the bottom and 0 is the top
-        elevationMax = self.getPressure(self.elevation['min'])
-        elevationMin = self.getPressure(self.elevation['max'])
-        
-        minNum = min(total_ele37, key=lambda x:abs(x-elevationMin))
-        maxNum = min(total_ele37, key=lambda x:abs(x-elevationMax))
-        
-        if (minNum > elevationMin and total_ele37.index(minNum) > 0 ):
+        elevationMax = pressure_from_elevation(self.elevation['min'])
+        elevationMin = pressure_from_elevation(self.elevation['max'])
+
+        minNum = min(total_ele37, key=lambda x:abs(x - elevationMin))
+        maxNum = min(total_ele37, key=lambda x:abs(x - elevationMax))
+
+        if (minNum > elevationMin and total_ele37.index(minNum) > 0):
             elevationMinRange = total_ele37.index(minNum) - 1
         else:
             elevationMinRange = total_ele37.index(minNum)
-        
-        if (maxNum < elevationMin and total_ele37.index(maxNum) < 36 ):
+
+        if (maxNum < elevationMin and total_ele37.index(maxNum) < 36):
             elevationMaxRange = total_ele37.index(maxNum) - 1
         else:
             elevationMaxRange = total_ele37.index(maxNum)
-            
+
         elevation = []
         for e in range(elevationMinRange, elevationMaxRange + 1):
             elevation.append(total_ele37[e])
-        
+
         elevation = [str(ele) for ele in elevation]
         elevation = '/'.join(elevation)
-        
+
         return elevation
-        
-    
+
     def getDictionary(self):
         self.dictionary = {
                 'dataset': 'ds628.0',
                 'date': self.makeDate(),
                 'param': '/'.join(self.param),
-                'level': 'Isobaric surface:'+self.getPressureLevels(),
+                'level': 'Isobaric surface:' + self.getPressureLevels(),
                 'oformat': 'netCDF',
                 'nlat': str(self.area['north']),
                 'slat': str(self.area['south']),
@@ -431,50 +410,48 @@ def getDictionary(self):
                 'product': 'Analysis',
                 'compression': 'NN',
                 'gridproj': 'latLon',
-                'griddef': '288:145:90N:0E:90S:1.25W:1.25:1.25'
-                }
-        
+                'griddef': '288:145:90N:0E:90S:1.25W:1.25:1.25'}
+
         return self.dictionary
-               
+
 
 class JRAsa(object):
-    
+
     def __init__(self, date, area, variables, rda):
         '''Returns an object for JRA55 data that has methods for querying the
         NCAR server for surface forecast variables (prec, swin, lwin). '''
-        
+
         self.date = date
         self.area = area
-        #self.directory = directory
 
         dpar = {'air_temperature'  : ['Temperature'],
                 'relative_humidity': ['Relative humidity'],
-                'wind_speed'       : ['u-component of wind', 
+                'specific_humidity': ['Specific humidity'],
+                'wind_speed'       : ['u-component of wind',
                                       'v-component of wind']}
-        
+
         self.param = self.getParam(dpar, variables)
-        
+
     def getParam(self, dpar, variables):
-        
-        varlist = [] 
+
+        varlist = []
         for var in variables:
             varlist.append(dpar.get(var))
 
         varlist = [item for item in varlist if item is not None]
         varlist = [item for sublist in varlist for item in sublist]
-        
+
         return varlist
-    
+
     def makeDate(self):
         '''convert data format to NCAR RDA request'''
-        
+
         beg = self.date['beg'].strftime('%Y%m%d%H%M')
         end = self.date['end'].strftime('%Y%m%d%H%M')
         dateRange = beg + '/to/' + end
-        
+
         return dateRange
-        
-    
+
     def getDictionary(self):
         self.dictionary = {
                 'dataset': 'ds628.0',
@@ -489,56 +466,54 @@ def getDictionary(self):
                 'product': 'Analysis',
                 'compression': 'NN',
                 'gridproj': 'latLon',
-                'griddef': '288:145:90N:0E:90S:1.25W:1.25:1.25'
-                }
-        
+                'griddef': '288:145:90N:0E:90S:1.25W:1.25:1.25'}
+
         return self.dictionary
-    
+
 
 class JRAsf(object):
-    
+
     def __init__(self, date, area, variables, rda):
         '''Returns an object for JRA55 data that has methods for querying the
         NCAR server for surface forecast variables (prec, swin, lwin). '''
-        
+
         self.date = date
         self.area = area
-        
-        dpar = {'precipitation_amount'              :
+
+        dpar = {'precipitation_amount':
                     ['Total precipitation'],
-                'downwelling_shortwave_flux_in_air' : 
+                'downwelling_shortwave_flux_in_air':
                     ['Downward solar radiation flux'],
-                'downwelling_longwave_flux_in_air'  : 
+                'downwelling_longwave_flux_in_air':
                     ['Downward longwave radiation flux'],
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky': 
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky':
                     ['Clear sky downward solar radiation flux'],
                 'downwelling_longwave_flux_in_air_assuming_clear_sky':
                     ['Clear sky downward longwave radiation flux']}
-        
+
         self.param = self.getParam(dpar, variables)
-        
+
     def getParam(self, dpar, variables):
-        
-        varlist = [] 
+
+        varlist = []
         for var in variables:
             varlist.append(dpar.get(var))
 
         varlist = [item for item in varlist if item is not None]
         varlist = [item for sublist in varlist for item in sublist]
         varlist.append('Pressure')
-        
+
         return varlist
-    
+
     def makeDate(self):
         '''convert data format to NCAR RDA request'''
-        
+
         beg = self.date['beg'].strftime('%Y%m%d%H%M')
         end = self.date['end'].strftime('%Y%m%d%H%M')
         dateRange = beg + '/to/' + end
-        
+
         return dateRange
-        
-    
+
     def getDictionary(self):
         self.dictionary = {
                 'dataset': 'ds628.0',
@@ -553,67 +528,44 @@ def getDictionary(self):
                 'product': '3-hour Average (initial+0 to initial+3)',
                 'compression': 'NN',
                 'gridproj': 'latLon',
-                'griddef': '288:145:90N:0E:90S:1.25W:1.25:1.25'
-                }
-        
+                'griddef': '288:145:90N:0E:90S:1.25W:1.25:1.25'}
+
         return self.dictionary
 
 
-class JRAdownload(object):
+class JRAdownload(GenericDownload):
     '''Return an objet to download JRA55 dataset client based on RDA'''
-    
-    #TODO add credential
+
+    # TODO: add credential
     def __init__(self, pfile):
+        super().__init__(pfile)
+        par = self.par
+        self._set_data_directory("jra55")
+
         self.dsID = 'ds628.0'
-        self.pfile = pfile
-        
-        par = ParameterIO(self.pfile)
+
         self.__varCheck(par)
-        
-        # assign bounding box
-        self.area  = {'north':  par.bbN,
-                      'south':  par.bbS,
-                      'west' :  par.bbW,
-                      'east' :  par.bbE}
-        
-        # sanity check to make sure area is good
-        if (par.bbN < par.bbS) or (par.bbE < par.bbW):
-            raise Exception("Bounding box is invalid: {}".format(self.area))
-            
-        if (np.abs(par.bbN-par.bbS) < 1.5) or (np.abs(par.bbE-par.bbW) < 1.5):
-            raise Exception("Download area is too small to conduct interpolation.")
-                 
+
         # time bounds
         self.date  = self.getDate(par)
 
-        # elevation
-        self.elevation = {'min' : par.ele_min, 
-                          'max' : par.ele_max}
-        
-        # data directory for JRA-55  
-        self.directory = par.project_directory + '/jra55'
-        
-        self.credential = path.join(par.credentials_directory, ".jrarc")
-        #print(self.credential)
+        self.credential = path.join(par['credentials_directory'], ".jrarc")
         self.account = open(self.credential, "r")
         self.inf = self.account.readlines()
         self.username = ''.join(self.inf[0].split())
-        self.password = ''.join(self.inf[1].split()) 
-     
-        # variables
-        self.variables = par.variables
-            
-        # chunk size for downloading and storing data [days]        
-        self.chunk_size = par.chunk_size*2000
-        
+        self.password = ''.join(self.inf[1].split())
+
+        # chunk size for downloading and storing data [days]
+        self.chunk_size = par['chunk_size'] * 2000
+
         self.ncfVar  = {
-                'initial_time0_hours':   'time', 
+                'initial_time0_hours':   'time',
                 'initial_time0':         'time',
                 'initial_time0_encoded': 'time',
-                'lv_ISBL1':              'level', 
-                'g0_lat_1':              'latitude', 
+                'lv_ISBL1':              'level',
+                'g0_lat_1':              'latitude',
                 'g0_lon_2':              'longitude',
-                'g0_lat_2':              'latitude', 
+                'g0_lat_2':              'latitude',
                 'g0_lon_3':              'longitude',
                 'HGT_GDS0_ISBL':         'Geopotential height',
                 'RH_GDS0_ISBL':          'Relative humidity',
@@ -624,6 +576,7 @@ def __init__(self, pfile):
                 'VGRD_GDS0_HTGL':        'v-component of wind',
                 'UGRD_GDS0_HTGL':        'u-component of wind',
                 'RH_GDS0_HTGL':          'Relative humidity',
+                'SPFH_GDS0_HTGL':        'Specific humidity',
                 'PRES_GDS0_SFC_ave3h':   'Pressure',
                 'TPRAT_GDS0_SFC_ave3h':  'Total precipitation',
                 'CSDSF_GDS0_SFC_ave3h':  'Clear sky downward solar radiation flux',
@@ -631,25 +584,25 @@ def __init__(self, pfile):
                 'DSWRF_GDS0_SFC_ave3h':  'Downward solar radiation flux',
                 'DLWRF_GDS0_SFC_ave3h':  'Downward longwave radiation flux'}
 
-    
     def __varCheck(self, par):
         '''convert one variable to a list'''
-        
-        if not isinstance(par.variables, (list,)):
-            par.variables = [par.variables]
-            
+
+        if not isinstance(par['variables'], (list,)):
+            par['variables'] = [par['variables']]
+
     def getDate(self, par):
         '''get download daterange'''
-        
-        dateRange = {'beg' : par.beg, 'end' : par.end}
+
+        dateRange = {'beg': datetime.strptime(par['beg'], '%Y/%m/%d'),
+                     'end': datetime.strptime(par['end'], '%Y/%m/%d')}
         dateRange['end'] = dateRange['end'] + timedelta(hours=23)
-        
+
         return dateRange
-        
+
     def getDataLev(self, dsi):
         '''get data level of the download data set'''
-        
-        flist = glob.glob(path.join(self.directory, '*'+dsi+'*'))
+
+        flist = glob.glob(path.join(self.directory, '*' + dsi + '*'))
         lev = path.basename(flist[0]).split('_')[1].split('.')[0]
         if lev == 'p125':
             dataLev = 'pl'
@@ -657,186 +610,183 @@ def getDataLev(self, dsi):
             dataLev = 'sa'
         elif lev == 'phy2m125':
             dataLev = 'sf'
-        
+
         return dataLev
-    
+
     def getVars(self, dsi):
         '''get all download variable names'''
-        
-        flist = glob.glob(path.join(self.directory, '*'+dsi+'*'))
+
+        flist = glob.glob(path.join(self.directory, '*' + dsi + '*'))
         varlist = []
         for f in flist:
             fname = path.basename(f)
             var = fname.split('_')[2]
             varlist.append(var.split('.')[0])
-        
+
         variables = np.unique(varlist)
-        
+
         return variables
-        
+
     def getOutFile(self, ncf, dataLev):
-        
-        times = nc.num2date(ncf[self.timeName][:], 
-                            units = ncf[self.timeName].units, 
+
+        times = nc.num2date(ncf[self.timeName][:],
+                            units=ncf[self.timeName].units,
                             calendar='standard')
         begStr = np.min(times).strftime('%Y%m%d')
         endStr = np.max(times).strftime('%Y%m%d')
-        
+
         fileName = ['jra55', dataLev, begStr, 'to', endStr]
         fileName = '_'.join(fileName) + '.nc'
         fileName = path.join(self.directory, fileName)
-        
+
         return fileName
-    
+
     def getDimName(self, dataLev):
-        '''get the dimension [time, level, latitude, longitude] name in the 
+        '''get the dimension [time, level, latitude, longitude] name in the
         original JRA55 ncf'''
-        
+
         self.timeName = 'initial_time0_hours'
         self.levName = 'lv_ISBL1'
-        
+
         if dataLev == 'pl':
             self.lonName = 'g0_lon_3'
             self.latName = 'g0_lat_2'
         elif dataLev in ['sa', 'sf']:
             self.lonName = 'g0_lon_2'
             self.latName = 'g0_lat_1'
-        
-    
+
     def makeNCF(self, dsi):
-        
+
         variables = self.getVars(dsi)
         dataLev = self.getDataLev(dsi)
         self.getDimName(dataLev)
-        
-        varf = np.sort(glob.glob(path.join(self.directory, 
-                                           '*' + variables[0]+'*')))
-        ncf = nc.MFDataset(varf.tolist(), aggdim ='initial_time0_hours')
-        
+
+        varf = np.sort(glob.glob(path.join(self.directory,
+                                           f'*{variables[0]}*')))
+        ncf = nc.MFDataset(varf.tolist(), aggdim='initial_time0_hours')
+
         if dataLev == 'pl':
             Levs = ncf['lv_ISBL1'][:].data
-            
+
         Times = ncf[self.timeName][:]
         Lats  = ncf[self.latName][:].data
         Lons  = ncf[self.lonName][:].data
-        
+
         file_new = self.getOutFile(ncf, dataLev)
-        
-        #initialize new data file and create group
+
+        # initialize new data file and create group
         ncn = nc.Dataset(file_new, 'w', format='NETCDF4_CLASSIC')
-        
-        #make dimensions
+
+        # make dimensions
         if dataLev == 'pl':
             Levs = ncf[self.levName][:].data
             ncn.createDimension('level', len(Levs))
-            levels     = ncn.createVariable('level',  'i4',('level',))
+            levels     = ncn.createVariable('level', 'i4',('level',))
             levels.long_name  = 'pressure level'
-            levels.units      = 'mbar' 
+            levels.units      = 'mbar'
             levels[:] = Levs
-        ncn.createDimension('time',  len(Times))
-        ncn.createDimension('latitude',  len(Lats))
+        ncn.createDimension('time', len(Times))
+        ncn.createDimension('latitude', len(Lats))
         ncn.createDimension('longitude', len(Lons))
-        
-        #make dimension variables
-        times      = ncn.createVariable('time',    'd',('time',))
-        latitudes  = ncn.createVariable('latitude',   'f8', ('latitude',))
-        longitudes = ncn.createVariable('longitude',  'f8', ('longitude',))
-        
+
+        # make dimension variables
+        times      = ncn.createVariable('time', 'd',('time',))
+        latitudes  = ncn.createVariable('latitude', 'f8', ('latitude',))
+        longitudes = ncn.createVariable('longitude', 'f8', ('longitude',))
+
         times.standard_name = 'time'
         times.units     = ncf[self.timeName].units
         times.calendar  = 'standard'
         latitudes.standard_name  = ncf[self.latName].long_name
-        latitudes.units      = ncf[self.latName].units 
+        latitudes.units      = ncf[self.latName].units
         longitudes.standard_name = ncf[self.lonName].long_name
         longitudes.units     = ncf[self.lonName].units
-        
+
         ncf.close()
-        
-        #assign dimensions
+
+        # assign dimensions
         times[:] = Times
         longitudes[:] = Lons
         latitudes[:] = Lats
-        
+
         for vari in variables:
-            flist = np.sort(glob.glob(path.join(self.directory, '*'+vari+'*')))
-            ncf = nc.MFDataset(flist.tolist(), aggdim = self.timeName)
+            flist = np.sort(glob.glob(path.join(self.directory, f'*{vari}*')))
+            ncf = nc.MFDataset(flist.tolist(), aggdim=self.timeName)
             for n, var in enumerate(ncf.variables.keys()):
                 if variables_skip(self.ncfVar[var]):
-                    continue                 
+                    continue
                 print("VAR: ", var)
                 if dataLev == 'pl':
-                    vari = ncn.createVariable(self.ncfVar[var],'f4',
-                                              ('time','level',
-                                               'latitude','longitude',))
+                    vari = ncn.createVariable(self.ncfVar[var], 'f4',
+                                              ('time', 'level',
+                                               'latitude', 'longitude',))
                     vari[:,:,:,:] = ncf[var][:,:,:,:]
                 else:
                     vari = ncn.createVariable(self.ncfVar[var],'f4',
                                               ('time',
-                                               'latitude','longitude'))
+                                               'latitude', 'longitude'))
                     vari[:,:,:] = ncf[var][:,:,:]
-                    
+
                 vari.long_name = ncf[var].long_name
                 vari.units     = ncf[var].units
-            
-            
-            # clear
+
             ncf.close()
             for f in flist:
                 remove(f)
-        
+
         ncn.close()
-        
+
     def requestClear(self, rda):
         '''clear online datasets before downloading'''
-        
+
         print('\n======== Clear Online Datasets From NCAR Server ========\n')
         dsIndex = rda.getDSindex()
         if len(dsIndex) > 1:
             rda.purge(dsIndex)
         print('\n======== Online Dateset Cleared ========\n')
-        
+
     def requestSubmit(self, rda):
-        
+
         print('\n======== Submit Request ========\n')
-        
-        slices = floor(float((self.date['end'] - self.date['beg']).days)/
+
+        slices = floor(float((self.date['end'] - self.date['beg']).days) /
                        self.chunk_size) + 1
-        
+
         dsN = 0
-        for ind in range (0, int(slices)): 
+        for ind in range(0, int(slices)):
             date_i = {}
-            #prepare time slices   
-            date_i['beg'] = self.date['beg'] + timedelta(days = 
-                            self.chunk_size * ind)
-            date_i['end'] = self.date['beg'] + timedelta(days = 
-                            self.chunk_size * (ind+1) - 1)
-            if ind == (slices-1):
+            # prepare time slices
+            date_i['beg'] = (self.date['beg']
+                             + timedelta(days=self.chunk_size * ind))
+            date_i['end'] = (self.date['beg']
+                             + timedelta(days=self.chunk_size * (ind + 1) - 1))
+            if ind == (slices - 1):
                 date_i['end'] = self.date['end']
-            
-            pl = JRApl(date_i, self.area, self.elevation, 
+
+            pl = JRApl(date_i, self.area, self.elevation,
                        self.variables, rda)
             sa = JRAsa(date_i, self.area, self.variables, rda)
             sf = JRAsf(date_i, self.area, self.variables, rda)
-            
+
             # get download data level
             JRAli = []
             for jrai in [pl, sa, sf]:
                 if len(jrai.param) > 0:
                     JRAli.append(jrai)
-                    
+
             for jrai in JRAli:
                 rda.submit(jrai.getDictionary())
                 dsN += 1
-        print('\n======== Submit Completed ========\n')  
-        
+        print('\n======== Submit Completed ========\n')
+
         return dsN
-        
+
     def requestDownload(self, rda, dsN):
         '''download all the request'''
-        
+
         doneI = []
         while len(doneI) < dsN:
-            
+
             print('\n======== Geting Available Dataset ========\n')
             dsIndex = rda.getDSindex()
             dsIndex = [item for item in dsIndex if item not in doneI]
@@ -845,32 +795,30 @@ def requestDownload(self, rda, dsN):
                     rda.download(self.directory, ds)
                     self.makeNCF(ds)
                 doneI += dsIndex
-            time.sleep(60*10)# check available data every 10 mins
-        
+            time.sleep(60 * 10)  # check available data every 10 mins
+
         print('''\n======== Download Completed ========\n''')
-        
+
     def retrieve(self):
         '''submit and download all the dataset'''
-        
+
         print('''\n======== JRA55: STRAT ========\n''')
-        
-        rda = RDA(self.username, self.password) # initialize RDA server
-        self.requestClear(rda) # clear online request
-        dsN = self.requestSubmit(rda) # submit request
-        self.requestDownload(rda, dsN) # download dataset
-        
+
+        rda = RDA(self.username, self.password)  # initialize RDA server
+        self.requestClear(rda)  # clear online request
+        dsN = self.requestSubmit(rda)  # submit request
+        self.requestDownload(rda, dsN)  # download dataset
+
         print('''\n======== JRA55: STOP ========\n''')
-        
-         
 
 
-class JRAinterpolate(object):
+class JRAinterpolate(GenericInterpolate):
     """
-    Algorithms to interpolate JRA55 netCDF files to station coordinates. 
-    All variables retains their original units and time-steps. 
-    
-    Referenced from era_interim.py (Dr.Stephan Gruber): Class ERAinterpolate()     
-    
+    Algorithms to interpolate JRA55 netCDF files to station coordinates.
+    All variables retains their original units and time-steps.
+
+    Referenced from era_interim.py (Dr.Stephan Gruber): Class ERAinterpolate()
+
     Args:
         ifile: Full path to a Globsim Interpolate Paramter file
         JRAinterpolate(ifile)
@@ -879,328 +827,99 @@ class JRAinterpolate(object):
     Example:
         ifile = '/home/xquan/src/globsim/examples/par/examples.globsim_interpolate'
         JRAinterpolate(ifile)
-      
+
     """
 
     def __init__(self, ifile):
-        #read parameter file
-        self.ifile = ifile
-        par = ParameterIO(self.ifile)
-        self.dir_inp = path.join(par.project_directory,'jra55') 
-        self.dir_out = self.makeOutDir(par)
-        self.variables = par.variables     
-        self.list_name = par.station_list.split(path.extsep)[0]
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-        
-        # time bounds
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end + timedelta(days=1)}
-
-        # chunk size: how many time steps to interpolate at the same time?
-        # A small chunk size keeps memory usage down but is slow.
-        self.cs  = int(par.chunk_size) * 200
-    
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirIntp = path.join(par.project_directory, 'interpolated')
-        
-        if not (path.isdir(dirIntp)):
-            makedirs(dirIntp)
-            
-        return dirIntp
-    
-        
-    def netCDF_empty(self, ncfile_out, stations, nc_in):
-        '''
-        Creates an empty station file to hold interpolated reults. The number of 
-        stations is defined by the variable stations, variables are determined by 
-        the variable list passed from the gridded original netCDF.
-        
-        ncfile_out: full name of the file to be created
-        stations:   station list read with generic.StationListRead() 
-        variables:  variables read from netCDF handle
-        lev:        list of pressure levels, empty is [] (default)
-        '''
-        
-        #Build the netCDF file
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4_CLASSIC')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'JRA55, interpolated bilinearly to stations'
-        rootgrp.featureType = "timeSeries"
-                                                
-        # dimensions
-        station = rootgrp.createDimension('station', len(stations))
-        time    = rootgrp.createDimension('time', None)
-                
-        # base variables
-        time           = rootgrp.createVariable('time', 'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1800-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station', 
-                                                     'i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude', 
-                                                     'f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude',
-                                                     'f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east' 
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-        
-        # assign station characteristics            
-        station[:]   = list(stations['station_number'])
-        latitude[:]  = list(stations['latitude_dd'])
-        longitude[:] = list(stations['longitude_dd'])
-        height[:]    = list(stations['elevation_m'])
-        
-        # extra treatment for pressure level files
-        try:
-            lev = nc_in.variables['level'][:]
-            print("== 3D: file has pressure levels")
-            level = rootgrp.createDimension('level', len(lev))
-            level           = rootgrp.createVariable('level','i4',('level'))
-            level.long_name = 'pressure_level'
-            level.units     = 'hPa'  
-            level[:] = lev 
-        except:
-            print("== 2D: file without pressure levels")
-            lev = []
-                    
-        # create and assign variables based on input file
-        for n, var in enumerate(nc_in.variables):
-            if variables_skip(var):
-                continue                 
-            print("VAR: ", var)
-            # extra treatment for pressure level files           
-            if len(lev):
-                tmp = rootgrp.createVariable(var,'f4', 
-                                             ('time', 'level', 'station'))
-            else:
-                tmp = rootgrp.createVariable(var,'f4',('time', 'station'))     
-            tmp.long_name = str_encode(nc_in.variables[var].long_name)
-            tmp.units     = str_encode(nc_in.variables[var].units)  
-                    
-        #close the file
-        rootgrp.close()
-        
-        
-    def JRAinterp2D(self, ncfile_in, ncf_in, points, tmask_chunk,
-                    variables=None, date=None):    
-        """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
-        to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all jra55 files).
-          
-        Args:
-            ncfile_in: Full path to an JRA-55 derived netCDF file. This can
-                        contain wildcards to point to multiple files if temporal
-                        chunking was used.
-              
-            ncf_in: A netCDF4.MFDataset derived from reading in JRA-55 
-                    multiple files (def JRA2station())
-            
-              
-            points: A dictionary of locations. See method StationListRead in
-                    generic.py for more details.
-        
-            variables:  List of variable(s) to interpolate such as 
-                        [air_temperature, easteard_wind, northward_wind, relative_humidy, surface_temperature, 
-                        downwelling_shortwave_flux_in_air, downwelling_longwave_flux_in_air,
-                        downwelling_shortwave_flux_in_air_assuming_clear_sky, 
-                        downwelling_longwave_flux_in_air_assuming_clear_sky].
-                        Defaults to using all variables available.
-        
-            date: Directory to specify begin and end time for the derived time 
-                  series. Defaluts to using all times available in ncfile_in.
-              
-        Example:
-            from datetime import datetime
-            date  = {'beg' : datetime(2008, 1, 1),
-                      'end' : datetime(2008,12,31)}
-            variables  = [air_temperature, easteard_wind, northward_wind]       
-            stations = StationListRead("points.csv")      
-            JRA2station('jra_surf_*.nc', 'jra_sa_inter.nc', stations, 
-                        variables=variables, date=date)        
-        """   
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
-
-        # get spatial dimensions
-        if pl: # only for pressure level files
-            lev  = ncf_in.variables['level'][:]
-            nlev = len(lev)
-              
-        # test if time steps to interpolate remain
-        nt = sum(tmask_chunk)
-        if nt == 0:
-            raise ValueError('No time steps from netCDF file selected.')
-    
-        # get variables
-        varlist = [str_encode(x) for x in ncf_in.variables.keys()]
-        varlist.remove('time')
-        varlist.remove('latitude')
-        varlist.remove('longitude')
-        if pl: #only for pressure level files
-            varlist.remove('level')
-    
-        #list variables that should be interpolated
-        if variables is None:
-            variables = varlist
-        #test is variables given are available in file
-        if (set(variables) < set(varlist) == 0):
-            raise ValueError('One or more variables not in netCDF file.')           
-
-        # Create source grid from a SCRIP formatted file. As ESMF needs one
-        # file rather than an MFDataset, give first file in directory.
-        ncsingle = filter(listdir(self.dir_inp), path.basename(ncfile_in))[0]
-        ncsingle = path.join(self.dir_inp, ncsingle)
-        sgrid = ESMF.Grid(filename=ncsingle, filetype=ESMF.FileFormat.GRIDSPEC)
-
-        # create source field on source grid
-        if pl: #only for pressure level files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt, nlev])
-        else: # 2D files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt])
-
-        # assign data from ncdf: (variable, time, latitude, longitude) 
-        for n, var in enumerate(variables):
-            if pl: # only for pressure level files
-                if ESMFnew:
-                    sfield.data[:,:,n,:,:] = ncf_in.variables[var][
-                            tmask_chunk,:,:,:].transpose((3,2,0,1)) 
-                else:
-                    sfield.data[n,:,:,:,:] = ncf_in.variables[var][
-                            tmask_chunk,:,:,:].transpose((0,1,3,2)) 
-            else:
-                if ESMFnew:
-                    sfield.data[:,:,n,:] = ncf_in.variables[var][
-                            tmask_chunk,:,:].transpose((2,1,0))
-                else:
-                    sfield.data[n,:,:,:] = ncf_in.variables[var][
-                            tmask_chunk,:,:].transpose((0,2,1))
-
-        # create locstream, CANNOT have third dimension!!!
-        locstream = ESMF.LocStream(len(self.stations), 
-                                   coord_sys=ESMF.CoordSys.SPH_DEG)
-        locstream["ESMF:Lon"] = list(self.stations['longitude_dd'])
-        locstream["ESMF:Lat"] = list(self.stations['latitude_dd'])
-
-        # create destination field
-        if pl: # only for pressure level files
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt, nlev])
-        else:
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt])    
+        super().__init__(ifile)
+        par = self.par
 
-        # regridding function, consider ESMF.UnmappedAction.ERROR
-        regrid2D = ESMF.Regrid(sfield, dfield,
-                                regrid_method=ESMF.RegridMethod.BILINEAR,
-                                unmapped_action=ESMF.UnmappedAction.IGNORE,
-                                dst_mask_values=None)
-                  
-        # regrid operation, create destination field (variables, times, points)
-        dfield = regrid2D(sfield, dfield)        
-        sfield.destroy() #free memory                  
+        self.dir_raw = path.join(par['project_directory'],'jra55')
 
-        return dfield, variables
+        # Override inherited chunk size
+        self.cs *= 200
 
     def JRA2station(self, ncfile_in, ncfile_out, points,
-                    variables = None, date = None):
-        
+                    variables=None, date=None):
+
         """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
+        Biliner interpolation from fields on regular grid (latitude, longitude)
         to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all JRA55 files). The type 
+        surface and for pressure level files (all JRA55 files). The type
         of variable and file structure are determined from the input.
-        
-        This function creates an empty of netCDF file to hold the interpolated 
-        results, by calling self.netCDF_empty(). Then, data is 
-        interpolated in temporal chunks and appended. The temporal chunking can 
+
+        This function creates an empty of netCDF file to hold the interpolated
+        results, by calling self.netCDF_empty(). Then, data is
+        interpolated in temporal chunks and appended. The temporal chunking can
         be set in the interpolation parameter file.
-        
+
         Args:
         ncfile_in: Full path to an JRA-55 derived netCDF file. This can
                    contain wildcards to point to multiple files if temporal
                   chunking was used.
-            
-        ncfile_out: Full path to the output netCDF file to write.     
-        
+
+        ncfile_out: Full path to the output netCDF file to write.
+
         points: A dictionary of locations. See method StationListRead in
-                generic.py for more details.
-    
-        variables:  List of variable(s) to interpolate such as 
-                    [air_temperature, easteard_wind, northward_wind, 
-                    relative_humidy, surface_temperature, 
-                    downwelling_shortwave_flux_in_air, 
+                common_utils.py for more details.
+
+        variables:  List of variable(s) to interpolate such as
+                    [air_temperature, easteard_wind, northward_wind,
+                    relative_humidy, surface_temperature,
+                    downwelling_shortwave_flux_in_air,
                     downwelling_longwave_flux_in_air,
-                    downwelling_shortwave_flux_in_air_assuming_clear_sky, 
+                    downwelling_shortwave_flux_in_air_assuming_clear_sky,
                     downwelling_longwave_flux_in_air_assuming_clear_sky].
                     Defaults to using all variables available.
-    
-        date: Directory to specify begin and end time for the derived time 
+
+        date: Directory to specify begin and end time for the derived time
                 series. Defaluts to using all times available in ncfile_in.
-        
-        cs: chunk size, i.e. how many time steps to interpolate at once. This 
+
+        cs: chunk size, i.e. how many time steps to interpolate at once. This
             helps to manage overall memory usage (small cs is slower but less
-            memory intense).          
+            memory intense).
         """
-        
-        # read in one type of mutiple netcdf files       
-        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim ='time')
+
+        # read in one type of mutiple netcdf files
+        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim='time')
+
         # is it a file with pressure levels?
         pl = 'level' in ncf_in.dimensions.keys()
 
         # build the output of empty netCDF file
-        self.netCDF_empty(ncfile_out, self.stations, ncf_in) 
-                                     
+        rootgrp = new_interpolated_netcdf(ncfile_out, self.stations, ncf_in,
+                                          time_units='hours since 1800-01-01 00:00:0.0')
+        rootgrp.source = 'JRA55, interpolated bilinearly to stations'
+        rootgrp.close()
+
         # open the output netCDF file, set it to be appendable ('a')
         ncf_out = nc.Dataset(ncfile_out, 'a')
 
         # get time and convert to datetime object
         nctime = ncf_in.variables['time'][:]
-        #"hours since 1900-01-01 00:00:0.0"
-        t_unit = ncf_in.variables['time'].units 
-        try :
+        # "hours since 1900-01-01 00:00:0.0"
+        t_unit = ncf_in.variables['time'].units
+        try:
             t_cal = ncf_in.variables['time'].calendar
-        except AttributeError :  # attribute doesn't exist
-            t_cal = u"gregorian" # standard
-        time = nc.num2date(nctime, units = t_unit, calendar = t_cal)
-        
+        except AttributeError:  # attribute doesn't exist
+            t_cal = u"gregorian"  # standard
+        time = nc.num2date(nctime, units=t_unit, calendar=t_cal)
+
         # detect invariant files (topography etc.)
-        if len(time) ==1:
-            invariant=True
-        else:
-            invariant=False                                                                         
-                                                                                                                                                                                                                                            
+        invariant = True if len(time) == 1 else False
+
         # restrict to date/time range if given
         if date is None:
             tmask = time < datetime(3000, 1, 1)
         else:
             tmask = (time < date['end']) * (time >= date['beg'])
-                              
+
         # get time indices
-        time_in = nctime[tmask]     
+        time_in = nctime[tmask]
 
         # ensure that chunk sizes cover entire period even if
         # len(time_in) is not an integer multiple of cs
-        niter  = len(time_in) // self.cs
+        niter = len(time_in) // self.cs
         niter += ((len(time_in) % self.cs) > 0)
 
         # loop over chunks
@@ -1208,72 +927,63 @@ def JRA2station(self, ncfile_in, ncfile_out, points,
             # indices
             beg = n * self.cs
             # restrict last chunk to lenght of tmask plus one (to get last time)
-            end = min(n*self.cs + self.cs, len(time_in)) -1
-            
-            # time to make tmask for chunk 
+            end = min(n * self.cs + self.cs, len(time_in)) - 1
+
+            # time to make tmask for chunk
             beg_time = nc.num2date(time_in[beg], units=t_unit, calendar=t_cal)
             if invariant:
                 # allow topography to work in same code, len(nctime) = 1
                 end_time = nc.num2date(nctime[0], units=t_unit, calendar=t_cal)
-                #end = 1
+                # end = 1
             else:
                 end_time = nc.num2date(time_in[end], units=t_unit, calendar=t_cal)
-                
-            #'<= end_time', would damage appending
+
+            # '<= end_time', would damage appending
             tmask_chunk = (time <= end_time) * (time >= beg_time)
             if invariant:
                 # allow topography to work in same code
                 tmask_chunk = [True]
-                 
+
             # get the interpolated variables
-            dfield, variables = self.JRAinterp2D(ncfile_in, ncf_in, 
-                                                     self.stations, tmask_chunk,
-                                                     variables=None, date=None) 
+            dfield, variables = self.interp2D(ncfile_in, ncf_in,
+                                              self.stations, tmask_chunk,
+                                              variables=None, date=None)
 
             # append time
-            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:], 
+            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:],
                                                      time_in[beg:end+1])
-                                  
-            #append variables
+
+            # append variables
             for i, var in enumerate(variables):
                 if variables_skip(var):
                     continue
-                                                              
+
                 if pl:
                     lev = ncf_in.variables['level'][:]
-                    
-                    if ESMFnew:
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:].transpose((1,2,0))
-                    else:
-                        # dimension: time, level, latitude, longitude
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[i,:,:,:]      
+
+                    ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:].transpose((1,2,0))
                 else:
-                    if ESMFnew:
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:].transpose((1,0))
-                    else:
-                        # time, latitude, longitude
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[i,:,:]
-                    
-                    
-        #close the file
+                    ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:].transpose((1,0))
+
+        # close the file
         ncf_in.close()
         ncf_out.close()
 
-   
-    def levels2elevation(self, ncfile_in, ncfile_out):    
+    def levels2elevation(self, ncfile_in, ncfile_out):
         """
         Linear 1D interpolation of pressure level data available for individual
-        stations to station elevation. Where and when stations are below the 
-        lowest pressure level, they are assigned the value of the lowest 
+        stations to station elevation. Where and when stations are below the
+        lowest pressure level, they are assigned the value of the lowest
         pressure level.
-        
+
         """
-        # open file 
+        # open file
+
         ncf = nc.MFDataset(ncfile_in, 'r', aggdim='time')
         height = ncf.variables['height'][:]
         nt = len(ncf.variables['time'][:])
         nl = len(ncf.variables['level'][:])
-        
+
         # list variables
         varlist = [str_encode(x) for x in ncf.variables.keys()]
         for V in ['time', 'station', 'latitude', 'longitude', 'level', 'height']:
@@ -1285,509 +995,443 @@ def levels2elevation(self, ncfile_in, ncfile_out):
         #            others: ...(time, station)
         # stations are integer numbers
         # create a file (Dataset object, also the root group).
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'JRA55, interpolated (bi)linearly to stations'
-        rootgrp.featureType = "timeSeries"
-
-        # dimensions
-        station = rootgrp.createDimension('station', len(height))
-        time    = rootgrp.createDimension('time', nt)
-
-        # base variables
-        time           = rootgrp.createVariable('time',     'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1800-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station',  'i4',
-                                                     ('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude', 'f4',
-                                                     ('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',
-                                                     ('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east'  
-        height           = rootgrp.createVariable('height','f4',
-                                                  ('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-       
+        rootgrp = netcdf_base(ncf, ncfile_out, len(height), nt,
+                              'hours since 1800-01-01 00:00:0.0')
+        rootgrp.source = 'JRA-55, interpolated (bi)linearly to stations'
+
+        # access variables
+        time = rootgrp['time']
+        station = rootgrp['station']
+        latitude = rootgrp['latitude']
+        longitude = rootgrp['longitude']
+        height = rootgrp['height']
+
         # assign base variables
-        time[:] = ncf.variables['time'][:]
+        time[:]      = ncf.variables['time'][:]
         station[:]   = ncf.variables['station'][:]
         latitude[:]  = ncf.variables['latitude'][:]
         longitude[:] = ncf.variables['longitude'][:]
         height[:]    = ncf.variables['height'][:]
-        
+
         # create and assign variables from input file
         for var in varlist:
             vname = str_encode(ncf.variables[var].long_name)
-            tmp   = rootgrp.createVariable(vname,'f4',('time', 'station'))    
+            tmp   = rootgrp.createVariable(vname,'f4',('time', 'station'))
             tmp.long_name = str_encode(ncf.variables[var].long_name)
             tmp.units     = str_encode(ncf.variables[var].units)
-            
+
         # add air pressure as new variable
         var = 'air_pressure'
         varlist.append(var)
-        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
+        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))
         tmp.long_name = str_encode(var)
-        tmp.units     = str_encode('hPa') 
+        tmp.units     = str_encode('hPa')
         # end file preparation ================================================
-    
-                                                                                                
+
         # loop over stations
-        for n, h in enumerate(height): 
+        for n, h in enumerate(height):
             # convert geopotential [millibar] to height [m]
             # shape: (time, level)
-            ele = ncf.variables['Geopotential height'][:,:,n]
-            
-            # difference in elevation. 
+            elevation = ncf.variables['Geopotential height'][:,:,n]
+
+            # difference in elevation.
             # level directly above will be >= 0
-            dele = ele - h
-            # vector of level indices that fall directly above station. 
+            elev_diff = elevation - h
+            # vector of level indices that fall directly above station.
             # Apply after ravel() of data.
-            va = np.argmin(dele + (dele < 0) * 100000, axis=1) 
+            va = np.argmin(elev_diff + (elev_diff < 0) * 100000, axis=1)
             # mask for situations where station is below lowest level
-            mask = va < (nl-1)
-            va += np.arange(ele.shape[0]) * ele.shape[1]
-            
+            mask = va < (nl - 1)
+            va += np.arange(elevation.shape[0]) * elevation.shape[1]
+
             # Vector level indices that fall directly below station.
             # Apply after ravel() of data.
-            vb = va + mask # +1 when OK, +0 when below lowest level
-            
-            # weights
-            wa = np.absolute(dele.ravel()[vb]) 
-            wb = np.absolute(dele.ravel()[va])
-            
-            wt = wa + wb
-            
-            wa /= wt # Apply after ravel() of data.
-            wb /= wt # Apply after ravel() of data.
-            
-            #loop over variables and apply interpolation weights
+            vb = va + mask  # +1 when OK, +0 when below lowest level
+
+            wa, wb = self.calculate_weights(elev_diff, va, vb)
+
+            # loop over variables and apply interpolation weights
             for v, var in enumerate(varlist):
                 if var == 'air_pressure':
                     # pressure [Pa] variable from levels, shape: (time, level)
                     data = np.repeat([ncf.variables['level'][:]],
-                                      len(time),axis=0).ravel()                 
+                                     len(time),axis=0).ravel()
                 else:
-                    #read data from netCDF
+                    # read data from netCDF
                     data = ncf.variables[var][:,:,n].ravel()
-            
+
                 multvawa = np.multiply(data[va], wa)
                 multvbwb = np.multiply(data[vb], wb)
                 ipol = multvawa + multvbwb
-                rootgrp.variables[var][:,n] = ipol # assign to file   
-    
+                rootgrp.variables[var][:,n] = ipol  # assign to file
+
         rootgrp.close()
         ncf.close()
         # closed file =========================================================
 
-
-    def TranslateCF2short(self, dpar):
-        """
-        Map CF Standard Names into short codes used in JRA-55 netCDF files.
-        """
-        varlist = [] 
-        for var in self.variables:
-            varlist.append(dpar.get(var))
-        # drop none
-        varlist = [item for item in varlist if item is not None]      
-        # flatten
-        varlist = [item for sublist in varlist for item in sublist]         
-        return(varlist) 
-
     def process(self):
         """
-        Interpolate point time series from downloaded data. Provides access to 
+        Interpolate point time series from downloaded data. Provides access to
         the more generically JRA-like interpolation functions.
-        """                       
+        """
 
-        # === 2D Interpolation for Surface  Data ===    
+        # === 2D Interpolation for Surface  Data ===
         # dictionary to translate CF Standard Names into JRA55
-        # pressure level variable keys. 
-        dpar = {'air_temperature'   : ['surface_temperature'], # [K] 2m values
-                'relative_humidity' : ['relative_humidity'], # [%]                                                       
-                'wind_speed' : ['eastward_wind', 'northward_wind']} # [m s-1] 2m & 10m values   
+        # pressure level variable keys.
+        dpar = {'air_temperature'   : ['surface_temperature'],  # [K] 2m values
+                'relative_humidity' : ['relative_humidity'],  # [%]
+                'wind_speed' : ['eastward_wind', 'northward_wind']}  # [m s-1] 2m & 10m values
         varlist = self.TranslateCF2short(dpar)
-        self.JRA2station(path.join(self.dir_inp,'jra55_sa_*.nc'),
-                            path.join(self.dir_out,'jra_sa_' + 
+        self.JRA2station(path.join(self.dir_raw,'jra55_sa_*.nc'),
+                            path.join(self.dir_inp,'jra_sa_' +
                                       self.list_name + '.nc'), self.stations,
-                                      varlist, date = self.date)
-        
-        # 2D Interpolation for Radiation Data 
+                                      varlist, date=self.date)
+
+        # 2D Interpolation for Radiation Data
         # dictionary to translate CF Standard Names into JRA55
-        # pressure level variable keys.       
-        dpar = {'downwelling_shortwave_flux_in_air': ['downwelling_shortwave_flux_in_air'], # [W/m2] short-wave downward
-                'downwelling_longwave_flux_in_air' : ['downwelling_longwave_flux_in_air'], # [W/m2] long-wave downward
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky': ['downwelling_shortwave_flux_in_air_assuming_clear_sky'], # [W/m2] short-wave downward assuming clear sky
+        # pressure level variable keys.
+        dpar = {'downwelling_shortwave_flux_in_air': ['downwelling_shortwave_flux_in_air'],  # [W/m2] short-wave downward
+                'downwelling_longwave_flux_in_air' : ['downwelling_longwave_flux_in_air'],  # [W/m2] long-wave downward
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky': ['downwelling_shortwave_flux_in_air_assuming_clear_sky'],  # [W/m2] short-wave downward assuming clear sky
                 'downwelling_longwave_flux_in_air_assuming_clear_sky': ['downwelling_longwave_flux_in_air_assuming_clear_sky'],
-                'precipitation_amount' : ['total_precipitation']} # [W/m2] long-wave downward assuming clear sky
-        varlist = self.TranslateCF2short(dpar)                           
-        self.JRA2station(path.join(self.dir_inp,'jra55_sf_*.nc'), 
-                          path.join(self.dir_out,'jra_sf_' + 
-                                    self.list_name + '.nc'), self.stations,
-                                    varlist, date = self.date)          
+                'precipitation_amount' : ['total_precipitation']}  # [W/m2] long-wave downward assuming clear sky
+        varlist = self.TranslateCF2short(dpar)
+        self.JRA2station(path.join(self.dir_raw,'jra55_sf_*.nc'),
+                         path.join(self.dir_inp,'jra_sf_' +
+                                   self.list_name + '.nc'), self.stations,
+                                   varlist, date=self.date)
 
         # 2D Interpolation for Pressure-Level, Analyzed Meteorological DATA
         # dictionary to translate CF Standard Names into MERRA2
-        # pressure level variable keys. 
+        # pressure level variable keys.
         dpar = {'air_temperature'   : ['air_temperature'],  # [K]
-                'relative_humidity' : ['relative_humidity'], # [%]
+                'relative_humidity' : ['relative_humidity'],  # [%]
                 'wind_speed'        : ['eastward_wind', 'northward_wind']}  # [m s-1]
         varlist = self.TranslateCF2short(dpar).append('geopotential_height')
-        self.JRA2station(path.join(self.dir_inp,'jra55_pl_*.nc'), 
-                          path.join(self.dir_out,'jra_pl_' + 
-                                    self.list_name + '.nc'), self.stations,
-                                    varlist, date = self.date)  
-                                                                                          
-        # 1D Interpolation for Pressure Level Data 
-        self.levels2elevation(path.join(self.dir_out,'jra_pl_' + 
-                                        self.list_name + '.nc'), 
-                              path.join(self.dir_out,'jra_pl_' + 
+        self.JRA2station(path.join(self.dir_raw,'jra55_pl_*.nc'),
+                         path.join(self.dir_inp,'jra_pl_' +
+                                   self.list_name + '.nc'), self.stations,
+                                   varlist, date=self.date)
+
+        # 1D Interpolation for Pressure Level Data
+        self.levels2elevation(path.join(self.dir_inp,'jra_pl_' +
+                                        self.list_name + '.nc'),
+                              path.join(self.dir_inp,'jra_pl_' +
                                         self.list_name + '_surface.nc'))
 
 
-   
-class JRAscale(object):
+class JRAscale(GenericScale):
     """
     Class for JRA-55 data that has methods for scaling station data to
     better resemble near-surface fluxes.
-    
+
     Processing kernels have names in UPPER CASE.
-       
+
     Args:
-        sfile: Full path to a Globsim Scaling Parameter file. 
-              
-    Example:          
-        JRAd = JRAscale(sfile) 
+        sfile: Full path to a Globsim Scaling Parameter file.
+
+    Example:
+        JRAd = JRAscale(sfile)
         JRAd.process()
     """
-        
+    NAME = "JRA-55"
+
     def __init__(self, sfile):
-        # read parameter file
-        self.sfile = sfile
-        par = ParameterIO(self.sfile)
-        self.intpdir = path.join(par.project_directory, 'interpolated')
-        self.scdir = self.makeOutDir(par)
-        self.list_name = par.station_list.split(path.extsep)[0]
-        
-        # read kernels
-        self.kernels = par.kernels
-        if not isinstance(self.kernels, list):
-            self.kernels = [self.kernels]
-            
+        super().__init__(sfile)
+        par = self.par
+
         # input file names
-        self.nc_pl = nc.Dataset(path.join(self.intpdir,'jra_pl_' + 
+        self.nc_pl = nc.Dataset(path.join(self.intpdir,'jra_pl_' +
                                 self.list_name + '_surface.nc'), 'r')
-        self.nc_sa = nc.Dataset(path.join(self.intpdir,'jra_sa_' + 
+        self.nc_sa = nc.Dataset(path.join(self.intpdir,'jra_sa_' +
                                 self.list_name + '.nc'), 'r')
-        self.nc_sf = nc.Dataset(path.join(self.intpdir,'jra_sf_' + 
+        self.nc_sf = nc.Dataset(path.join(self.intpdir,'jra_sf_' +
                                 self.list_name + '.nc'), 'r')
-                               
+
         # check if output file exists and remove if overwrite parameter is set
         self.output_file = self.getOutNCF(par, 'jra55')
-            
+
         # time vector for output data
         # get time and convert to datetime object
         nctime = self.nc_pl.variables['time'][:]
         self.t_unit = self.nc_pl.variables['time'].units
         self.t_cal  = self.nc_pl.variables['time'].calendar
-        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
-        
-        #number of time steps
-        self.nt = floor((max(time)-min(time)).total_seconds()/3600/par.time_step)+1
-        self.time_step = par.time_step
-                              
+        time = nc.num2date(nctime, units=self.t_unit, calendar=self.t_cal)
+
+        # number of time steps
+        self.nt = floor((max(time) - min(time)).total_seconds() / 3600 / par['time_step']) + 1
+        self.time_step = par['time_step']
+
         # vector of output time steps as datetime object
-        self.times_out    = [min(time) + timedelta(hours=x*par.time_step) 
-                                for x in range(0, self.nt)]
+        self.times_out    = [min(time) + timedelta(hours=x * par['time_step'])
+                             for x in range(0, self.nt)]
         # vector of output time steps as written in ncdf file
-        self.times_out_nc = nc.date2num(self.times_out, units = self.t_unit, 
-                                        calendar = self.t_cal)
-        # get the station file
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)
-        
-        
+        self.times_out_nc = nc.date2num(self.times_out, units=self.t_unit,
+                                        calendar=self.t_cal)
+
     def process(self):
         """
-        Run all relevant processes and save data. Each kernel processes one 
+        Run all relevant processes and save data. Each kernel processes one
         variable and adds it to the netCDF file.
-        """    
-        self.rg = ScaledFileOpen(self.output_file, self.nc_pl, 
-                                 self.times_out_nc, self.nc_pl['time'].units)
-        
+        """
+        self.rg = new_scaled_netcdf(self.output_file, self.nc_pl,
+                                    self.times_out_nc, self.nc_pl['time'].units)
+
         # add station names to netcdf
         # first convert to character array
         names_out = nc.stringtochar(np.array(self.stations['station_name'], 'S32'))
-        
+
         # create space in the netcdf
-        nchar        = self.rg.createDimension('name_strlen', 32) 
-        st           = self.rg.createVariable('station_name', "S1", 
+        _            = self.rg.createDimension('name_strlen', 32)
+        st           = self.rg.createVariable('station_name', "S1",
                                               ('station', 'name_strlen'))
         st.standard_name = 'platform_name'
         st.units     = ''
-        
+
         # add data
         st[:] = names_out
-        
+
         # iterate through kernels and start process
         for kernel_name in self.kernels:
             if hasattr(self, kernel_name):
                 print(kernel_name)
                 getattr(self, kernel_name)()
-            
+
         # self.conv_geotop()
-            
-        # close netCDF files   
+
+        # close netCDF files
         self.rg.close()
         self.nc_pl.close()
         self.nc_sf.close()
         self.nc_sa.close()
-        
-    def getOutNCF(self, par, src, scaleDir = 'scale'):
-        '''make out file name'''
-        
-        timestep = str(par.time_step) + 'h'
-        src = '_'.join(['scaled', src, timestep])
-        src = src + '.nc'
-        fname = path.join(self.scdir, src)
-        
-        return fname
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirSC = path.join(par.project_directory, 'scaled')
-        
-        if not (path.isdir(dirSC)):
-            makedirs(dirSC)
-            
-        return dirSC
-
 
     def PRESS_Pa_pl(self):
         """
         Surface air pressure from pressure levels.
-        """        
+        """
         # add variable to ncdf file
-        vn = 'PRESS_JRA55_Pa_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_pressure JRA-55 pressure levels only'
+        vn = 'PRESS_pl'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
+        var.long_name = 'air_pressure {} pressure levels only'.format(self.NAME)
         var.units     = 'Pa'
         var.standard_name = 'surface_air_pressure'
-        
+
         # interpolate station by station
-        time_in = self.nc_pl.variables['time'][:].astype(np.int64)  
+        time_in = self.nc_pl.variables['time'][:].astype(np.int64)
         values  = self.nc_pl.variables['air_pressure'][:]
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            #scale from hPa to Pa 
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                        time_in, values[:, n]) * 100
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            # scale from hPa to Pa
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                                             time_in,
+                                                             values[:, n]) * 100
 
     def AIRT_C_pl(self):
         """
         Air temperature derived from pressure levels, exclusively.
-        """        
-        vn = 'AIRT_JRA55_C_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_temperature JRA55 pressure levels only'
+        """
+        vn = 'AIRT_pl'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
+        var.long_name = 'air_temperature {} pressure levels only'.format(self.NAME)
         var.units     = 'degrees_C'
         var.standard_name = 'air_temperature'
-        
+
         # interpolate station by station
         time_in = self.nc_pl.variables['time'][:]
         values  = self.nc_pl.variables['Temperature'][:]
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n])-273.15            
-                
- 
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n]) - 273.15
+
     def AIRT_C_sur(self):
         """
         Air temperature derived from surface data, exclusively.
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'AIRT_JRA55_C_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '2_metre_temperature JRA55 surface only'
+        vn = 'AIRT_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '2_metre_temperature {} surface only'.format(self.NAME)
         var.units     = 'degrees_C'
         var.standard_name = 'air_temperature'
-        
+
         # interpolate station by station
         time_in = self.nc_sa.variables['time'][:]
-        values  = self.nc_sa.variables['Temperature'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n])-273.15  
+        values  = self.nc_sa.variables['Temperature'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n]) - 273.15
+
     def RH_per_sur(self):
         """
         Relative Humidity derived from surface data, exclusively.
-        """        
+        """
         # add variable to ncdf file
-        vn = 'RH_JRA55_per_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'relative humidity JRA55 surface only'
+        vn = 'RH_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'relative humidity {} surface only'.format(self.NAME)
         var.units     = 'percent'
         var.standard_name = 'relative_humidity'
-        
+
         # interpolate station by station
         time_in = self.nc_sa.variables['time'][:]
-        values  = self.nc_sa.variables['Relative humidity'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n])  
+        values  = self.nc_sa.variables['Relative humidity'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n])
 
     def WIND_sur(self):
         """
         Wind at 10 metre derived from surface data, exclusively.
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = '10 metre U wind component' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
+        vn = '10 metre U wind component'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
         var.long_name = '10 metre U wind component'
-        var.units     = str_encode(self.nc_sa.variables['u-component of wind'].units)  
-        
+        var.units     = str_encode(self.nc_sa.variables['u-component of wind'].units)
+
         # interpolate station by station
         time_in = self.nc_sa.variables['time'][:]
-        values  = self.nc_sa.variables['u-component of wind'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n]) 
+        values  = self.nc_sa.variables['u-component of wind'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n])
 
         # add variable to ncdf file
-        vn = '10 metre V wind component' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
+        vn = '10 metre V wind component'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
         var.long_name = '10 metre V wind component'
-        var.units     = str_encode(self.nc_sa.variables['v-component of wind'].units)  
-        
+        var.units     = str_encode(self.nc_sa.variables['v-component of wind'].units)
+
         # interpolate station by station
         time_in = self.nc_sa.variables['time'][:]
-        values  = self.nc_sa.variables['v-component of wind'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n]) 
+        values  = self.nc_sa.variables['v-component of wind'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n])
 
         # add variable to ncdf file
-        vn = 'WSPD_JRA55_ms_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 metre wind speed JRA55 surface only'
-        var.units     = 'm s-1'  
+        vn = 'WSPD_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '10 metre wind speed {} surface only'.format(self.NAME)
+        var.units     = 'm s-1'
         var.standard_name = 'wind_speed'
-        
+
         # add variable to ncdf file
-        vn = 'WDIR_JRA55_deg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 metre wind direction JRA55 surface only'
-        var.units     = 'degree'  
+        vn = 'WDIR_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '10 metre wind direction {} surface only'.format(self.NAME)
+        var.units     = 'degree'
         var.standard_name = 'wind_from_direction'
-                                
+
         # convert
         # u is the ZONAL VELOCITY, i.e. horizontal wind TOWARDS EAST.
         # v is the MERIDIONAL VELOCITY, i.e. horizontal wind TOWARDS NORTH.
         V = self.rg.variables['10 metre V wind component'][:]
-        U = self.rg.variables['10 metre U wind component'][:] 
+        U = self.rg.variables['10 metre U wind component'][:]
 
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            WS = np.sqrt(np.power(V,2) + np.power(U,2))
-            WD = [atan2(V[i, n], U[i, n])*(180/pi) + 
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            WS = np.sqrt(np.power(V, 2) + np.power(U, 2))
+            WD = [atan2(V[i, n], U[i, n]) * (180 / pi) +
                   180 for i in np.arange(V.shape[0])]
-            self.rg.variables['WSPD_JRA55_ms_sur'][:, n] = WS
-            self.rg.variables['WDIR_JRA55_deg_sur'][:,n] = WD
-    
+
+            self.rg.variables['WSPD_sur'][:, n] = WS
+            self.rg.variables['WDIR_sur'][:,n] = WD
 
     def SW_Wm2_sur(self):
         """
         solar radiation downwards derived from surface data, exclusively.
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'SW_JRA55_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface solar radiation downwards JRA-55 surface only'
+        vn = 'SW_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface solar radiation downwards {} surface only'.format(self.NAME)
         var.units     = 'W m-2'
         var.standard_name = 'surface_downwelling_shortwave_flux'
 
         # interpolate station by station
         time_in = self.nc_sf.variables['time'][:]
-        values  = self.nc_sf.variables['Downward solar radiation flux'][:]                                
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n]) 
+        values  = self.nc_sf.variables['Downward solar radiation flux'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n])
 
     def LW_Wm2_sur(self):
         """
         Long-wave radiation downwards derived from surface data, exclusively.
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'LW_JRA55_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface thermal radiation downwards JRA-55 surface only'
-        var.units     = 'W m-2' 
+        vn = 'LW_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface thermal radiation downwards {} surface only'.format(self.NAME)
+        var.units     = 'W m-2'
         var.standard_name = 'surface_downwelling_longwave_flux'
 
         # interpolate station by station
         time_in = self.nc_sf.variables['time'][:]
-        values  = self.nc_sf.variables['Downward longwave radiation flux'][:]                                
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc, 
-                                                    time_in, values[:, n]) 
+        values  = self.nc_sf.variables['Downward longwave radiation flux'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n])
 
     def PREC_mm_sur(self):
         """
         Precipitation derived from surface data, exclusively.
         Convert unit: mm/day to mm/time_step (hours)
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'PREC_JRA55_mm_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Total precipitation JRA55 surface only'
+        vn = 'PREC_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Total precipitation {} surface only'.format(self.NAME)
         var.units     = 'kg m-2 s-1'
         var.standard_name = 'precipitation_amount'
-        
+
         # interpolate station by station
         time_in = self.nc_sf.variables['time'][:]
-        values  = self.nc_sf.variables['Total precipitation'][:]/24/3600 #[mm/h]
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            f = interp1d(time_in, values[:, n], kind = 'linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc) * 3600           
+        values  = self.nc_sf.variables['Total precipitation'][:] / 24 / 3600  # [mm/h]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in, values[:, n], kind='linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc) * 3600
 
     def LW_Wm2_topo(self):
         """
         Long-wave radiation downwards [W/m2]
         https://www.geosci-model-dev.net/7/387/2014/gmd-7-387-2014.pdf
-        """             
+        """
         # get sky view, and interpolate in time
         N = np.asarray(list(self.stations['sky_view'][:]))
 
         # add variable to ncdf file
-        vn = 'LW_JRA55_Wm2_topo' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Incoming long-wave radiation JRA-55 surface only'
-        var.units     = str_encode('W m-2') 
+        vn = 'LW_topo'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Incoming long-wave radiation {} surface only'.format(self.NAME)
+        var.units     = str_encode('W m-2')
         var.standard_name = 'surface_downwelling_longwave_flux'
 
-        # compute                            
-        for i in range(0, len(self.rg.variables['RH_JRA55_per_sur'][:])):
+        # compute
+        for i in range(0, len(self.rg.variables['RH_sur'][:])):
             for n, s in enumerate(self.rg.variables['station'][:].tolist()):
-                LW = LW_downward(self.rg.variables['RH_JRA55_per_sur'][i, n],
-                     self.rg.variables['AIRT_JRA55_C_sur'][i, n]+273.15, N[n])
+                LW = LW_downward(self.rg.variables['RH_sur'][i, n],
+                                 self.rg.variables['AIRT_sur'][i, n] + 273.15, N[n])
                 self.rg.variables[vn][i, n] = LW
 
     def SH_kgkg_sur(self):
         '''
-        Specific humidity [kg/kg]
-        https://crudata.uea.ac.uk/cru/pubs/thesis/2007-willett/2INTRO.pdf
+        Specific humidity [kg/kg] derived from surface data, exclusively
         '''
-        print("Warning: SH_JRA_kgkg_sur is not defined. Specific humidity data are not currently available")
+        # add variable to ncdf file
+        vn = 'SH_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'specific humidity {} surface only'.format(self.NAME)
+        var.units     = 'kg/kg'
+        var.standard_name = 'specific_humidity'
+
+        # interpolate station by station
+        time_in = self.nc_sa.variables['time'][:]
+        values  = self.nc_sa.variables['Specific humidity'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = np.interp(self.times_out_nc,
+                                                    time_in, values[:, n])
diff --git a/__init__.py b/globsim/__init__.py
similarity index 82%
rename from __init__.py
rename to globsim/__init__.py
index 64bef68..559ec4d 100644
--- a/__init__.py
+++ b/globsim/__init__.py
@@ -2,6 +2,6 @@
 # from ecmwfapi.api import ECMWFDataServer
 # from era_interim import *
 # from globsim import *
-# from generic import *
+# from common_utils import *
 
 name = "globsim"
\ No newline at end of file
diff --git a/globsim/__main__.py b/globsim/__main__.py
new file mode 100644
index 0000000..a873ef9
--- /dev/null
+++ b/globsim/__main__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+"""
+globsim.__main__: executed when globsim directory is called as script.
+"""
+
+from globsim.globsim_cli import main
+
+main()
\ No newline at end of file
diff --git a/globsim/common_utils.py b/globsim/common_utils.py
new file mode 100644
index 0000000..8a033b7
--- /dev/null
+++ b/globsim/common_utils.py
@@ -0,0 +1,614 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Generic classes, methods, functions used for more than one reanalysis.
+#
+#
+# (C) Copyright Stephan Gruber (2017)
+#
+#     This program is free software: you can redistribute it and/or modify
+#     it under the terms of the GNU General Public License as published by
+#     the Free Software Foundation, either version 3 of the License, or
+#     (at your option) any later version.
+#
+#     This program is distributed in the hope that it will be useful,
+#     but WITHOUT ANY WARRANTY; without even the implied warranty of
+#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#     GNU General Public License for more details.
+#
+#     You should have received a copy of the GNU General Public License
+#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ===============================================================================
+from __future__ import print_function
+
+from datetime import datetime, timedelta
+from os import mkdir, path, makedirs, listdir
+from fnmatch import filter as fnmatch_filter
+
+import tomlkit
+import pandas as pd
+import netCDF4 as nc
+import numpy as np
+
+import glob
+
+import re
+
+# handle python 3 string types
+try:
+    basestring
+except NameError:
+    basestring = str
+
+try:
+    import ESMF
+
+    # Check ESMF version.  7.0.1 behaves differently than 7.1.0r
+    ESMFv = int(re.sub("[^0-9]", "", ESMF.__version__))
+    ESMFnew = ESMFv > 701
+except ImportError:
+    print("*** ESMF not imported, interpolation not possible. ***")
+    pass
+
+
+class GenericDownload(object):
+    """
+    Generic functionality for download classes
+    """
+
+    def __init__(self, pfile):
+        # read parameter file
+        self.pfile = pfile
+        with open(self.pfile) as FILE:
+            config = tomlkit.parse(FILE.read())
+            self.par = par = config['download']
+
+        self._set_elevation(par)
+        self._set_area(par)
+        self._check_area(par)
+
+        self.variables = par['variables']
+
+    def _check_area(self, par):
+        if (par['bbN'] < par['bbS']) or (par['bbE'] < par['bbW']):
+            raise ValueError("Bounding box is invalid: {}".format(self.area))
+
+        if (np.abs(par['bbN'] - par['bbS']) < 1.5) or (np.abs(par['bbE'] - par['bbW']) < 1.5):
+            raise ValueError("Download area is too small to conduct interpolation.")
+
+    def _set_area(self, par):
+        self.area = {'north': par['bbN'],
+                     'south': par['bbS'],
+                     'west': par['bbW'],
+                     'east': par['bbE']}
+
+    def _set_elevation(self, par):
+        self.elevation = {'min': par['ele_min'],
+                          'max': par['ele_max']}
+
+    def _set_data_directory(self, name):
+        self.directory = path.join(self.par['project_directory'], name)
+        if not path.isdir(self.directory):
+            makedirs(self.directory)
+
+
+class GenericInterpolate:
+
+    def __init__(self, ifile):
+        # read parameter file
+        self.ifile = ifile
+        with open(self.ifile) as FILE:
+            config = tomlkit.parse(FILE.read())
+            self.par = par = config['interpolate']
+        self.dir_inp = self.make_output_directory(par)
+        self.variables = par['variables']
+        self.list_name = par['station_list'].split(path.extsep)[0]
+        self.stations_csv = path.join(par['project_directory'],
+                                      'par', par['station_list'])
+
+        # read station points
+        self.stations = StationListRead(self.stations_csv)
+
+        # time bounds, add one day to par['end'] to include entire last day
+        self.date = {'beg': datetime.strptime(par['beg'], '%Y/%m/%d'),
+                     'end': datetime.strptime(par['end'], '%Y/%m/%d') +
+                     timedelta(days=1)}
+
+        # chunk size: how many time steps to interpolate at the same time?
+        # A small chunk size keeps memory usage down but is slow.
+        self.cs = int(par['chunk_size'])
+
+    def _set_data_directory(self, name):
+        self.dir_raw = path.join(self.par['project_directory'], name)
+
+    def TranslateCF2short(self, dpar):
+        """
+        Map CF Standard Names into short codes used in netCDF files.
+        """
+        varlist = []
+        for var in self.variables:
+            varlist.append(dpar.get(var))
+        # drop none
+        varlist = [item for item in varlist if item is not None]
+        # flatten
+        varlist = [item for sublist in varlist for item in sublist]
+        return(varlist)
+
+    def interp2D(self, ncfile_in, ncf_in, points, tmask_chunk,
+                 variables=None, date=None):
+        """
+        Bilinear interpolation from fields on regular grid (latitude, longitude)
+        to individual point stations (latitude, longitude). This works for
+        surface and for pressure level files
+
+        Args:
+            ncfile_in: Full path to an Era-Interim derived netCDF file. This can
+                       contain wildcards to point to multiple files if temporal
+                       chunking was used.
+
+            ncf_in: A netCDF4.MFDataset derived from reading in Era-Interim
+                    multiple files (def ERA2station())
+
+            points: A dictionary of locations. See method StationListRead in
+                    common_utils.py for more details.
+
+            tmask_chunk:
+
+            variables:  List of variable(s) to interpolate such as
+                        ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
+                        Defaults to using all variables available.
+
+            date: Directory to specify begin and end time for the derived time
+                  series. Defaluts to using all times available in ncfile_in.
+
+        Example:
+            from datetime import datetime
+            date  = {'beg' : datetime(2008, 1, 1),
+                     'end' : datetime(2008,12,31)}
+            variables  = ['t','u', 'v']
+            stations = StationListRead("points.csv")
+            ERA2station('era_sa.nc', 'era_sa_inter.nc', stations,
+                        variables=variables, date=date)
+        """
+
+        # is it a file with pressure levels?
+        pl = 'level' in ncf_in.dimensions.keys()
+        ens = 'number' in ncf_in.dimensions.keys()
+
+        # get spatial dimensions
+        if pl:  # only for pressure level files
+            lev = ncf_in.variables['level'][:]
+            nlev = len(lev)
+        if ens:
+            num = ncf_in.variables['number'][:]
+
+        # test if time steps to interpolate remain
+        nt = sum(tmask_chunk)
+        if nt == 0:
+            raise ValueError('No time steps from netCDF file selected.')
+
+        # get variables
+        varlist = [str_encode(x) for x in ncf_in.variables.keys()]
+        self.remove_select_variables(varlist, pl, ens=False)
+
+        # list variables that should be interpolated
+        if variables is None:
+            variables = varlist
+        # test is variables given are available in file
+        if (set(variables) < set(varlist) == 0):
+            raise ValueError('One or more variables not in netCDF file.')
+
+        sgrid = self.create_source_grid(ncfile_in)
+
+        # create source field on source grid
+        if ens:
+            sfield = []
+            for ni in num:
+                if pl:  # only for pressure level files
+                    sfield.append(
+                        ESMF.Field(sgrid, name='sgrid',
+                                   staggerloc=ESMF.StaggerLoc.CENTER,
+                                   ndbounds=[len(variables), nt, nlev]))
+                else:  # 2D files
+                    sfield.append(
+                        ESMF.Field(sgrid, name='sgrid',
+                                   staggerloc=ESMF.StaggerLoc.CENTER,
+                                   ndbounds=[len(variables), nt]))
+
+        else:
+            if pl:  # only for pressure level files
+                sfield = ESMF.Field(sgrid, name='sgrid',
+                                    staggerloc=ESMF.StaggerLoc.CENTER,
+                                    ndbounds=[len(variables), nt, nlev])
+            else:  # 2D files
+                sfield = ESMF.Field(sgrid, name='sgrid',
+                                    staggerloc=ESMF.StaggerLoc.CENTER,
+                                    ndbounds=[len(variables), nt])
+
+        self.nc_data_to_source_field(variables, sfield, ncf_in,
+                                     tmask_chunk, pl, ens)
+
+        locstream = self.create_loc_stream()
+
+        # create destination field
+        if ens:
+            dfield = []
+            for ni in num:
+                if pl:  # only for pressure level files
+                    di = ESMF.Field(locstream, name='dfield',
+                                    ndbounds=[len(variables), nt, nlev])
+                else:
+                    di = ESMF.Field(locstream, name='dfield',
+                                    ndbounds=[len(variables), nt])
+                dfield.append(self.regrid(sfield[ni], di))
+        else:
+            if pl:  # only for pressure level files
+                dfield = ESMF.Field(locstream, name='dfield',
+                                    ndbounds=[len(variables), nt, nlev])
+            else:
+                dfield = ESMF.Field(locstream, name='dfield',
+                                    ndbounds=[len(variables), nt])
+
+            dfield = self.regrid(sfield, dfield)
+
+        return dfield, variables
+
+    def make_output_directory(self, par):
+        """make directory to hold outputs"""
+
+        dirIntp = path.join(par['project_directory'], 'interpolated')
+
+        if not (path.isdir(dirIntp)):
+            makedirs(dirIntp)
+
+        return dirIntp
+
+    def create_source_grid(self, ncfile_in):
+        # Create source grid from a SCRIP formatted file. As ESMF needs one
+        # file rather than an MFDataset, give first file in directory.
+        flist = np.sort(fnmatch_filter(listdir(self.dir_raw),
+                                       path.basename(ncfile_in)))
+        ncsingle = path.join(self.dir_raw, flist[0])
+        sgrid = ESMF.Grid(filename=ncsingle, filetype=ESMF.FileFormat.GRIDSPEC)
+
+        return sgrid
+
+    def create_loc_stream(self):
+        # CANNOT have third dimension!!!
+        locstream = ESMF.LocStream(len(self.stations),
+                                   coord_sys=ESMF.CoordSys.SPH_DEG)
+        locstream["ESMF:Lon"] = list(self.stations['longitude_dd'])
+        locstream["ESMF:Lat"] = list(self.stations['latitude_dd'])
+
+        return locstream
+
+    @staticmethod
+    def regrid(sfield, dfield):
+        # regridding function, consider ESMF.UnmappedAction.ERROR
+        regrid2D = ESMF.Regrid(sfield, dfield,
+                               regrid_method=ESMF.RegridMethod.BILINEAR,
+                               unmapped_action=ESMF.UnmappedAction.IGNORE,
+                               dst_mask_values=None)
+
+        # regrid operation, create destination field (variables, times, points)
+        dfield = regrid2D(sfield, dfield)
+        sfield.destroy()  # free memory
+
+        return dfield
+
+    @staticmethod
+    def nc_data_to_source_field(variables, sfield, ncf_in,
+                                tmask_chunk, pl, ens):
+        # assign data from ncdf: (variable, time, latitude, longitude)
+
+        print(variables)
+        for n, var in enumerate(variables):
+            if ens:
+                for ni in ncf_in['number'][:]:
+                    if pl:
+                        # sfield.data [lon, lat, var, time, number, level]
+                        # vi [time, number, level, lat, lon]
+                        vi = ncf_in[var][tmask_chunk,ni,:,:,:]
+                        sfield[ni].data[:,:,n,:,:] = vi.transpose((3,2,0,1))
+                    else:
+                        vi = ncf_in[var][tmask_chunk,ni,:,:]
+                        sfield[ni].data[:,:,n,:] = vi.transpose((2,1,0))
+            else:
+                if pl:
+                    vi = ncf_in[var][tmask_chunk,:,:,:]
+                    sfield.data[:,:,n,:,:] = vi.transpose((3,2,0,1))
+
+                else:
+                    vi = ncf_in[var][tmask_chunk,:,:]
+                    sfield.data[:,:,n,:] = vi.transpose((2,1,0))
+
+    @staticmethod
+    def remove_select_variables(varlist, pl, ens=False):
+        varlist.remove('time')
+        varlist.remove('latitude')
+        varlist.remove('longitude')
+        if pl:  # only for pressure level files
+            varlist.remove('level')
+        if ens:
+            varlist.remove('number')
+
+    @staticmethod
+    def ele_interpolate(elevation, h, nl):
+        # difference in elevation, level directly above will be >= 0
+        elev_diff = elevation - h
+        # vector of level indices that fall directly above station.
+        # Apply after ravel() of data.
+        va = np.argmin(elev_diff + (elev_diff < 0) * 100000, axis=1)
+        # mask for situations where station is below lowest level
+        mask = va < (nl - 1)
+        va += np.arange(elevation.shape[0]) * elevation.shape[1]
+
+        # Vector level indices that fall directly below station.
+        # Apply after ravel() of data.
+        vb = va + mask  # +1 when OK, +0 when below lowest level
+
+        return elev_diff, va, vb
+
+    @staticmethod
+    def calculate_weights(elev_diff, va, vb):
+        wa = np.absolute(elev_diff.ravel()[vb])
+        wb = np.absolute(elev_diff.ravel()[va])
+        wt = wa + wb
+        wa /= wt  # Apply after ravel() of data.
+        wb /= wt  # Apply after ravel() of data.
+
+        return wa, wb
+
+
+class GenericScale:
+
+    def __init__(self, sfile):
+        # read parameter file
+        self.sfile = sfile
+        with open(self.sfile) as FILE:
+            config = tomlkit.parse(FILE.read())
+            self.par = par = config['scale']
+        self.intpdir = path.join(par['project_directory'], 'interpolated')
+        self.scdir = self.makeOutDir(par)
+        self.list_name = par['station_list'].split(path.extsep)[0]
+
+        # get the station file
+        self.stations_csv = path.join(par['project_directory'],
+                                      'par', par['station_list'])
+        # read station points
+        self.stations = StationListRead(self.stations_csv)
+
+        # read kernels
+        self.kernels = par['kernels']
+        if not isinstance(self.kernels, list):
+            self.kernels = [self.kernels]
+
+    def getOutNCF(self, par, src, scaleDir='scale'):
+        """make out file name"""
+
+        timestep = str(par['time_step']) + 'h'
+        src = '_'.join(['scaled', src, timestep])
+
+        src = src + '.nc'
+        fname = path.join(self.scdir, src)
+
+        return fname
+
+    def makeOutDir(self, par):
+        """make directory to hold outputs"""
+
+        dirSC = path.join(par['project_directory'], 'scaled')
+
+        if not (path.isdir(dirSC)):
+            makedirs(dirSC)
+
+        return dirSC
+
+
+def variables_skip(variable_name):
+    """
+    Which variable names to use? Drop the ones that are dimensions.
+    """
+    skip = 0
+    dims = ('time', 'number', 'level',
+            'latitude', 'longitude', 'station', 'height')
+    if variable_name in dims:
+        skip = 1
+    return skip
+
+
+def StationListRead(sfile):
+    """
+    Reads ASCII station list and returns a pandas dataframe.
+
+    # read station list
+    stations = StationListRead('examples/par/examples_list1.globsim_interpolate')
+    print(stations['station_number'])
+    """
+    # read file
+    raw = pd.read_csv(sfile)
+    raw = raw.rename(columns=lambda x: x.strip())
+    return(raw)
+
+
+def convert_cummulative(data):
+    """
+    Convert values that are serially cummulative, such as precipitation or
+    radiation, into a cummulative series from start to finish that can be
+    interpolated on for sacling.
+    data: 1-dimensional time series
+    """
+    # get increment per time step
+    diff = np.diff(data)
+    diff = np.concatenate(([data[0]], diff))
+
+    # where new forecast starts, the increment will be smaller than 0
+    # and the actual value is used
+    mask = diff < 0
+    diff[mask] = data[mask]
+
+    # get full cummulative sum
+    return np.cumsum(diff, dtype=np.float64)
+
+
+def cummulative2total(data, time):
+    """
+    Convert values that are serially cummulative, such as precipitation or
+    radiation, into a cummulative series from start to finish that can be
+    interpolated on for sacling.
+    data: 1-dimensional time series
+    """
+    # get increment per time step
+    diff = np.diff(data)
+    diff = np.concatenate(([data[0]], diff))
+
+    # where new forecast starts, the increment will be smaller than 0
+    # and the actual value is used
+
+    mask = [timei.hour in [3, 15] for timei in time]
+    diff[mask] = data[mask]
+
+    mask = diff < 0
+    diff[mask] = 0
+
+    return diff
+
+
+def get_begin_date(par, data_folder, match_strings):
+    """ Get the date to begin downloading when some files already exist
+
+    Parameters
+    ----------
+    par : dict
+        download section of configuration file as read in by tomlkit
+    data_folder : str
+        name of subdirectory containing data files. Examples: merra2, era5
+    match_strings : list
+        list of glob-style strings to check. Examples ["merra_pl*", "merra_sa*","merra_sf*"]
+    Returns
+    -------
+    datetime
+        datetime object corresponding to the desired begin date (replaces par['beg'])
+    This makes an inventory of all the files that have been downloaded so far and
+    returns the next date to begin downloading.  If all match_strings are downloaded up to the same
+    day, then the following day is returned. Otherwise, the
+    """
+    directory = par['project_directory']
+    print("Searching for existing files in directory")
+
+    if not all([len(glob.glob(path.join(directory, data_folder, s))) > 0 for s in match_strings]):
+        print("No existing files found. Starting download from {}".format(par['beg'].strftime("%Y-%m-%d")))
+        begin_date = datetime.strptime(par['beg'], '%Y/%m/%d')
+    else:
+        datasets = [nc.MFDataset(path.join(directory, data_folder, s)) for s in match_strings]
+        dates = [nc.num2date(x['time'][:], x['time'].units, x['time'].calendar) for x in datasets]
+
+        latest = [max(d) for d in dates]
+        latest = [dt.replace(hour=0, minute=0, second=0, microsecond=0) for dt in latest]
+        latest_complete = min(latest)
+        begin_date = latest_complete + timedelta(days=1)
+
+        print("Found some files in directory. Beginning download on {}".format(begin_date.strftime("%Y-%m-%d")))
+
+    return(begin_date)
+
+
+def series_interpolate(time_out, time_in, value_in, cum=False):
+    """
+    Interpolate single time series. Convenience function for usage in scaling
+    kernels.
+    time_out: Array of times [s] for which output is desired. Integer.
+    time_in:  Array of times [s] for which value_in is given. Integer.
+    value_in: Value time series. Must have same length as time_in.
+    cum:      Is valiable serially cummulative like LWin? Default: False.
+    """
+    time_step_sec = time_out[1] - time_out[0]
+
+    # convert to continuous cummulative, if values are serially cummulative
+    if cum:
+        value_in = convert_cummulative(value_in)
+
+    # interpolate
+    vi = np.interp(time_out, time_in, value_in)
+
+    # convert from cummulative to normal time series if needed
+    if cum:
+        vi = np.diff(vi) / time_step_sec
+        vi = np.float32(np.concatenate(([vi[0]], vi)))
+
+    return vi
+
+
+def str_encode(value, encoding="UTF8"):
+    """
+    handles encoding to allow compatibility between python 2 and 3
+    specifically with regards to netCDF variables. Python 2 imports
+    variable names as unicode, whereas python 3 imports them as str.
+    """
+    if type(value) == str:
+        return(value)
+    else:
+        return(value.encode(encoding))
+
+
+def create_globsim_directory(target_dir, name):
+    """
+    creates globsim directory
+    """
+    # create top-level
+    TL = path.join(target_dir, name)
+    mkdir(TL)
+
+    # create subdirectories
+    mkdir(path.join(TL, "eraint"))
+    mkdir(path.join(TL, "Grib"))
+    mkdir(path.join(TL, "jra55"))
+    mkdir(path.join(TL, "merra2"))
+    mkdir(path.join(TL, "par"))
+    mkdir(path.join(TL, "scale"))
+    mkdir(path.join(TL, "station"))
+    mkdir(path.join(TL, "era5"))
+
+    return(True)
+
+
+def get_begin_date(par, data_folder, match_strings):
+    """ Get the date to begin downloading when some files already exist
+
+    Parameters
+    ----------
+    par : dict
+        download section of configuration file as read in by tomlkit
+    data_folder : str
+        name of subdirectory containing data files. Examples: merra2, era5
+    match_strings : list
+        list of glob-style strings to check. Examples ["merra_pl*", "merra_sa*","merra_sf*"]
+
+    Returns
+    -------
+    datetime
+        datetime object corresponding to the desired begin date (replaces par['beg'])
+
+    This makes an inventory of all the files that have been downloaded so far and
+    returns the next date to begin downloading.  If all match_strings are downloaded up to the same
+    day, then the following day is returned. Otherwise, the
+    """
+    directory = par['project_directory']
+    print("Searching for existing files in directory")
+
+    if not all([len(glob.glob(path.join(directory, data_folder, s))) > 0 for s in match_strings]):
+        print("No existing files found. Starting download from {}".format(par['beg']))
+        return par['beg']
+
+    datasets = [nc.MFDataset(path.join(directory, data_folder, s)) for s in match_strings]
+    dates = [nc.num2date(x['time'][:], x['time'].units, x['time'].calendar) for x in datasets]
+
+    latest = [max(d) for d in dates]
+    latest = [dt.replace(hour=0, minute=0, second=0, microsecond=0) for dt in latest]
+    latest_complete = min(latest)
+
+    begin_date = latest_complete + timedelta(days=1)
+
+    print("Found some files in directory. Beginning download on {}".format(begin_date.strftime("%Y-%m-%d")))
+    return(begin_date)
diff --git a/globsim/data_check.py b/globsim/data_check.py
new file mode 100644
index 0000000..cbd5dea
--- /dev/null
+++ b/globsim/data_check.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python2.7
+# -*- coding: utf-8 -*-
+#
+# Copyright Xiaojing Quan & Stephan Gruber
+# ==============================================================================
+# A script for checking the continuity of Time with the given chunk for downloaded netCDF
+# files from ERA_Interim and MERRA-2,
+#
+# -- Step 1: read in downloaded netCDF files with a wildcard expression;
+# -- Step 2: check the time coverage of variables to be continuous;
+# -- Step 3: if there was any missing one, add to list the missing series of time
+#
+# Referenced from data_checker.py (Dr.Stephan Gruber): Class MERRA2DataCheck()
+# ===============================================================================
+from __future__ import print_function
+
+import tomlkit
+import netCDF4 as nc
+
+from os import path
+
+from globsim.common_utils import StationListRead
+
+
+class DataCheck(object):
+    """
+        To check  the continuity of the time coverage from all the downloaded
+        netCDF files with a wildward expression
+        Args:
+        ifile: Full path to interpolation parameter file.
+    """
+
+    def __init__(self, ifile, varF):
+        self.ifile = ifile                      # read parameter file for interpolation
+        with open(self.ifile) as FILE:  # Reads generic par files and makes values available as dictionary.
+            config = tomlkit.parse(FILE.read())
+            par = config['interpolate']
+            
+        # read file, access first reanalysis variable, access data_directory, access N of bounding box
+        self.dir_inp = path.join(par['project_directory'], varF)
+        self.dir_out = path.join(par['project_directory'], 'station')
+        self.variables = par['variables']
+
+        # self.list_name = par['list_name']
+        self.stations_csv = path.join(par['project_directory'],
+                                      'par', par['station_list'])
+
+        # read station points
+        self.stations = StationListRead(self.stations_csv)
+
+        # time bounds
+        self.date = {'beg': par['beg'], 'end': par['end']}
+
+    def findStep(self, file_in):
+        '''
+        Function for determining time step to be used to ensure all time data is present.
+        '''
+        f = file_in.split('/')
+        f = f[-1]
+        stepDic = {'merra_pl': 6, 'merra_sa': 1, 'merra_sf': 1, 'erai_sf_': 3,
+                   'erai_pl_': 6, 'erai_sa_': 6}
+        if f[:4] == 'era5':
+            step = 1
+        elif f[:5] == 'jra55':
+            step = 6
+        else:
+            step = stepDic.get(f[:8])
+        return step
+
+    def checkVar(self, ):
+        '''
+        Takes list of missing times
+        want to check individual variables for time gaps - long periods of data drought
+        '''
+
+    def DataReadin(self, file_in):
+        """
+        To read in all the downloaded netCDF files with a wildward expression
+        Args:
+            ncfile_in: Full path to an reanalysis derived netCDF file. This can
+                        contain wildcards to point to multiple files if temporal
+                        chunking was used.
+            ncfile_out: Full path to the output netCDF file to write.
+          """
+        # open netcdf file handle, can be one file of several with wildcards
+        # noinspection PyBroadException
+        try:
+            ncf = nc.MFDataset(file_in, 'r', aggdim='time')
+        except IndexError:
+            print('%s does not exist' % file_in)
+            return 0
+        # get variable 'time'
+        nctime = ncf.variables['time'][:]
+        t_unit = ncf.variables['time'].units
+        try:
+            t_cal = ncf.variables['time'].calendar
+        except AttributeError:  # Attribute doesn't exist
+            t_cal = u"gregorian"  # or standard
+        STEP = self.findStep(file_in)
+        # STEP DEFINED BY DATA TIME FREQUENCY (6HR, 3HR, 1HR)
+        try:
+            missingIndex = list(set(range(nctime[0], nctime[-1] + 1, STEP)) - set(nctime))
+        except TypeError:
+            missingIndex = list(set(range(int(nctime[0]), int(nctime[-1]) + 1, STEP)) - set(nctime))
+
+        if missingIndex == []:
+            print("NO MISSING TIME FOR", file_in)
+        else:
+            missingIndex.sort()
+            missingTime = nc.num2date(missingIndex, units=t_unit, calendar=t_cal)
+
+            print('FOR', file_in)
+
+            print("Number of Missing Indices of Time : ", len(missingIndex))
+
+            print("Unit of Time: ", t_unit)
+
+            print('Missing Time List:', list(missingTime))
+
+    def process(self, varF):
+        """
+        combine the specific type of single or mutiple netCDF files
+        """
+        # === Surface  Data, Pressure-level Data, Radiation Data ===
+        if varF == 'merra2':
+            varF = 'merra'
+        for d in ['sa', 'pl', 'sf']:
+            self.DataReadin(path.join(self.dir_inp, '%s_%s_*.nc' % (varF, d)))
\ No newline at end of file
diff --git a/globsim/era5.py b/globsim/era5.py
new file mode 100755
index 0000000..e8ef6a3
--- /dev/null
+++ b/globsim/era5.py
@@ -0,0 +1,1287 @@
+# -*- coding: utf-8 -*-
+#
+# Methods for downloading ERA5 data from the ECMWF server for limited
+# areas and limited times.
+#
+#
+# (C) Copyright Stephan Gruber (2013–2017)
+#
+#     This program is free software: you can redistribute it and/or modify
+#     it under the terms of the GNU General Public License as published by
+#     the Free Software Foundation, either version 3 of the License, or
+#     (at your option) any later version.
+#
+#     This program is distributed in the hope that it will be useful,
+#     but WITHOUT ANY WARRANTY; without even the implied warranty of
+#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#     GNU General Public License for more details.
+#
+#     You should have received a copy of the GNU General Public License
+#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#  OVERALL WORKFLOW
+#
+#  See: https://github.com/geocryology/globsim/wiki/Globsim
+#       and the examples directory on github
+#
+# ECMWF and netCDF information:
+# https://software.ecmwf.int/wiki/display/WEBAPI/Python+ERA-interim+examples
+#
+# For variable codes and units, see:
+#     http://www.ecmwf.int/publications/manuals/d/gribapi/param/
+#
+# Check ECMWF job status: http://apps.ecmwf.int/webmars/joblist/
+#
+#  CF-Convention: this is how you check netcdf file conformity:
+#  http://pumatest.nerc.ac.uk/cgi-bin/cf-checker-dev.pl
+#
+# ===============================================================================
+
+from __future__ import print_function
+
+import numpy as np
+import netCDF4 as nc
+import cdsapi
+
+from datetime import datetime, timedelta
+from math import floor, atan2, pi
+from os import path, listdir
+from scipy.interpolate import interp1d
+from fnmatch import filter
+
+import urllib3
+
+from globsim.common_utils import series_interpolate, variables_skip, str_encode, GenericDownload, GenericScale, GenericInterpolate
+from globsim.nc_elements import netcdf_base, new_interpolated_netcdf, new_scaled_netcdf
+from globsim.meteorology import spec_hum_kgkg, pressure_from_elevation
+
+urllib3.disable_warnings()
+
+
+class ERA5generic(object):
+    """
+    Parent class for other ERA5 classes.
+    """
+    CDS_DICT = {'130.128': 'temperature',
+                '157.128': 'relative_humidity',
+                '131.128': 'u_component_of_wind',
+                '132.128': 'v_component_of_wind',
+                '129.128': 'geopotential',
+                '167.128': '2m_temperature',
+                '168.128': '2m_dewpoint_temperature',
+                '206.128': 'total_column_ozone',         # also consider surface_solar_radiation_downward_clear_sky
+                '137.128': 'total_column_water_vapour',  # also consider surface_solar_radiation_downward_clear_sky
+                '165.128': '10m_u_component_of_wind',
+                '166.128': '10m_v_component_of_wind',
+                '228.128': 'total_precipitation',
+                '169.128': 'surface_solar_radiation_downwards',
+                '175.128': 'surface_thermal_radiation_downwards',
+                '172.128': 'land_sea_mask'}
+
+    def areaString(self, area):
+        """Converts numerical coordinates into string: North/West/South/East"""
+        res = str(round(area['north'],2)) + "/"
+        res += str(round(area['west'], 2)) + "/"
+        res += str(round(area['south'],2)) + "/"
+        res += str(round(area['east'], 2))
+        return(res)
+
+    def timeString(self, era5type):
+
+        if era5type == 'ensemble_members':
+            times = np.arange(0, 24, 3)
+        elif era5type == 'reanalysis':
+            times = np.arange(0, 24)
+
+        times = [str(t).zfill(2) for t in times]
+        times = [t + ':00' for t in times]
+
+        return times
+
+    def dateString(self, date):
+        """Converts datetime objects into string"""
+        res = (date['beg'].strftime("%Y-%m-%d") + "/" +
+               date['end'].strftime("%Y-%m-%d"))
+        return(res)
+
+    def typeString(self, era5type):
+
+        if era5type == 'ensemble_members':
+            return 'era5_ens'
+        elif era5type == 'reanalysis':
+            return 'era5_rea'
+
+    def getPressureLevels(self, elevation):
+        """Restrict list of ERA5 pressure levels to be downloaded"""
+        Pmax = pressure_from_elevation(elevation['min']) + 55
+        Pmin = pressure_from_elevation(elevation['max']) - 55
+        levs = np.array([300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 775,
+                         800, 825, 850, 875, 900, 925, 950, 975, 1000])
+        mask = (levs >= Pmin) * (levs <= Pmax)  # select
+        levs = [str(levi) for levi in levs[mask]]
+        return levs
+
+    def getDictionaryGen(self, area, date):
+        """
+        Makes dictionary of generic variables for a server call
+        """
+        dictionary_gen = {
+            'area'    : self.areaString(area),
+            'date'    : self.dateString(date),
+            'dataset' : "era5",
+            'stream'  : "oper",
+            'class'   : "ea",
+            'format'  : "netcdf"}
+        return dictionary_gen
+
+    def getDstring(self):
+        return ('_' + self.date['beg'].strftime("%Y%m%d")
+                + "_to_"
+                + self.date['end'].strftime("%Y%m%d"))
+
+    def getDownFile(self, levStr):
+        '''make outfile name with data level string'''
+        typeStr = self.typeString(self.era5type)
+        outfile = typeStr + '_' + levStr + self.getDstring() + '.nc'
+        return outfile
+
+    def download(self, storage='cds'):
+        ''' 'patched' download function to allow download using cdsapi from CDS servers'''
+        server = cdsapi.Client()
+
+        query = self.getDictionary()
+        target = query.pop('target')
+        levtype = query.pop('levtype')
+
+        # select dataset
+        dataset = {'pl': 'reanalysis-era5-pressure-levels',
+                   'sfc': 'reanalysis-era5-single-levels'
+                   }[levtype]
+        query = self.ECM2CDS(query)
+
+        # launch download request
+        print(server.info('=== ERA5 ({}API): START ACCESS ON {} ===='.format("CDS", storage.upper())))
+        if path.isfile(target):
+            print("WARNING: File '{}' already exists and was skipped".format(target))
+        else:
+            server.retrieve(dataset, query, target)
+        print(server.info('=== ERA5 ({}API): END ACCESS ON {} ===='.format("CDS", storage.upper())))
+
+    def ECM2CDS(self, query):
+        ''' convert ECMWF query to CDS format '''
+
+        # remove unnecessary keys
+        for key in ['class', 'dataset', 'stream', 'type']:
+            query.pop(key)
+
+        # replace key('param'):val(str)  string with key('variables'):val(list)
+        query['variable'] = [self.CDS_DICT[L] for L in query.pop('param').split('/')]
+
+        return(query)
+
+    def TranslateCF2ERA(self, variables, dpar):
+        """
+        Translate CF Standard Names into ERA5 code numbers.
+        """
+        self.param = ''
+        for var in variables:
+            try:
+                self.param += dpar.get(var) + '/'
+
+            except TypeError:
+                pass
+        self.param = self.param.rstrip('/')  # fix last
+
+    def __str__(self):
+        string = ("List of generic variables to query ECMWF server for "
+                  "ERA5 data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERA5pl(ERA5generic):
+    """Returns an object for ERA5 data that has methods for querying the
+    ECMWF server.
+
+    Args:
+        date: A dictionary specifying the time period desired with a begin
+              and an end date given as a datetime.datetime object.
+
+        area: A dictionary delimiting the area to be queried with the latitudes
+              north and south, and the longitudes west and east [decimal deg].
+
+        elevation: A dictionary specifying the min/max elevation of the area of
+                   interest. This is used to determine the pressure levels
+                   needed. Unit: [m].
+
+        variables:  List of variable(s) to download based on CF Standard Names.
+
+        directory: Directory to hold output files
+
+    Example:
+        from datetime import datetime
+        date  = {'beg' : datetime(1994, 1, 1),
+                 'end' : datetime(2013, 1, 1)}
+        area  = {'north' :  40.0,
+                 'south' :  15.0,
+                 'west'  :  60.0,
+                 'east'  : 105.0}
+        elevation = {'min' :    0,
+                     'max' : 8850}
+        variables  = ['air_temperature', 'relative_humidity']
+        directory = '/Users/stgruber/Desktop'
+        ERA5pl = ERA5pl(date, area, elevation, variables, directory)
+        ERA5pl.download()
+    """
+    def __init__(self, era5type, date, area, elevation, variables, directory):
+        self.era5type = era5type
+        self.date       = date
+        self.area       = area
+        self.elevation  = elevation
+        self.directory  = directory
+        self.file_ncdf  = path.join(self.directory, self.getDownFile('pl'))
+
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable names.
+        dpar = {'air_temperature'   : '130.128',           # [K]
+                'relative_humidity' : '157.128',           # [%]
+                'wind_speed'        : '131.128/132.128'}   # [m s-1]
+
+        # translate variables into those present in ERA pl data
+        self.TranslateCF2ERA(variables, dpar)
+        self.param += '/129.128'  # geopotential always needed [m2 s-2]
+
+    def getDictionary(self):
+        self.dictionary = {'product_type': self.era5type,
+                           'levtype'  : "pl",
+                           'levellist': self.getPressureLevels(self.elevation),
+                           'time'     : self.timeString(self.era5type),
+                           'step'     : "0",
+                           'type'     : "an",
+                           'param'    : self.param,
+                           'target'   : self.file_ncdf
+                           }
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("List of variables to query ECMWF server for "
+                  "ERA5 data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERA5sa(ERA5generic):
+    """
+    Returns an object for ERA5 data that has methods for querying the
+    ECMWF server for surface analysis variables (airt2,dewp2, ozone, vapor,
+    wind10).
+
+    Args:
+
+        target:    File name of the netcdf file to be created.
+
+        variable:  List of variable(s) to download based on CF Standard Names
+
+    Example:
+        from datetime import datetime
+        date  = {'beg' : datetime(1994, 1, 1),
+                 'end' : datetime(2013, 1, 1)}
+        area  = {'north' :  40.0,
+                 'south' :  15.0,
+                 'west'  :  60.0,
+                 'east'  : 105.0}
+        variables  = ['air_temperature', 'relative_humidity']
+        directory = '/Users/stgruber/Desktop'
+        ERA5sa = ERA5sa(date, area, variables, directory)
+        ERA5sa.download()
+    """
+    def __init__(self, era5type, date, area, variables, directory):
+        self.era5type = era5type
+        self.date       = date
+        self.area       = area
+        self.directory  = directory
+        outfile         = self.getDownFile('sa')
+        self.file_ncdf  = path.join(self.directory, outfile)
+
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable names.
+        dpar = {'air_temperature': '167.128',  # [K] 2m values
+                'relative_humidity': '168.128',  # [K] 2m values
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky':
+                    '206.128/137.128',  # [kg m-2] Total column ozone
+                                        # [kg m-2] Total column W vapor
+                'wind_speed': '165.128/166.128'}   # [m s-1] 10m values
+
+        # translate variables into those present in ERA pl data
+        self.TranslateCF2ERA(variables, dpar)
+
+    def getTime(self):
+
+        times = np.arange(0, 24)
+        times = [str(t).zfill(2) for t in times]
+        times = [t + ':00:00' for t in times]
+        times = '/'.join(times)
+
+        return times
+
+    def getDictionary(self):
+        self.dictionary = {'product_type': self.era5type,
+                           'levtype'  : "sfc",
+                           'time'     : self.timeString(self.era5type),
+                           'step'     : "0",
+                           'type'     : "an",
+                           'param'    : self.param,
+                           'target'   : self.file_ncdf
+                           }
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("Class for ERA5 surface analysis data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERA5sf(ERA5generic):
+    """
+    Returns an object for ERA5 data that has methods for querying the
+    ECMWF server for surface forecast variables (prec, swin, lwin).
+
+    Args:
+        target:    File name of the netcdf file to be created.
+
+        variables:  List of variable(s) to download that can include one, several
+                   , or all of these: ['airt', 'rh', 'geop', 'wind'].
+
+        ERAgen:    ERA5generic() object with generic information on the area and
+                   time span to be
+
+    Example:
+        from datetime import datetime
+        date  = {'beg' : datetime(1994, 1, 1),
+                 'end' : datetime(1994, 1, 1)}
+        area  = {'north' :  40.0,
+                 'south' :  40.0,
+                 'west'  :  60.0,
+                 'east'  :  60.0}
+        variables  = ['prec','swin','lwin']
+        directory = '/Users/stgruber/Desktop'
+        ERA5sf = ERA5sf(date, area, variables, directory)
+        ERA5sf.download()
+    """
+    def __init__(self, era5type, date, area, variables, directory):
+        self.era5type = era5type
+        self.date       = date
+        self.area       = area
+        self.directory  = directory
+        outfile         = self.getDownFile('sf')
+        self.file_ncdf  = path.join(self.directory, outfile)
+
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable names.
+        # [m] total precipitation
+        # [J m-2] short-wave downward
+        # [J m-2] long-wave downward
+        dpar = {'precipitation_amount'              : '228.128',  # [kg m**-2 s**-1]
+                'downwelling_shortwave_flux_in_air' : '169.128',  # [J m**-2]
+                'downwelling_longwave_flux_in_air'  : '175.128'}  # [J m**-2]
+
+        # translate variables into those present in ERA pl data
+        self.TranslateCF2ERA(variables, dpar)
+
+    def getStep(self):
+        steps = np.arange(1, 13)
+        steps = [str(s) for s in steps]
+        steps = '/'.join(steps)
+
+        return steps
+
+    def getDictionary(self):
+
+        self.dictionary = {'product_type': self.era5type,
+                           'levtype'  : "sfc",
+                           'time'     : self.timeString(self.era5type),
+                           'step'     : self.getStep(),
+                           'type'     : "fc",
+                           'param'    : self.param,
+                           'target'   : self.file_ncdf}
+
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("List of variables to query ECMWF server for "
+                  "ERA5 air tenperature data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERA5to(ERA5generic):
+    """
+    Returns an object for downloading and handling ERA5
+    topography (invariant).
+
+    Args:
+
+    Example:
+        area  = {'north' :  40.0,
+                 'south' :  45.0,
+                 'west'  :  60.0,
+                 'east'  :  65.0}
+        directory = '/Users/stgruber/Desktop'
+        ERA5to = ERA5to(area, directory)
+        ERA5to.download()
+    """
+    def __init__(self, era5type, area, directory):
+        self.era5type   = era5type
+        self.area       = area
+        self.date       = {'beg' : datetime(2017, 1, 1),
+                           'end' : datetime(2017, 1, 1)}
+        self.directory  = directory
+        self.file_ncdf  = path.join(self.directory, self.typeString(self.era5type) + '_to.nc')
+
+    def getDictionary(self):
+        self.dictionary = {
+           'product_type' : self.era5type,
+           'levtype'  : "sfc",
+           'time'     : "00:00:00",
+           'step'     : "0",
+           'type'     : "an",
+           'param'    : "129.128/172.128",  # geopotential and land-sea mask
+           'target'   : self.file_ncdf}
+
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("List of variables to query ECMWF server for "
+                  "ERA5 air tenperature data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERA5download(GenericDownload, ERA5generic):
+    """
+    Class for ERA5 data that has methods for querying
+    the ECMWF server, returning all variables usually needed.
+
+    Args:
+        pfile: Full path to a Globsim Download Parameter file.
+        api  : Which API to use. Either 'cds' (default) or 'ecmwf' (deprecated'
+        storage : Which server to access data from. Either 'cds'
+                    (default) or 'ecmwf' (deprecated). Note
+                 that you can use the cds api to access the ecmwf storage (MARS)
+    Example:
+        ERAd = ERA5download(pfile)
+        ERAd.retrieve()
+    """
+
+    def __init__(self, pfile, era5type='reanalysis', api='cds', storage='cds'):
+        super().__init__(pfile)
+        par = self.par
+        self.era5type = era5type
+
+        if self.era5type == 'reanalysis':
+            self._set_data_directory("era5")
+            self.topo_file = 'era5_rea_to.nc'
+            
+        elif self.era5type == 'ensemble_members':
+            self._set_data_directory("era5ens")
+            self.topo_file = 'era5_ens_to.nc'
+            
+        # time bounds
+        self.date = {'beg': datetime.strptime(par['beg'], '%Y/%m/%d'),
+                     'end': datetime.strptime(par['end'], '%Y/%m/%d')}
+
+        # chunk size for downloading and storing data [days]
+        self.chunk_size = par['chunk_size']
+
+        # set api and download server/storage
+        self.api = api
+        self.storage = storage
+
+    def retrieve(self):
+        """
+        Retrieve all required ERA5 data from MARS/CDS server.
+        """
+        # prepare time loop
+        date_i = {}
+        slices = floor(float((self.date['end'] - self.date['beg']).days) /
+                       self.chunk_size) + 1
+
+        # topography
+        if path.isfile(path.join(self.directory, self.topo_file)):
+            print(f"WARNING: File {self.topo_file} already exists. Skipping.")
+        else:
+            top = ERA5to(self.era5type, self.area, self.directory)
+            top.download(self.storage)
+
+        for ind in range(0, int(slices)):
+            # prepare time slices
+            date_i['beg'] = (self.date['beg']
+                             + timedelta(days=self.chunk_size * ind))
+            date_i['end'] = (self.date['beg']
+                             + timedelta(days=self.chunk_size * (ind + 1) - 1))
+            if ind == (slices - 1):
+                date_i['end'] = self.date['end']
+
+            # actual functions
+            pl = ERA5pl(self.era5type, date_i, self.area, self.elevation,
+                        self.variables, self.directory)
+            sa = ERA5sa(self.era5type, date_i, self.area,
+                        self.variables, self.directory)
+            sf = ERA5sf(self.era5type, date_i, self.area,
+                        self.variables, self.directory)
+
+            ERAli = [pl, sa, sf]
+            for era in ERAli:
+                era.download(self.storage)
+
+        # report inventory
+        self.inventory()
+
+    def inventory(self):
+        """
+        Report on data avaialbe in directory: time slice, variables, area
+        """
+        print("\n\n\n")
+        print("=== INVENTORY FOR GLOBSIM ERA5 DATA ===\n")
+        print("Download parameter file: \n" + self.pfile + "\n")
+        # loop over filetypes, read, report
+        file_type = [self.typeString(self.era5type) + '_pl_*.nc',
+                     self.typeString(self.era5type) + '_sa_*.nc',
+                     self.typeString(self.era5type) + '_sf_*.nc',
+                     self.typeString(self.era5type) + '_t*.nc']
+        for ft in file_type:
+            infile = path.join(self.directory, ft)
+            nf = len(filter(listdir(self.directory), ft))
+            print(str(nf) + " FILE(S): " + infile)
+
+            if nf > 0:
+                # open dataset
+                ncf = nc.MFDataset(infile, 'r', aggdim='time')
+
+                # list variables
+                keylist = [str_encode(x) for x in ncf.variables.keys()]
+                print("    VARIABLES:")
+                print("        " + str(len(keylist)) +
+                      " variables, including dimensions")
+                for key in keylist:
+                    print("        " + ncf.variables[key].long_name)
+
+                # time slice
+                time = ncf.variables['time']
+
+                tmin = nc.num2date(min(time[:]), time.units,
+                                   calendar=time.calendar).strftime('%Y/%m/%d')
+                tmax = nc.num2date(max(time[:]), time.units,
+                                   calendar=time.calendar).strftime('%Y/%m/%d')
+
+                print("    TIME SLICE")
+                print("        " + str(len(time[:])) + " time steps")
+                print("        " + tmin + " to " + tmax)
+
+                # area
+                lon = ncf.variables['longitude']
+                lat = ncf.variables['latitude']
+                nlat = str(len(lat))
+                nlon = str(len(lon))
+                ncel = str(len(lat) * len(lon))
+                print("    BOUNDING BOX / AREA")
+                print("        " + ncel + " cells, " + nlon +
+                      " W-E and " + nlat + " S-N")
+                print("        N: " + str(max(lat)))
+                print("        S: " + str(min(lat)))
+                print("        W: " + str(min(lon)))
+                print("        E: " + str(max(lon)))
+
+                ncf.close()
+
+    def __str__(self):
+        return "Object for ERA5 data download and conversion"
+
+
+class ERA5interpolate(GenericInterpolate):
+    """
+    Collection of methods to interpolate ERA5 netCDF files to station
+    coordinates. All variables retain their original units and time stepping.
+    """
+
+    def __init__(self, ifile, era5type='reanalysis'):
+        super().__init__(ifile)
+        self.era5type = era5type
+
+        if self.era5type == 'reanalysis':
+            self._set_data_directory("era5")
+            self.ens = False
+        elif self.era5type == 'ensemble_members':
+            self._set_data_directory("era5ens")
+            self.ens = True
+
+        # convert longitude to ERA notation if using negative numbers
+        self.stations['longitude_dd'] = self.stations['longitude_dd'] % 360
+
+    def getInFile(self, levStr):
+
+        if self.ens:
+            typeStr = 'ens'
+        else:
+            typeStr = 'rea'
+
+        nome = 'era5_{}_{}_*.nc'
+
+        if levStr == 'to':
+            infile = path.join(self.dir_raw, 'era5_{}_to.nc'.format(typeStr))
+        else:
+            infile = path.join(self.dir_raw, nome.format(typeStr, levStr))
+
+        return infile
+
+    def getOutFile(self, levStr):
+
+        if self.ens:
+            nome = 'era5_ens_{}_'.format(levStr) + self.list_name + '.nc'
+        else:
+            nome = 'era5_rea_{}_'.format(levStr) + self.list_name + '.nc'
+        outfile = path.join(self.dir_inp, nome)
+
+        return outfile
+
+    def ERA2station(self, ncfile_in, ncfile_out, points,
+                    variables=None, date=None):
+
+        """
+        Biliner interpolation from fields on regular grid (latitude, longitude)
+        to individual point stations (latitude, longitude). This works for
+        surface and for pressure level files (all ERA5 files). The type
+        of variable and file structure are determined from the input.
+
+        This function creates an empty of netCDF file to hold the interpolated
+        results, by calling new_interpolated_netcdf(). Then, data is
+        interpolated in temporal chunks and appended. The temporal chunking can
+        be set in the interpolation parameter file.
+
+        Args:
+        ncfile_in: Full path to an ERA5 derived netCDF file. This can
+                   contain wildcards to point to multiple files if temporal
+                  chunking was used.
+
+        ncfile_out: Full path to the output netCDF file to write.
+
+        points: A dictionary of locations. See method StationListRead in
+                common_utils.py for more details.
+
+        variables:  List of variable(s) to interpolate such as
+                    ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
+                    Defaults to using all variables available.
+
+        date: Directory to specify begin and end time for the derived time
+                series. Defaluts to using all times available in ncfile_in.
+        """
+
+        # read in one type of mutiple netcdf files
+        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim='time')
+
+        # is it a file with pressure levels?
+        pl = 'level' in ncf_in.dimensions.keys()
+        ens = 'number' in ncf_in.dimensions.keys()
+
+        # build the output of empty netCDF file
+        rootgrp = new_interpolated_netcdf(ncfile_out, self.stations, ncf_in,
+                                          time_units='hours since 1900-01-01 00:00:0.0')
+        if self.ens:
+            rootgrp.source = 'ERA5 10-member ensemble, interpolated bilinearly to stations'
+        else:
+            rootgrp.source = 'ERA5, interpolated bilinearly to stations'
+
+        rootgrp.close()
+
+        # open the output netCDF file, set it to be appendable ('a')
+        ncf_out = nc.Dataset(ncfile_out, 'a')
+
+        # get time and convert to datetime object
+        nctime = ncf_in.variables['time'][:]
+        # "hours since 1900-01-01 00:00:0.0"
+        t_unit = ncf_in.variables['time'].units
+        try:
+            t_cal = ncf_in.variables['time'].calendar
+        except AttributeError:  # attribute doesn't exist
+            t_cal = u"gregorian"  # standard
+        time = nc.num2date(nctime, units=t_unit, calendar=t_cal)
+
+        # detect invariant files (topography etc.)
+        invariant = True if len(time) == 1 else False
+
+        # restrict to date/time range if given
+        if date is None:
+            tmask = time < datetime(3000, 1, 1)
+        else:
+            tmask = (time < date['end']) * (time >= date['beg'])
+
+        # get time vector for output
+        time_in = nctime[tmask]
+
+        # ensure that chunk sizes cover entire period even if
+        # len(time_in) is not an integer multiple of cs
+        niter = len(time_in) // self.cs
+        niter += ((len(time_in) % self.cs) > 0)
+
+        # loop over chunks
+        for n in range(niter):
+            # indices (relative to index of the output file)
+            beg = n * self.cs
+            # restrict last chunk to lenght of tmask plus one (to get last time)
+            end = min(n * self.cs + self.cs, len(time_in)) - 1
+
+            # time to make tmask for chunk
+            beg_time = nc.num2date(time_in[beg], units=t_unit, calendar=t_cal)
+            if invariant:
+                # allow topography to work in same code, len(nctime) = 1
+                end_time = nc.num2date(nctime[0], units=t_unit, calendar=t_cal)
+                # end = 1
+            else:
+                end_time = nc.num2date(time_in[end], units=t_unit, calendar=t_cal)
+
+            # '<= end_time', would damage appending
+            tmask_chunk = (time <= end_time) * (time >= beg_time)
+            if invariant:
+                # allow topography to work in same code
+                tmask_chunk = [True]
+
+            # get the interpolated variables
+            dfield, variables = self.interp2D(ncfile_in, ncf_in,
+                                              self.stations, tmask_chunk,
+                                              variables=None, date=None)
+            # append time
+            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:],
+                                                     time_in[beg:end+1])
+
+            # append variables
+            for i, var in enumerate(variables):
+                if variables_skip(var):
+                    continue
+
+                if ens:
+                    num = ncf_in.variables['number'][:]
+                    for ni in num:
+                        if pl:
+                            # dfield [station, variables, time, levels, number]
+                            vi = dfield[ni].data[:,i,:,:].transpose((1,2,0))
+                            ncf_out.variables[var][beg:end+1,ni,:,:] = vi
+                        else:
+                            vi = dfield[ni].data[:,i,:].transpose((1,0))
+                            ncf_out.variables[var][beg:end+1,ni,:] = vi
+                else:
+                    if pl:
+                        vi = dfield.data[:,i,:,:].transpose((1,2,0))
+                        ncf_out.variables[var][beg:end+1,:,:] = vi
+                    else:
+                        vi = dfield.data[:,i,:].transpose((1,0))
+                        ncf_out.variables[var][beg:end+1,:] = vi
+
+        # close the file
+        ncf_in.close()
+        ncf_out.close()
+
+    def levels2elevation(self, ncfile_in, ncfile_out):
+        """
+        Linear 1D interpolation of pressure level data available for individual
+        stations to station elevation. Where and when stations are below the
+        lowest pressure level, they are assigned the value of the lowest
+        pressure level.
+
+        """
+        # open file
+        # TODO: check the aggdim does not work
+        ncf = nc.MFDataset(ncfile_in, 'r', aggdim='time')
+        height = ncf.variables['height'][:]
+        nt = len(ncf.variables['time'][:])
+        nl = len(ncf.variables['level'][:])
+
+        # list variables
+        varlist = [str_encode(x) for x in ncf.variables.keys()]
+        for V in ['time', 'station', 'latitude', 'longitude',
+                  'level','height','z']:
+            varlist.remove(V)
+        if self.ens:
+            varlist.remove('number')
+
+        # === open and prepare output netCDF file ==============================
+        # dimensions: station, time
+        # variables: latitude(station), longitude(station), elevation(station)
+        #            others: ...(time, station)
+        # stations are integer numbers
+        # create a file (Dataset object, also the root group).
+        rootgrp = netcdf_base(ncf, ncfile_out, len(height), nt,
+                              'hours since 1900-01-01 00:00:0.0')
+        if self.ens:
+            rootgrp.source = 'ERA5 10-member ensemble, interpolated (bi)linearly to stations'
+        else:
+            rootgrp.source = 'ERA5, interpolated (bi)linearly to stations'
+
+        time = rootgrp['time']
+        station = rootgrp['station']
+        latitude = rootgrp['latitude']
+        longitude = rootgrp['longitude']
+        height = rootgrp['height']
+
+        # assign base variables
+        time[:]      = ncf.variables['time'][:]
+        station[:]   = ncf.variables['station'][:]
+        latitude[:]  = ncf.variables['latitude'][:]
+        longitude[:] = ncf.variables['longitude'][:]
+        height[:]    = ncf.variables['height'][:]
+
+        # create and assign variables from input file
+        for var in varlist:
+            if self.ens:
+                tmp = rootgrp.createVariable(var,
+                                             'f4',('time','number','station'))
+            else:
+                tmp = rootgrp.createVariable(var,'f4',('time', 'station'))
+            tmp.long_name = str_encode(ncf.variables[var].long_name)
+            tmp.units     = str_encode(ncf.variables[var].units)
+
+        # add air pressure as new variable
+        var = 'air_pressure'
+        varlist.append(var)
+        if self.ens:
+            tmp = rootgrp.createVariable(var,'f4',('time','number','station'))
+        else:
+            tmp = rootgrp.createVariable(var,'f4',('time','station'))
+        tmp.long_name = var.encode('UTF8')
+        tmp.units = 'hPa'.encode('UTF8')
+        # end file prepation ===================================================
+
+        # loop over stations
+        for n, h in enumerate(height):
+            if self.ens:
+                num = ncf.variables['number'][:]
+                for ni in num:
+                    elevation = ncf.variables['z'][:,ni,:,n] / 9.80665
+                    elev_diff, va, vb = self.ele_interpolate(elevation, h, nl)
+                    wa, wb = self.calculate_weights(elev_diff, va, vb)
+                    for v, var in enumerate(varlist):
+                        if var == 'air_pressure':
+                            # pressure [Pa] variable from levels, shape: (time, level)
+                            data = np.repeat([ncf.variables['level'][:]],
+                                             len(time),axis=0).ravel()
+                        else:
+                            # read data from netCDF
+                            data = ncf.variables[var][:,ni,:,n].ravel()
+
+                        ipol = data[va] * wa + data[vb] * wb   # interpolated value
+                        rootgrp.variables[var][:,ni,n] = ipol  # assign to file
+            else:
+                # convert geopotential [mbar] to height [m], shape: (time, level)
+                elevation = ncf.variables['z'][:,:,n] / 9.80665
+                elev_diff, va, vb = self.ele_interpolate(elevation, h, nl)
+                wa, wb = self.calculate_weights(elev_diff, va, vb)
+
+                # loop over variables and apply interpolation weights
+                for v, var in enumerate(varlist):
+                    if var == 'air_pressure':
+                        # pressure [Pa] variable from levels, shape: (time, level)
+                        data = np.repeat([ncf.variables['level'][:]],
+                                         len(time),axis=0).ravel()
+                    else:
+                        # read data from netCDF
+                        data = ncf.variables[var][:,:,n].ravel()
+
+                    ipol = data[va] * wa + data[vb] * wb   # interpolated value
+                    rootgrp.variables[var][:,n] = ipol  # assign to file
+
+        rootgrp.close()
+        ncf.close()
+        # closed file ==========================================================
+
+    def process(self):
+        """
+        Interpolate point time series from downloaded data. Provides access to
+        the more generically ERA-like interpolation functions.
+        """
+
+        # 2D Interpolation for Invariant Data
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable keys.
+        dummy_date  = {'beg': datetime(1979, 1, 1, 12, 0),
+                       'end': datetime(1979, 1, 1, 12, 0)}
+        self.ERA2station(self.getInFile('to'), self.getOutFile('to'),
+                         self.stations, ['z', 'lsm'], date=dummy_date)
+
+        # === 2D Interpolation for Surface Analysis Data ===
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable keys.
+        dpar = {'air_temperature'   : ['t2m'],  # [K] 2m values
+                'relative_humidity' : ['d2m'],  # [K] 2m values
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky':
+                    ['tco3', 'tcwv'],   # [kg m-2] Total column ozone
+                                        # [kg m-2] Total column W vapor
+                'wind_speed': ['u10', 'v10']}   # [m s-1] 10m values
+        varlist = self.TranslateCF2short(dpar)
+        self.ERA2station(self.getInFile('sa'), self.getOutFile('sa'),
+                         self.stations, varlist, date=self.date)
+
+        # 2D Interpolation for Surface Forecast Data    'tp', 'strd', 'ssrd'
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable keys.
+        # [m] total precipitation
+        # [J m-2] short-wave downward
+        # [J m-2] long-wave downward
+        dpar = {'precipitation_amount'              : ['tp'],
+                'downwelling_shortwave_flux_in_air' : ['ssrd'],
+                'downwelling_longwave_flux_in_air'  : ['strd']}
+        varlist = self.TranslateCF2short(dpar)
+        self.ERA2station(self.getInFile('sf'), self.getOutFile('sf'),
+                         self.stations, varlist, date=self.date)
+
+        # === 2D Interpolation for Pressure Level Data ===
+        # dictionary to translate CF Standard Names into ERA5
+        # pressure level variable keys.
+        dpar = {'air_temperature'   : ['t'],           # [K]
+                'relative_humidity' : ['r'],           # [%]
+                'wind_speed'        : ['u', 'v']}      # [m s-1]
+        varlist = self.TranslateCF2short(dpar).append('z')
+        self.ERA2station(self.getInFile('pl'), self.getOutFile('pl'),
+                         self.stations, varlist, date=self.date)
+
+        # 1D Interpolation for Pressure Level Data
+        if self.ens:
+            outf = 'era5_ens_pl_'
+        else:
+            outf = 'era5_rea_pl_'
+        outf = path.join(self.dir_inp, outf + self.list_name + '_surface.nc')
+        self.levels2elevation(self.getOutFile('pl'), outf)
+
+
+class ERA5scale(GenericScale):
+    """
+    Class for ERA5 data that has methods for scaling station data to
+    better resemble near-surface fluxes.
+
+    Processing kernels have names in UPPER CASE.
+
+    Args:
+        sfile: Full path to a Globsim Scaling Parameter file.
+
+    Example:
+        ERAd = ERA5scale(sfile)
+        ERAd.process()
+    """
+
+    def __init__(self, sfile, era5type='reanalysis'):
+        super().__init__(sfile)
+        par = self.par
+
+        if era5type == 'reanalysis':
+            self.ens = False
+            self.src = 'era5_rea'
+        elif era5type == 'ensemble_members':
+            self.ens = True
+            self.src = 'era5_ens'
+
+        # input file handles
+        self.nc_pl = nc.Dataset(path.join(self.intpdir,
+                                          '{}_pl_'.format(self.src) +
+                                          self.list_name + '_surface.nc'), 'r')
+        self.nc_sa = nc.Dataset(path.join(self.intpdir,
+                                          '{}_sa_'.format(self.src) +
+                                self.list_name + '.nc'), 'r')
+        self.nc_sf = nc.Dataset(path.join(self.intpdir,
+                                          '{}_sf_'.format(self.src) +
+                                self.list_name + '.nc'), 'r')
+        self.nc_to = nc.Dataset(path.join(self.intpdir,
+                                          '{}_to_'.format(self.src) +
+                                self.list_name + '.nc'), 'r')
+        self.nstation = len(self.nc_to.variables['station'][:])
+
+        # time vector for output data
+        # get time and convert to datetime object
+        nctime = self.nc_pl.variables['time'][:]
+        self.t_unit = self.nc_pl.variables['time'].units  # "hours since 1900-01-01 00:00:0.0"
+        self.t_cal  = self.nc_pl.variables['time'].calendar
+        time = nc.num2date(nctime, units=self.t_unit, calendar=self.t_cal)
+
+        # interpolation scale factor
+        self.time_step = par['time_step'] * 3600    # [s] scaled file
+        self.interval_in = (time[1] - time[0]).seconds  # interval in seconds
+        self.interpN = floor(self.interval_in / self.time_step)
+
+        # number of time steps for output, include last value
+        self.nt = int(floor((max(time) - min(time)).total_seconds()
+                            / 3600 / par['time_step'])) + 1
+
+        # vector of output time steps as datetime object
+        # 'seconds since 1900-01-01 00:00:0.0'
+        mt = min(time)
+        self.times_out = [mt + timedelta(seconds=(x * self.time_step))
+                          for x in range(0, self.nt)]
+
+        # vector of output time steps as written in ncdf file [s]
+        self.scaled_t_units = 'seconds since 1900-01-01 00:00:0.0'
+        self.times_out_nc = nc.date2num(self.times_out,
+                                        units=self.scaled_t_units,
+                                        calendar=self.t_cal)
+
+    def getValues(self, ncf, varStr, ni=10):
+        if self.ens:
+            values = ncf.variables[varStr][:, ni, :]
+        else:
+            values = ncf.variables[varStr][:]
+
+        return values
+
+    def indProcess(self, ni=10):
+        for kernel_name in self.kernels:
+            if hasattr(self, kernel_name):
+                print(kernel_name)
+                getattr(self, kernel_name)(ni)
+
+    def process(self, ni=10):
+        """
+        Run all relevant processes and save data. Each kernel processes one
+        variable and adds it to the netCDF file.
+        """
+
+        stations = self.stations['station_name']
+        # iterate thorugh kernels and start process
+        if self.ens:
+            for ni in self.nc_sa['number']:
+                src = '{}_{}'.format(self.src, ni)
+                self.output_file = self.getOutNCF(self.par, src)
+                self.rg = new_scaled_netcdf(self.output_file,
+                                            self.nc_pl, self.times_out_nc,
+                                            t_unit=self.scaled_t_units,
+                                            station_names=stations)
+                self.indProcess(ni)
+
+        else:
+            self.output_file = self.getOutNCF(self.par, self.src)
+            self.rg = new_scaled_netcdf(self.output_file,
+                                        self.nc_pl, self.times_out_nc,
+                                        t_unit=self.scaled_t_units,
+                                        station_names=stations)
+            self.indProcess(ni=10)
+
+        # close netCDF files
+        self.rg.close()
+        self.nc_pl.close()
+        self.nc_sf.close()
+        self.nc_sa.close()
+        self.nc_to.close()
+
+    def PRESS_Pa_pl(self, ni=10):
+        """
+        Surface air pressure from pressure levels.
+        """
+        # add variable to ncdf file
+        vn = 'PRESS_pl'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
+        var.long_name = 'air_pressure ERA-5 pressure levels only'
+        var.units     = 'Pa'
+        var.standard_name = 'surface_air_pressure'
+
+        # interpolate station by station
+        time_in = self.nc_pl.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_pl, 'air_pressure', ni)
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            # scale from hPa to Pa
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                          time_in * 3600, values[:, n]) * 100
+
+    def AIRT_C_pl(self, ni=10):
+        """
+        Air temperature derived from pressure levels, exclusively.
+        """
+        # add variable to ncdf file
+        vn = 'AIRT_pl'  # variable name
+        var           = self.rg.createVariable(vn, 'f4', ('time', 'station'))
+        var.long_name = 'air_temperature ERA-5 pressure levels only'
+        var.units     = 'degrees_C'
+        var.standard_name = 'air_temperature'
+
+        # interpolate station by station
+        time_in = self.nc_pl.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_pl, 't', ni)  # self.nc_pl.variables['t'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                                             time_in * 3600, values[:, n] - 273.15)
+
+    def AIRT_C_sur(self, ni=10):
+        """
+        Air temperature derived from surface data, exclusively.
+        """
+        # add variable to ncdf file
+        vn = 'AIRT_sur'   # variable name
+        var           = self.rg.createVariable(vn, 'f4', ('time', 'station'))
+        var.long_name = '2_metre_temperature ERA-5 surface only'
+        var.units     = self.nc_sa.variables['t2m'].units.encode('UTF8')
+
+        # interpolate station by station
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sa, 't2m', ni)  # self.nc_sa.variables['t2m'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                                             time_in * 3600,
+                                                             values[:, n] - 273.15)
+
+    def AIRT_redcapp(self, ni=10):
+        """
+        Air temperature derived from surface data and pressure level data as
+        shown by the method REDCAPP.
+        """
+        print("AIRT_redcapp")
+
+    def PREC_mm_sur(self, ni=10):
+        """
+        Precipitation sum in mm for the time step given.
+        """
+        # add variable to ncdf file
+        vn  = 'PREC_sur'  # variable name
+        var = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Total precipitation ERA-5 surface only'
+        var.units     = 'kg m-2 s-1'
+        var.standard_name = 'precipitation_amount'
+
+        # interpolation scale factor
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+
+        # total prec [mm] in 1 second
+        values  = self.getValues(self.nc_sf, 'tp', ni)  # self.nc_sf.variables['tp'][:]*1000/self.interval_in
+        values  = values * 1000 / self.interval_in
+
+        # interpolate station by station
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in * 3600, values[:, n], kind='linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
+
+    def RH_per_sur(self, ni=10):
+        """
+        Relative humdity derived from surface data, exclusively. Clipped to
+        range [0.1,99.9]. Kernel AIRT_C_sur must be run before.
+        """
+        # temporary variable,  interpolate station by station
+        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sa, 'd2m', ni)  # self.nc_sa.variables['d2m'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            dewp[:, n] = series_interpolate(self.times_out_nc,
+                                            time_in * 3600, values[:, n] - 273.15)
+
+        # add variable to ncdf file
+        vn = 'RH_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Relative humidity ERA-5 surface only'
+        var.units     = 'percent'
+        var.standard_name = 'relative_humidity'
+
+        # simple: https://doi.org/10.1175/BAMS-86-2-225
+        RH = 100 - 5 * (self.rg.variables['AIRT_sur'][:, :] - dewp[:, :])
+        self.rg.variables[vn][:, :] = RH.clip(min=0.1, max=99.9)
+
+    def WIND_sur(self, ni=10):
+        """
+        Wind speed and direction temperature derived from surface data,
+        exclusively.
+        """
+        # temporary variable, interpolate station by station
+        U = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sa, 'u10', ni)  # self.nc_sa.variables['u10'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            U[:, n] = series_interpolate(self.times_out_nc,
+                                         time_in * 3600, values[:, n])
+
+        # temporary variable, interpolate station by station
+        V = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sa, 'v10', ni)  # self.nc_sa.variables['v10'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            V[:, n] = series_interpolate(self.times_out_nc,
+                                         time_in * 3600, values[:, n])
+
+        # wind speed, add variable to ncdf file, convert
+        vn_spd = 'WSPD_sur'  # variable name
+        var           = self.rg.createVariable(vn_spd,'f4',('time', 'station'))
+        var.long_name = '10 wind speed ERA-5 surface only'
+        var.units     = 'm s-1'
+        var.standard_name = 'wind_speed'
+
+        # wind direction, add variable to ncdf file, convert, relative to North
+        vn_dir = 'WDIR_sur'  # variable name
+        var           = self.rg.createVariable(vn_dir,'f4',('time', 'station'))
+        var.long_name = '10 wind direction ERA-5 surface only'
+        var.units     = 'degree'
+        var.standard_name = 'wind_from_direction'
+
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            WS = np.sqrt(np.power(V,2) + np.power(U,2))
+            WD = [atan2(V[i, n], U[i, n]) * (180 / pi) +
+                  180 for i in np.arange(V.shape[0])]
+            self.rg.variables[vn_spd][:, n] = WS
+            self.rg.variables[vn_dir][:,n] = WD
+
+    def SW_Wm2_sur(self, ni=10):
+        """
+        Short-wave downwelling radiation derived from surface data, exclusively.
+        This kernel only interpolates in time.
+        """
+
+        # add variable to ncdf file
+        vn = 'SW_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface solar radiation downwards ERA-5 surface only'
+        var.units     = 'W m-2'
+        var.standard_name = 'surface_downwelling_shortwave_flux'
+
+        # interpolate station by station
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sf, 'ssrd', ni)  # self.nc_sf.variables['ssrd'][:]/3600/self.interval_in#[w/m2/s]
+        values  = values / 3600 / self.interval_in  # [w/m2/s]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in * 3600, values[:, n], kind='linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
+
+    def LW_Wm2_sur(self, ni=10):
+        """
+        Long-wave downwelling radiation derived from surface data, exclusively.
+        This kernel only interpolates in time.
+        """
+
+        # add variable to ncdf file
+        vn = 'LW_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface thermal radiation downwards ERA-5 surface only'
+        var.standard_name = 'surface_downwelling_longwave_flux'
+        var.units     = 'W m-2'
+
+        # interpolate station by station
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sf, 'strd', ni)  # self.nc_sf.variables['strd'][:]/3600/self.interval_in #[w m-2 s-1]
+        values  = values / 3600 / self.interval_in  # [w m-2 s-1]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in * 3600, values[:, n], kind='linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
+
+    def SH_kgkg_sur(self, ni=10):
+        '''
+        Specific humidity [kg/kg]
+        https://crudata.uea.ac.uk/cru/pubs/thesis/2007-willett/2INTRO.pdf
+        '''
+
+        # temporary variable,  interpolate station by station
+        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.getValues(self.nc_sa, 'd2m', ni)  # self.nc_sa.variables['d2m'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            dewp[:, n] = series_interpolate(self.times_out_nc,
+                                            time_in * 3600, values[:, n] - 273.15)
+
+        # compute
+        SH = spec_hum_kgkg(dewp[:, :],
+                           self.rg.variables['PRESS_pl'][:, :])
+
+        # add variable to ncdf file
+        vn = 'SH_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Specific humidity ERA-5 surface only'
+        var.units     = '1'
+        var.standard_name = 'specific_humidity'
+        self.rg.variables[vn][:, :] = SH
+
diff --git a/globsim/era5_example.py b/globsim/era5_example.py
new file mode 100755
index 0000000..2026146
--- /dev/null
+++ b/globsim/era5_example.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# topofeature
+# ==== DOWNLOAD ===============================================================
+import cdsapi
+
+from datetime import timedelta
+from math import floor
+from os import path
+from ecmwfapi.api import ECMWFDataServer
+
+from globsim.era5 import ERA5download, ERA5scale, ERA5interpolate
+
+pfile = "/path/to/examples.globsim_download"
+ifile = "/path/to/examples.globsim_interpolate"
+sfile = "/path/to/examples.globsim_scale"
+
+# ======================================================
+
+# download
+ERAd = ERA5download(pfile, 'ensemble_members')
+
+
+'''
+# prepare time loop
+date_i = {}
+slices = floor(float((ERAd.date['end'] - ERAd.date['beg']).days)/
+               ERAd.chunk_size)+1
+
+# topography
+#if path.isfile(path.join(ERAd.directory,'era5_to.nc')):
+#    print("WARNING: File 'era5_to.nc' already exists. Skipping.")
+#else:
+#    top = ERA5to(ERAd.area, ERAd.directory, ERAd.product)
+#    top.download(ERAd.api, ERAd.storage)
+ind = 0
+date_i['beg'] = ERAd.date['beg'] + timedelta(days =
+                ERAd.chunk_size * ind)
+date_i['end'] = ERAd.date['beg'] + timedelta(days =
+                ERAd.chunk_size * (ind+1) - 1)
+if ind == (slices-1):
+    date_i['end'] = ERAd.date['end']
+from era5 import ERA5pl, ERA5sa, ERA5sf, ERA5to
+#actual functions
+to = ERA5to(ERAd.product, ERAd.area, ERAd.directory)
+sa = ERA5sa(ERAd.product, date_i, ERAd.area, ERAd.variables, ERAd.directory)
+sf = ERA5sf(ERAd.product, date_i, ERAd.area, ERAd.variables, ERAd.directory)
+pl = ERA5pl(ERAd.product, date_i, ERAd.area, ERAd.elevation, ERAd.variables, ERAd.directory)
+to.dictionary = {
+    'product_type': to.productType,
+    'variable'    : ['orography', 'land_sea_mask'],
+       }
+to.dictionary.update(sf.getDictionaryGen(sf.area, sf.date))
+era = to
+to.download(api, storage)
+# ======================================================
+# era.download(ERAd.api, ERAd.storage)
+api = ERAd.api
+storage = ERAd.api
+server = cdsapi.Client()
+
+query   = era.getDictionary()
+target  = era.file_ncdf
+levtype = era.levelType
+#query = era.ECM2CDS(query)
+server.retrieve(levtype, query, target)
+# report inventory
+self.inventory()
+'''
+
+ERAd.retrieve()
+ERAd.inventory()
+
+# ERAsc = ERAscale(sfile)
+# ERAsc.process()
diff --git a/era_interim.py b/globsim/era_interim.py
old mode 100644
new mode 100755
similarity index 52%
rename from era_interim.py
rename to globsim/era_interim.py
index 653fcd5..64c72f1
--- a/era_interim.py
+++ b/globsim/era_interim.py
@@ -1,1672 +1,1352 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Methods for downloading ERA-Interim data from the ECMWF server for limited
-# areas and limited times.
-#
-#
-# (C) Copyright Stephan Gruber (2013–2017)
-#         
-#     This program is free software: you can redistribute it and/or modify
-#     it under the terms of the GNU General Public License as published by
-#     the Free Software Foundation, either version 3 of the License, or
-#     (at your option) any later version.
-#
-#     This program is distributed in the hope that it will be useful,
-#     but WITHOUT ANY WARRANTY; without even the implied warranty of
-#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#     GNU General Public License for more details.
-#
-#     You should have received a copy of the GNU General Public License
-#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
-# 
-#  OVERALL WORKFLOW
-# 
-#  See: https://github.com/geocryology/globsim/wiki/Globsim
-#       and the examples directory on github
-#
-# ECMWF and netCDF information:
-# https://software.ecmwf.int/wiki/display/WEBAPI/Python+ERA-interim+examples
-#
-# For variable codes and units, see: 
-#     http://www.ecmwf.int/publications/manuals/d/gribapi/param/
-#
-# Check ECMWF job status: http://apps.ecmwf.int/webmars/joblist/
-#
-#  CF-Convention: this is how you check netcdf file conformity: 
-#  http://pumatest.nerc.ac.uk/cgi-bin/cf-checker-dev.pl 
-#
-#===============================================================================
-from __future__   import print_function
-
-import glob
-import re
-import numpy   as np
-import netCDF4 as nc
-
-from datetime     import datetime, timedelta
-from ecmwfapi.api import ECMWFDataServer
-from math         import exp, floor, atan2, pi
-from os           import path, listdir, remove, makedirs
-from globsim.generic     import ParameterIO, StationListRead, ScaledFileOpen, series_interpolate, variables_skip, spec_hum_kgkg, LW_downward, str_encode, cummulative2total
-from fnmatch      import filter
-from scipy.interpolate import interp1d
-
-try:
-    from nco import Nco
-except ImportError:
-    print("*** NCO not imported, netCDF appending not possible. ***")
-    pass 
-
-try:
-    import ESMF
-    
-    # Check ESMF version.  7.0.1 behaves differently than 7.1.0r 
-    ESMFv = int(re.sub("[^0-9]", "", ESMF.__version__))
-    ESMFnew = ESMFv > 701   
-except ImportError:
-    print("*** ESMF not imported, interpolation not possible. ***")
-    pass   
-
- 
-
-class ERAIgeneric(object):
-    """
-    Parent class for other ERA-Interim classes.
-    """
-        
-    def areaString(self, area):
-        """Converts numerical coordinates into string: North/West/South/East"""
-        res  = str(round(area['north'],2)) + "/"
-        res += str(round(area['west'], 2)) + "/"
-        res += str(round(area['south'],2)) + "/"
-        res += str(round(area['east'], 2))        
-        return(res)
-        
-    def dateString(self, date):
-        """Converts datetime objects into string"""
-        res  = (date['beg'].strftime("%Y-%m-%d") + "/to/" +
-                date['end'].strftime("%Y-%m-%d"))       
-        return(res)    
-        
-    def getPressure(self, elevation):
-        """Convert elevation into air pressure using barometric formula"""
-        g  = 9.80665   #Gravitational acceleration [m/s2]
-        R  = 8.31432   #Universal gas constant for air [N·m /(mol·K)]    
-        M  = 0.0289644 #Molar mass of Earth's air [kg/mol]
-        P0 = 101325    #Pressure at sea level [Pa]
-        T0 = 288.15    #Temperature at sea level [K]
-        #http://en.wikipedia.org/wiki/Barometric_formula
-        return P0 * exp((-g * M * elevation) / (R * T0)) / 100 #[hPa] or [bar]
-    
-    def getPressureLevels(self, elevation):
-        """Restrict list of ERA-interim pressure levels to be downloaded"""
-        Pmax = self.getPressure(elevation['min']) + 55
-        Pmin = self.getPressure(elevation['max']) - 55 
-        levs = np.array([300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 775, 
-                         800, 825, 850, 875, 900, 925, 950, 975, 1000])
-        mask = (levs >= Pmin) * (levs <= Pmax) #select
-        levs = '/'.join(map(str, levs[mask]))
-        return levs
-
-    def getDictionaryGen(self, area, date):
-        """
-        Makes dictionary of generic variables for a server call
-        """
-        dictionary_gen = {
-          'area'    : self.areaString(area),
-          'date'    : self.dateString(date),
-          'dataset' : "interim",
-          'stream'  : "oper",
-          'class'   : "ei",
-          'format'  : "netcdf",
-          'grid'    : "0.75/0.75"}
-        return dictionary_gen
- 
-    def getDstring(self):
-        return ('_' + self.date['beg'].strftime("%Y%m%d") + "_to_" +
-                      self.date['end'].strftime("%Y%m%d"))
-    
-    def download(self):
-        #TODO test for file existence
-        server = ECMWFDataServer()
-        print(server.trace('=== ERA Interim: START ===='))
-        server.retrieve(self.getDictionary())
-        print(server.trace('=== ERA Interim: STOP =====')  )
-
-    def TranslateCF2ERA(self, variables, dpar):
-        """
-        Translate CF Standard Names into ERA-Interim code numbers.
-        """
-        self.param = ''
-        for var in variables:
-            try:
-                self.param += dpar.get(var)+'/'
-            except TypeError:
-                pass    
-        self.param = self.param.rstrip('/') #fix last                                        
-                
-    def __str__(self):
-        string = ("List of generic variables to query ECMWF server for "
-                  "ERA-Interim data: {0}")
-        return string.format(self.getDictionary) 
-    
-    def netCDF_empty(self, ncfile_out, stations, nc_in):
-        '''
-        Creates an empty station file to hold interpolated reults. The number of 
-        stations is defined by the variable stations, variables are determined by 
-        the variable list passed from the gridded original netCDF.
-        
-        ncfile_out: full name of the file to be created
-        stations:   station list read with generic.StationListRead() 
-        variables:  variables read from netCDF handle
-        lev:        list of pressure levels, empty is [] (default)
-        '''
-        
-        #Build the netCDF file
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4_CLASSIC')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'ERA_Interim, interpolated bilinearly to stations'
-        rootgrp.featureType = "timeSeries"
-                                                
-        # dimensions
-        station = rootgrp.createDimension('station', len(stations))
-        time    = rootgrp.createDimension('time', None)
-                
-        # base variables
-        time           = rootgrp.createVariable('time', 'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1900-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station', 'i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude', 'f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east' 
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-        
-        # assign station characteristics            
-        station[:]   = list(stations['station_number'])
-        latitude[:]  = list(stations['latitude_dd'])
-        longitude[:] = list(stations['longitude_dd'])
-        height[:]    = list(stations['elevation_m'])
-        
-        # extra treatment for pressure level files
-        try:
-            lev = nc_in.variables['level'][:]
-            print("== 3D: file has pressure levels")
-            level = rootgrp.createDimension('level', len(lev))
-            level           = rootgrp.createVariable('level','i4',('level'))
-            level.long_name = 'pressure_level'
-            level.units     = 'hPa'  
-            level[:] = lev 
-        except:
-            print("== 2D: file without pressure levels")
-            lev = []
-                    
-        # create and assign variables based on input file
-        for n, var in enumerate(nc_in.variables):
-            if variables_skip(var):
-                continue                 
-            print("VAR: ", var)
-            # extra treatment for pressure level files           
-            if len(lev):
-                tmp = rootgrp.createVariable(var,'f4',('time', 'level', 'station'))
-            else:
-                tmp = rootgrp.createVariable(var,'f4',('time', 'station'))     
-            tmp.long_name = nc_in.variables[var].long_name.encode('UTF8') 
-            tmp.units     = nc_in.variables[var].units.encode('UTF8')  
-                    
-        #close the file
-        rootgrp.close()
-        
-    def netCDF_merge(self, directory):
-        """
-        To combine mutiple downloaded erai netCDF files into a large file with specified chunk_size(e.g. 500), 
-        -- give the full name of merged file to the output = outfile
-        -- pass all data from the first input netfile to the merged file name
-        -- loop over the files_list, append file one by one into the merge file
-        -- pass the mergae netcdf file to interpolation module to process( to use nc.MFDataset by reading it)
-        
-        Args:
-            ncfile_in: the full name of downloaded files (file directory + files names)
-        e.g.:
-              '/home/xquan/src/globsim/examples/erai/era_sa_*.nc' 
-              '/home/xquan/src/globsim/examples/erai/era_pl_*.nc'
-              '/home/xquan/src/globsim/examples/erai/era_sf_*.nc'
-
-        Output: merged netCDF files
-        era_all_0.nc, era_all_1.nc, ...,
-                
-        """
-        #set up nco operator
-        nco = Nco()
-  
-        # loop over filetypes, read, report
-        file_type = ['erai_sa_*.nc', 'erai_sf_*.nc', 'erai_pl_*.nc']
-        for ft in file_type:
-            ncfile_in = path.join(directory, ft)
-            
-            #get the file list
-            files_list = glob.glob(ncfile_in)
-            files_list.sort()
-            num = len(files_list)
-                        
-            #set up the name of merged file
-            if ncfile_in[-7:-5] == 'sa':
-                merged_file = path.join(ncfile_in[:-11],'erai_sa_all_'+ files_list[0][-23:-15] + "_" + files_list[num-1][-11:-3] +'.nc')
-            elif ncfile_in[-7:-5] == 'sf':
-                merged_file = path.join(ncfile_in[:-11],'erai_sf_all_' + files_list[0][-23:-15] + '_' + files_list[num-1][-11:-3] + '.nc')
-            elif ncfile_in[-7:-5] == 'pl':
-                merged_file = path.join(ncfile_in[:-11],'erai_pl_all_'+ files_list[0][-23:-15] + '_' + files_list[num-1][-11:-3] +'.nc')
-            else:
-                print('There is not such type of file'    )
-                        
-            # combined files into merged files
-            nco.ncrcat(input=files_list,output=merged_file, append = True)
-            
-            print('The Merged File below is saved:')
-            print(merged_file)
-            
-            #clear up the data
-            for fl in files_list:
-                remove(fl)
-
-    def mergeFiles(self, ncfile_in):
-        """
-        To combine mutiple downloaded erai netCDF files into a large file. 
-        
-        Args:
-            ncfile_in: the full name of downloaded files (file directory + files names)
-        e.g.:
-              '/home/xquan/src/globsim/examples/erai/era_sa_*.nc' 
-              '/home/xquan/src/globsim/examples/erai/era_pl_*.nc'
-              '/home/xquan/src/globsim/examples/erai/era_sf_*.nc'
-
-        Output: merged netCDF files
-        erai_sa_all.nc, erai_sf_all.nc, erai_pl_all.nc
-                
-        """
-     
-        # read in one type of mutiple netcdf files       
-        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim ='time')
-
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
-
-        # get spatial dimensions
-        lat  = ncf_in.variables['latitude'][:]
-        lon  = ncf_in.variables['longitude'][:]
-        if pl: # only for pressure level files
-            lev  = ncf_in.variables['level'][:]
-            nlev = len(lev)
-            
-        # get time and convert to datetime object
-        nctime = ncf_in.variables['time'][:]
-    
-        #set up the name of merged file
-        if ncfile_in[-7:-5] == 'sa':
-            ncfile_out = path.join(ncfile_in[:-11],'erai_sa_all' + '.nc')
-        elif ncfile_in[-7:-5] == 'sf':
-            ncfile_out = path.join(ncfile_in[:-11],'erai_sf_all' + '.nc')
-        elif ncfile_in[-7:-5] == 'pl':
-            ncfile_out = path.join(ncfile_in[:-11],'erai_pl_all' + '.nc')
-        else:
-            print('There is not such type of file'    )
-        
-        # get variables
-        varlist = [str_encode(x) for x in ncf_in.variables.keys()]
-        varlist.remove('time')
-        varlist.remove('latitude')
-        varlist.remove('longitude')
-        if pl: #only for pressure level files
-            varlist.remove('level')
-        
-        #Build the netCDF file
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4_CLASSIC')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'ERA_Interim, merged downloaded original files'
-        rootgrp.featureType = "timeSeries"
-                                                
-        # dimensions
-        latitude = rootgrp.createDimension('latitude', len(lat))
-        longitude = rootgrp.createDimension('longitude', len(lon))
-        time    = rootgrp.createDimension('time', None)
-                
-        # base variables
-        time           = rootgrp.createVariable('time', 'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1900-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        latitude            = rootgrp.createVariable('latitude', 'f4',('latitude'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('longitude'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east' 
-        
-        # assign station characteristics            
-        latitude[:]  = lat[:]
-        longitude[:] = lon[:]
-        time[:]    = nctime[:]
-        
-        # extra treatment for pressure level files
-        try:
-            lev = ncf_in.variables['level'][:]
-            print("== 3D: file has pressure levels")
-            level = rootgrp.createDimension('level', len(lev))
-            level           = rootgrp.createVariable('level','i4',('level'))
-            level.long_name = 'pressure_level'
-            level.units     = 'hPa'  
-            level[:] = lev 
-        except:
-            print("== 2D: file without pressure levels")
-            lev = []
-                    
-        # create and assign variables based on input file
-        for n, var in enumerate(varlist):
-            print("VAR: ", var)
-            # extra treatment for pressure level files            
-            if len(lev):
-                tmp = rootgrp.createVariable(var,'f4',('time', 'level', 'latitude', 'longitude'))
-            else:
-                tmp = rootgrp.createVariable(var,'f4',('time', 'latitude', 'longitude'))     
-            tmp.long_name = ncf_in.variables[var].long_name.encode('UTF8') # for erai
-            tmp.units     = ncf_in.variables[var].units.encode('UTF8') 
-            
-            # assign values
-            if pl: # only for pressure level files
-                tmp[:] = ncf_in.variables[var][:,:,:,:]
-            else:
-                tmp[:] = ncf_in.variables[var][:,:,:]    
-              
-                    
-        #close the file
-        rootgrp.close()
-        ncf_in.close()
-        
-        #get the file list
-        files_list = glob.glob(ncfile_in)
-        files_list.sort()
-        
-        #clear up the data
-        for fl in files_list:
-            remove(fl)
-
-                                                                                                                                                                                                                                              
-class ERAIpl(ERAIgeneric):
-    """Returns an object for ERA-Interim data that has methods for querying the
-    ECMWF server.
-       
-    Args:
-        date: A dictionary specifying the time period desired with a begin 
-              and an end date given as a datetime.datetime object.
-              
-        area: A dictionary delimiting the area to be queried with the latitudes
-              north and south, and the longitudes west and east [decimal deg].  
-              
-        elevation: A dictionary specifying the min/max elevation of the area of
-                   interest. This is used to determine the pressure levels 
-                   needed. Unit: [m].
-        
-        variables:  List of variable(s) to download based on CF Standard Names.
-        
-        directory: Directory to hold output files
-              
-    Example:
-        from datetime import datetime
-        date  = {'beg' : datetime(1994, 1, 1),
-                 'end' : datetime(2013, 1, 1)}
-        area  = {'north' :  40.0,
-                 'south' :  15.0,
-                 'west'  :  60.0,
-                 'east'  : 105.0}
-        elevation = {'min' :    0, 
-                     'max' : 8850}
-        variables  = ['air_temperature', 'relative_humidity']             
-        directory = '/Users/stgruber/Desktop'             
-        ERAIpl = ERAIpl(date, area, elevation, variables, directory) 
-        ERAIpl.download()
-    """
-    def __init__(self, date, area, elevation, variables, directory):
-        self.date       = date
-        self.area       = area
-        self.elevation  = elevation
-        self.directory  = directory
-        outfile = 'erai_pl' + self.getDstring() + '.nc'
-        self.file_ncdf  = path.join(self.directory, outfile)
- 
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable names. 
-        dpar = {'air_temperature'   : '130.128',           # [K]
-                'relative_humidity' : '157.128',           # [%]
-                'wind_speed'        : '131.128/132.128'}   # [m s-1]
-                
-        # translate variables into those present in ERA pl data        
-        self.TranslateCF2ERA(variables, dpar)
-        self.param += '/129.128' # geopotential always needed [m2 s-2] 
-    
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "pl",
-           'levellist': self.getPressureLevels(self.elevation),
-           'time'     : "00/06/12/18",
-           'step'     : "0",
-           'type'     : "an",
-           'param'    : self.param,
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary    
-        
-    def __str__(self):
-        string = ("List of variables to query ECMWF server for "
-                  "ERA-Interim data: {0}")
-        return string.format(self.getDictionary) 
-
-                                
-class ERAIsa(ERAIgeneric):
-    """
-    Returns an object for ERA-Interim data that has methods for querying the
-    ECMWF server for surface analysis variables (airt2,dewp2, ozone, vapor,
-    wind10).
-       
-    Args:
-        
-        target:    File name of the netcdf file to be created.
-        
-        variable:  List of variable(s) to download based on CF Standard Names 
-              
-    Example:
-        from datetime import datetime
-        date  = {'beg' : datetime(1994, 1, 1),
-                 'end' : datetime(2013, 1, 1)}
-        area  = {'north' :  40.0,
-                 'south' :  15.0,
-                 'west'  :  60.0,
-                 'east'  : 105.0}
-        variables  = ['air_temperature', 'relative_humidity']             
-        directory = '/Users/stgruber/Desktop'             
-        ERAIsa = ERAIsa(date, area, variables, directory) 
-        ERAIsa.download()      
-    """
-    def __init__(self, date, area, variables, directory):
-        self.date       = date
-        self.area       = area
-        self.directory  = directory
-        outfile = 'erai_sa' + self.getDstring() + '.nc'
-        self.file_ncdf  = path.join(self.directory, outfile)
-        
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable names. 
-        dpar = {'air_temperature'   : '167.128',  # [K] 2m values
-                'relative_humidity' : '168.128',  # [K] 2m values
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky' : 
-                    '206.128/137.128',  # [kg m-2] Total column ozone 
-                                        # [kg m-2] Total column W vapor                                                             
-                'wind_speed' : '165.128/166.128'}   # [m s-1] 10m values
-                
-        # translate variables into those present in ERA pl data        
-        self.TranslateCF2ERA(variables, dpar)
-        print(self.param)
-
-
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "sfc",
-           'time'     : "00/06/12/18",
-           'step'     : "0",
-           'type'     : "an",
-           'param'    : self.param,
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary  
-        
-    def __str__(self):
-        string = ("Class for ERA-Interim surface analysis data: {0}")
-        return string.format(self.getDictionary)         
-
-
-class ERAIsf(ERAIgeneric):
-    """
-    Returns an object for ERA-Interim data that has methods for querying the
-    ECMWF server for surface forecast variables (prec, swin, lwin).
-       
-    Args:        
-        target:    File name of the netcdf file to be created.
-        
-        variables:  List of variable(s) to download that can include one, several
-                   , or all of these: ['airt', 'rh', 'geop', 'wind'].
-        
-        ERAgen:    ERAIgeneric() object with generic information on the area and
-                   time span to be
-              
-    Example:
-        from datetime import datetime
-        date  = {'beg' : datetime(1994, 1, 1),
-                 'end' : datetime(1994, 1, 1)}
-        area  = {'north' :  40.0,
-                 'south' :  40.0,
-                 'west'  :  60.0,
-                 'east'  :  60.0}
-        variables  = ['prec','swin','lwin']             
-        directory = '/Users/stgruber/Desktop'             
-        ERAIsf = ERAIsf(date, area, variables, directory) 
-        ERAIsf.download()   
-    """
-    def __init__(self, date, area, variables, directory):
-        self.date       = date
-        self.area       = area
-        self.directory  = directory
-        outfile = 'erai_sf' + self.getDstring() + '.nc'
-        self.file_ncdf  = path.join(self.directory, outfile)
-
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable names. 
-        # [m] total precipitation
-        # [J m-2] short-wave downward
-        # [J m-2] long-wave downward
-        dpar = {'precipitation_amount'              : '228.128',   
-                'downwelling_shortwave_flux_in_air' : '169.128',   
-                'downwelling_longwave_flux_in_air'  : '175.128'}   
-                
-        # translate variables into those present in ERA pl data        
-        self.TranslateCF2ERA(variables, dpar)
-        print(self.param)
-
-
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "sfc",
-           'time'     : "00/12",
-           'step'     : "3/6/9/12",
-           'type'     : "fc",
-           'param'    : self.param,
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary  
-        
-    def __str__(self):
-        string = ("List of variables to query ECMWF server for "
-                  "ERA-Interim air tenperature data: {0}")
-        return string.format(self.getDictionary)      
-                
-class ERAIto(ERAIgeneric):
-    """
-    Returns an object for downloading and handling ERA-Interim 
-    topography (invariant).
-       
-    Args:      
-              
-    Example:
-        area  = {'north' :  40.0,
-                 'south' :  45.0,
-                 'west'  :  60.0,
-                 'east'  :  65.0}            
-        directory = '/Users/stgruber/Desktop'             
-        ERAIto = ERAIto(area, directory) 
-        ERAIto.download()       
-    """
-    def __init__(self, area, directory):
-        self.area       = area
-        self.date       = {'beg' : datetime(1979, 1, 1),
-                           'end' : datetime(1979, 1, 1)}
-        self.directory  = directory
-        self.file_ncdf  = path.join(self.directory,'erai_to.nc')
-
-    def getDictionary(self):
-        self.dictionary = {
-           'levtype'  : "sfc",
-           'time'     : "12",
-           'step'     : "0",
-           'type'     : "an",
-           'param'    : "129.128/172.128", # geopotential and land-sea mask
-           'target'   : self.file_ncdf
-           } 
-        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
-        return self.dictionary   
-        
-    def __str__(self):
-        string = ("List of variables to query ECMWF server for "
-                  "ERA-Interim air tenperature data: {0}")
-        return string.format(self.getDictionary) 
-    
-
-class ERAIdownload(object):
-    """
-    Class for ERA-Interim data that has methods for querying 
-    the ECMWF server, returning all variables usually needed.
-       
-    Args:
-        pfile: Full path to a Globsim Download Parameter file. 
-              
-    Example:          
-        ERAd = ERAdownload(pfile) 
-        ERAd.retrieve()
-    """
-        
-    def __init__(self, pfile):
-        # read parameter file
-        self.pfile = pfile
-        par = ParameterIO(self.pfile)
-        
-        # assign bounding box
-        self.area  = {'north':  par.bbN,
-                      'south':  par.bbS,
-                      'west' :  par.bbW,
-                      'east' :  par.bbE}
-        
-        # sanity check to make sure area is good
-        if (par.bbN < par.bbS) or (par.bbE < par.bbW):        
-            raise Exception("Bounding box is invalid: {}".format(self.area))
-        
-        if (np.abs(par.bbN-par.bbS) < 1.5) or (np.abs(par.bbE-par.bbW) < 1.5):
-            raise Exception("Download area is too small to conduct interpolation.")
-            
-        # time bounds
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end}
-
-        # elevation
-        self.elevation = {'min' : par.ele_min, 
-                          'max' : par.ele_max}
-        
-        # data directory for ERA-Interim  
-        self.directory = path.join(par.project_directory, "erai")  
-        if path.isdir(self.directory) == False:
-            makedirs(self.directory)
-     
-        # variables
-        self.variables = par.variables
-            
-        # chunk size for downloading and storing data [days]        
-        self.chunk_size = par.chunk_size            
-
-                           
-    def retrieve(self):
-        """
-        Retrieve all required ERA-Interim data from MARS server.
-        """        
-        # prepare time loop
-        date_i = {}
-        slices = floor(float((self.date['end'] - self.date['beg']).days)/
-                       self.chunk_size)+1
-        # topography
-        top = ERAIto(self.area, self.directory)
-        top.download()
-        
-        for ind in range (0, int(slices)): 
-            #prepare time slices   
-            date_i['beg'] = self.date['beg'] + timedelta(days = 
-                            self.chunk_size * ind)
-            date_i['end'] = self.date['beg'] + timedelta(days = 
-                            self.chunk_size * (ind+1) - 1)
-            if ind == (slices-1):
-                date_i['end'] = self.date['end']
-            
-            #actual functions                                                                           
-            pl = ERAIpl(date_i, self.area, self.elevation, 
-                       self.variables, self.directory) 
-            sa = ERAIsa(date_i, self.area, self.variables, self.directory) 
-            sf = ERAIsf(date_i, self.area, self.variables, self.directory) 
-        
-            #download from ECMWF server convert to netCDF  
-            ERAli = [pl, sa, sf]
-            for era in ERAli:
-                era.download()          
-                                         
-        
-        # report inventory
-        self.inventory()  
-        
-                                                                                                                                                                                                           
-    def inventory(self):
-        """
-        Report on data avaialbe in directory: time slice, variables, area 
-        """
-        print("\n\n\n")
-        print("=== INVENTORY FOR GLOBSIM ERA-INTERIM DATA === \n")
-        print("Download parameter file: \n" + self.pfile + "\n")
-        # loop over filetypes, read, report
-        file_type = ['erai_pl_*.nc', 'erai_sa_*.nc', 'erai_sf_*.nc', 'erai_t*.nc']
-        for ft in file_type:
-            infile = path.join(self.directory, ft)
-            nf = len(filter(listdir(self.directory), ft))
-            print(str(nf) + " FILE(S): " + infile)
-            
-            if nf > 0:
-                # open dataset
-                ncf = nc.MFDataset(infile, 'r')
-                
-                # list variables
-                keylist = [str_encode(x) for x in ncf.variables.keys()]                
-                    
-                print("    VARIABLES:")
-                print("        " + str(len(keylist)) + 
-                      " variables, inclusing dimensions")
-                for key in keylist:
-                    print("        " + ncf.variables[key].long_name)
-                
-                # time slice
-                time = ncf.variables['time']
-                tmin = '{:%Y/%m/%d}'.format(nc.num2date(min(time[:]), 
-                                     time.units, calendar=time.calendar))
-                tmax = '{:%Y/%m/%d}'.format(nc.num2date(max(time[:]), 
-                                     time.units, calendar=time.calendar))
-                print("    TIME SLICE")                     
-                print("        " + str(len(time[:])) + " time steps")
-                print("        " + tmin + " to " + tmax)
-                      
-                # area
-                lon = ncf.variables['longitude']
-                lat = ncf.variables['latitude']
-                nlat = str(len(lat))
-                nlon = str(len(lon))
-                ncel = str(len(lat) * len(lon))
-                print("    BOUNDING BOX / AREA")
-                print("        " + ncel + " cells, " + nlon + 
-                      " W-E and " + nlat + " S-N")
-                print("        N: " + str(max(lat)))
-                print("        S: " + str(min(lat)))
-                print("        W: " + str(min(lon)))
-                print("        E: " + str(max(lon)))
-                            
-                ncf.close()
-                   
-    def __str__(self):
-        return "Object for ERA-Interim data download and conversion"  
-                 
-
-class ERAIinterpolate(object):
-    """
-    Collection of methods to interpolate ERA-Interim netCDF files to station
-    coordinates. All variables retain theit original units and time stepping.
-    """
-        
-    def __init__(self, ifile):
-        #read parameter file
-        self.ifile = ifile
-        par = ParameterIO(self.ifile)
-        self.dir_inp = path.join(par.project_directory,'erai') 
-        self.dir_out = self.makeOutDir(par)
-        self.variables = par.variables
-        self.list_name = par.station_list.split(path.extsep)[0]
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-        #convert longitude to ERA notation if using negative numbers  
-        self.stations['longitude_dd'] = self.stations['longitude_dd'] % 360             
-        
-        # time bounds, add one day to par.end to include entire last day
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end + timedelta(days=1)}
-    
-        # chunk size: how many time steps to interpolate at the same time?
-        # A small chunk size keeps memory usage down but is slow.
-        self.cs  = int(par.chunk_size)
-        
-        
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirIntp = path.join(par.project_directory, 'interpolated')
-        
-        if not (path.isdir(dirIntp)):
-            makedirs(dirIntp)
-            
-        return dirIntp
-    
-
-    def ERAinterp2D(self, ncfile_in, ncf_in, points, tmask_chunk,
-                        variables=None, date=None):    
-        """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
-        to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all Era_Interim files).
-          
-        Args:
-            ncfile_in: Full path to an Era-Interim derived netCDF file. This can
-                       contain wildcards to point to multiple files if temporal
-                       chunking was used.
-              
-            ncf_in: A netCDF4.MFDataset derived from reading in Era-Interim 
-                    multiple files (def ERA2station())
-            
-            points: A dictionary of locations. See method StationListRead in
-                    generic.py for more details.
-        
-            variables:  List of variable(s) to interpolate such as 
-                        ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
-                        Defaults to using all variables available.
-        
-            date: Directory to specify begin and end time for the derived time 
-                  series. Defaluts to using all times available in ncfile_in.
-              
-        Example:
-            from datetime import datetime
-            date  = {'beg' : datetime(2008, 1, 1),
-                      'end' : datetime(2008,12,31)}
-            variables  = ['t','u', 'v']       
-            stations = StationListRead("points.csv")      
-            ERA2station('era_sa.nc', 'era_sa_inter.nc', stations, 
-                        variables=variables, date=date)        
-        """   
-        
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
-
-
-        # get spatial dimensions
-        if pl: # only for pressure level files
-            lev  = ncf_in.variables['level'][:]
-            nlev = len(lev)
-              
-        # test if time steps to interpolate remain
-        nt = sum(tmask_chunk)
-        if nt == 0:
-            raise ValueError('No time steps from netCDF file selected.')
-    
-        # get variables
-        varlist = [str_encode(x) for x in ncf_in.variables.keys()]
-        varlist.remove('time')
-        varlist.remove('latitude')
-        varlist.remove('longitude')
-        if pl: #only for pressure level files
-            varlist.remove('level')
-    
-        #list variables that should be interpolated
-        if variables is None:
-            variables = varlist
-        #test is variables given are available in file
-        if (set(variables) < set(varlist) == 0):
-            raise ValueError('One or more variables not in netCDF file.')           
-
-        # Create source grid from a SCRIP formatted file. As ESMF needs one
-        # file rather than an MFDataset, give first file in directory.
-        flist = np.sort(filter(listdir(self.dir_inp), 
-                               path.basename(ncfile_in)))
-        ncsingle = path.join(self.dir_inp, flist[0])
-        sgrid = ESMF.Grid(filename=ncsingle, filetype=ESMF.FileFormat.GRIDSPEC)
-        
-        # create source field on source grid
-        if pl: #only for pressure level files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt, nlev])
-        else: # 2D files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt])
-
-        # assign data from ncdf: (variable, time, latitude, longitude) 
-        for n, var in enumerate(variables):
-
-            if pl: # only for pressure level files
-                if ESMFnew:
-                    sfield.data[:,:,n,:,:] = ncf_in.variables[var][tmask_chunk,:,:,:].transpose((3,2,0,1)) 
-                else:
-                    sfield.data[n,:,:,:,:] = ncf_in.variables[var][tmask_chunk,:,:,:].transpose((0,1,3,2)) # original
-
-            else:
-                if ESMFnew:
-                    sfield.data[:,:,n,:] = ncf_in.variables[var][tmask_chunk,:,:].transpose((2,1,0))
-                else:
-                    sfield.data[n,:,:,:] = ncf_in.variables[var][tmask_chunk,:,:].transpose((0,2,1)) # original
-                
-
-        # create locstream, CANNOT have third dimension!!!
-        locstream = ESMF.LocStream(len(self.stations), 
-                                   coord_sys=ESMF.CoordSys.SPH_DEG)
-        locstream["ESMF:Lon"] = list(self.stations['longitude_dd'])
-        locstream["ESMF:Lat"] = list(self.stations['latitude_dd'])
-
-        # create destination field
-        if pl: # only for pressure level files
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt, nlev])
-        else:
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt])    
-        
-        # regridding function, consider ESMF.UnmappedAction.ERROR
-        regrid2D = ESMF.Regrid(sfield, dfield,
-                                regrid_method=ESMF.RegridMethod.BILINEAR,
-                                unmapped_action=ESMF.UnmappedAction.IGNORE,
-                                dst_mask_values=None)
-                  
-        # regrid operation, create destination field (variables, times, points)
-        dfield = regrid2D(sfield, dfield)        
-        sfield.destroy() #free memory                  
-            
-        return dfield, variables
-
-    def ERA2station(self, ncfile_in, ncfile_out, points,
-                    variables = None, date = None):
-        
-        """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
-        to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all Era_Interim files). The type 
-        of variable and file structure are determined from the input.
-        
-        This function creates an empty of netCDF file to hold the interpolated 
-        results, by calling ERAIgeneric().netCDF_empty. Then, data is 
-        interpolated in temporal chunks and appended. The temporal chunking can 
-        be set in the interpolation parameter file.
-        
-        Args:
-        ncfile_in: Full path to an Era-Interim derived netCDF file. This can
-                   contain wildcards to point to multiple files if temporal
-                  chunking was used.
-            
-        ncfile_out: Full path to the output netCDF file to write.     
-        
-        points: A dictionary of locations. See method StationListRead in
-                generic.py for more details.
-    
-        variables:  List of variable(s) to interpolate such as 
-                    ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
-                    Defaults to using all variables available.
-    
-        date: Directory to specify begin and end time for the derived time 
-                series. Defaluts to using all times available in ncfile_in.
-        
-        cs: chunk size, i.e. how many time steps to interpolate at once. This 
-            helps to manage overall memory usage (small cs is slower but less
-            memory intense).          
-        """
-                
-        # read in one type of mutiple netcdf files       
-        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim ='time')
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
-
-        # build the output of empty netCDF file
-        ERAIgeneric().netCDF_empty(ncfile_out, self.stations, ncf_in) 
-                                     
-        # open the output netCDF file, set it to be appendable ('a')
-        ncf_out = nc.Dataset(ncfile_out, 'a')
-
-        # get time and convert to datetime object
-        nctime = ncf_in.variables['time'][:]
-        #"hours since 1900-01-01 00:00:0.0"
-        t_unit = ncf_in.variables['time'].units 
-        try :
-            t_cal = ncf_in.variables['time'].calendar
-        except AttributeError :  # attribute doesn't exist
-            t_cal = u"gregorian" # standard
-        time = nc.num2date(nctime, units = t_unit, calendar = t_cal)
-        
-        # detect invariant files (topography etc.)
-        if len(time) ==1:
-            invariant=True
-        else:
-            invariant=False                                                                         
-                                                                                                                                                                                                                                            
-        # restrict to date/time range if given
-        if date is None:
-            tmask = time < datetime(3000, 1, 1)
-        else:
-            tmask = (time < date['end']) * (time >= date['beg'])
-                          
-        # get time vector for output
-        time_in = nctime[tmask]     
-
-        # ensure that chunk sizes cover entire period even if
-        # len(time_in) is not an integer multiple of cs
-        niter  = len(time_in) // self.cs
-        niter += ((len(time_in) % self.cs) > 0)
-
-        # loop over chunks
-        for n in range(niter):
-            # indices (relative to index of the output file)
-            beg = n * self.cs
-            # restrict last chunk to lenght of tmask plus one (to get last time)
-            end = min(n*self.cs + self.cs, len(time_in))-1
-            
-            # time to make tmask for chunk 
-            beg_time = nc.num2date(time_in[beg], units=t_unit, calendar=t_cal)
-            if invariant:
-                # allow topography to work in same code, len(nctime) = 1
-                end_time = nc.num2date(nctime[0], units=t_unit, calendar=t_cal)
-                #end = 1
-            else:
-                end_time = nc.num2date(time_in[end], units=t_unit, calendar=t_cal)
-                
-            #'<= end_time', would damage appending
-            tmask_chunk = (time <= end_time) * (time >= beg_time)
-            if invariant:
-                # allow topography to work in same code
-                tmask_chunk = [True]
-                 
-            # get the interpolated variables
-            dfield, variables = self.ERAinterp2D(ncfile_in, ncf_in, 
-                                                     self.stations, tmask_chunk,
-                                                     variables=None, date=None) 
-
-            # append time
-            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:], 
-                                                     time_in[beg:end+1])
-                                  
-            #append variables
-            for i, var in enumerate(variables):
-                if variables_skip(var):
-                    continue
-                                                              
-                if pl:
-                    if ESMFnew:
-                        # dfield has dimensions (station, variables, time, pressure levels)
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:].transpose((1,2,0))
-                    else:
-                        # dimension: time, level, station (pressure level files)
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[i,:,:,:]        
-                else:
-                    if ESMFnew:
-                        # dfield has dimensions (station, variables, time)
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:].transpose((1,0))
-                    else:
-                        # dfield has dimensions time, station (2D files)
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[i,:,:]
-                                     
-        #close the file
-        ncf_in.close()
-        ncf_out.close()         
-                         
-       
-    def levels2elevation(self, ncfile_in, ncfile_out):    
-        """
-        Linear 1D interpolation of pressure level data available for individual
-        stations to station elevation. Where and when stations are below the 
-        lowest pressure level, they are assigned the value of the lowest 
-        pressure level.
-        
-        """
-        # open file 
-        ncf = nc.MFDataset(ncfile_in, 'r', aggdim='time')
-        height = ncf.variables['height'][:]
-        nt = len(ncf.variables['time'][:])
-        nl = len(ncf.variables['level'][:])
-        
-        # list variables
-        varlist = [str_encode(x) for x in ncf.variables.keys()]
-        varlist.remove('time')
-        varlist.remove('station')
-        varlist.remove('latitude')
-        varlist.remove('longitude')
-        varlist.remove('level')
-        varlist.remove('height')
-        varlist.remove('z')
-
-        # === open and prepare output netCDF file ==============================
-        # dimensions: station, time
-        # variables: latitude(station), longitude(station), elevation(station)
-        #            others: ...(time, station)
-        # stations are integer numbers
-        # create a file (Dataset object, also the root group).
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'ERA-Interim, interpolated (bi)linearly to stations'
-        rootgrp.featureType = "timeSeries"
-
-        # dimensions
-        station = rootgrp.createDimension('station', len(height))
-        time    = rootgrp.createDimension('time', nt)
-
-        # base variables
-        time           = rootgrp.createVariable('time',     'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1900-01-01 00:00:0.0'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station','i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude','f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east'  
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-       
-        # assign base variables
-        time[:]      = ncf.variables['time'][:]
-        station[:]   = ncf.variables['station'][:]
-        latitude[:]  = ncf.variables['latitude'][:]
-        longitude[:] = ncf.variables['longitude'][:]
-        height[:]    = ncf.variables['height'][:]
-        
-        # create and assign variables from input file
-        for var in varlist:
-            tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
-            tmp.long_name = ncf.variables[var].long_name.encode('UTF8')
-            tmp.units     = ncf.variables[var].units.encode('UTF8')  
-
-        # add air pressure as new variable
-        var = 'air_pressure'
-        varlist.append(var)
-        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
-        tmp.long_name = var.encode('UTF8')
-        tmp.units     = 'hPa'.encode('UTF8') 
-        # end file prepation ===================================================
-                                                                                                
-        # loop over stations
-        for n, h in enumerate(height): 
-            # convert geopotential [mbar] to height [m], shape: (time, level)
-            ele = ncf.variables['z'][:,:,n] / 9.80665
-            # TODO: check if height of stations in data range
-            
-            # difference in elevation, level directly above will be >= 0
-            dele = ele - h
-            # vector of level indices that fall directly above station. 
-            # Apply after ravel() of data.
-            va = np.argmin(dele + (dele < 0) * 100000, axis=1) 
-            # mask for situations where station is below lowest level
-            mask = va < (nl-1)
-            va += np.arange(ele.shape[0]) * ele.shape[1]
-            
-            # Vector level indices that fall directly below station.
-            # Apply after ravel() of data.
-            vb = va + mask # +1 when OK, +0 when below lowest level
-            
-            # weights
-            wa = np.absolute(dele.ravel()[vb]) 
-            wb = np.absolute(dele.ravel()[va])
-            wt = wa + wb
-            wa /= wt # Apply after ravel() of data.
-            wb /= wt # Apply after ravel() of data.
-            
-            #loop over variables and apply interpolation weights
-            for v, var in enumerate(varlist):
-                if var == 'air_pressure':
-                    # pressure [Pa] variable from levels, shape: (time, level)
-                    data = np.repeat([ncf.variables['level'][:]],
-                                      len(time),axis=0).ravel() 
-                else:    
-                    #read data from netCDF
-                    data = ncf.variables[var][:,:,n].ravel()
-                ipol = data[va]*wa + data[vb]*wb   # interpolated value                    
-                rootgrp.variables[var][:,n] = ipol # assign to file   
-    
-        rootgrp.close()
-        ncf.close()
-        # closed file ==========================================================    
-
-
-    def TranslateCF2short(self, dpar):
-        """
-        Map CF Standard Names into short codes used in ERA-Interim netCDF files.
-        """
-        varlist = [] 
-        for var in self.variables:
-            varlist.append(dpar.get(var))
-        # drop none
-        varlist = [item for item in varlist if item is not None]      
-        # flatten
-        varlist = [item for sublist in varlist for item in sublist]         
-        return(varlist) 
-    
-    def process(self):
-        """
-        Interpolate point time series from downloaded data. Provides access to 
-        the more generically ERA-like interpolation functions.
-        """                       
-
-        # 2D Interpolation for Invariant Data      
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable keys.            
-        dummy_date  = {'beg' : datetime(1979, 1, 1, 12, 0),
-                       'end' : datetime(1979, 1, 1, 12, 0)}        
-        self.ERA2station(path.join(self.dir_inp,'erai_to.nc'), 
-                         path.join(self.dir_out,'erai_to_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   ['z', 'lsm'], date = dummy_date)  
-                                  
-        # === 2D Interpolation for Surface Analysis Data ===    
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable keys. 
-        dpar = {'air_temperature'   : ['t2m'],  # [K] 2m values
-                'relative_humidity' : ['d2m'],  # [K] 2m values
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky' : 
-                    ['tco3', 'tcwv'],   # [kg m-2] Total column ozone 
-                                        # [kg m-2] Total column W vapor                                                             
-                'wind_speed' : ['u10', 'v10']}   # [m s-1] 10m values   
-        varlist = self.TranslateCF2short(dpar)                      
-        self.ERA2station(path.join(self.dir_inp,'erai_sa_*.nc'), 
-                         path.join(self.dir_out,'erai_sa_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   varlist, date = self.date)          
-        
-        # 2D Interpolation for Surface Forecast Data    'tp', 'strd', 'ssrd' 
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable keys.       
-        # [m] total precipitation
-        # [J m-2] short-wave downward
-        # [J m-2] long-wave downward
-        dpar = {'precipitation_amount'              : ['tp'],   
-                'downwelling_shortwave_flux_in_air' : ['ssrd'], 
-                'downwelling_longwave_flux_in_air'  : ['strd']} 
-        varlist = self.TranslateCF2short(dpar)                           
-        self.ERA2station(path.join(self.dir_inp,'erai_sf_*.nc'), 
-                         path.join(self.dir_out,'erai_sf_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   varlist, date = self.date)          
-                         
-        
-        # === 2D Interpolation for Pressure Level Data ===
-        # dictionary to translate CF Standard Names into ERA-Interim
-        # pressure level variable keys. 
-        dpar = {'air_temperature'   : ['t'],           # [K]
-                'relative_humidity' : ['r'],           # [%]
-                'wind_speed'        : ['u', 'v']}      # [m s-1]
-        varlist = self.TranslateCF2short(dpar).append('z')
-        self.ERA2station(path.join(self.dir_inp,'erai_pl_*.nc'), 
-                         path.join(self.dir_out,'erai_pl_' + 
-                                   self.list_name + '.nc'), self.stations,
-                                   varlist, date = self.date)  
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
-        # 1D Interpolation for Pressure Level Data
-        self.levels2elevation(path.join(self.dir_out,'erai_pl_' + 
-                                        self.list_name + '.nc'), 
-                              path.join(self.dir_out,'erai_pl_' + 
-                                        self.list_name + '_surface.nc')) 
-        
-
-                                                        
-class ERAIscale(object):
-    """
-    Class for ERA-Interim data that has methods for scaling station data to
-    better resemble near-surface fluxes.
-    
-    Processing kernels have names in UPPER CASE.
-       
-    Args:
-        sfile: Full path to a Globsim Scaling Parameter file. 
-              
-    Example:          
-        ERAd = ERAIscale(sfile) 
-        ERAd.process()
-    """
-        
-    def __init__(self, sfile):
-        # read parameter file
-        self.sfile = sfile
-        par = ParameterIO(self.sfile)
-        self.intpdir = path.join(par.project_directory, 'interpolated')
-        self.scdir = self.makeOutDir(par)
-        self.list_name = par.station_list.split(path.extsep)[0]
-        
-        # read kernels
-        self.kernels = par.kernels
-        if not isinstance(self.kernels, list):
-            self.kernels = [self.kernels]
-        
-        # input file handles
-        self.nc_pl = nc.Dataset(path.join(self.intpdir, 'erai_pl_' + 
-                                          self.list_name + '_surface.nc'), 'r')
-        self.nc_sa = nc.Dataset(path.join(self.intpdir,'erai_sa_' + 
-                                          self.list_name + '.nc'), 'r')
-        self.nc_sf = nc.Dataset(path.join(self.intpdir, 'erai_sf_' + 
-                                          self.list_name + '.nc'), 'r')
-        self.nc_to = nc.Dataset(path.join(self.intpdir, 'erai_to_' + 
-                                          self.list_name + '.nc'), 'r')
-        self.nstation = len(self.nc_to.variables['station'][:])                     
-                              
-        # check if output file exists and remove if overwrite parameter is set
-        self.output_file = self.getOutNCF(par, 'erai')
-        
-        # time vector for output data 
-        # get time and convert to datetime object
-        nctime = self.nc_sf.variables['time'][:]
-        self.t_unit = self.nc_sf.variables['time'].units #"hours since 1900-01-01 00:00:0.0"
-        self.t_cal  = self.nc_sf.variables['time'].calendar
-        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal) 
-        
-        # interpolation scale factor
-        self.time_step = par.time_step * 3600    # [s] scaled file
-        interval_in = (time[1]-time[0]).seconds #interval in seconds
-        self.interpN = floor(interval_in/self.time_step)
-        
-        #number of time steps for output, include last value
-        self.nt = int(floor((max(time) - min(time)).total_seconds() 
-                      / 3600 / par.time_step))+1
-        
-        # vector of output time steps as datetime object
-        # 'seconds since 1900-01-01 00:00:0.0'
-        mt = min(time)
-        self.times_out = [mt + timedelta(seconds = (x*self.time_step)) 
-                          for x in range(0, self.nt)]                                                                   
-                                      
-        # vector of output time steps as written in ncdf file [s]
-        self.scaled_t_units = 'seconds since 1900-01-01 00:00:00'
-        self.times_out_nc = nc.date2num(self.times_out, 
-                                        units = self.scaled_t_units, 
-                                        calendar = self.t_cal) 
-        # get the station file
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-               
-    def process(self):
-        """
-        Run all relevant processes and save data. Each kernel processes one 
-        variable and adds it to the netCDF file.
-        """
-        if path.isfile(self.output_file):
-            print("Warning, output file already exists. This may cause problems")    
-        self.rg = ScaledFileOpen(self.output_file, self.nc_pl, 
-                                 self.times_out_nc, 
-                                 t_unit = self.scaled_t_units, 
-                                 station_names = self.stations['station_name'])
-        
-        # iterate through kernels and start process
-        for kernel_name in self.kernels:
-            if hasattr(self, kernel_name):	
-                print(kernel_name)
-                getattr(self, kernel_name)()   
-            
-        # close netCDF files   
-        self.rg.close()
-        self.nc_pl.close()
-        self.nc_sf.close()
-        self.nc_sa.close()
-        self.nc_to.close()
-        
-    def getOutNCF(self, par, src, scaleDir = 'scale'):
-        '''make out file name'''
-        
-        timestep = str(par.time_step) + 'h'
-        src = '_'.join(['scaled', src, timestep])
-        src = src + '.nc'
-        fname = path.join(self.scdir, src)
-        
-        return fname
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirSC = path.join(par.project_directory, 'scaled')
-        
-        if not (path.isdir(dirSC)):
-            makedirs(dirSC)
-            
-        return dirSC
-        
-        
-    def PRESS_Pa_pl(self):
-        """
-        Surface air pressure from pressure levels.
-        """        
-        # add variable to ncdf file
-        vn = 'PRESS_ERAI_Pa_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_pressure ERA-I pressure levels only'
-        var.units     = 'Pa'.encode('UTF8')  
-        
-        # interpolate station by station
-        time_in = self.nc_pl.variables['time'][:].astype(np.int64)  
-        values  = self.nc_pl.variables['air_pressure'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            #scale from hPa to Pa 
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
-                             time_in * 3600, values[:, n]) * 100          
-
-    def AIRT_C_pl(self):
-        """
-        Air temperature derived from pressure levels, exclusively.
-        """        
-        # add variable to ncdf file
-        vn = 'AIRT_ERAI_C_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_temperature ERA-I pressure levels only'
-        var.units     = self.nc_pl.variables['t'].units.encode('UTF8')  
-        
-        # interpolate station by station
-        time_in = self.nc_pl.variables['time'][:].astype(np.int64)  
-        values  = self.nc_pl.variables['t'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                        time_in*3600, values[:, n]-273.15)          
-
-    def AIRT_C_sur(self):
-        """
-        Air temperature derived from surface data, exclusively.
-        """
-        # add variable to ncdf file
-        vn = 'AIRT_ERAI_C_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '2_metre_temperature ERA-I surface only'
-        var.units     = self.nc_sa.variables['t2m'].units.encode('UTF8')  
-        
-        # interpolate station by station
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)      
-        values  = self.nc_sa.variables['t2m'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                                    time_in*3600, 
-                                                    values[:, n]-273.15)           
-        
-    def AIRT_redcapp(self):
-        """
-        Air temperature derived from surface data and pressure level data as
-        shown by the method REDCAPP.
-        """       
-        print("AIRT_ERAI_redcapp")            
-
-    def PREC_mm_sur(self):
-        """
-        Precipitation sum in mm for the time step given.
-        """   
-        # add variable to ncdf file	
-        vn = 'PREC_ERAI_mm_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Total precipitation ERA-I surface only'
-        var.units     = 'kg m-2 s-1'
-        var.standard_name = 'precipitation_amount'
-        
-        # interpolate station by station
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sf.variables['tp'][:]*1000 #[mm]
-        
-        nctime = self.nc_sf['time'][:]
-        self.t_unit = self.nc_sf['time'].units #"hours since 1900-01-01 00:00:0.0"
-        self.t_cal  = self.nc_sf['time'].calendar
-        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal) 
-        
-        interval_in = (time[1]-time[0]).seconds
-        
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
-            f = interp1d(time_in*3600, 
-                         cummulative2total(values[:, n], time)/interval_in,
-                         kind = 'linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
-            
-    def RH_per_sur(self):
-        """
-        Relative humdity derived from surface data, exclusively. Clipped to
-        range [0.1,99.9]. Kernel AIRT_ERAI_C_sur must be run before.
-        """         
-        # temporary variable,  interpolate station by station
-        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['d2m'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            dewp[:, n] = series_interpolate(self.times_out_nc, 
-                                            time_in*3600, values[:, n]-273.15) 
-                                                    
-        # add variable to ncdf file
-        vn = 'RH_ERAI_per_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Relative humidity ERA-I surface only'
-        var.units     = 'percent'
-        var.standard_name = 'relative_humidity'
-        
-        # simple: https://doi.org/10.1175/BAMS-86-2-225
-        RH = 100 - 5 * (self.rg.variables['AIRT_ERAI_C_sur'][:, :]-dewp[:, :])
-        self.rg.variables[vn][:, :] = RH.clip(min=0.1, max=99.9)    
-        
-        
-    def WIND_sur(self):
-        """
-        Wind speed and direction temperature derived from surface data, 
-        exclusively.
-        """    
-        # temporary variable, interpolate station by station
-        U = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['u10'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            U[:, n] = series_interpolate(self.times_out_nc, 
-                                         time_in*3600, values[:, n]) 
-        
-        # temporary variable, interpolate station by station
-        V = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['v10'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            V[:, n] = series_interpolate(self.times_out_nc, 
-                                         time_in*3600, values[:, n]) 
-
-        # wind speed, add variable to ncdf file, convert
-        vn = 'WSPD_ERAI_ms_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 wind speed ERA-I surface only'
-        var.units     = 'm s-1'  
-        var.standard_name = 'wind_speed'
-                
-        # wind direction, add variable to ncdf file, convert, relative to North 
-        vn = 'WDIR_ERAI_deg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 wind direction ERA-I surface only'
-        var.units     = 'degree'
-        var.standard_name = 'wind_from_direction'
-
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            WS = np.sqrt(np.power(V,2) + np.power(U,2))
-            WD = [atan2(V[i, n], U[i, n])*(180/pi) + 
-                  180 for i in np.arange(V.shape[0])]
-            self.rg.variables['WSPD_ERAI_ms_sur'][:, n] = WS
-            self.rg.variables['WDIR_ERAI_deg_sur'][:,n] = WD
-        
-    def SW_Wm2_sur(self):
-        """
-        Short-wave downwelling radiation derived from surface data, exclusively.  
-        This kernel only interpolates in time.
-        """   
-        
-        # add variable to ncdf file
-        vn = 'SW_ERAI_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface solar radiation downwards ERA-I surface only'
-        var.units     = self.nc_sf.variables['ssrd'].units.encode('UTF8')  
-        
-        # interpolation scale factor
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sf.variables['ssrd'][:]/3600 #w/m2
-        
-        nctime = self.nc_sf['time'][:]
-        self.t_unit = self.nc_sf['time'].units #"hours since 1900-01-01 00:00:0.0"
-        self.t_cal  = self.nc_sf['time'].calendar
-        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal) 
-        
-        interval_in = (time[1]-time[0]).seconds
-        
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
-            f = interp1d(time_in*3600, 
-                         cummulative2total(values[:, n], time)/interval_in,
-                         kind = 'linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
-                
-
-    def LW_Wm2_sur(self):
-        """
-        Long-wave downwelling radiation derived from surface data, exclusively.  
-        This kernel only interpolates in time.
-        """   
-        
-        # add variable to ncdf file
-        vn = 'LW_ERAI_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface thermal radiation downwards ERA-I surface only'
-        var.units     = 'W m-2'
-        var.standard_name = 'surface_downwelling_longwave_flux'
-        
-        # interpolate station by station
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sf.variables['strd'][:]/3600 #[w m-2]
-        
-        nctime = self.nc_sf['time'][:]
-        self.t_unit = self.nc_sf['time'].units #"hours since 1900-01-01 00:00:0.0"
-        self.t_cal  = self.nc_sf['time'].calendar
-        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
-		
-        # interpolation scale factor
-        interval_in = (time[1]-time[0]).seconds
-        
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            f = interp1d(time_in*3600, 
-                         cummulative2total(values[:, n], time)/interval_in, 
-                         kind='linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step 
-                                                  
-    def SH_kgkg_sur(self):
-        '''
-        Specific humidity [kg/kg]
-        https://crudata.uea.ac.uk/cru/pubs/thesis/2007-willett/2INTRO.pdf
-        '''
-        # add variable to ncdf file
-        vn = 'SH_ERAI_kgkg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Specific humidity ERA-I surface only'
-        var.units     = '1'
-        var.standard_name = 'specific_humidity'
-        
-        # temporary variable, interpolate station by station
-        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
-        time_in = self.nc_sa.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sa.variables['d2m'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            dewp[:, n] = series_interpolate(self.times_out_nc, 
-                                            time_in*3600, values[:, n]-273.15) 
-
-        # compute
-        SH = spec_hum_kgkg(dewp[:, :], 
-                           self.rg.variables['PRESS_ERAI_Pa_pl'][:, :])  
-        
- 
-        self.rg.variables[vn][:, :] = SH
-
-    def LW_Wm2_topo(self):
-        """
-        Long-wave radiation downwards [W/m2]
-        https://www.geosci-model-dev.net/7/387/2014/gmd-7-387-2014.pdf
-        """             
-        # get sky view, and interpolate in time
-        N = np.asarray(list(self.stations['sky_view'][:]))
-
-        # add variable to ncdf file
-        vn = 'LW_ERAI_Wm2_topo' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Incoming long-wave radiation ERA-I surface only'
-        var.units     = 'W m-2'
-        var.standard_name = 'surface_downwelling_longwave_flux'
-
-        # compute                            
-        for i in range(0, len(self.rg.variables['RH_ERAI_per_sur'][:])):
-            for n, s in enumerate(self.rg.variables['station'][:].tolist()):
-                LW = LW_downward(self.rg.variables['RH_ERAI_per_sur'][i, n],
-                     self.rg.variables['AIRT_ERAI_C_sur'][i, n]+273.15, N[n])
-                self.rg.variables[vn][i, n] = LW
-
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Methods for downloading ERA-Interim data from the ECMWF server for limited
+# areas and limited times.
+#
+#
+# (C) Copyright Stephan Gruber (2013–2017)
+#
+#     This program is free software: you can redistribute it and/or modify
+#     it under the terms of the GNU General Public License as published by
+#     the Free Software Foundation, either version 3 of the License, or
+#     (at your option) any later version.
+#
+#     This program is distributed in the hope that it will be useful,
+#     but WITHOUT ANY WARRANTY; without even the implied warranty of
+#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#     GNU General Public License for more details.
+#
+#     You should have received a copy of the GNU General Public License
+#     along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#  OVERALL WORKFLOW
+#
+#  See: https://github.com/geocryology/globsim/wiki/Globsim
+#       and the examples directory on github
+#
+# ECMWF and netCDF information:
+# https://software.ecmwf.int/wiki/display/WEBAPI/Python+ERA-interim+examples
+#
+# For variable codes and units, see:
+#     http://www.ecmwf.int/publications/manuals/d/gribapi/param/
+#
+# Check ECMWF job status: http://apps.ecmwf.int/webmars/joblist/
+#
+#  CF-Convention: this is how you check netcdf file conformity:
+#  http://pumatest.nerc.ac.uk/cgi-bin/cf-checker-dev.pl
+#
+# ===============================================================================
+from __future__ import print_function
+
+import glob
+import re
+import numpy as np
+import netCDF4 as nc
+
+from datetime import datetime, timedelta
+from ecmwfapi.api import ECMWFDataServer
+from math import floor, atan2, pi
+from os import path, listdir, remove
+from scipy.interpolate import interp1d
+from fnmatch import filter
+
+from globsim.common_utils import (series_interpolate, variables_skip, str_encode,
+                                  cummulative2total, GenericDownload, GenericScale,
+                                  GenericInterpolate)
+from globsim.nc_elements import netcdf_base, new_interpolated_netcdf, new_scaled_netcdf
+from globsim.meteorology import spec_hum_kgkg, LW_downward, pressure_from_elevation
+
+try:
+    from nco import Nco
+except ImportError:
+    print("*** NCO not imported, netCDF appending not possible. ***")
+    pass
+
+try:
+    import ESMF
+
+    # Check ESMF version.  7.0.1 behaves differently than 7.1.0r
+    ESMFv = int(re.sub("[^0-9]", "", ESMF.__version__))
+    ESMFnew = ESMFv > 701
+except ImportError:
+    print("*** ESMF not imported, interpolation not possible. ***")
+    pass
+
+
+class ERAIgeneric(object):
+    """
+    Parent class for other ERA-Interim classes.
+    """
+
+    def areaString(self, area):
+        """Converts numerical coordinates into string: North/West/South/East"""
+        res = str(round(area['north'],2)) + "/"
+        res += str(round(area['west'], 2)) + "/"
+        res += str(round(area['south'],2)) + "/"
+        res += str(round(area['east'], 2))
+        return(res)
+
+    def dateString(self, date):
+        """Converts datetime objects into string"""
+        res = (date['beg'].strftime("%Y-%m-%d") + "/to/" +
+               date['end'].strftime("%Y-%m-%d"))
+        return(res)
+
+    def getPressureLevels(self, elevation):
+        """Restrict list of ERA-interim pressure levels to be downloaded"""
+        Pmax = pressure_from_elevation(elevation['min']) + 55
+        Pmin = pressure_from_elevation(elevation['max']) - 55
+        levs = np.array([300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 775,
+                         800, 825, 850, 875, 900, 925, 950, 975, 1000])
+        mask = (levs >= Pmin) * (levs <= Pmax)  # select
+        levs = '/'.join(map(str, levs[mask]))
+        return levs
+
+    def getDictionaryGen(self, area, date):
+        """
+        Makes dictionary of generic variables for a server call
+        """
+        dictionary_gen = {
+          'area'    : self.areaString(area),
+          'date'    : self.dateString(date),
+          'dataset' : "interim",
+          'stream'  : "oper",
+          'class'   : "ei",
+          'format'  : "netcdf",
+          'grid'    : "0.75/0.75"}
+        return dictionary_gen
+
+    def getDstring(self):
+        return ('_' + self.date['beg'].strftime("%Y%m%d") + "_to_" +
+                self.date['end'].strftime("%Y%m%d"))
+
+    def download(self):
+        query = self.getDictionary()
+        target = query.pop('target')
+
+        if path.isfile(target):
+            print("WARNING: File '{}' already exists and was skipped".format(target))
+        else:
+            server = ECMWFDataServer()
+            print(server.trace('=== ERA Interim: START ===='))
+            server.retrieve(self.getDictionary())
+            print(server.trace('=== ERA Interim: STOP ====='))
+
+    def TranslateCF2ERA(self, variables, dpar):
+        """
+        Translate CF Standard Names into ERA-Interim code numbers.
+        """
+        self.param = ''
+        for var in variables:
+            try:
+                self.param += dpar.get(var) + '/'
+            except TypeError:
+                pass
+        self.param = self.param.rstrip('/')  # fix last
+
+    def __str__(self):
+        string = ("List of generic variables to query ECMWF server for "
+                  "ERA-Interim data: {0}")
+        return string.format(self.getDictionary)
+
+    def netCDF_merge(self, directory):
+        """
+        To combine mutiple downloaded erai netCDF files into a large file with
+        specified chunk_size(e.g. 500),
+        -- give the full name of merged file to the output = outfile
+        -- pass all data from the first input netfile to the merged file name
+        -- loop over the files_list, append file one by one into the merge file
+        -- pass the mergae netcdf file to interpolation module to process
+        (to use nc.MFDataset by reading it)
+
+        Args:
+            ncfile_in: the full name of downloaded files (file directory + files names)
+        e.g.:
+              '/home/xquan/src/globsim/examples/erai/era_sa_*.nc'
+              '/home/xquan/src/globsim/examples/erai/era_pl_*.nc'
+              '/home/xquan/src/globsim/examples/erai/era_sf_*.nc'
+
+        Output: merged netCDF files
+        era_all_0.nc, era_all_1.nc, ...,
+
+        """
+        # set up nco operator
+        nco = Nco()
+
+        # loop over filetypes, read, report
+        file_type = ['erai_sa_*.nc', 'erai_sf_*.nc', 'erai_pl_*.nc']
+        for ft in file_type:
+            ncfile_in = path.join(directory, ft)
+
+            # get the file list
+            files_list = glob.glob(ncfile_in)
+            files_list.sort()
+            num = len(files_list)
+
+            # set up the name of merged file
+            if ncfile_in[-7:-5] == 'sa':
+                merged_file = path.join(ncfile_in[:-11],'erai_sa_all_'+ files_list[0][-23:-15] + "_" + files_list[num-1][-11:-3] +'.nc')
+            elif ncfile_in[-7:-5] == 'sf':
+                merged_file = path.join(ncfile_in[:-11],'erai_sf_all_' + files_list[0][-23:-15] + '_' + files_list[num-1][-11:-3] + '.nc')
+            elif ncfile_in[-7:-5] == 'pl':
+                merged_file = path.join(ncfile_in[:-11],'erai_pl_all_'+ files_list[0][-23:-15] + '_' + files_list[num-1][-11:-3] +'.nc')
+            else:
+                print('There is not such type of file')
+
+            # combined files into merged files
+            nco.ncrcat(input=files_list,output=merged_file, append=True)
+
+            print('The Merged File below is saved:')
+            print(merged_file)
+
+            #clear up the data
+            for fl in files_list:
+                remove(fl)
+
+    def mergeFiles(self, ncfile_in):
+        """
+        To combine mutiple downloaded erai netCDF files into a large file.
+
+        Args:
+            ncfile_in: the full name of downloaded files (file directory + files names)
+        e.g.:
+              '/home/xquan/src/globsim/examples/erai/era_sa_*.nc'
+              '/home/xquan/src/globsim/examples/erai/era_pl_*.nc'
+              '/home/xquan/src/globsim/examples/erai/era_sf_*.nc'
+
+        Output: merged netCDF files
+        erai_sa_all.nc, erai_sf_all.nc, erai_pl_all.nc
+
+        """
+
+        # read in one type of mutiple netcdf files
+        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim='time')
+
+        # is it a file with pressure levels?
+        pl = 'level' in ncf_in.dimensions.keys()
+
+        # get spatial dimensions
+        lat  = ncf_in.variables['latitude'][:]
+        lon  = ncf_in.variables['longitude'][:]
+        if pl: # only for pressure level files
+            lev  = ncf_in.variables['level'][:]
+            nlev = len(lev)
+
+        # get time and convert to datetime object
+        nctime = ncf_in.variables['time'][:]
+
+        #set up the name of merged file
+        if ncfile_in[-7:-5] == 'sa':
+            ncfile_out = path.join(ncfile_in[:-11],'erai_sa_all' + '.nc')
+        elif ncfile_in[-7:-5] == 'sf':
+            ncfile_out = path.join(ncfile_in[:-11],'erai_sf_all' + '.nc')
+        elif ncfile_in[-7:-5] == 'pl':
+            ncfile_out = path.join(ncfile_in[:-11],'erai_pl_all' + '.nc')
+        else:
+            print('There is not such type of file'    )
+
+        # get variables
+        varlist = [str_encode(x) for x in ncf_in.variables.keys()]
+        varlist.remove('time')
+        varlist.remove('latitude')
+        varlist.remove('longitude')
+        if pl: #only for pressure level files
+            varlist.remove('level')
+
+        #Build the netCDF file
+        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4_CLASSIC')
+        rootgrp.Conventions = 'CF-1.6'
+        rootgrp.source      = 'ERA_Interim, merged downloaded original files'
+        rootgrp.featureType = "timeSeries"
+
+        # dimensions
+        latitude = rootgrp.createDimension('latitude', len(lat))
+        longitude = rootgrp.createDimension('longitude', len(lon))
+        time    = rootgrp.createDimension('time', None)
+
+        # base variables
+        time           = rootgrp.createVariable('time', 'i4',('time'))
+        time.long_name = 'time'
+        time.units     = 'hours since 1900-01-01 00:00:0.0'
+        time.calendar  = 'gregorian'
+        latitude            = rootgrp.createVariable('latitude', 'f4',('latitude'))
+        latitude.long_name  = 'latitude'
+        latitude.units      = 'degrees_north'
+        longitude           = rootgrp.createVariable('longitude','f4',('longitude'))
+        longitude.long_name = 'longitude'
+        longitude.units     = 'degrees_east'
+
+        # assign station characteristics
+        latitude[:]  = lat[:]
+        longitude[:] = lon[:]
+        time[:]    = nctime[:]
+
+        # extra treatment for pressure level files
+        try:
+            lev = ncf_in.variables['level'][:]
+            print("== 3D: file has pressure levels")
+            level = rootgrp.createDimension('level', len(lev))
+            level           = rootgrp.createVariable('level','i4',('level'))
+            level.long_name = 'pressure_level'
+            level.units     = 'hPa'
+            level[:] = lev
+        except:
+            print("== 2D: file without pressure levels")
+            lev = []
+
+        # create and assign variables based on input file
+        for n, var in enumerate(varlist):
+            print("VAR: ", var)
+            # extra treatment for pressure level files
+            if len(lev):
+                tmp = rootgrp.createVariable(var,'f4',('time', 'level', 'latitude', 'longitude'))
+            else:
+                tmp = rootgrp.createVariable(var,'f4',('time', 'latitude', 'longitude'))
+            tmp.long_name = ncf_in.variables[var].long_name.encode('UTF8') # for erai
+            tmp.units     = ncf_in.variables[var].units.encode('UTF8')
+
+            # assign values
+            if pl: # only for pressure level files
+                tmp[:] = ncf_in.variables[var][:,:,:,:]
+            else:
+                tmp[:] = ncf_in.variables[var][:,:,:]
+
+
+        #close the file
+        rootgrp.close()
+        ncf_in.close()
+
+        #get the file list
+        files_list = glob.glob(ncfile_in)
+        files_list.sort()
+
+        #clear up the data
+        for fl in files_list:
+            remove(fl)
+
+
+class ERAIpl(ERAIgeneric):
+    """Returns an object for ERA-Interim data that has methods for querying the
+    ECMWF server.
+
+    Args:
+        date: A dictionary specifying the time period desired with a begin
+              and an end date given as a datetime.datetime object.
+
+        area: A dictionary delimiting the area to be queried with the latitudes
+              north and south, and the longitudes west and east [decimal deg].
+
+        elevation: A dictionary specifying the min/max elevation of the area of
+                   interest. This is used to determine the pressure levels
+                   needed. Unit: [m].
+
+        variables:  List of variable(s) to download based on CF Standard Names.
+
+        directory: Directory to hold output files
+
+    Example:
+        from datetime import datetime
+        date  = {'beg' : datetime(1994, 1, 1),
+                 'end' : datetime(2013, 1, 1)}
+        area  = {'north' :  40.0,
+                 'south' :  15.0,
+                 'west'  :  60.0,
+                 'east'  : 105.0}
+        elevation = {'min' :    0,
+                     'max' : 8850}
+        variables  = ['air_temperature', 'relative_humidity']
+        directory = '/Users/stgruber/Desktop'
+        ERAIpl = ERAIpl(date, area, elevation, variables, directory)
+        ERAIpl.download()
+    """
+    def __init__(self, date, area, elevation, variables, directory):
+        self.date       = date
+        self.area       = area
+        self.elevation  = elevation
+        self.directory  = directory
+        outfile = 'erai_pl' + self.getDstring() + '.nc'
+        self.file_ncdf  = path.join(self.directory, outfile)
+
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable names.
+        dpar = {'air_temperature'   : '130.128',           # [K]
+                'relative_humidity' : '157.128',           # [%]
+                'wind_speed'        : '131.128/132.128'}   # [m s-1]
+
+        # translate variables into those present in ERA pl data
+        self.TranslateCF2ERA(variables, dpar)
+        self.param += '/129.128' # geopotential always needed [m2 s-2]
+
+    def getDictionary(self):
+        self.dictionary = {
+           'levtype'  : "pl",
+           'levellist': self.getPressureLevels(self.elevation),
+           'time'     : "00/06/12/18",
+           'step'     : "0",
+           'type'     : "an",
+           'param'    : self.param,
+           'target'   : self.file_ncdf
+           }
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("List of variables to query ECMWF server for "
+                  "ERA-Interim data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERAIsa(ERAIgeneric):
+    """
+    Returns an object for ERA-Interim data that has methods for querying the
+    ECMWF server for surface analysis variables (airt2,dewp2, ozone, vapor,
+    wind10).
+
+    Args:
+
+        target:    File name of the netcdf file to be created.
+
+        variable:  List of variable(s) to download based on CF Standard Names
+
+    Example:
+        from datetime import datetime
+        date  = {'beg' : datetime(1994, 1, 1),
+                 'end' : datetime(2013, 1, 1)}
+        area  = {'north' :  40.0,
+                 'south' :  15.0,
+                 'west'  :  60.0,
+                 'east'  : 105.0}
+        variables  = ['air_temperature', 'relative_humidity']
+        directory = '/Users/stgruber/Desktop'
+        ERAIsa = ERAIsa(date, area, variables, directory)
+        ERAIsa.download()
+    """
+    def __init__(self, date, area, variables, directory):
+        self.date       = date
+        self.area       = area
+        self.directory  = directory
+        outfile = 'erai_sa' + self.getDstring() + '.nc'
+        self.file_ncdf  = path.join(self.directory, outfile)
+
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable names.
+        dpar = {'air_temperature'   : '167.128',  # [K] 2m values
+                'relative_humidity' : '168.128',  # [K] 2m values
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky' :
+                    '206.128/137.128',  # [kg m-2] Total column ozone
+                                        # [kg m-2] Total column W vapor
+                'wind_speed' : '165.128/166.128'}   # [m s-1] 10m values
+
+        # translate variables into those present in ERA pl data
+        self.TranslateCF2ERA(variables, dpar)
+        print(self.param)
+
+
+    def getDictionary(self):
+        self.dictionary = {
+           'levtype'  : "sfc",
+           'time'     : "00/06/12/18",
+           'step'     : "0",
+           'type'     : "an",
+           'param'    : self.param,
+           'target'   : self.file_ncdf
+           }
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("Class for ERA-Interim surface analysis data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERAIsf(ERAIgeneric):
+    """
+    Returns an object for ERA-Interim data that has methods for querying the
+    ECMWF server for surface forecast variables (prec, swin, lwin).
+
+    Args:
+        target:    File name of the netcdf file to be created.
+
+        variables:  List of variable(s) to download that can include one, several
+                   , or all of these: ['airt', 'rh', 'geop', 'wind'].
+
+        ERAgen:    ERAIgeneric() object with generic information on the area and
+                   time span to be
+
+    Example:
+        from datetime import datetime
+        date  = {'beg' : datetime(1994, 1, 1),
+                 'end' : datetime(1994, 1, 1)}
+        area  = {'north' :  40.0,
+                 'south' :  40.0,
+                 'west'  :  60.0,
+                 'east'  :  60.0}
+        variables  = ['prec','swin','lwin']
+        directory = '/Users/stgruber/Desktop'
+        ERAIsf = ERAIsf(date, area, variables, directory)
+        ERAIsf.download()
+    """
+    def __init__(self, date, area, variables, directory):
+        self.date       = date
+        self.area       = area
+        self.directory  = directory
+        outfile = 'erai_sf' + self.getDstring() + '.nc'
+        self.file_ncdf  = path.join(self.directory, outfile)
+
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable names.
+        # [m] total precipitation
+        # [J m-2] short-wave downward
+        # [J m-2] long-wave downward
+        dpar = {'precipitation_amount'              : '228.128',
+                'downwelling_shortwave_flux_in_air' : '169.128',
+                'downwelling_longwave_flux_in_air'  : '175.128'}
+
+        # translate variables into those present in ERA pl data
+        self.TranslateCF2ERA(variables, dpar)
+        print(self.param)
+
+
+    def getDictionary(self):
+        self.dictionary = {
+           'levtype'  : "sfc",
+           'time'     : "00/12",
+           'step'     : "3/6/9/12",
+           'type'     : "fc",
+           'param'    : self.param,
+           'target'   : self.file_ncdf
+           }
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("List of variables to query ECMWF server for "
+                  "ERA-Interim air tenperature data: {0}")
+        return string.format(self.getDictionary)
+
+class ERAIto(ERAIgeneric):
+    """
+    Returns an object for downloading and handling ERA-Interim
+    topography (invariant).
+
+    Args:
+
+    Example:
+        area  = {'north' :  40.0,
+                 'south' :  45.0,
+                 'west'  :  60.0,
+                 'east'  :  65.0}
+        directory = '/Users/stgruber/Desktop'
+        ERAIto = ERAIto(area, directory)
+        ERAIto.download()
+    """
+    def __init__(self, area, directory):
+        self.area       = area
+        self.date       = {'beg' : datetime(1979, 1, 1),
+                           'end' : datetime(1979, 1, 1)}
+        self.directory  = directory
+        self.file_ncdf  = path.join(self.directory,'erai_to.nc')
+
+    def getDictionary(self):
+        self.dictionary = {
+           'levtype'  : "sfc",
+           'time'     : "12",
+           'step'     : "0",
+           'type'     : "an",
+           'param'    : "129.128/172.128", # geopotential and land-sea mask
+           'target'   : self.file_ncdf
+           }
+        self.dictionary.update(self.getDictionaryGen(self.area, self.date))
+        return self.dictionary
+
+    def __str__(self):
+        string = ("List of variables to query ECMWF server for "
+                  "ERA-Interim air tenperature data: {0}")
+        return string.format(self.getDictionary)
+
+
+class ERAIdownload(GenericDownload):
+    """
+    Class for ERA-Interim data that has methods for querying
+    the ECMWF server, returning all variables usually needed.
+
+    Args:
+        pfile: Full path to a Globsim Download Parameter file.
+
+    Example:
+        ERAd = ERAdownload(pfile)
+        ERAd.retrieve()
+    """
+
+    def __init__(self, pfile):
+        super().__init__(pfile)
+        par = self.par
+        self._set_data_directory("erai")
+
+        # time bounds
+        self.date  = {'beg': datetime.strptime(par['beg'], '%Y/%m/%d'),
+                      'end': datetime.strptime(par['end'], '%Y/%m/%d')}
+
+        # chunk size for downloading and storing data [days]
+        self.chunk_size = par['chunk_size']
+
+    def retrieve(self):
+        """
+        Retrieve all required ERA-Interim data from MARS server.
+        """
+        # prepare time loop
+        date_i = {}
+        slices = floor(float((self.date['end'] - self.date['beg']).days)/
+                       self.chunk_size)+1
+        # topography
+        top = ERAIto(self.area, self.directory)
+        top.download()
+
+        for ind in range (0, int(slices)):
+            #prepare time slices
+            date_i['beg'] = self.date['beg'] + timedelta(days =
+                            self.chunk_size * ind)
+            date_i['end'] = self.date['beg'] + timedelta(days =
+                            self.chunk_size * (ind+1) - 1)
+            if ind == (slices-1):
+                date_i['end'] = self.date['end']
+
+            #actual functions
+            pl = ERAIpl(date_i, self.area, self.elevation,
+                       self.variables, self.directory)
+            sa = ERAIsa(date_i, self.area, self.variables, self.directory)
+            sf = ERAIsf(date_i, self.area, self.variables, self.directory)
+
+            #download from ECMWF server convert to netCDF
+            ERAli = [pl, sa, sf]
+            for era in ERAli:
+                era.download()
+
+
+        # report inventory
+        self.inventory()
+
+
+    def inventory(self):
+        """
+        Report on data avaialbe in directory: time slice, variables, area
+        """
+        print("\n\n\n")
+        print("=== INVENTORY FOR GLOBSIM ERA-INTERIM DATA === \n")
+        print("Download parameter file: \n" + self.pfile + "\n")
+        # loop over filetypes, read, report
+        file_type = ['erai_pl_*.nc', 'erai_sa_*.nc', 'erai_sf_*.nc', 'erai_t*.nc']
+        for ft in file_type:
+            infile = path.join(self.directory, ft)
+            nf = len(filter(listdir(self.directory), ft))
+            print(str(nf) + " FILE(S): " + infile)
+
+            if nf > 0:
+                # open dataset
+                ncf = nc.MFDataset(infile, 'r')
+
+                # list variables
+                keylist = [str_encode(x) for x in ncf.variables.keys()]
+
+                print("    VARIABLES:")
+                print("        " + str(len(keylist)) +
+                      " variables, inclusing dimensions")
+                for key in keylist:
+                    print("        " + ncf.variables[key].long_name)
+
+                # time slice
+                time = ncf.variables['time']
+                tmin = nc.num2date(min(time[:]), time.units,
+                                   calendar=time.calendar).strftime('%Y/%m/%d')
+                tmax = nc.num2date(max(time[:]), time.units,
+                                   calendar=time.calendar).strftime('%Y/%m/%d')
+                print("    TIME SLICE")
+                print("        " + str(len(time[:])) + " time steps")
+                print("        " + tmin + " to " + tmax)
+
+                # area
+                lon = ncf.variables['longitude']
+                lat = ncf.variables['latitude']
+                nlat = str(len(lat))
+                nlon = str(len(lon))
+                ncel = str(len(lat) * len(lon))
+                print("    BOUNDING BOX / AREA")
+                print("        " + ncel + " cells, " + nlon +
+                      " W-E and " + nlat + " S-N")
+                print("        N: " + str(max(lat)))
+                print("        S: " + str(min(lat)))
+                print("        W: " + str(min(lon)))
+                print("        E: " + str(max(lon)))
+
+                ncf.close()
+
+    def __str__(self):
+        return "Object for ERA-Interim data download and conversion"
+
+
+class ERAIinterpolate(GenericInterpolate):
+    """
+    Collection of methods to interpolate ERA-Interim netCDF files to station
+    coordinates. All variables retain theit original units and time stepping.
+    """
+
+    def __init__(self, ifile):
+        super().__init__(ifile)
+        par = self.par
+        self.dir_inp = path.join(par['project_directory'], 'erai')
+
+        #convert longitude to ERA notation if using negative numbers
+        self.stations['longitude_dd'] = self.stations['longitude_dd'] % 360
+
+    def ERA2station(self, ncfile_in, ncfile_out, points,
+                    variables = None, date = None):
+
+        """
+        Biliner interpolation from fields on regular grid (latitude, longitude)
+        to individual point stations (latitude, longitude). This works for
+        surface and for pressure level files (all Era_Interim files). The type
+        of variable and file structure are determined from the input.
+
+        This function creates an empty of netCDF file to hold the interpolated
+        results, by calling ERAIgeneric().netCDF_empty. Then, data is
+        interpolated in temporal chunks and appended. The temporal chunking can
+        be set in the interpolation parameter file.
+
+        Args:
+        ncfile_in: Full path to an Era-Interim derived netCDF file. This can
+                   contain wildcards to point to multiple files if temporal
+                  chunking was used.
+
+        ncfile_out: Full path to the output netCDF file to write.
+
+        points: A dictionary of locations. See method StationListRead in
+                common_utils.py for more details.
+
+        variables:  List of variable(s) to interpolate such as
+                    ['r', 't', 'u','v', 't2m', 'u10', 'v10', 'ssrd', 'strd', 'tp'].
+                    Defaults to using all variables available.
+
+        date: Directory to specify begin and end time for the derived time
+                series. Defaluts to using all times available in ncfile_in.
+
+        cs: chunk size, i.e. how many time steps to interpolate at once. This
+            helps to manage overall memory usage (small cs is slower but less
+            memory intense).
+        """
+
+        # read in one type of mutiple netcdf files
+        ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim ='time')
+
+        # is it a file with pressure levels?
+        pl = 'level' in ncf_in.dimensions.keys()
+
+        # build the output of empty netCDF file
+        rootgrp = new_interpolated_netcdf(ncfile_out, self.stations, ncf_in,
+                                      time_units= 'hours since 1900-01-01 00:00:0.0')
+        rootgrp.source = 'ERA_Interim, interpolated bilinearly to stations'
+        rootgrp.close()
+
+        # open the output netCDF file, set it to be appendable ('a')
+        ncf_out = nc.Dataset(ncfile_out, 'a')
+
+        # get time and convert to datetime object
+        nctime = ncf_in.variables['time'][:]
+        #"hours since 1900-01-01 00:00:0.0"
+        t_unit = ncf_in.variables['time'].units
+        try :
+            t_cal = ncf_in.variables['time'].calendar
+        except AttributeError :  # attribute doesn't exist
+            t_cal = u"gregorian" # standard
+        time = nc.num2date(nctime, units = t_unit, calendar = t_cal)
+
+        # detect invariant files (topography etc.)
+        invariant = True if len(time) == 1 else False
+
+        # restrict to date/time range if given
+        if date is None:
+            tmask = time < datetime(3000, 1, 1)
+        else:
+            tmask = (time < date['end']) * (time >= date['beg'])
+
+        # get time vector for output
+        time_in = nctime[tmask]
+
+        # ensure that chunk sizes cover entire period even if
+        # len(time_in) is not an integer multiple of cs
+        niter  = len(time_in) // self.cs
+        niter += ((len(time_in) % self.cs) > 0)
+
+        # loop over chunks
+        for n in range(niter):
+            # indices (relative to index of the output file)
+            beg = n * self.cs
+            # restrict last chunk to lenght of tmask plus one (to get last time)
+            end = min(n*self.cs + self.cs, len(time_in))-1
+
+            # time to make tmask for chunk
+            beg_time = nc.num2date(time_in[beg], units=t_unit, calendar=t_cal)
+            if invariant:
+                # allow topography to work in same code, len(nctime) = 1
+                end_time = nc.num2date(nctime[0], units=t_unit, calendar=t_cal)
+                #end = 1
+            else:
+                end_time = nc.num2date(time_in[end], units=t_unit, calendar=t_cal)
+
+            #'<= end_time', would damage appending
+            tmask_chunk = (time <= end_time) * (time >= beg_time)
+            if invariant:
+                # allow topography to work in same code
+                tmask_chunk = [True]
+
+            # get the interpolated variables
+            dfield, variables = self.interp2D(ncfile_in, ncf_in,
+                                                     self.stations, tmask_chunk,
+                                                     variables=None, date=None)
+
+            # append time
+            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:],
+                                                     time_in[beg:end+1])
+
+            #append variables
+            for i, var in enumerate(variables):
+                if variables_skip(var):
+                    continue
+
+                if pl:
+                    if ESMFnew:
+                        # dfield has dimensions (station, variables, time, pressure levels)
+                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:].transpose((1,2,0))
+                    else:
+                        # dimension: time, level, station (pressure level files)
+                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[i,:,:,:]
+                else:
+                    if ESMFnew:
+                        # dfield has dimensions (station, variables, time)
+                        ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:].transpose((1,0))
+                    else:
+                        # dfield has dimensions time, station (2D files)
+                        ncf_out.variables[var][beg:end+1,:] = dfield.data[i,:,:]
+
+        #close the file
+        ncf_in.close()
+        ncf_out.close()
+
+
+    def levels2elevation(self, ncfile_in, ncfile_out):
+        """
+        Linear 1D interpolation of pressure level data available for individual
+        stations to station elevation. Where and when stations are below the
+        lowest pressure level, they are assigned the value of the lowest
+        pressure level.
+
+        """
+        # open file
+
+        ncf = nc.MFDataset(ncfile_in, 'r', aggdim='time')
+        height = ncf.variables['height'][:]
+        nt = len(ncf.variables['time'][:])
+        nl = len(ncf.variables['level'][:])
+
+        # list variables
+        varlist = [str_encode(x) for x in ncf.variables.keys()]
+        for V in ['time', 'station', 'latitude', 'longitude', 'level','height','z']:
+            varlist.remove(V)
+
+        # === open and prepare output netCDF file ==============================
+        # dimensions: station, time
+        # variables: latitude(station), longitude(station), elevation(station)
+        #            others: ...(time, station)
+        # stations are integer numbers
+        # create a file (Dataset object, also the root group).
+        rootgrp = netcdf_base(ncfile_out, len(height), nt, 'hours since 1900-01-01 00:00:0.0')
+        rootgrp.source = 'ERA-Interim, interpolated (bi)linearly to stations'
+
+        time = rootgrp['time']
+        station = rootgrp['station']
+        latitude = rootgrp['latitude']
+        longitude = rootgrp['longitude']
+        height = rootgrp['height']
+
+        # assign base variables
+        time[:]      = ncf.variables['time'][:]
+        station[:]   = ncf.variables['station'][:]
+        latitude[:]  = ncf.variables['latitude'][:]
+        longitude[:] = ncf.variables['longitude'][:]
+        height[:]    = ncf.variables['height'][:]
+
+        # create and assign variables from input file
+        for var in varlist:
+            tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))
+            tmp.long_name = str_encode(ncf.variables[var].long_name)
+            tmp.units     = str_encode(ncf.variables[var].units)
+
+        # add air pressure as new variable
+        var = 'air_pressure'
+        varlist.append(var)
+        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))
+        tmp.long_name = var.encode('UTF8')
+        tmp.units     = 'hPa'.encode('UTF8')
+        # end file prepation ===================================================
+
+        # loop over stations
+        for n, h in enumerate(height):
+            # convert geopotential [mbar] to height [m], shape: (time, level)
+            elevation = ncf.variables['z'][:,:,n] / 9.80665
+            # TODO: check if height of stations in data range
+
+            # difference in elevation, level directly above will be >= 0
+            elev_diff = elevation - h
+            # vector of level indices that fall directly above station.
+            # Apply after ravel() of data.
+            va = np.argmin(elev_diff + (elev_diff < 0) * 100000, axis=1)
+            # mask for situations where station is below lowest level
+            mask = va < (nl-1)
+            va += np.arange(elevation.shape[0]) * elevation.shape[1]
+
+            # Vector level indices that fall directly below station.
+            # Apply after ravel() of data.
+            vb = va + mask # +1 when OK, +0 when below lowest level
+
+            wa, wb = self.calculate_weights(elev_diff, va, vb)
+
+            #loop over variables and apply interpolation weights
+            for v, var in enumerate(varlist):
+                if var == 'air_pressure':
+                    # pressure [Pa] variable from levels, shape: (time, level)
+                    data = np.repeat([ncf.variables['level'][:]],
+                                      len(time),axis=0).ravel()
+                else:
+                    #read data from netCDF
+                    data = ncf.variables[var][:,:,n].ravel()
+                ipol = data[va]*wa + data[vb]*wb   # interpolated value
+                rootgrp.variables[var][:,n] = ipol # assign to file
+
+        rootgrp.close()
+        ncf.close()
+        # closed file ==========================================================
+
+
+    def process(self):
+        """
+        Interpolate point time series from downloaded data. Provides access to
+        the more generically ERA-like interpolation functions.
+        """
+
+        # 2D Interpolation for Invariant Data
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable keys.
+        dummy_date  = {'beg' : datetime(1979, 1, 1, 12, 0),
+                       'end' : datetime(1979, 1, 1, 12, 0)}
+        self.ERA2station(path.join(self.dir_inp,'erai_to.nc'),
+                         path.join(self.dir_out,'erai_to_' +
+                                   self.list_name + '.nc'), self.stations,
+                                   ['z', 'lsm'], date = dummy_date)
+
+        # === 2D Interpolation for Surface Analysis Data ===
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable keys.
+        dpar = {'air_temperature'   : ['t2m'],  # [K] 2m values
+                'relative_humidity' : ['d2m'],  # [K] 2m values
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky' :
+                    ['tco3', 'tcwv'],   # [kg m-2] Total column ozone
+                                        # [kg m-2] Total column W vapor
+                'wind_speed' : ['u10', 'v10']}   # [m s-1] 10m values
+        varlist = self.TranslateCF2short(dpar)
+        self.ERA2station(path.join(self.dir_inp,'erai_sa_*.nc'),
+                         path.join(self.dir_out,'erai_sa_' +
+                                   self.list_name + '.nc'), self.stations,
+                                   varlist, date = self.date)
+
+        # 2D Interpolation for Surface Forecast Data    'tp', 'strd', 'ssrd'
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable keys.
+        # [m] total precipitation
+        # [J m-2] short-wave downward
+        # [J m-2] long-wave downward
+        dpar = {'precipitation_amount'              : ['tp'],
+                'downwelling_shortwave_flux_in_air' : ['ssrd'],
+                'downwelling_longwave_flux_in_air'  : ['strd']}
+        varlist = self.TranslateCF2short(dpar)
+        self.ERA2station(path.join(self.dir_inp,'erai_sf_*.nc'),
+                         path.join(self.dir_out,'erai_sf_' +
+                                   self.list_name + '.nc'), self.stations,
+                                   varlist, date = self.date)
+
+
+        # === 2D Interpolation for Pressure Level Data ===
+        # dictionary to translate CF Standard Names into ERA-Interim
+        # pressure level variable keys.
+        dpar = {'air_temperature'   : ['t'],           # [K]
+                'relative_humidity' : ['r'],           # [%]
+                'wind_speed'        : ['u', 'v']}      # [m s-1]
+        varlist = self.TranslateCF2short(dpar).append('z')
+        self.ERA2station(path.join(self.dir_inp,'erai_pl_*.nc'),
+                         path.join(self.dir_out,'erai_pl_' +
+                                   self.list_name + '.nc'), self.stations,
+                                   varlist, date = self.date)
+
+        # 1D Interpolation for Pressure Level Data
+        self.levels2elevation(path.join(self.dir_out,'erai_pl_' +
+                                        self.list_name + '.nc'),
+                              path.join(self.dir_out,'erai_pl_' +
+                                        self.list_name + '_surface.nc'))
+
+
+
+class ERAIscale(GenericScale):
+    NAME = "ERA-I"
+
+    """
+    Class for ERA-Interim data that has methods for scaling station data to
+    better resemble near-surface fluxes.
+
+    Processing kernels have names in UPPER CASE.
+
+    Args:
+        sfile: Full path to a Globsim Scaling Parameter file.
+
+    Example:
+        ERAd = ERAIscale(sfile)
+        ERAd.process()
+    """
+
+    def __init__(self, sfile):
+        super().__init__(sfile)
+        par = self.par
+
+        # input file handles
+        self.nc_pl = nc.Dataset(path.join(self.intpdir, 'erai_pl_' +
+                                          self.list_name + '_surface.nc'), 'r')
+        self.nc_sa = nc.Dataset(path.join(self.intpdir,'erai_sa_' +
+                                          self.list_name + '.nc'), 'r')
+        self.nc_sf = nc.Dataset(path.join(self.intpdir, 'erai_sf_' +
+                                          self.list_name + '.nc'), 'r')
+        self.nc_to = nc.Dataset(path.join(self.intpdir, 'erai_to_' +
+                                          self.list_name + '.nc'), 'r')
+        self.nstation = len(self.nc_to.variables['station'][:])
+
+        # check if output file exists and remove if overwrite parameter is set
+        self.output_file = self.getOutNCF(par, 'erai')
+
+        # time vector for output data
+        # get time and convert to datetime object
+        nctime = self.nc_sf.variables['time'][:]
+        self.t_unit = self.nc_sf.variables['time'].units #"hours since 1900-01-01 00:00:0.0"
+        self.t_cal  = self.nc_sf.variables['time'].calendar
+        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
+
+        # interpolation scale factor
+        self.time_step = par['time_step'] * 3600    # [s] scaled file
+        interval_in = (time[1]-time[0]).seconds #interval in seconds
+        self.interpN = floor(interval_in/self.time_step)
+
+        #number of time steps for output, include last value
+        self.nt = int(floor((max(time) - min(time)).total_seconds()
+                      / 3600 / par['time_step']))+1
+
+        # vector of output time steps as datetime object
+        # 'seconds since 1900-01-01 00:00:0.0'
+        mt = min(time)
+        self.times_out = [mt + timedelta(seconds = (x*self.time_step))
+                          for x in range(0, self.nt)]
+
+        # vector of output time steps as written in ncdf file [s]
+        self.scaled_t_units = 'seconds since 1900-01-01 00:00:00'
+        self.times_out_nc = nc.date2num(self.times_out,
+                                        units = self.scaled_t_units,
+                                        calendar = self.t_cal)
+
+
+    def process(self):
+        """
+        Run all relevant processes and save data. Each kernel processes one
+        variable and adds it to the netCDF file.
+        """
+        if path.isfile(self.output_file):
+            print("Warning, output file already exists. This may cause problems")
+        self.rg = new_scaled_netcdf(self.output_file, self.nc_pl,
+                                 self.times_out_nc,
+                                 t_unit = self.scaled_t_units,
+                                 station_names = self.stations['station_name'])
+
+        # iterate through kernels and start process
+        for kernel_name in self.kernels:
+            if hasattr(self, kernel_name):
+                print(kernel_name)
+                getattr(self, kernel_name)()
+
+        # close netCDF files
+        self.rg.close()
+        self.nc_pl.close()
+        self.nc_sf.close()
+        self.nc_sa.close()
+        self.nc_to.close()
+
+    def PRESS_Pa_pl(self):
+        """
+        Surface air pressure from pressure levels.
+        """
+        # add variable to ncdf file
+        vn = 'PRESS_pl' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
+        var.long_name = 'air_pressure {} pressure levels only'.format(self.NAME)
+        var.units     = 'Pa'.encode('UTF8')
+        var.standard_name = 'surface_air_pressure'
+
+        # interpolate station by station
+        time_in = self.nc_pl.variables['time'][:].astype(np.int64)
+        values  = self.nc_pl.variables['air_pressure'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            #scale from hPa to Pa
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                             time_in * 3600, values[:, n]) * 100
+
+    def AIRT_C_pl(self):
+        """
+        Air temperature derived from pressure levels, exclusively.
+        """
+        # add variable to ncdf file
+        vn = 'AIRT_pl' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
+        var.long_name = 'air_temperature {} pressure levels only'.format(self.NAME)
+        var.units     = self.nc_pl.variables['t'].units.encode('UTF8')
+
+        # interpolate station by station
+        time_in = self.nc_pl.variables['time'][:].astype(np.int64)
+        values  = self.nc_pl.variables['t'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                        time_in*3600, values[:, n]-273.15)
+
+    def AIRT_C_sur(self):
+        """
+        Air temperature derived from surface data, exclusively.
+        """
+        # add variable to ncdf file
+        vn = 'AIRT_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '2_metre_temperature {} surface only'.format(self.NAME)
+        var.units     = self.nc_sa.variables['t2m'].units.encode('UTF8')
+
+        # interpolate station by station
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.nc_sa.variables['t2m'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                                    time_in*3600,
+                                                    values[:, n]-273.15)
+
+    def AIRT_redcapp(self):
+        """
+        Air temperature derived from surface data and pressure level data as
+        shown by the method REDCAPP.
+        """
+        print("AIRT_redcapp")
+
+    def PREC_mm_sur(self):
+        """
+        Precipitation sum in mm for the time step given.
+        """
+        # add variable to ncdf file
+        vn = 'PREC_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Total precipitation {} surface only'.format(self.NAME)
+        var.units     = 'kg m-2 s-1'
+        var.standard_name = 'precipitation_amount'
+
+        # interpolate station by station
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+        values  = self.nc_sf.variables['tp'][:]*1000 #[mm]
+
+        nctime = self.nc_sf['time'][:]
+        self.t_unit = self.nc_sf['time'].units #"hours since 1900-01-01 00:00:0.0"
+        self.t_cal  = self.nc_sf['time'].calendar
+        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
+
+        interval_in = (time[1]-time[0]).seconds
+
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in*3600,
+                         cummulative2total(values[:, n], time)/interval_in,
+                         kind = 'linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
+
+    def RH_per_sur(self):
+        """
+        Relative humdity derived from surface data, exclusively. Clipped to
+        range [0.001, 0.999]. Kernel AIRT_ERAI_C_sur must be run before.
+        """
+        # temporary variable,  interpolate station by station
+        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.nc_sa.variables['d2m'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            dewp[:, n] = series_interpolate(self.times_out_nc,
+                                            time_in*3600, values[:, n]-273.15)
+
+        # add variable to ncdf file
+        vn = 'RH_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Relative humidity {} surface only'.format(self.NAME)
+        var.units     = 'percent'
+        var.standard_name = 'relative_humidity'
+
+        # simple: https://doi.org/10.1175/BAMS-86-2-225
+        RH = 100 - 5 * (self.rg.variables['AIRT_sur'][:, :]-dewp[:, :])
+        self.rg.variables[vn][:, :] = RH.clip(min=0.1, max=99.9)/100
+
+
+    def WIND_sur(self):
+        """
+        Wind speed and direction temperature derived from surface data,
+        exclusively.
+        """
+        # temporary variable, interpolate station by station
+        U = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.nc_sa.variables['u10'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            U[:, n] = series_interpolate(self.times_out_nc,
+                                         time_in*3600, values[:, n])
+
+        # temporary variable, interpolate station by station
+        V = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.nc_sa.variables['v10'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            V[:, n] = series_interpolate(self.times_out_nc,
+                                         time_in*3600, values[:, n])
+
+        # wind speed, add variable to ncdf file, convert
+        vna = 'WSPD_sur' # variable name
+        var           = self.rg.createVariable(vna,'f4',('time', 'station'))
+        var.long_name = '10 wind speed {} surface only'.format(self.NAME)
+        var.units     = 'm s-1'
+        var.standard_name = 'wind_speed'
+
+        # wind direction, add variable to ncdf file, convert, relative to North
+        vnb = 'WDIR_sur' # variable name
+        var           = self.rg.createVariable(vnb,'f4',('time', 'station'))
+        var.long_name = '10 wind direction {} surface only'.format(self.NAME)
+        var.units     = 'degree'
+        var.standard_name = 'wind_from_direction'
+
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            WS = np.sqrt(np.power(V,2) + np.power(U,2))
+            WD = [atan2(V[i, n], U[i, n])*(180/pi) +
+                  180 for i in np.arange(V.shape[0])]
+            self.rg.variables[vna][:, n] = WS
+            self.rg.variables[vnb][:,n] = WD
+
+    def SW_Wm2_sur(self):
+        """
+        Short-wave downwelling radiation derived from surface data, exclusively.
+        This kernel only interpolates in time.
+        """
+
+        # add variable to ncdf file
+        vn = 'SW_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface solar radiation downwards {} surface only'.format(self.NAME)
+        var.units     = self.nc_sf.variables['ssrd'].units.encode('UTF8')
+        var.standard_name = 'surface_downwelling_shortwave_flux'
+
+        # interpolation scale factor
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+        values  = self.nc_sf.variables['ssrd'][:]/3600 #w/m2
+
+        nctime = self.nc_sf['time'][:]
+        self.t_unit = self.nc_sf['time'].units #"hours since 1900-01-01 00:00:0.0"
+        self.t_cal  = self.nc_sf['time'].calendar
+        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
+
+        interval_in = (time[1]-time[0]).seconds
+
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in*3600,
+                         cummulative2total(values[:, n], time)/interval_in,
+                         kind = 'linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc) * self.time_step
+
+
+    def LW_Wm2_sur(self):
+        """
+        Long-wave downwelling radiation derived from surface data, exclusively.
+        This kernel only interpolates in time.
+        """
+
+        # add variable to ncdf file
+        vn = 'LW_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface thermal radiation downwards {} surface only'.format(self.NAME)
+        var.units     = 'W m-2'
+        var.standard_name = 'surface_downwelling_longwave_flux'
+
+        # interpolate station by station
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+        values  = self.nc_sf.variables['strd'][:]/3600 #[w m-2]
+
+        nctime = self.nc_sf['time'][:]
+        self.t_unit = self.nc_sf['time'].units #"hours since 1900-01-01 00:00:0.0"
+        self.t_cal  = self.nc_sf['time'].calendar
+        time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
+
+        # interpolation scale factor
+        interval_in = (time[1]-time[0]).seconds
+
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            f = interp1d(time_in*3600,
+                         cummulative2total(values[:, n], time)/interval_in,
+                         kind='linear')
+            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step
+
+    def SH_kgkg_sur(self):
+        '''
+        Specific humidity [kg/kg]
+        https://crudata.uea.ac.uk/cru/pubs/thesis/2007-willett/2INTRO.pdf
+        '''
+        # add variable to ncdf file
+        vn = 'SH_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Specific humidity {} surface only'.format(self.NAME)
+        var.units     = '1'
+        var.standard_name = 'specific_humidity'
+
+        # temporary variable, interpolate station by station
+        dewp = np.zeros((self.nt, self.nstation), dtype=np.float32)
+        time_in = self.nc_sa.variables['time'][:].astype(np.int64)
+        values  = self.nc_sa.variables['d2m'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            dewp[:, n] = series_interpolate(self.times_out_nc,
+                                            time_in*3600, values[:, n]-273.15)
+
+        # compute
+        SH = spec_hum_kgkg(dewp[:, :],
+                           self.rg.variables['PRESS_pl'][:, :])
+
+
+        self.rg.variables[vn][:, :] = SH
+
+    def LW_Wm2_topo(self):
+        """
+        Long-wave radiation downwards [W/m2]
+        https://www.geosci-model-dev.net/7/387/2014/gmd-7-387-2014.pdf
+        """
+        # get sky view, and interpolate in time
+        N = np.asarray(list(self.stations['sky_view'][:]))
+
+        # add variable to ncdf file
+        vn = 'LW_topo' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Incoming long-wave radiation {} surface only'.format(self.NAME)
+        var.units     = 'W m-2'
+        var.standard_name = 'surface_downwelling_longwave_flux'
+
+        # compute
+        for i in range(0, len(self.rg.variables['RH_sur'][:])):
+            for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+                LW = LW_downward(self.rg.variables['RH_sur'][i, n],
+                     self.rg.variables['AIRT_sur'][i, n]+273.15, N[n])
+                self.rg.variables[vn][i, n] = LW
+
diff --git a/globsim/exporttools.py b/globsim/exporttools.py
new file mode 100644
index 0000000..8614caa
--- /dev/null
+++ b/globsim/exporttools.py
@@ -0,0 +1,284 @@
+"""
+Export functions to convert globsim output to other file types
+"""
+from os import path
+
+import pandas as pd
+import netCDF4 as nc
+import numpy as np
+
+from globsim.common_utils import variables_skip
+
+
+def globsimScaled2Pandas(ncdf_in, station_nr):
+    """
+    Read a scaled (or interpolated) globsim netCDF file and return all values
+    for one station as a Pandas data frame.
+
+    ncdf_in: full path to a globsim netCDF (by station)
+
+    station_nr: station_number, as given in the stations .csv file to identify
+                the station.
+
+    """
+    # open file
+    ncf = nc.Dataset(ncdf_in, 'r')
+
+    # station mask
+    sm = ncf.variables['station'][:] == int(station_nr)
+    # list variables
+    varlist = [x.encode('UTF8') for x in ncf.variables.keys()]
+
+    # get and convert time
+    time = ncf.variables['time'][:]
+    t_unit = ncf.variables['time'].units
+    t_cal = ncf.variables['time'].calendar
+    time = nc.num2date(time, units=t_unit, calendar=t_cal)
+
+    # make data frame with time
+    df = pd.DataFrame(data=time, columns=['time'])
+    # add variables
+    for var in varlist:
+        if variables_skip(var):
+            continue
+        data = ncf.variables[var][:,sm]
+        df = pd.concat([df, pd.DataFrame(data=data, columns=[var])], axis=1)
+
+    return df
+
+
+def globsim2CLASS(ncdf_globsim, met_class, station_nr):
+    """
+    Convert globsim scaled netCDF to CLASS-CTEM .met file.
+
+    ncdf_globsim: full path to a globsim scaled netCDF (by station)
+
+    met_class: full path to the CLASS-CTEM met file to write.
+
+    station_nr: station_number, as given in the stations .csv file to identify
+                the station.
+
+    The columns in CLASS-CTEM MET files are:
+    1)  Hour
+    2)  Minute
+    3)  Day of year
+    4)  Year YYYY
+    5)  Shortwave Radiation (W/m2)
+    6)  Longwave Radiation (W/m2)
+    7)  Precip (mm/s)
+    8)  Temp.(°C)
+    9)  Specific Humidity (Kg/Kg)
+    10) Wind Speed (m/s)
+    11) Pressure (Pa)
+
+    """
+
+    # columns to export
+    columns = ['time', 'SW_sur', 'LW_sur', 'PREC_sur',
+               'AIRT_sur', 'SH_sur', 'WSPD_sur',
+               'AIRT_pl']
+
+    # output ASCII formatting
+    formatters = {"time": "  {:%H %M  %j  %Y}".format,
+                  "SW_ERA_Wm2_sur": "{:8.2f}".format,
+                  "LW_ERA_Wm2_sur": "{:8.2f}".format,
+                  "PREC_ERA_mmsec_sur": "{:13.4E}".format,
+                  "AIRT_ERA_C_sur": "{:8.2f}".format,
+                  "SH_ERA_kgkg_sur": "{:11.3E}".format,
+                  "WSPD_ERA_ms_sur": "{:7.2f}".format,
+                  "AIRT_PRESS_Pa_pl": "{:11.2f}".format}
+
+    # get data
+    df = globsimScaled2Pandas(ncdf_globsim, station_nr)
+
+    # convert precipitation
+    df["PREC_ERA_mmsec_sur"] = df["PREC_ERA_mm_sur"] / 1800.0
+
+    # write FORTRAN formatted ASCII file
+    with open(met_class, 'w') as f:
+        f.write(' ')
+        f.write(df.to_string(columns=columns,
+                formatters=formatters,
+                header=False, index=False))
+    f.close()
+
+
+def globsim_to_classic_met(ncd, out_dir, site=None):
+    """
+    @args
+    ncd : str
+        path to output globsim dataset
+    out_dir : str
+        where files should be saved
+    site : int or str
+        index of site (zero-indexed) or site name if only one site is desired.
+        For all sites, leave as None
+
+    Returns
+    list : paths of output files
+    """
+    # open netcdf if string provided
+    if type(ncd) is str:
+        n = nc.Dataset(ncd)
+
+    # find number of stations
+    nstn = len(n['station'][:])
+
+    # get date / time columns
+    time = nc.num2date(n['time'][:],
+                       units=n['time'].units,
+                       calendar=n['time'].calendar)
+    time_step = (time[1] - time[0]).seconds  # in second
+
+    HH = [x.timetuple().tm_hour for x in time]
+    MM = [x.timetuple().tm_min for x in time]
+    DDD = [x.timetuple().tm_yday for x in time]
+    YYYY = [x.timetuple().tm_year for x in time]
+
+    TIME = np.stack((HH, MM, DDD, YYYY))
+
+    # get shortwave
+    SW = "SW_sur"
+    SW = n[SW][:]
+
+    # get longwave
+    LW = "LW_sur"
+    LW = n[LW][:]
+
+    # get precip
+    PREC = "PREC_sur"
+    PREC = n[PREC][:]
+    PREC /= time_step  # [mm] to [mm/s]
+
+    # get temp
+    AIRT = "AIRT_sur"
+    AIRT = n[AIRT][:]
+
+    # get specific humidity
+    SH = "SH_sur"
+    SH = n[SH][:]
+
+    # get wind speed
+    WSPD = "WSPD_sur"
+    WSPD = n[WSPD][:]
+
+    # get pressure
+    PRESS = "PRESS_pl"
+    PRESS = n[PRESS][:]
+
+    # get site names
+    NAMES = nc.chartostring(n['station_name'][:])
+
+    data = np.stack((SW, LW, PREC, AIRT, SH, WSPD, PRESS))
+
+    # write output files
+    files = []
+    for i in range(nstn):
+        if (site is None) or (site == i) or (site == NAMES[i]):
+            # massage data into the right shape
+            out_array = data[:, :, i]
+            out_array = np.concatenate((TIME, out_array), 0)
+            out_array = np.transpose(out_array)
+
+            # get station name
+            st_name = NAMES[i]
+
+            filename = "{}_{}.MET".format(i, st_name)
+            savepath = path.join(out_dir, filename)
+            files.append(savepath)
+            # create file
+            np.savetxt(savepath, out_array,
+                       newline='\n',  # windows line endings breaks the converter
+                       fmt=[" %02u", "%02u", " %03u",
+                            " %2u", "%8.2f", "%8.2f",
+                            "%13.4E", "%8.2f", "%11.3E",
+                            "%7.2f", "%11.2f"])
+    return files
+
+
+def globsim_to_geotop(ncd, out_dir, site=None, start=None, end=None):
+    """
+    @args
+    ncd: netcdf dataset
+    site: site name or index
+    """
+    # open netcdf if string provided
+    if type(ncd) is str:
+        n = nc.Dataset(ncd)
+
+    # find number of stations
+    nstn = len(n['station'][:])
+
+    # get date / time column
+    time = nc.num2date(n['time'][:],
+                       units=n['time'].units,
+                       calendar=n['time'].calendar)
+
+    time = [x.strftime('%d/%m/%Y %H:%M') for x in time]
+    time = pd.DataFrame(time)
+
+    # get precip
+    PREC = "PREC_sur"
+    PREC = n[PREC][:]
+
+    # get wind velocity
+    WSPD = "WSPD_sur"
+    WSPD = n[WSPD][:]
+
+    # get wind direction
+    WDIR = "WDIR_sur"
+    WDIR = n[WDIR][:]
+
+    # get windx and windy
+
+    # get RH
+    RH = "RH_sur"
+    RH = n[RH][:]
+
+    # get air temp
+    AIRT = "AIRT_sur"
+    AIRT = n[AIRT][:]
+
+    # get dew temp (missing?)
+
+    # get air pressure
+    PRESS = "PRESS_pl"
+    PRESS = n[PRESS][:]
+    PRESS *= 1e-5      # convert to bar for geotop
+
+    # get shortwave solar global (direct / diffuse missing?)
+    SW = "SW_sur"
+    SW = n[SW][:]
+
+    # get longwave incoming
+    LW = "LW_sur"
+    LW = n[LW][:]
+
+    # get site names
+    NAMES = nc.chartostring(n['station_name'][:])
+
+    # combine data variables into array
+    data = np.stack((PREC, WSPD, WDIR, RH, AIRT, PRESS, SW, LW))
+
+    # write output files
+    files = []
+    for i in range(nstn):
+        if (site is None) or (site == i) or (site == NAMES[i]):
+            # massage data into the right shape
+            out_df = pd.DataFrame(np.transpose(data[:, :, i]))
+            out_df = pd.concat([time, out_df], axis=1)
+            out_df.columns = ["Date", "IPrec", "WindVelocity", "WindDirection", "RH",
+                              "AirTemp", "AirPress", "SWglobal", "LWin"]
+
+            # get station name
+            st_name = NAMES[i]
+
+            # prepare paths
+            filename = "{}-{}_Forcing_0001.txt".format(i, st_name)
+            savepath = path.join(out_dir, filename)
+            files.append(savepath)
+
+            # create file
+            out_df.to_csv(savepath, index=False, float_format="%10.5f")
+
+    return files
diff --git a/globsim/globsim_cli.py b/globsim/globsim_cli.py
new file mode 100644
index 0000000..92fea23
--- /dev/null
+++ b/globsim/globsim_cli.py
@@ -0,0 +1,45 @@
+"""globsim.globsim: provides entry point main()."""
+
+
+__version__ = "2.0.1"
+
+
+import sys
+import argparse
+
+from globsim.globsim_download import main as globsim_download
+from globsim.globsim_interpolate import main as globsim_interpolate
+from globsim.globsim_scale import main as globsim_scale
+
+action_dict = {'download': globsim_download,
+               'interpolate': globsim_interpolate,
+               'scale': globsim_scale}
+
+def main():
+    parser = argparse.ArgumentParser(description="GlobSim: meteorological reanalysis for point-scale simulation. Find out more at https://globsim.readthedocs.io/en/latest",
+                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+
+    parser.add_argument("-v", "--version", action='version', version=f"GlobSim version {__version__}")
+
+    parser.add_argument('action', default=None, type=str, choices=["download", "interpolate", "scale"],
+                        help="Which GlobSim action should be run.")
+
+    parser.add_argument("-f", "--config-file", default=None, type=str,
+                        dest='f', help="Path to GlobSim configuration file.")
+
+    parser.add_argument("-d", default=None, nargs="*", type=str, choices=["ERAI", "ERA5", "ERA5ENS", "MERRA", "JRA"],
+                        dest='d', help="What data sources should run?")
+
+    parser.add_argument('-r', '--retry',  default=1,    type=int, help="Number of times to re-launch download if it crashes (globsim_download only) ")
+    parser.add_argument('-m', '--multi', action='store_true',    help="Download all data sources simultaneously (globsim_download only) ")
+
+    if len(sys.argv) == 1:
+        parser.print_help(sys.stderr)
+        sys.exit(1)
+
+    else:
+        args = parser.parse_args()
+        print(f"Running globsim_{args.action}")
+        task = action_dict[args.action]
+        task(args)
+
diff --git a/globsim_download.py b/globsim/globsim_download.py
similarity index 66%
rename from globsim_download.py
rename to globsim/globsim_download.py
index b24b640..72ead45 100755
--- a/globsim_download.py
+++ b/globsim/globsim_download.py
@@ -23,38 +23,43 @@
 from globsim.globsim_main import GlobsimDownload
 
 import argparse
-import configparser
 import time
 
-#=====download the wanted varialbe from multiple re-analysis date sources======
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(description="Get MET data",
-                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-    parser.add_argument('-f',             default=None, type=str, help="file path to download parameter file")
-    parser.add_argument('-d',             default=None, type=str, nargs="*", help="What data sources should run? ERAI, ERA5, MERRA, JRA")
-    parser.add_argument('-r', '--retry',  default=1,    type=int, help="Number of times to re-launch download if it crashes.")
-    parser.add_argument('-s', '--serial', action='store_true',    help="All data sources run on one process")
-    
-    args = parser.parse_args()
-    
-    multi = False if args.serial else True
+
+def main(args):
     pfile = args.f
-    
-    ERAI =  True if args.d is None or "ERAI"  in args.d else False
-    ERA5 =  True if args.d is None or "ERA5"  in args.d else False
-    JRA =   True if args.d is None or "JRA"   in args.d else False
-    MERRA = True if args.d is None or "MERRA" in args.d else False
-    
+    ERAI    = True if args.d is None or "ERAI"  in args.d else False
+    ERA5    = True if args.d is None or "ERA5"  in args.d else False
+    ERA5ENS = True if args.d is None or "ERA5ENS"  in args.d else False
+    JRA     = True if args.d is None or "JRA"   in args.d else False
+    MERRA   = True if args.d is None or "MERRA" in args.d else False
+
     r_max = args.retry
     i = 0
-    
+
     if r_max <= 1:
-        GlobsimDownload(pfile, ERAI=ERAI, ERA5=ERA5, JRA=JRA, MERRA=MERRA, multithread=multi)
+        GlobsimDownload(pfile, ERAI=ERAI, ERA5=ERA5, ERA5ENS=ERA5ENS, 
+                        JRA=JRA, MERRA=MERRA, multithread=args.multi)
     else:
         while i < r_max:
             try:
-                GlobsimDownload(pfile, ERAI=ERAI, ERA5=ERA5, JRA=JRA, MERRA=MERRA, multithread=multi)
+                GlobsimDownload(pfile, ERAI=ERAI, ERA5=ERA5, ERA5ENS=ERA5ENS, 
+                                JRA=JRA, MERRA=MERRA, multithread=args.multi)
             except Exception as e:
                 print(e)
             time.sleep(360)
-            i += 1
\ No newline at end of file
+            i += 1
+
+
+# =====download the wanted varialbe from multiple re-analysis date sources======
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Get MET data",
+                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('-f',             default=None, type=str, help="file path to download parameter file")
+    parser.add_argument('-d',             default=None, type=str, nargs="*", help="What data sources should run? ERAI, ERA5, MERRA, JRA")
+    parser.add_argument('-r', '--retry',  default=1,    type=int, help="Number of times to re-launch download if it crashes.")
+    parser.add_argument('-m', '--multi', action='store_true',    help="Download all data sources simultaneously")
+
+    args = parser.parse_args()
+
+    main(args)
diff --git a/globsim_interpolate.py b/globsim/globsim_interpolate.py
similarity index 76%
rename from globsim_interpolate.py
rename to globsim/globsim_interpolate.py
index 99d373a..73deee2 100755
--- a/globsim_interpolate.py
+++ b/globsim/globsim_interpolate.py
@@ -21,12 +21,28 @@
 #
 #===============================================================================
 import argparse
-import configparser
 
 from globsim.globsim_main import GlobsimInterpolateStation
 
 
-#===interpolate the variables from multiple re-analysis data to individual stations===
+def main(args):
+    pfile = args.f
+
+    ERAI 	=  True if args.d is None or "ERAI"  in args.d else False
+    ERA5 	=  True if args.d is None or "ERA5"  in args.d else False
+    ERA5ENS =  True if args.d is None or "ERA5ENS"  in args.d else False
+    JRA 	=  True if args.d is None or "JRA"   in args.d else False
+    MERRA 	= True if args.d is None or "MERRA" in args.d else False
+
+    if sum([ERAI, ERA5, ERA5ENS, JRA, MERRA]) > 0:
+
+        GlobsimInterpolateStation(pfile, ERAI = ERAI, ERA5 = ERA5, ERA5ENS = ERA5ENS,
+                                         JRA = JRA, MERRA = MERRA)
+
+    else: print("Failed! Reanalysis source should be ERAI, ERA5, MERRA, JRA, please check")
+
+
+# ===interpolate the variables from multiple re-analysis data to individual stations===
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description="Interpolate reanalysis data",
                                      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
@@ -34,20 +50,7 @@
                         help="file path to download parameter file")
     parser.add_argument('-d',    default=None, nargs="*", type=str, 
                         help="What data sources should run? ERAI, ERA5, MERRA, JRA")
-    
+
     args = parser.parse_args()
 
-    pfile = args.f
-    
-    ERAI =  True if args.d is None or "ERAI"  in args.d else False
-    ERA5 =  True if args.d is None or "ERA5"  in args.d else False
-    JRA =   True if args.d is None or "JRA"   in args.d else False
-    MERRA = True if args.d is None or "MERRA" in args.d else False
-    
-    if sum([ERAI, ERA5, JRA, MERRA]) > 0:
-        
-        GlobsimInterpolateStation(pfile, ERAI = ERAI, ERA5 = ERA5, 
-                                         JRA = JRA, MERRA = MERRA)
-        
-    else: print("Failed! Reanalysis source should be ERAI, ERA5, MERRA, JRA, please check")
-        
+    main(args)
diff --git a/globsim_main.py b/globsim/globsim_main.py
old mode 100644
new mode 100755
similarity index 79%
rename from globsim_main.py
rename to globsim/globsim_main.py
index 7a189cb..429f406
--- a/globsim_main.py
+++ b/globsim/globsim_main.py
@@ -24,22 +24,24 @@
 #
 #
 # === CONTRIBUTIONS ============================================================
-# Intial code for GlobSim is designed by Stephan Gruber. Xiaojing Quan developed
+# Intial code for globsim is designed by Stephan Gruber. Xiaojing Quan developed
 # MERRA-2, download part of ERA-Interim, and intial interpolation and scale  
-# parts of GlobSim. Bin Cao wrote the download parts of ERA5, JRA-55, and  
+# parts of globsim. Bin Cao wrote the download parts of ERA5, JRA-55, and
 # finilized the interpolation and scale part. Nicholas Brown improved ERA5, 
-# wrote the CF conventions part, and tested GlobSim.
+# wrote the CF conventions part, and tested globsim.
 #===============================================================================
 
 
 from globsim.era_interim import ERAIdownload, ERAIinterpolate, ERAIscale
 from globsim.era5 import ERA5download, ERA5interpolate, ERA5scale
-from globsim.merra_2 import MERRAdownload, MERRAinterpolate, MERRAscale 
+from globsim.merra_2 import MERRAdownload, MERRAinterpolate, MERRAscale
 from globsim.JRA import JRAdownload, JRAinterpolate, JRAscale
 from multiprocessing.dummy import Pool as ThreadPool
 
 
-def GlobsimDownload(pfile, multithread = True, ERAI=True, ERA5=True, MERRA=True, JRA=True):
+def GlobsimDownload(pfile, multithread = True, 
+                    ERAI=True, ERA5=True, 
+                    ERA5ENS=True, MERRA=True, JRA=True):
     """
     Download data from multiple reanalyses. Each reanalysis is run as one 
     separate thread if 'multithread = True'. If 'multithread = False', each
@@ -53,10 +55,15 @@ def GlobsimDownload(pfile, multithread = True, ERAI=True, ERA5=True, MERRA=True,
         ERAIdownl = ERAIdownload(pfile)
         objects.append(ERAIdownl)
     
-    # === ERA-5 ===
+    # === ERA5 ===
     if ERA5:
-        ERA5downl = ERA5download(pfile)
-        objects.append(ERA5downl)
+        ERA5REAdownl = ERA5download(pfile, 'reanalysis')
+        objects.append(ERA5REAdownl)
+    
+    # === ERA5 10-member ensemble ===
+    if ERA5ENS:
+        ERA5ENSdownl = ERA5download(pfile, 'ensemble_members')
+        objects.append(ERA5ENSdownl)
     
     # === MERRA-2 ===
     if MERRA:
@@ -82,7 +89,8 @@ def GlobsimDownload(pfile, multithread = True, ERAI=True, ERA5=True, MERRA=True,
             print(result)
         print('Serial download finished')
     
-def GlobsimInterpolateStation(ifile, ERAI=True, ERA5=True, MERRA=True, JRA=True):
+def GlobsimInterpolateStation(ifile, ERAI=True, ERA5=True, ERA5ENS=True, 
+                              MERRA=True, JRA=True):
     """
     Interpolate re-analysis data to individual stations (points: lat, lon, ele).
     The temporal granularity and variables of each re-analysis are preserved. 
@@ -99,6 +107,11 @@ def GlobsimInterpolateStation(ifile, ERAI=True, ERA5=True, MERRA=True, JRA=True)
     if ERA5:
         ERA5interp = ERA5interpolate(ifile)
         ERA5interp.process()
+        
+    # === ERA5ENS ===
+    if ERA5ENS:
+        ERA5interp = ERA5interpolate(ifile, 'ensemble_members')
+        ERA5interp.process()
     
     # === MERRA-2 ===
     if MERRA:
@@ -111,7 +124,7 @@ def GlobsimInterpolateStation(ifile, ERAI=True, ERA5=True, MERRA=True, JRA=True)
         JRAinterp.process()
   
             
-def GlobsimScale(sfile, ERAI=True, ERA5=True, MERRA=True, JRA=True):
+def GlobsimScale(sfile, ERAI=True, ERA5=True, ERA5ENS=True, MERRA=True, JRA=True):
     """
     Use re-analysis data that has been interpolated to station locations to 
     derive fluxes scaled / converted / adjusted to drive point-scale 
@@ -127,6 +140,11 @@ def GlobsimScale(sfile, ERAI=True, ERA5=True, MERRA=True, JRA=True):
     if ERA5:
         ERA5sc = ERA5scale(sfile)
         ERA5sc.process()
+        
+    # === ERA5ENS ===
+    if ERA5ENS:
+        ERA5sc = ERA5scale(sfile, 'ensemble_members')
+        ERA5sc.process()
     
     # # === MERRA-2 ===
     if MERRA:
diff --git a/globsim_scale.py b/globsim/globsim_scale.py
similarity index 67%
rename from globsim_scale.py
rename to globsim/globsim_scale.py
index c24f1b6..80a30c7 100755
--- a/globsim_scale.py
+++ b/globsim/globsim_scale.py
@@ -26,6 +26,21 @@
 from globsim.globsim_main import GlobsimScale
 
 
+def main(args):
+        sfile = args.f
+        
+        ERAI    = True if args.d is None or "ERAI"     in args.d else False
+        ERA5    = True if args.d is None or "ERA5"     in args.d else False
+        ERA5ENS = True if args.d is None or "ERA5ENS"  in args.d else False
+        JRA     = True if args.d is None or "JRA"      in args.d else False
+        MERRA   = True if args.d is None or "MERRA"    in args.d else False
+        
+        if sum([ERAI, ERA5, ERA5ENS, JRA, MERRA]) > 0:
+            GlobsimScale(sfile, ERAI=ERAI, ERA5=ERA5, ERA5ENS=ERA5ENS, 
+                        JRA=JRA, MERRA=MERRA)
+        
+        else: print("Failed! Reanalysis source should be ERAI, ERA5, MERRA, JRA, please check")
+
 #===scale the variables from mutiple re-analysis data at stations===
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description="Scale reanalysis data",
@@ -33,23 +48,9 @@
     parser.add_argument('-f', default=None, type=str, 
                         help="file path to download parameter file")
     parser.add_argument('-d', default=None, nargs="*", type=str, 
-                        help="What data sources should run? ERAI, ERA5, MERRA, JRA")
+                        help="What data sources should run? ERAI, ERA5, ERA5ENS, MERRA, JRA")
     
     
     args = parser.parse_args()
-    sfile = args.f
-    
-    ERAI =  True if args.d is None or "ERAI"  in args.d else False
-    ERA5 =  True if args.d is None or "ERA5"  in args.d else False
-    JRA =   True if args.d is None or "JRA"   in args.d else False
-    MERRA = True if args.d is None or "MERRA" in args.d else False
-    
-    if sum([ERAI, ERA5, JRA, MERRA]) > 0:
-        GlobsimScale(sfile, ERAI=ERAI, ERA5=ERA5, JRA=JRA, MERRA=MERRA)
-    
-    else: print("Failed! Reanalysis source should be ERAI, ERA5, MERRA, JRA, please check")
-
-
-
-
 
+    main(args)
diff --git a/jra_example.py b/globsim/jra_example.py
similarity index 91%
rename from jra_example.py
rename to globsim/jra_example.py
index 94278ba..fd97761 100755
--- a/jra_example.py
+++ b/globsim/jra_example.py
@@ -1,40 +1,40 @@
-from .JRA import JRAdownload, JRAinterpolate, JRAscale
-from os   import path
-
-# ==== SETTING-UP =============================================================
-
-dir_path = '/Users/bincao/OneDrive/GitHub/globsim/examples/par'
-#dir_path = 'C:/OneDrive/GitHub/globsim/examples/par'
-
-pfile = 'examples.globsim_download'
-ifile = 'examples.globsim_interpolate'
-sfile = 'examples.globsim_scale'
-
-pfile = path.join(dir_path, pfile)
-ifile = path.join(dir_path, ifile)
-sfile = path.join(dir_path, sfile)
-
-# ==== Download ===============================================================
-
-JRAdownl = JRAdownload(pfile)
-JRAdownl.retrieve()
-
-'''
-from JRA import RDA
-rda = RDA(JRAdownl.username, JRAdownl.password)
-#rda.getStatus()
-#dsIndex = rda.getDSindex()
-
-ds = '328839'
-rda.download(JRAdownl.directory, ds)
-JRAdownl.makeNCF(ds)
-'''
-
-# ==== Interpolation ==========================================================
-
-JRAinterp = JRAinterpolate(ifile)
-#JRAinterp.process()
-
-# ==== Scale ==================================================================
-JRAsc = JRAscale(sfile)
+from globsim.JRA import JRAdownload, JRAinterpolate, JRAscale
+from os   import path
+
+# ==== SETTING-UP =============================================================
+
+dir_path = '/Users/bincao/OneDrive/GitHub/globsim/examples/par'
+#dir_path = 'C:/OneDrive/GitHub/globsim/examples/par'
+
+pfile = 'examples.globsim_download'
+ifile = 'examples.globsim_interpolate'
+sfile = 'examples.globsim_scale'
+
+pfile = path.join(dir_path, pfile)
+ifile = path.join(dir_path, ifile)
+sfile = path.join(dir_path, sfile)
+
+# ==== Download ===============================================================
+
+JRAdownl = JRAdownload(pfile)
+JRAdownl.retrieve()
+
+'''
+from JRA import RDA
+rda = RDA(JRAdownl.username, JRAdownl.password)
+#rda.getStatus()
+#dsIndex = rda.getDSindex()
+
+ds = '328839'
+rda.download(JRAdownl.directory, ds)
+JRAdownl.makeNCF(ds)
+'''
+
+# ==== Interpolation ==========================================================
+
+JRAinterp = JRAinterpolate(ifile)
+#JRAinterp.process()
+
+# ==== Scale ==================================================================
+JRAsc = JRAscale(sfile)
 JRAsc.process()
\ No newline at end of file
diff --git a/merra_2.py b/globsim/merra_2.py
old mode 100644
new mode 100755
similarity index 62%
rename from merra_2.py
rename to globsim/merra_2.py
index 904db9e..0a1263c
--- a/merra_2.py
+++ b/globsim/merra_2.py
@@ -1,13 +1,13 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*- 
-#
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
 # Copyright Xiaojing Quan & Stephan Gruber
-# =============================================================================    
-# REVISION HISTORY 
+# =============================================================================
+# REVISION HISTORY
 # 20170510 -- Initial Version Created
 # 20171208 -- First Draft Completed
 #
-#==============================================================================
+# ==============================================================================
 # A scripts for downloading MERRA-2 reanalysis data:
 # -- Geoppotential Height at pressure levels [time*level*lat*lon] (Unit:m) (6-hourly/day)
 # -- Air Temperature at pressure levels [time*level*lat*lon] (Unit:K) (6-hourly/day)
@@ -15,7 +15,7 @@
 # -- Easteward Wind at Pressure Levels [time*level*lat*lon] (Unit:m/s) (6-hourly/day)
 # -- Northward Wind at Pressure Levels [time*level*lat*lon] (Unit:m/s) (6-hourly/day)
 # -- Air Temperature at 2 Meter [time*lat*lon] (Unit:K) (1-hourly/day)
-# -- Eastward Wind at 2 Meter [time*lat*lon] (Unit:K) (1-hourly/day) 
+# -- Eastward Wind at 2 Meter [time*lat*lon] (Unit:K) (1-hourly/day)
 # -- Northward Wind at 2 Meter [time*lat*lon] (Unit: m/s) (1-hourly/day)
 # -- Eastward Wind at 10 Meter  [time*lat*lon] (Unit: m/s) (1-hourly/day)
 # -- Northward Wind at 10 Meter [time*lat*lon] (Unit: m/s) (1-hourly/day)
@@ -25,33 +25,33 @@
 # -- Surface Net Downward Longwave Flux [time*lat*lon] (Unit:W/m2) (1-hourly/day)
 # -- Surface Net Downward Longwave Flux Assuming Clear Sky [time*lat*lon] (Unit:W/m2) (1-hourly/day)
 # -- Longwave Flux Emitted from Surface [time*lat*lon] (Unit:W/m2) (1-hourly/day)
-# 
+#
 # -- Surface Absorbed Longwave Flux [time*lat*lon] (Unit:W/m2) (1-hourly/day)
 # -- Surface Absorbed Longwave Flux Assuming Clear Sky [time*lat*lon] (Unit:W/m2) (1-hourly/day)
-# 
+#
 # Saved as netCDF 4
-#====================HOW TO RUN THIS ==========================================
+# ====================HOW TO RUN THIS ==========================================
 #
-# (1) Register a New User in Earthdata Login: 
+# (1) Register a New User in Earthdata Login:
 #  https://wiki.earthdata.nasa.gov/display/EL/How+To+Register+With+Earthdata+Login
 #
 # (2) Authorize NASA GESDISC DATA ARCHIVE in Earthdata Login
 # https://disc.gsfc.nasa.gov/registration/authorizing-gesdisc-data-access-in-earthdata_login
 #
-# (3) Adapt the script below with: 
+# (3) Adapt the script below with:
 #    - Authrized Username and Password (setup in .merrarc file),
-#    - Input parameters: Date, Area, Elevation, Chunk_size, Variables, etc. 
+#    - Input parameters: Date, Area, Elevation, Chunk_size, Variables, etc.
 #      (setup in Globsim download parameter file )
 #
 # (4) Obtaining the URL addresses of the objected datasets at:
 #     https://disc.sci.gsfc.nasa.gov/daac-bin/FTPSubset2.pl
-# 
+#
 # (5) Obtianing the mutiple datasets with spefici spacial and temporal)
 #
-# (6) Get all varialbes which are needed, and saved in NetCDF files 
+# (6) Get all varialbes which are needed, and saved in NetCDF files
 #
-#==============================================================================
-# IMPORTANT Notes: 
+# ==============================================================================
+# IMPORTANT Notes:
 
 # 1. Samples of Selected URLs list:
 
@@ -59,11 +59,11 @@
 # url = ('https://goldsmr5.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/M2I6NPANA.5.12.4'
 #        '/2016/01/MERRA2_400.inst6_3d_ana_Np.20160101.nc4')
 
-# 3d,3-hourly,Instantaneous,Pressure-Level,Assimilation,Assimilated Meteorological Fields 
+# 3d,3-hourly,Instantaneous,Pressure-Level,Assimilation,Assimilated Meteorological Fields
 # url = ('https://goldsmr5.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/M2I3NPASM.5.12.4'
 #        '/2016/01/MERRA2_400.inst3_3d_asm_Np.20160201.nc4')
 
-# 2d,1-hourly,Instantaneous,Single-level,Assimilation,Single-Level Diagnostics                                                   
+# 2d,1-hourly,Instantaneous,Single-level,Assimilation,Single-Level Diagnostics
 # url = ('https://goldsmr4.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/M2I1NXASM.5.12.4'
 #         '/2016/01/MERRA2_400.inst1_2d_asm_Nx.20160102.nc4')
 
@@ -73,134 +73,121 @@
 
 # 2d,1-Hourly,Time-Averaged,Single-Level,Assimilation,Radiation Diagnostics
 # url = ('https://goldsmr5.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/M2T3NPRAD.5.12.4'
-#       '/2016/01/MERRA2_400.tavg3_3d_rad_Np.20160102.nc4')   
+#       '/2016/01/MERRA2_400.tavg3_3d_rad_Np.20160102.nc4')
 
-#2d 1-Hourly,Time-Averaged,Single-Level,Assimilation,Single-Level Diagnostics V5.12.4                      
+# 2d 1-Hourly,Time-Averaged,Single-Level,Assimilation,Single-Level Diagnostics V5.12.4
 # url = ('https://goldsmr4.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/M2T1NXSLV.5.12.4'
-#'       '/2016/01/MERRA2_400.tavg1_2d_slv_Nx.20160101.nc4')
-                  
+# '       '/2016/01/MERRA2_400.tavg1_2d_slv_Nx.20160101.nc4')
+
 # 2. Radiation Variables Processing:
 # Considering the variables below as equal utilization:
 #
-# downwelling_shortwave_flux_in_air_assuming_clear_sky = Surface_Incoming_Shoertwave_Flux 
+# downwelling_shortwave_flux_in_air_assuming_clear_sky = Surface_Incoming_Shoertwave_Flux
 #
 # downwelling_shortwave_flux_in_air_assuming_clear_sky = Surface_Incoming_Shortwave_Flux_Assuming_Clear_Sky
 #
-# downwelling_longwave_flux_in_air = Longwave Flux Emitted from Surface + Surface Net Downward Longwave Flux 
+# downwelling_longwave_flux_in_air = Longwave Flux Emitted from Surface + Surface Net Downward Longwave Flux
 #
 # downwelling_longwave_flux_in_air_assuming_clear_sky = Longwave Flux Emitted from Surface + Surface Net Downward Longwave Flux Assuming Clear Sky
-            
-#==============================================================================
+
+# ==============================================================================
+
 from __future__        import print_function
 
-import pydap.lib
 import numpy as np
-import csv
 import netCDF4 as nc
 import itertools
 import pandas
 import time as tc
 import sys
-import glob
-import nco
-import re
 
 from pydap.client      import open_url
 from pydap.cas.urs     import setup_session
-from datetime          import datetime, timedelta, date
-from os                import path, listdir, makedirs, remove
-from netCDF4           import Dataset, MFDataset
+from datetime          import datetime, timedelta
+from os                import path, makedirs
+from netCDF4           import Dataset
 from dateutil.rrule    import rrule, DAILY
-from math              import exp, floor, atan2, pi
-from globsim.generic   import ParameterIO, StationListRead, ScaledFileOpen, str_encode, series_interpolate, variables_skip, spec_hum_kgkg, LW_downward
-from fnmatch           import filter
-from scipy.interpolate import interp1d, griddata, RegularGridInterpolator, NearestNDInterpolator, LinearNDInterpolator
+from math              import floor, atan2, pi
+
+from scipy.interpolate import interp1d, griddata
 from time              import sleep
-from numpy.random      import uniform
-from nco               import Nco
-
-try:
-    import ESMF
-    
-    # Check ESMF version.  7.0.1 behaves differently than 7.1.0r 
-    ESMFv = int(re.sub("[^0-9]", "", ESMF.__version__))
-    ESMFnew = ESMFv > 701   
-except ImportError:
-    print("*** ESMF not imported, interpolation not possible. ***")
-    pass   
-
-    
+
+from globsim.common_utils import str_encode, series_interpolate, variables_skip, GenericDownload, GenericScale, GenericInterpolate
+from globsim.meteorology import LW_downward, pressure_from_elevation
+from globsim.nc_elements import netcdf_base, new_scaled_netcdf
+
+
 class MERRAgeneric():
     """
     Parent class for other merra classes.
     """
-           
-    def getURLs(self, date):                                                                                                                                          
+
+    def getURLs(self, date):
         """ Set up urls by given range of date and type of data to get objected
         url address
-        
+
             Args:
             baseurl_2d: baseurl for 2D dataset
             baseurl_3d: baseurl for 3D dataset
             baseurl_3dn_*: sub url of 3D Pressure Levels Analyzed Meteorological Fields data
             baseurl_3da_*: sub url of 3D Pressure Levels Assimilated Meteorological Fields data
             baseurl_2dm_*: sub url of 2D Single-Level Diagnostics
-            baseurl_2dr_*: sub url of 2D radiation Diagnostics data 
+            baseurl_2dr_*: sub url of 2D radiation Diagnostics data
             baseurl_2ds_*: sub url of 2D suface flux Diagnostics data
             baseurl_2dv_*: sub url of 2D Single-Level,Assimilation Single-Level Diagnostics data
-            
+
             Return:
-            urls_3dmana: urls of 3D Analyzed Meteorological Fields datasets  
+            urls_3dmana: urls of 3D Analyzed Meteorological Fields datasets
             urls_3dmasm: urls of 3D Assimilated Meteorological Fields datasets
             urls_2dm: urls of 2D meteorological Diagnostics datasets
             urls_2ds: urls of 2D suface flux Diagnostics datasets
             urls_2dr: urls of 2D radiation Diagnostics datasets
             urls_2dv: urls of 2D Assimilation Single-Level Diagnostics datasets
             url_2dc: urls of 2D single-level constant model parameters datasets
-        """          
-        #Setup the based url strings    
-        baseurl_2d = ('https://goldsmr4.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/') 
+        """
+        # Setup the based url strings
+        baseurl_2d = ('https://goldsmr4.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/')
         baseurl_3d = ('https://goldsmr5.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2/')
-        # 1980 ~ 1991 
-        baseurl_3dn_1 = ('M2I6NPANA.5.12.4/','/MERRA2_100.inst6_3d_ana_Np.')                   
-        baseurl_3da_1 = ('M2I3NPASM.5.12.4/','/MERRA2_100.inst3_3d_asm_Np.')            
-        baseurl_2dm_1 = ('M2I1NXASM.5.12.4/','/MERRA2_100.inst1_2d_asm_Nx.')                
-        baseurl_2dr_1 = ('M2T1NXRAD.5.12.4/','/MERRA2_100.tavg1_2d_rad_Nx.')             
-        baseurl_2ds_1 = ('M2T1NXFLX.5.12.4/','/MERRA2_100.tavg1_2d_flx_Nx.')            
-        baseurl_2dv_1 = ('M2T1NXSLV.5.12.4/','/MERRA2_100.tavg1_2d_slv_Nx.')                                                                                                                                            
+        # 1980 ~ 1991
+        baseurl_3dn_1 = ('M2I6NPANA.5.12.4/','/MERRA2_100.inst6_3d_ana_Np.')
+        baseurl_3da_1 = ('M2I3NPASM.5.12.4/','/MERRA2_100.inst3_3d_asm_Np.')
+        baseurl_2dm_1 = ('M2I1NXASM.5.12.4/','/MERRA2_100.inst1_2d_asm_Nx.')
+        baseurl_2dr_1 = ('M2T1NXRAD.5.12.4/','/MERRA2_100.tavg1_2d_rad_Nx.')
+        baseurl_2ds_1 = ('M2T1NXFLX.5.12.4/','/MERRA2_100.tavg1_2d_flx_Nx.')
+        baseurl_2dv_1 = ('M2T1NXSLV.5.12.4/','/MERRA2_100.tavg1_2d_slv_Nx.')
         # 1992 ~ 2000
-        baseurl_3dn_2 = ('M2I6NPANA.5.12.4/','/MERRA2_200.inst6_3d_ana_Np.')                     
-        baseurl_3da_2 = ('M2I3NPASM.5.12.4/','/MERRA2_200.inst3_3d_asm_Np.')            
-        baseurl_2dm_2 = ('M2I1NXASM.5.12.4/','/MERRA2_200.inst1_2d_asm_Nx.')                 
-        baseurl_2dr_2 = ('M2T1NXRAD.5.12.4/','/MERRA2_200.tavg1_2d_rad_Nx.')             
-        baseurl_2ds_2 = ('M2T1NXFLX.5.12.4/','/MERRA2_200.tavg1_2d_flx_Nx.')            
-        baseurl_2dv_2 = ('M2T1NXSLV.5.12.4/','/MERRA2_200.tavg1_2d_slv_Nx.')                                                                                                                                                                                                          
-        # 2001 ~ 2010  
-        baseurl_3dn_3 = ('M2I6NPANA.5.12.4/','/MERRA2_300.inst6_3d_ana_Np.')                   
-        baseurl_3da_3 = ('M2I3NPASM.5.12.4/','/MERRA2_300.inst3_3d_asm_Np.')            
-        baseurl_2dm_3 = ('M2I1NXASM.5.12.4/','/MERRA2_300.inst1_2d_asm_Nx.')               
-        baseurl_2dr_3 = ('M2T1NXRAD.5.12.4/','/MERRA2_300.tavg1_2d_rad_Nx.')           
-        baseurl_2ds_3 = ('M2T1NXFLX.5.12.4/','/MERRA2_300.tavg1_2d_flx_Nx.')                                                                                       
-        baseurl_2dv_3 = ('M2T1NXSLV.5.12.4/','/MERRA2_300.tavg1_2d_slv_Nx.')                                                                                                                                                                                                          
+        baseurl_3dn_2 = ('M2I6NPANA.5.12.4/','/MERRA2_200.inst6_3d_ana_Np.')
+        baseurl_3da_2 = ('M2I3NPASM.5.12.4/','/MERRA2_200.inst3_3d_asm_Np.')
+        baseurl_2dm_2 = ('M2I1NXASM.5.12.4/','/MERRA2_200.inst1_2d_asm_Nx.')
+        baseurl_2dr_2 = ('M2T1NXRAD.5.12.4/','/MERRA2_200.tavg1_2d_rad_Nx.')
+        baseurl_2ds_2 = ('M2T1NXFLX.5.12.4/','/MERRA2_200.tavg1_2d_flx_Nx.')
+        baseurl_2dv_2 = ('M2T1NXSLV.5.12.4/','/MERRA2_200.tavg1_2d_slv_Nx.')
+        # 2001 ~ 2010
+        baseurl_3dn_3 = ('M2I6NPANA.5.12.4/','/MERRA2_300.inst6_3d_ana_Np.')
+        baseurl_3da_3 = ('M2I3NPASM.5.12.4/','/MERRA2_300.inst3_3d_asm_Np.')
+        baseurl_2dm_3 = ('M2I1NXASM.5.12.4/','/MERRA2_300.inst1_2d_asm_Nx.')
+        baseurl_2dr_3 = ('M2T1NXRAD.5.12.4/','/MERRA2_300.tavg1_2d_rad_Nx.')
+        baseurl_2ds_3 = ('M2T1NXFLX.5.12.4/','/MERRA2_300.tavg1_2d_flx_Nx.')
+        baseurl_2dv_3 = ('M2T1NXSLV.5.12.4/','/MERRA2_300.tavg1_2d_slv_Nx.')
         # 2011 ~ present
-        baseurl_3dn_4 = ('M2I6NPANA.5.12.4/','/MERRA2_400.inst6_3d_ana_Np.')               
-        baseurl_3da_4 = ('M2I3NPASM.5.12.4/','/MERRA2_400.inst3_3d_asm_Np.')           
-        baseurl_2dm_4 = ('M2I1NXASM.5.12.4/','/MERRA2_400.inst1_2d_asm_Nx.')           
-        baseurl_2dr_4 = ('M2T1NXRAD.5.12.4/','/MERRA2_400.tavg1_2d_rad_Nx.')           
-        baseurl_2ds_4 = ('M2T1NXFLX.5.12.4/','/MERRA2_400.tavg1_2d_flx_Nx.')                                                               
+        baseurl_3dn_4 = ('M2I6NPANA.5.12.4/','/MERRA2_400.inst6_3d_ana_Np.')
+        baseurl_3da_4 = ('M2I3NPASM.5.12.4/','/MERRA2_400.inst3_3d_asm_Np.')
+        baseurl_2dm_4 = ('M2I1NXASM.5.12.4/','/MERRA2_400.inst1_2d_asm_Nx.')
+        baseurl_2dr_4 = ('M2T1NXRAD.5.12.4/','/MERRA2_400.tavg1_2d_rad_Nx.')
+        baseurl_2ds_4 = ('M2T1NXFLX.5.12.4/','/MERRA2_400.tavg1_2d_flx_Nx.')
         baseurl_2dv_4 = ('M2T1NXSLV.5.12.4/','/MERRA2_400.tavg1_2d_slv_Nx.')
-        # netCDF4         
+        # netCDF4
         format = ('.nc4')
-                        
-        #Setup the start and end of dates
+
+        # Setup the start and end of dates
         Begin = date['beg']
         End  =  date['end']
-                
-        #Setup the based string of dates for urls 
+
+        # Setup the based string of dates for urls
         res1 = [d.strftime("%Y/%m") for d in pandas.date_range(Begin,End)]
-        res2 = [d.strftime("%Y%m%d") for d in pandas.date_range(Begin,End)]        
-            
-       # build the urls list
+        res2 = [d.strftime("%Y%m%d") for d in pandas.date_range(Begin,End)]
+
+        # build the urls list
         urls_3dmana = []
         urls_3dmasm = []
         urls_2dm = []
@@ -208,68 +195,67 @@ def getURLs(self, date):
         urls_2ds = []
         urls_2dv = []
         for i in range(0,len(res1)):
-            #subset (1980~1991) 
-             if res1[i] >= '1980/01' and res1[i] <= '1991/12': 
-                urls_3dmana.append(baseurl_3d + baseurl_3dn_1[0] + res1[i] + 
-                                   baseurl_3dn_1[1] + res2[i] + format)           
-                urls_3dmasm.append(baseurl_3d + baseurl_3da_1[0] + res1[i] + 
-                                   baseurl_3da_1[1] + res2[i] + format)                    
-                urls_2dm.append(baseurl_2d + baseurl_2dm_1[0] + res1[i] + 
-                                baseurl_2dm_1[1] + res2[i] + format)      
-                urls_2ds.append(baseurl_2d + baseurl_2ds_1[0] + res1[i] + 
-                                baseurl_2ds_1[1] + res2[i] + format)       
-                urls_2dr.append(baseurl_2d + baseurl_2dr_1[0] + res1[i] + 
-                                baseurl_2dr_1[1] + res2[i] + format)     
-                urls_2dv.append(baseurl_2d + baseurl_2dv_1[0] + res1[i] + 
+            # subset (1980~1991)
+            if res1[i] >= '1980/01' and res1[i] <= '1991/12':
+                urls_3dmana.append(baseurl_3d + baseurl_3dn_1[0] + res1[i] +
+                                   baseurl_3dn_1[1] + res2[i] + format)
+                urls_3dmasm.append(baseurl_3d + baseurl_3da_1[0] + res1[i] +
+                                   baseurl_3da_1[1] + res2[i] + format)
+                urls_2dm.append(baseurl_2d + baseurl_2dm_1[0] + res1[i] +
+                                baseurl_2dm_1[1] + res2[i] + format)
+                urls_2ds.append(baseurl_2d + baseurl_2ds_1[0] + res1[i] +
+                                baseurl_2ds_1[1] + res2[i] + format)
+                urls_2dr.append(baseurl_2d + baseurl_2dr_1[0] + res1[i] +
+                                baseurl_2dr_1[1] + res2[i] + format)
+                urls_2dv.append(baseurl_2d + baseurl_2dv_1[0] + res1[i] +
                                 baseurl_2dv_1[1] + res2[i] + format)
-            #subset (1992~2000) 
-             elif res1[i] >= '1992/01' and res1[i] <= '2000/12': 
-                urls_3dmana.append(baseurl_3d + baseurl_3dn_2[0] + res1[i] + 
-                                   baseurl_3dn_2[1] + res2[i] + format)              
-                urls_3dmasm.append(baseurl_3d + baseurl_3da_2[0] + res1[i] + 
-                                   baseurl_3da_2[1] + res2[i] + format)             
-                urls_2dm.append(baseurl_2d + baseurl_2dm_2[0] + res1[i] + 
-                                baseurl_2dm_2[1] + res2[i] + format)     
-                urls_2ds.append(baseurl_2d + baseurl_2ds_2[0] + res1[i] + 
-                                baseurl_2ds_2[1] + res2[i] + format)     
-                urls_2dr.append(baseurl_2d + baseurl_2dr_2[0] + res1[i] + 
-                                baseurl_2dr_2[1] + res2[i] + format)     
-                urls_2dv.append(baseurl_2d + baseurl_2dv_2[0] + res1[i] + 
+            # subset (1992~2000)
+            elif res1[i] >= '1992/01' and res1[i] <= '2000/12':
+                urls_3dmana.append(baseurl_3d + baseurl_3dn_2[0] + res1[i] +
+                                   baseurl_3dn_2[1] + res2[i] + format)
+                urls_3dmasm.append(baseurl_3d + baseurl_3da_2[0] + res1[i] +
+                                   baseurl_3da_2[1] + res2[i] + format)
+                urls_2dm.append(baseurl_2d + baseurl_2dm_2[0] + res1[i] +
+                                baseurl_2dm_2[1] + res2[i] + format)
+                urls_2ds.append(baseurl_2d + baseurl_2ds_2[0] + res1[i] +
+                                baseurl_2ds_2[1] + res2[i] + format)
+                urls_2dr.append(baseurl_2d + baseurl_2dr_2[0] + res1[i] +
+                                baseurl_2dr_2[1] + res2[i] + format)
+                urls_2dv.append(baseurl_2d + baseurl_2dv_2[0] + res1[i] +
                                 baseurl_2dv_2[1] + res2[i] + format)
-            #subset (2001~ 2010) 
-             elif res1[i] >= '2001/01' and res1[i] <= '2010/12': 
-                urls_3dmana.append(baseurl_3d + baseurl_3dn_3[0] + res1[i] + 
-                                   baseurl_3dn_3[1] + res2[i] + format)              
-                urls_3dmasm.append(baseurl_3d + baseurl_3da_3[0] + res1[i] + 
-                                   baseurl_3da_3[1] + res2[i] + format)                   
-                urls_2dm.append(baseurl_2d + baseurl_2dm_3[0] + res1[i] + 
-                                baseurl_2dm_3[1] + res2[i] + format)     
-                urls_2ds.append(baseurl_2d + baseurl_2ds_3[0] + res1[i] + 
-                                baseurl_2ds_3[1] + res2[i] + format)     
-                urls_2dr.append(baseurl_2d + baseurl_2dr_3[0] + res1[i] + 
-                                baseurl_2dr_3[1] + res2[i] + format)    
-                urls_2dv.append(baseurl_2d + baseurl_2dv_3[0] + res1[i] + 
-                                baseurl_2dv_3[1] + res2[i] + format)     
-             elif res1[i] >= '2011/01': #subset (2011 ~ present)
-                urls_3dmana.append(baseurl_3d + baseurl_3dn_4[0] + res1[i] + 
-                                   baseurl_3dn_4[1] + res2[i] + format)             
-                urls_3dmasm.append(baseurl_3d + baseurl_3da_4[0] + res1[i] + 
-                                   baseurl_3da_4[1] + res2[i] + format)                     
-                urls_2dm.append(baseurl_2d + baseurl_2dm_4[0] + res1[i] + 
-                                baseurl_2dm_4[1] + res2[i] + format)     
-                urls_2ds.append(baseurl_2d + baseurl_2ds_4[0] + res1[i] + 
-                                baseurl_2ds_4[1] + res2[i] + format)     
-                urls_2dr.append(baseurl_2d + baseurl_2dr_4[0] + res1[i] + 
-                                baseurl_2dr_4[1] + res2[i] + format)       
-                urls_2dv.append(baseurl_2d + baseurl_2dv_4[0] + res1[i] + 
+            # subset (2001~ 2010)
+            elif res1[i] >= '2001/01' and res1[i] <= '2010/12':
+                urls_3dmana.append(baseurl_3d + baseurl_3dn_3[0] + res1[i] +
+                                   baseurl_3dn_3[1] + res2[i] + format)
+                urls_3dmasm.append(baseurl_3d + baseurl_3da_3[0] + res1[i] +
+                                   baseurl_3da_3[1] + res2[i] + format)
+                urls_2dm.append(baseurl_2d + baseurl_2dm_3[0] + res1[i] +
+                                baseurl_2dm_3[1] + res2[i] + format)
+                urls_2ds.append(baseurl_2d + baseurl_2ds_3[0] + res1[i] +
+                                baseurl_2ds_3[1] + res2[i] + format)
+                urls_2dr.append(baseurl_2d + baseurl_2dr_3[0] + res1[i] +
+                                baseurl_2dr_3[1] + res2[i] + format)
+                urls_2dv.append(baseurl_2d + baseurl_2dv_3[0] + res1[i] +
+                                baseurl_2dv_3[1] + res2[i] + format)
+            elif res1[i] >= '2011/01':  # subset (2011 ~ present)
+                urls_3dmana.append(baseurl_3d + baseurl_3dn_4[0] + res1[i] +
+                                   baseurl_3dn_4[1] + res2[i] + format)
+                urls_3dmasm.append(baseurl_3d + baseurl_3da_4[0] + res1[i] +
+                                   baseurl_3da_4[1] + res2[i] + format)
+                urls_2dm.append(baseurl_2d + baseurl_2dm_4[0] + res1[i] +
+                                baseurl_2dm_4[1] + res2[i] + format)
+                urls_2ds.append(baseurl_2d + baseurl_2ds_4[0] + res1[i] +
+                                baseurl_2ds_4[1] + res2[i] + format)
+                urls_2dr.append(baseurl_2d + baseurl_2dr_4[0] + res1[i] +
+                                baseurl_2dr_4[1] + res2[i] + format)
+                urls_2dv.append(baseurl_2d + baseurl_2dv_4[0] + res1[i] +
                                 baseurl_2dv_4[1] + res2[i] + format)
-    
-     
-        #Setup URL for getting constant model parameters (2D, single-level, full horizontal resolution)
+
+        # Setup URL for getting constant model parameters (2D, single-level, full horizontal resolution)
         url_2dc = ['https://goldsmr4.gesdisc.eosdis.nasa.gov:443/opendap/MERRA2_MONTHLY/M2C0NXASM.5.12.4/1980/MERRA2_101.const_2d_asm_Nx.00000000.nc4']
- 
+
         return urls_3dmana, urls_3dmasm, urls_2dm, urls_2ds, urls_2dr, url_2dc, urls_2dv
- 
+
     def download(self, username, password, urls):
         """ Access the MERRA server by account information and defined urls
             Args:
@@ -277,56 +263,56 @@ def download(self, username, password, urls):
                  password = "xxxxxx"
                  urls = urls_3dmana, urls_3dmasm, urls_2dm, urls_2ds, urls_2dr
             Return:
-                ds:  The dictionary of each individual original dataset with     
+                ds:  The dictionary of each individual original dataset with
                      children['variables']
         """
 
         print('================ MERRA-2 SERVER ACCESS: START ================')
-        print('TIME TO GET A COFFEE')        
+        print('TIME TO GET A COFFEE')
         ds = {}
         for i in range(len(urls)):
             for delay in range(0,60):
-                try: # try to download the file
-                    session = setup_session(username, password, check_url=urls[i])        
+                try:  # try to download the file
+                    session = setup_session(username, password, check_url=urls[i])
                     ds[i] = open_url(urls[i], session=session)
                     break
                 except Exception as e:
                     if delay < 59:
-                       print("Error downloading file: {}. Trying again ({})".format(urls[i], str(delay)))
-                       print("Error message : \n{}".format(e))
-                       sleep(delay)
-                       pass
-                    else:    
+                        print("Error downloading file: {}. Trying again ({})".format(urls[i], str(delay)))
+                        print("Error message : \n{}".format(e))
+                        sleep(delay)
+                        pass
+                    else:
                         print("Error downloading file: " + urls[i] + ". Giving up.")
                         print("Error message : \n{}".format(e))
                         raise RuntimeError("==> Unsuccessful after 60 attempts.")
-            #print('------COMPLETED------','URL NO.:', i+1)
+            # print('------COMPLETED------','URL NO.:', i+1)
             print(urls[i])
-        print(ds[0].keys()    )
+        print(ds[0].keys())
         print('================ MERRA-2 SERVER ACCESS: COMPLETED ================')
         infor = urls[0].split('/')
         print('Dataset:', infor[2], infor[3],infor[4])
         print('Type:', type(ds))
-        print('Days:', len(urls)    )
-            
-        return ds        
-    
+        print('Days:', len(urls))
+
+        return ds
+
     def Variables(self, variable, ds):
         """Get the objected variables from the specific MERRA-2 datasets
         Args:
-        variable = ['T','U','V','H','lat','lon','lev','time'] 
-        variable = ['RH','H','lat','lon','lev','time'] 
+        variable = ['T','U','V','H','lat','lon','lev','time']
+        variable = ['RH','H','lat','lon','lev','time']
         variable = ['U2M','T2M','TQL','V2M','V10M','U10M','QV2M','lat','lon','time']
-        variable = ['PRECTOT','PRECTOTCOR', 'lat','lon','time'] 
-        variable = ['SWGDN','LWGNT', 'SWGDNCLR', 'LWGNTCLR', 'LWGEM', 'lat','lon','time'] 
-        variable = ['PHIS', 'FRLAND', 'FRLANDICE', 'lat', 'lon','time'] 
-        ds = MERRAgeneric().download (username, password, urls_3dmana, urls_3dmasm,   
+        variable = ['PRECTOT','PRECTOTCOR', 'lat','lon','time']
+        variable = ['SWGDN','LWGNT', 'SWGDNCLR', 'LWGNTCLR', 'LWGEM', 'lat','lon','time']
+        variable = ['PHIS', 'FRLAND', 'FRLANDICE', 'lat', 'lon','time']
+        ds = MERRAgeneric().download (username, password, urls_3dmana, urls_3dmasm,
              urls_2dm, urls_2dr, urls_2ds, chunk_size)
         Return:
         out_variable: The extracted variables in the order of given list
         Structure of out_variable: [chunk_size * lenghs of variables]
         """
-        
+
         out_variable = {}
         for i in range(0, len(ds)):
             # print("Run", "NO.:", i+1)
@@ -339,356 +325,346 @@ def Variables(self, variable, ds):
                 foundVariable = False
                 if outputVar[j] in var:
                     for k in range(len(var)):
-                        if foundVariable != True:
+                        if not foundVariable:
                             if var[k] == outputVar[j]:
                                 temp = "" + var[k]
                                 outputVar[j] = ds[i][temp]
                                 foundVariable = True
             out_variable[i] = outputVar
-        
+
         return out_variable
-    
-    def getArea(self, area, ds): 
+
+    def getArea(self, area, ds):
         """Get the specific indices  of the latitudes and longitudes of given area
-        An example: 
+        An example:
             area = {'north':65.0, 'south': 60.0, 'west': -115.0, 'east': -110.0}
-            ds = MERRAgeneric().download(username, password, urls_3dmana, urls_3dmasm,   
+            ds = MERRAgeneric().download(username, password, urls_3dmana, urls_3dmasm,
                  urls_2dm, urls_2dr, urls_2ds, chunk_size)
         Return:
-            id_lat: indices of latitudes 
-            id_lon: indices of longitudes 
+            id_lat: indices of latitudes
+            id_lon: indices of longitudes
         """
-        
+
         # pass the value of individual row lat and lon to Lat and Lon for the area subset
         Lat = ds[0].lat[:]
         Lon = ds[0].lon[:]
-                        
+
         # get the indices of selected range of Lat,Lon
-        id_lon = np.where((Lon[:] >= area['west']) & (Lon[:] <= area['east'])) 
-        id_lat = np.where((Lat[:] >= area['south']) & (Lat[:] <= area['north'])) 
-       
+        id_lon = np.where((Lon[:] >= area['west']) & (Lon[:] <= area['east']))
+        id_lat = np.where((Lat[:] >= area['south']) & (Lat[:] <= area['north']))
+
         # convert id_lat, id_lon from tuples to string
-        id_lon = list(itertools.chain(*id_lon))   
+        id_lon = list(itertools.chain(*id_lon))
         id_lat = list(itertools.chain(*id_lat))
-               
-        return id_lat, id_lon 
-
-    def getPressure(self, elevation):
-        """Convert elevation into air pressure using barometric formula"""
-        g  = 9.80665   #Gravitational acceleration [m/s2]
-        R  = 8.31432   #Universal gas constant for air [N·m /(mol·K)]    
-        M  = 0.0289644 #Molar mass of Earth's air [kg/mol]
-        P0 = 101325    #Pressure at sea level [Pa]
-        T0 = 288.15    #Temperature at sea level [K]
-        #http://en.wikipedia.org/wiki/Barometric_formula
-        return P0 * exp((-g * M * elevation) / (R * T0)) / 100 #[hPa] or [bar]
-    
-    def getPressureLevels(self, elevation): 
+
+        return id_lat, id_lon
+
+    def getPressureLevels(self, elevation):
         """Restrict list of MERRA-2 pressure levels to be download"""
-        Pmax = self.getPressure(elevation['min']) + 55
-        Pmin = self.getPressure(elevation['max']) - 55
-        # Pmax = self.getPressure(ele_min) + 55
-        # Pmin = self.getPressure(ele_max) - 55
-        levs = np.array([1000, 975, 950, 925, 900, 875, 850, 825, 800, 775, 750, 
-                         725, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250, 
-                         200, 150, 100, 70, 50, 40, 30, 20, 10, 7.0, 5.0, 4.0, 
+        Pmax = pressure_from_elevation(elevation['min']) + 55
+        Pmin = pressure_from_elevation(elevation['max']) - 55
+        # Pmax = pressure_from_elevation(ele_min) + 55
+        # Pmin = pressure_from_elevation(ele_max) - 55
+        levs = np.array([1000, 975, 950, 925, 900, 875, 850, 825, 800, 775, 750,
+                         725, 700, 650, 600, 550, 500, 450, 400, 350, 300, 250,
+                         200, 150, 100, 70, 50, 40, 30, 20, 10, 7.0, 5.0, 4.0,
                          3.0, 2.0, 1.0, 0.7, 0.5, 0.4, 0.3, 0.1])
- 
-        #Get the indics of selected range of elevation 
+
+        # Get the indics of selected range of elevation
         id_lev = np.where((levs >= Pmin) & (levs <= Pmax))
-        id_lev = list(itertools.chain(*id_lev))    
-                
+        id_lev = list(itertools.chain(*id_lev))
+
         return id_lev
 
-    def latLon_3d(self, out_variable, id_lat, id_lon, id_lev): 
-        """Get Latitude, Longitude, Levels, and Time for datasets at the 
+    def latLon_3d(self, out_variable, id_lat, id_lon, id_lev):
+        """Get Latitude, Longitude, Levels, and Time for datasets at the
         pressure levels
-        
+
            Args:
-           out_variable = MERRAgeneric().getVariables(variable, ds) 
+           out_variable = MERRAgeneric().getVariables(variable, ds)
            id_lat, id_lon = MERRAgeneric().getArea(area, ds)
            -4 = id_Latitude
            -3 = id_Longitude
            -2 = id_Level
-           -1 = id_Time 
-           
-           Return: 
-           lat, lon, lev, time: the extracted latitudes, longitudes, levels 
+           -1 = id_Time
+
+           Return:
+           lat, lon, lev, time: the extracted latitudes, longitudes, levels
            Structure of lat: chunk_size * lengths of extracted latitudes
            Structure of lon: chunk_size * lengths of extracted longitudes
            Structure of lev: chunk_size * lengths of extracted levels
            Structure of time: chunk_size * total number of hours per day
-        """   
-        
+        """
+
         Lat  = {}
         Lon  = {}
         Lev  = {}
         time = {}
         for i in range(0, len(out_variable)):
             for delay in range(0,60):
-                try: # try to obtain the data of Lat, Lon, Lev, time each by each
+                try:  # try to obtain the data of Lat, Lon, Lev, time each by each
                     # print("run", "NO.:", i+1)
                     Lat[i]  = out_variable[i][-4][:]
                     Lon[i]  = out_variable[i][-3][:]
                     Lev[i]  = out_variable[i][-2][:]
                     time[i] = out_variable[i][-1][:]
                     break
-                except:
+                except Exception:
                     if delay < 59:
                         print("Error downloading data: " + ". Trying again (" + str(delay) + ")")
                         sleep(delay)
                         pass
-                    else:    
+                    else:
                         print("Error downloading data: " + ". Giving up.")
                         raise RuntimeError("==> Unsuccessful after 60 attempts.")
-                
-        #For Latitude and Longitude   
+
+        # For Latitude and Longitude
         lat = {}
-        lon = {}               
+        lon = {}
         for i in range(0, len(Lat)):
-            lat[i] = Lat[i][id_lat]                
+            lat[i] = Lat[i][id_lat]
             lon[i] = Lon[i][id_lon]
 
-        #For elevation 
-        lev = {}               
+        # For elevation
+        lev = {}
         for i in range(0, len(Lev)):
-           lev[i] = Lev[i][id_lev]                       
-         
-        return lat, lon, lev, time    
- 
-    def latLon_2d(self, out_variable, id_lat, id_lon): 
-        """Get Latitude, Longitude, Levels, and Time for datasets at surface 
+            lev[i] = Lev[i][id_lev]
+
+        return lat, lon, lev, time
+
+    def latLon_2d(self, out_variable, id_lat, id_lon):
+        """Get Latitude, Longitude, Levels, and Time for datasets at surface
         level
            Args:
-           out_variable = MERRAgeneric().getVariables(variable, ds) 
+           out_variable = MERRAgeneric().getVariables(variable, ds)
            id_lat, id_lon = MERRAgeneric().getArea(area, ds)
            -3 = id_Latitude
            -2 = id_Longitude
-           -1 = id_Time 
+           -1 = id_Time
            Return:
            lat, lon, lev, time: the extracted latitudes, longitudes (2D)
            Structure of lat: [chunk_size * lengths of extracted latitudes
            Structure of lon: [chunk_size * lengths of extracted longitudes]
-           Structure of time: [lchunk_size * total number of hours per day]      
+           Structure of time: [lchunk_size * total number of hours per day]
         """
         Lat  = {}
         Lon  = {}
         time = {}
         for i in range(0, len(out_variable)):
             for delay in range(0,60):
-                try: # try to obtain the data of Lat, Lon, time each by each
+                try:  # try to obtain the data of Lat, Lon, time each by each
                     # print("run", "NO.:", i+1)
                     Lat[i]  = out_variable[i][-3][:]
                     Lon[i]  = out_variable[i][-2][:]
                     time[i] = out_variable[i][-1][:]
                     break
-                except:
+                except Exception:
                     if delay < 59:
                         print("Error downloading data: " + ". Trying again (" + str(delay) + ")")
                         sleep(delay)
                         pass
-                    else:    
+                    else:
                         print("Error downloading data: " + ". Giving up.")
                         raise RuntimeError("==> Unsuccessful after 60 attempts.")
-                
-        #For Latitude and Longitude 
+
+        # For Latitude and Longitude
         lat = {}
-        lon = {}               
+        lon = {}
         for i in range(0, len(Lat)):
-            lat[i] = Lat[i][id_lat]                
+            lat[i] = Lat[i][id_lat]
             lon[i] = Lon[i][id_lon]
-        
+
         return lat, lon, time
 
-    def dataStuff_3d(self, position, id_lat, id_lon, id_lev, out_variable):  
-        """Define the outputs ones and pass the values of extracted variables 
+    def dataStuff_3d(self, position, id_lat, id_lon, id_lev, out_variable):
+        """Define the outputs ones and pass the values of extracted variables
         to the output ones and restrict the area (3D data product).
         Args:
-        id_lat, id_lon = MERRAgeneric().getArea(area, ds) 
+        id_lat, id_lon = MERRAgeneric().getArea(area, ds)
         position: index of the variable in get_variables (setup automatically)
         And examples:
         For 3d Analyzed Meteorological Fields datasets:
-        t = MERRAgeneric().dataStuff (position, lat, lon, lev, time, 
+        t = MERRAgeneric().dataStuff (position, lat, lon, lev, time,
         id_lat, id_lon, out_variable)
-        u = MERRAgeneric().dataStuff(position, lat, lon, lev, time, 
+        u = MERRAgeneric().dataStuff(position, lat, lon, lev, time,
         id_lat, id_lon, out_variable)
-        v = MERRAgeneric().dataStuff(position, lat, lon, lev, time, 
+        v = MERRAgeneric().dataStuff(position, lat, lon, lev, time,
         id_lat, id_lon, out_variable)
-        h = MERRAgeneric().dataStuff(position, lat, lon, lev, time, 
+        h = MERRAgeneric().dataStuff(position, lat, lon, lev, time,
         id_lat, id_lon, out_variable)
        Return:
-       data_area: the extracted individual variable at pressure levels at the 
+       data_area: the extracted individual variable at pressure levels at the
        given area
-       Structure of data_area: [chunk_size *[time*level*lat*lon]]       
+       Structure of data_area: [chunk_size *[time*level*lat*lon]]
         """
         print("Get Data")
         data = {}
         for i in range(0, len(out_variable)):
             for delay in range(0,60):
-                try: # try to obtain the data of variable each by each
-                    print("Run", "Day NO.:", i+1 )
+                try:  # try to obtain the data of variable each by each
+                    print(f"Run Day NO.: {i + 1}")
                     data[i] = out_variable[i][position][:]
                     break
-                except:
+                except Exception:
                     if delay < 59:
-                       print("Error downloading data: " + ". Trying again (" + str(delay) + ")")
-                       sleep(delay)
-                       pass
-                    else:    
-                        print("Error downloading data: " + ". Giving up.")
+                        print("Error downloading data:  Trying again (" + str(delay) + ")")
+                        sleep(delay)
+                        pass
+                    else:
+                        print("Error downloading data: Giving up.")
                         raise RuntimeError("==> Unsuccessful after 60 attempts.")
-                             
+
         # Restrict the area for data set
         print("Restrict Area and Elevation")
         data_area = {}
-        for i in range(0, len(data)): 
-            print("Run", "Day NO.:", i+1)
-            data_area[i] = data[i][:,id_lev,:,:]  
+        for i in range(0, len(data)):
+            print("Run", "Day NO.:", i + 1)
+            data_area[i] = data[i][:,id_lev,:,:]
 
         for j in range(0, len(data_area)):
             data_area[j] = data_area[j][:,:,id_lat,:]
         for j in range(0, len(data_area)):
             data_area[j] = data_area[j][:,:,:,id_lon]
-            
-        del data 
+
+        del data
 
         return data_area
 
-    def dataStuff_2d(self, position, id_lat, id_lon, out_variable):  
-        """Define the outputs ones and pass the values of extracted variables 
+    def dataStuff_2d(self, position, id_lat, id_lon, out_variable):
+        """Define the outputs ones and pass the values of extracted variables
         to the output ones and restrict the area (2D data products).
-        Args: 
-        id_lat, id_lon = MERRAgeneric().getArea(area, ds) 
+        Args:
+        id_lat, id_lon = MERRAgeneric().getArea(area, ds)
         position: index of the variable in get_variables (setup automatically)
         An examples:
         For 2D meteorological Diagnostics datasets:
-        t2m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat, 
+        t2m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat,
+              id_lon, out_variable_2dm)
+        u2m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat,
               id_lon, out_variable_2dm)
-        u2m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat, 
+        v2m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat,
               id_lon, out_variable_2dm)
-        v2m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat, 
-              id_lon, out_variable_2dm) 
-        u10m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat, 
+        u10m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat,
               id_lon, out_variable_2dm)
-        v10m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat, 
+        v10m = MERRAgeneric().dataStuff_2d(position, lat, lon, time, id_lat,
                id_lon, out_variable_2dm)
         Return:
         data_area: the extracted individual variable at surface levels
-        Structure of data_area: chunk_size * [time*lat*lon]         
+        Structure of data_area: chunk_size * [time*lat*lon]
         """
         print("Get Data")
         data = {}
         for i in range(0, len(out_variable)):
             for delay in range(0,60):
-                try: # try to obtain the data of variable each by each
-                    print("Run", "Day NO.:", i+1)
+                try:  # try to obtain the data of variable each by each
+                    print(f"Run Day NO.: {i+1}")
                     data[i] = out_variable[i][position][:]
                     break
-                except:
+                except Exception:
                     if delay < 59:
                         print("Error downloading data: " + ". Trying again (" + str(delay) + ")")
                         sleep(delay)
                         pass
-                    else:    
+                    else:
                         print("Error downloading data: " + ". Giving up.")
                         raise RuntimeError("==> Unsuccessful after 60 attempts.")
-                    
+
         # Restrict the area for data set
         print("Restrict Area")
         data_area = {}
-        for i in range(0, len(data)): 
-            print("Run","Day NO.:", i+1)
+        for i in range(0, len(data)):
+            print(f"Run Day NO.: {i+1}")
             data_area[i] = data[i][:,id_lat,:]
         for j in range(0, len(data_area)):
             data_area[j] = data_area[j][:,:,id_lon]
-            
-        del data 
+
+        del data
 
         return data_area
-           
-    def getTime(self, date):                                                                                                                                          
+
+    def getTime(self, date):
         """set up date and time series for netCDF output results
-        Return: 
+        Return:
         date_ind: a string list of date in the range of begin and end
         time_ind1: the time series for 6-hours step in the range of date
         time_ind2: the time series for 3-hours step in the range of date
         time_ind3: the time series for 1-hour step in the range of date
-        """ 
+        """
         Start = date['beg']
         End   = date['end']
- 
+
         # Set up the time step
         time_step1 = '6H'
         time_step2 = '3H'
         time_step3 = '1H'
-            
-        #get extra one one more day for getting the full range of time series
-        End1 = End + timedelta(days=1)           
-            
-        #get the Datetime index associated with time_step 
-        time_ind1 = (pandas.date_range(Start, End1, freq = time_step1))[0:-1]       
-        time_ind2 = (pandas.date_range(Start, End1, freq = time_step2))[0:-1] 
-        time_ind3 = (pandas.date_range(Start, End1, freq = time_step3))[0:-1]
+
+        # get extra one one more day for getting the full range of time series
+        End1 = End + timedelta(days=1)
+
+        # get the Datetime index associated with time_step
+        time_ind1 = (pandas.date_range(Start, End1, freq=time_step1))[0:-1]
+        time_ind2 = (pandas.date_range(Start, End1, freq=time_step2))[0:-1]
+        time_ind3 = (pandas.date_range(Start, End1, freq=time_step3))[0:-1]
 
         # Convert datetime
-        try:                         # for pandas 0.19 
+        try:                         # for pandas 0.19
             time_ind1.to_datetime()
             time_ind2.to_datetime()
             time_ind3.to_datetime()
-        except:                       # for pandas 0.23
+        except Exception:                       # for pandas 0.23
             time_ind1 = pandas.to_datetime(time_ind1)
             time_ind2 = pandas.to_datetime(time_ind2)
             time_ind3 = pandas.to_datetime(time_ind3)
-                                 
+
         # get list of date series
         date_diff = End - Start
         date_ind = [Start + timedelta(days=x) for x in range(date_diff.days + 1)]
         date_ind = [d.strftime('%Y%m%d') for d in date_ind]
 
         return date_ind, time_ind1, time_ind2, time_ind3
- 
-    def restruDatastuff(self, data_area):                                                                                                                                          
-        """ Restructuring the dimension of abstracted data stuff for preparing 
+
+    def restruDatastuff(self, data_area):
+        """ Restructuring the dimension of abstracted data stuff for preparing
         to save netCDF output results.
-        return: 
+        return:
         data_total: [len(date)*len(time/day), level, lat, lon] (3D)
                     [len(date)*len(time/day), lat, lon] (2D)
-        """ 
-        data_total = []                                                   
-        for i in range(0, len(data_area)):                                           
+        """
+        data_total = []
+        for i in range(0, len(data_area)):
             for j in range(0,len(data_area[i])):
                 data_total.append(data_area[i][j][:])
-        
-        data_total = np.asarray(data_total, dtype = float)
+
+        data_total = np.asarray(data_total, dtype=float)
 
         return data_total
-        
+
     def tempExtrapolate(self, t_total, h_total, elevation):
-        """ Processing 1D vertical extrapolation for Air Temperature, at where 
-            the values are lacking  (marked by 9.9999999E14) from merra-2 3D 
+        """ Processing 1D vertical extrapolation for Air Temperature, at where
+            the values are lacking  (marked by 9.9999999E14) from merra-2 3D
             Analyzed Meteorological Fields datasets.
-            IMPORTANT TIP: 
-            To set up 'ele_max = 2500' (meter) or higher 
-            Reason: to make sure. get enough levels of geopotential height for 
-            conducting 1dinterp (linear) (2 points of values needed at least) 
-        """  
+            IMPORTANT TIP:
+            To set up 'ele_max = 2500' (meter) or higher
+            Reason: to make sure. get enough levels of geopotential height for
+            conducting 1dinterp (linear) (2 points of values needed at least)
+        """
 
-        #restructure t_total [time*lev*lat*lon] to [lat*lon*time*lev]
+        # restructure t_total [time*lev*lat*lon] to [lat*lon*time*lev]
         t_total = t_total[:,:,:,:].transpose((2,3,0,1))
         h_total = h_total[:,:,:,:].transpose((2,3,0,1))
 
-        #find the value gap and conduct 1d extrapolation 
-        for i in range(0, len(t_total)): 
+        # find the value gap and conduct 1d extrapolation
+        for i in range(0, len(t_total)):
             for j in range(0, len(t_total[0])):
-                 t_time = t_total[i][j][:]
-                 h_time = h_total[i][j][:]
-                 for k in range(0, len(t_time)) :
-                     t_lev = t_time[k][:]
-                     h_lev = h_time[k][:]
-                     id_interp = [] 
-                     for z in range(0, len(t_lev)):
-                         # find the indices of levels with missing values
-                         if t_lev[z] > 99999:
+                t_time = t_total[i][j][:]
+                h_time = h_total[i][j][:]
+                for k in range(0, len(t_time)):
+                    t_lev = t_time[k][:]
+                    h_lev = h_time[k][:]
+                    id_interp = []
+                    for z in range(0, len(t_lev)):
+                        # find the indices of levels with missing values
+                        if t_lev[z] > 99999:
                             id_interp.append(z)
 
                             if id_interp != []:
@@ -696,17 +672,17 @@ def tempExtrapolate(self, t_total, h_total, elevation):
                                 lev_interp = h_lev[id_interp]
                                 # pass the index of first found level with existing value to z_top
                                 z_top = id_interp[-1] + 1
-                                #get values at the lowest 3 levels of geopotential heights with existed values
+                                # get values at the lowest 3 levels of geopotential heights with existed values
                                 lev_3p = h_lev[z_top:z_top + 3]
-                                #get values at the lowest 3 levels of air temperature with existed values
+                                # get values at the lowest 3 levels of air temperature with existed values
                                 t_3p = t_lev[z_top:z_top + 3]
-                                #Using spicy.interpolate.interp1d function-------------------------
+                                # Using spicy.interpolate.interp1d function-------------------------
                                 # Require >= 2 points of levs and t in minimum
                                 if len(lev_3p) >= 2:
                                     # build linear function based on given values at lowest 3 levels of air temperature and geopotential heights
-                                    f = interp1d(lev_3p, t_3p, kind = 'linear', fill_value = 'extrapolate')
+                                    f = interp1d(lev_3p, t_3p, kind='linear', fill_value='extrapolate')
                                     # use built function to calculate the values of air temperature at the found missing-values levels
-                                    t_interp = f(lev_interp)    
+                                    t_interp = f(lev_interp)
                                     # fill the calculated values into missing-values levels
                                     t_lev[id_interp] = t_interp
                                 else:
@@ -714,271 +690,197 @@ def tempExtrapolate(self, t_total, h_total, elevation):
                                     print('Failed to conduct extrapolation at some points in the output')
                                     print('Current ele_max =', elevation['max'])
                                     print('Higher Value of "ele_max" is needed to reset: > 2500')
-                                    sys.exit(0)    
-        
-                         else: 
-                            t_lev[z] = t_lev[z]           
-                         h_lev[z] = h_lev[z]
-                     
-                     #assign back                       
-                     t_time[k][:] = t_lev
-                     h_time[k][:] = h_lev
-
-                 #replace the extrapolated value [time * level] to each individual cell
-                 t_total[i][j][:] = t_time
-                 h_total[i][j][:] = h_time  
-                                                 
-        #restructure back    
+                                    sys.exit(0)
+
+                        else:
+                            t_lev[z] = t_lev[z]
+                        h_lev[z] = h_lev[z]
+
+                    #assign back
+                    t_time[k][:] = t_lev
+                    h_time[k][:] = h_lev
+
+                #replace the extrapolated value [time * level] to each individual cell
+                t_total[i][j][:] = t_time
+                h_total[i][j][:] = h_time
+
+        #restructure back
         t_total = t_total[:,:,:,:].transpose((2,3,0,1))
         h_total = h_total[:,:,:,:].transpose((2,3,0,1))
-            
+
         return t_total
-                         
+
     def wind_rh_Extrapolate(self, data_total):
-        """Processing 1D vertical extrapolation for UV and RH, at where the 
-           values are lacking (marked by 9.9999999E14) from merra-2 3d Analyzed 
+        """Processing 1D vertical extrapolation for UV and RH, at where the
+           values are lacking (marked by 9.9999999E14) from merra-2 3d Analyzed
            Meteorological Fields datasets.
-           Wind (U,V) and Relative Humidity (RH) are utilized the value of at 
+           Wind (U,V) and Relative Humidity (RH) are utilized the value of at
            lowest pressure levels to the ones with value gaps
-        """ 
+        """
         #restructure u_total,v_total [time*lev*lat*lon] to [lat*lon*time*lev]
         data_total = data_total[:,:,:,:].transpose((2,3,0,1))
 
-        #find and fill the value gap  
+        #find and fill the value gap
         for i in range(0, len(data_total)):
             for j in range(0,len(data_total[0])):
                 data_time = data_total[i][j][:]
                 for k in range(0,len(data_time)):
                     data_lev = data_time[k][:]
-                    id_interp = [] 
+                    id_interp = []
                     for z in range(0, len(data_lev)):
                         if data_lev[z] > 99999:
                            id_interp.append(z)
 
-                        if id_interp != []: 
+                        if id_interp != []:
                             z_top = id_interp[-1] + 1
                             data_lev[id_interp] = data_lev[z_top]
-                        else: 
-                            data_lev[z] = data_lev[z]           
-                                           
+                        else:
+                            data_lev[z] = data_lev[z]
+
                     data_time[k][:] = data_lev
 
                 #replace the interpolation value to each single pixel
                 data_total[i][j][:] = data_time
-                           
-        #restructure back    
+
+        #restructure back
         data_total = data_total[:,:,:,:].transpose((2,3,0,1))
-               
+
         return data_total
-       
+
     def MERRA_skip(self, merralist):
         ''' To remove the extra variables from downloaded MERRA2 data'''
-        for x in merralist:
-            if x == 'LWGEM': 
-               merralist.remove('LWGEM')
-               merralist.remove('LWGNT')
-               merralist.remove('LWGNTCLR')
-        
-        return merralist
-                      
+        for var in ['LWGEM', 'LWGNT', 'LWGNTCLR']:
+            if var in merralist :
+               merralist.remove(var)
+
     def netCDF_empty(self, ncfile_out, stations, nc_in):
-        '''Creates an empty station file to hold interpolated reults. 
-        Args:
-            ncfile_out: full name of the file to be created
-            stations: station list read with generic.StationListRead() 
-            nc_in: read in the multiple netCDF files by nc.MFDataset
+        #TODO change date type from f4 to f8 for lat and lon
         '''
-        
-        #Build the netCDF file
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4_CLASSIC')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'MERRA-2, interpolated bilinearly to stations'
-        rootgrp.featureType = "timeSeries"
-                                                
-        # dimensions
-        station = rootgrp.createDimension('station', len(stations))
-        time    = rootgrp.createDimension('time', None)
-                
-        # base variables
-        time           = rootgrp.createVariable('time', 'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hour since 1980-01-01 00:00:00'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station', 'i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude', 'f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east' 
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-        
-        # assign station characteristics            
+        Creates an empty station file to hold interpolated reults. The number of
+        stations is defined by the variable stations, variables are determined by
+        the variable list passed from the gridded original netCDF.
+
+        ncfile_out: full name of the file to be created
+        stations:   station list read with common_utils.StationListRead()
+        variables:  variables read from netCDF handle
+        lev:        list of pressure levels, empty is [] (default)
+        '''
+        rootgrp = netcdf_base(nc_in, ncfile_out, len(stations), None,
+                              'hours since 1980-01-01 00:00:00')
+
+        station = rootgrp["station"]
+        latitude = rootgrp["latitude"]
+        longitude = rootgrp["longitude"]
+        height = rootgrp["height"]
+
+        # assign station characteristics
         station[:]   = list(stations['station_number'])
         latitude[:]  = list(stations['latitude_dd'])
         longitude[:] = list(stations['longitude_dd'])
         height[:]    = list(stations['elevation_m'])
-        
+
         # extra treatment for pressure level files
         try:
             lev = nc_in.variables['level'][:]
             print("== 3D: file has pressure levels")
-            level = rootgrp.createDimension('level', len(lev))
+            level           = rootgrp.createDimension('level', len(lev))
             level           = rootgrp.createVariable('level','i4',('level'))
             level.long_name = 'pressure_level'
-            level.units     = 'hPa'  
-            level[:] = lev 
+            level.units     = 'hPa'
+            level[:] = lev
         except:
             print("== 2D: file without pressure levels")
             lev = []
-        
+
         #remove extra variables
         varlist_merra = [str_encode(x) for x in nc_in.variables.keys()]
-        varlist_merra = self.MERRA_skip(varlist_merra)                
-        
+        self.MERRA_skip(varlist_merra)
+
         # create and assign variables based on input file
         for n, var in enumerate(varlist_merra):
             if variables_skip(var):
                 continue
-                                 
-            print("VAR: ", var            )
-            # extra treatment for pressure level files        
+            print("VAR: ", str_encode(var))
+            # extra treatment for pressure level files
             if len(lev):
-                tmp = rootgrp.createVariable(var,'f4',('time', 'level', 'station'))
+                tmp = rootgrp.createVariable(var,'f4', ('time', 'level', 'station'))
             else:
-                tmp = rootgrp.createVariable(var,'f4',('time', 'station'))     
-            tmp.long_name = str_encode(nc_in.variables[var].standard_name) # for merra2
-            tmp.units     = str_encode(nc_in.variables[var].units)  
-                    
+                tmp = rootgrp.createVariable(var,'f4', ('time', 'station'))
+            tmp.long_name = str_encode(nc_in.variables[var].long_name)  # for merra2
+            tmp.units     = str_encode(nc_in.variables[var].units)
+
         #close the file
         rootgrp.close()
-        
-# block commenting this out - I don't think it is used (no usage of the function in this file)
-# maybe delete later if everything still works (NB)
-'''
-    def netCDF_merge(self, directory):
-        """
-        To combine mutiple downloaded merra2 netCDF files into a large file with 
-        specified chunk_size(e.g. 500).
-        Args:
-        ncfile_in: the full name of downloaded files 
-        e.g.:
-        '/home/username/src/globsim/examples/merra2/merra_sa_*.nc' 
-        '/home/username/src/globsim/examples/merra2/merra_pl_*.nc'
-        '/home/username/src/globsim/examples/merra2/merra_sf_*.nc'
-        Output: 
-        merged netCDF file: merra2_all_0.nc, merra2_all_1.nc, ...,
 
-        """
-        #set up nco operator
-        nco = Nco()
-  
-        # loop over filetypes, read, report
-        file_type = ['merra_sa_*.nc', 'merra_sf_*.nc', 'merra_pl_*.nc']
-        for ft in file_type:
-            ncfile_in = path.join(directory, ft)
-            
-            #get the file list
-            files_list = glob.glob(ncfile_in)
-            files_list.sort()
-            num = len(files_list)
-                        
-            #set up the name of merged file
-            if ncfile_in[-7:-5] == 'sa':
-                merged_file = path.join(ncfile_in[:-11],
-                'merra2_sa_all_'+ files_list[0][-23:-15] + "_" + files_list[num-1][-11:-3] +'.nc')
-            elif ncfile_in[-7:-5] == 'sf':
-                merged_file = path.join(ncfile_in[:-11],
-                'merra2_sf_all_' + files_list[0][-23:-15] + '_' + files_list[num-1][-11:-3] + '.nc')
-            elif ncfile_in[-7:-5] == 'pl':
-                merged_file = path.join(ncfile_in[:-11],
-                'merra2_pl_all_'+ files_list[0][-23:-15] + '_' + files_list[num-1][-11:-3] +'.nc')
-            else:
-                print('There is not such type of file'    )
-                        
-            # combined files into merged files
-            nco.ncrcat(input=files_list,output=merged_file, append = True)
-            
-            print('The Merged File below is saved:')
-            print(merged_file)
-            
-            #clear up the data
-            for fl in files_list:
-                remove(fl)
-            '''
-class SaveNCDF_pl_3dm():                                                        
-        """ write output netCDF file for abstracted variables from original 
+class SaveNCDF_pl_3dm():
+        """ write output netCDF file for abstracted variables from original
             meteorological data at pressure levels
         """
 
-        def varList(self, date, get_variables_3dmasm, get_variables_3dmana, id_lat, 
+        def varList(self, date, get_variables_3dmasm, get_variables_3dmana, id_lat,
                     id_lon, id_lev, out_variable_3dmasm, out_variable_3dmana, chunk_size,
                     time, lev, lat, lon, dir_data, elevation):
             """
-            conduct 1D Extrapolation for T,U,V, and build the variable list for 
+            conduct 1D Extrapolation for T,U,V, and build the variable list for
             output netcdf file
             """
             date_ind, time_ind1,time_ind2, time_ind3 = MERRAgeneric().getTime(date)
-            
-            #Setup size of saving file
+
+            # Setup size of saving file
             date_size = len(date_ind)
-            
+
             # for t,v, u, h
             hour_size = len(time[0])
             int_size = date_size//chunk_size
             res_type = (date_size*hour_size)%(chunk_size*hour_size)
-            
+
             if (res_type > 0):
                 size_type = [chunk_size*hour_size]*int_size + [res_type]
-            
+
             else:
-                size_type = [chunk_size*hour_size]*int_size           
+                size_type = [chunk_size*hour_size]*int_size
 
-            #Get the variables and Set up the list of output variables
+            # Get the variables and Set up the list of output variables
             rh_total = []
             h_total = []
             t_total = []
             u_total = []
             v_total = []
-          
+
             var_out = {'RH':['relative_humidity','relative humidity','1', rh_total],
                        'H':['geopotential_height','geopotential_height', 'm', h_total],
                        'T':['air_temperature', 'air_temperature','K', t_total],
                        'U':['eastward_wind','eastward_wind_component','m/s', u_total],
-                       'V':['northward_wind','northward_wind_component', 'm/s', v_total]}            
-            
+                       'V':['northward_wind','northward_wind_component', 'm/s', v_total]}
+
             var_list = []
-            # get RH 
+            # get RH
             for i in range(0, len(get_variables_3dmasm[0:-4])):
                 for x in var_out.keys():
                     if x == get_variables_3dmasm[i]:
                         print("------Get Subset of Variable at Pressure Levels------", get_variables_3dmasm[i])
                         var = MERRAgeneric().dataStuff_3d(i, id_lat, id_lon, id_lev, out_variable_3dmasm)
-                        # restructuring the shape 
+                        # restructuring the shape
                         var_total = MERRAgeneric().restruDatastuff(var)
                         if x == 'RH':
                            rh_total = var_total
                            print("Conduct 1D Extrapolation for 'RH'")
                            var_total = MERRAgeneric().wind_rh_Extrapolate(rh_total)   #1D Extrapolation for Relative Humidity
                            rh_total = var_total
-                           
-                           # Extracting RH at same time indice as geopotential height 
-                           # rh_total[double_time, level, lat, lon] to rh_total[even position of time, level, lat, lon]            
+
+                           # Extracting RH at same time indice as geopotential height
+                           # rh_total[double_time, level, lat, lon] to rh_total[even position of time, level, lat, lon]
                            rh_total = rh_total[::2,:,:,:]
                         del var
                         var_out[x][3] = rh_total
                         var_list.append([get_variables_3dmasm[i], var_out[x][0], var_out[x][1], var_out[x][2], var_out[x][3]])
-            #get H,T,U,V
+            # get H,T,U,V
             for i in range(0, len(get_variables_3dmana[0:-4])):
                 for x in var_out.keys():
                     if x == get_variables_3dmana[i]:
                         print("------Get Subset of Variable at Pressure Levels------", get_variables_3dmana[i])
                         var = MERRAgeneric().dataStuff_3d(i, id_lat, id_lon, id_lev, out_variable_3dmana)
-                        # restructing the shape 
+                        # restructing the shape
                         var_total = MERRAgeneric().restruDatastuff(var)
                         del var
                         if x == 'H':
@@ -987,169 +889,170 @@ def varList(self, date, get_variables_3dmasm, get_variables_3dmana, id_lat,
                            t_total = var_total
                            print("Conduct 1D Extrapolation for 'T'")
                            var_total = MERRAgeneric().tempExtrapolate(t_total, h_total, elevation) # 1D Extrapolation for Air Temperature
-                        elif x == 'U': 
+                        elif x == 'U':
                            u_total = var_total
                            print("Conduct 1D Extrapolation for 'U'"                                       )
-                           var_total = MERRAgeneric().wind_rh_Extrapolate(u_total)   #1D Extrapolation for Eastward Wind
+                           var_total = MERRAgeneric().wind_rh_Extrapolate(u_total)   # 1D Extrapolation for Eastward Wind
                         elif x == 'V':
                            v_total = var_total
                            print("Conduct 1D Extrapolation for 'V'")
-                           var_total = MERRAgeneric().wind_rh_Extrapolate(v_total)   #1D Extrapolation for Northward Wind
-                        
+                           var_total = MERRAgeneric().wind_rh_Extrapolate(v_total)   # 1D Extrapolation for Northward Wind
+
                         var_out[x][3] = var_total
-                        
+
                         var_list.append([get_variables_3dmana[i],var_out[x][0], var_out[x][1], var_out[x][2], var_out[x][3]])
 
-                        
+
             return var_list, time_ind1, date_ind, size_type
-            
-        def saveData(self, date, get_variables_3dmasm, get_variables_3dmana, 
-                     id_lat, id_lon, id_lev, out_variable_3dmasm, out_variable_3dmana, 
+
+        def saveData(self, date, get_variables_3dmasm, get_variables_3dmana,
+                     id_lat, id_lon, id_lev, out_variable_3dmasm, out_variable_3dmana,
                      chunk_size, time, lev, lat, lon, dir_data, elevation):
             """
             build NetCDF file for saving output variables (T, U, V, H, RH, lat, lon, levels, time)
             """
-            
-            #get var_list, time indices 
+
+            # get var_list, time indices
             var_list, time_ind1, date_ind, size_type = self.varList(
-                    date, get_variables_3dmasm, get_variables_3dmana, id_lat, 
-                    id_lon, id_lev, out_variable_3dmasm, out_variable_3dmana, 
+                    date, get_variables_3dmasm, get_variables_3dmana, id_lat,
+                    id_lon, id_lev, out_variable_3dmasm, out_variable_3dmana,
                     chunk_size, time, lev, lat, lon, dir_data, elevation)
-            
-            # save nc file 
+
+            # save nc file
             var_low = 0
             var_up = 0
             for i in range(0, 1):
-            #for i in range(0, len(size_type)):
+            # for i in range(0, len(size_type)):
                 var = size_type[i]
                 var_low = var_up
                 var_up = var_low + var
-                
-                #set up file path and names 
-                file_ncdf  = path.join(dir_data,("merra_pl" + "_" + (date_ind[var_low // len(time[0])]) + "_" + "to" + "_" +(date_ind[var_up // len(time[0]) - 1]) + ".nc"))      
+
+                # set up file path and names
+                file_ncdf  = path.join(dir_data,("merra_pl" + "_" + (date_ind[var_low // len(time[0])]) + "_" + "to" + "_" +(date_ind[var_up // len(time[0]) - 1]) + ".nc"))
                 rootgrp = Dataset(file_ncdf, 'w', format='NETCDF4_CLASSIC')
                 print("Saved File Type:", rootgrp.file_format)
                 rootgrp.source      = 'Merra, abstrated meteorological variables from metadata at pressure levels'
                 rootgrp.featureType = "3_Dimension"
-    
-                #Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup 
+
+                # Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup
                 LEV = lev[0]
                 LAT = lat[0]
                 LON = lon[0]
 
-                #dimensions
+                # dimensions
                 time  = rootgrp.createDimension('time',  None)
                 level = rootgrp.createDimension('level', len(LEV))
                 lats  = rootgrp.createDimension('lats',  len(LAT))
                 lons  = rootgrp.createDimension('lons',  len(LON))
-                
-                #Output the results of output variables
+
+                # Output the results of output variables
                 for x in range(0, len(var_list)):
                     out_var = rootgrp.createVariable(var_list[x][0], 'f4', ('time', 'level', 'lats', 'lons'),fill_value=9.9999999E14)
                     out_var.standard_name = var_list[x][1]
                     out_var.long_name = var_list[x][2]
-                    out_var.units         = var_list[x][3] 
+                    out_var.units         = var_list[x][3]
                     out_var.missing_value = 9.9999999E14
 
                     # out_var.fmissing_value = (9.9999999E14, 'f')
-                    # out_var.vmin = (-9.9999999E14, 'f')   
+                    # out_var.vmin = (-9.9999999E14, 'f')
                     # out_var.vmax = (9.9999999E14, 'f')
 
-                    out_var[:,:,:,:] = var_list[x][4][var_low:var_up,:,:,:]    #data generic name with data stored in it
-    
+                    out_var[:,:,:,:] = var_list[x][4][var_low:var_up,:,:,:]    # data generic name with data stored in it
+
                 Time = rootgrp.createVariable('time', 'i4', ('time'))
                 Time.standard_name = "time"
-                Time.units  = "hours since 1980-1-1 00:00:00"                 
-                Time.calendar = "gregorian"   
-                
+                Time.units  = "hours since 1980-1-1 00:00:00"
+                Time.calendar = "gregorian"
+
                 # pass the values
                 netCDFTime = []
                 for x in range(0, len(time_ind1)):
                     netCDFTime.append(nc.date2num(time_ind1[x], units = Time.units, calendar = Time.calendar))
-                Time[:] = netCDFTime[var_low:var_up] 
-                
+                Time[:] = netCDFTime[var_low:var_up]
+
                 Level = rootgrp.createVariable('level','i4', ('level'))
                 Level.standard_name = "air_pressure"
                 Level.long_name = "vertical level"
                 Level.units = "hPa"
                 Level.positive = "down"
                 Level.axis = "Z"
-                Level[:] = [L for L in lev[0]] 
-                
+                Level[:] = [L for L in lev[0]]
+
                 Latitudes               = rootgrp.createVariable('latitude', 'f4',('lats'))
                 Latitudes.standard_name = "latitude"
                 Latitudes.units         = "degrees_north"
                 Latitudes.axis          = "Y"
-                Latitudes[:]  = [L for L in lat[0]]                                   
+                Latitudes[:]  = [L for L in lat[0]]
 
                 Longitudes               = rootgrp.createVariable('longitude', 'f4',('lons'))
                 Longitudes.standard_name = "longitude"
                 Longitudes.units         = "degrees_east"
                 Longitudes.axis          = "X"
-                Longitudes[:] = [L for L in lon[0]]                                   
-    
-                #close the root group
+                Longitudes[:] = [L for L in lon[0]]
+
+                # close the root group
                 rootgrp.close()
-          
-class SaveNCDF_sa():                                  
-        """ write output netCDF file for abstracted variables from original 2D 
+
+
+class SaveNCDF_sa():
+        """ write output netCDF file for abstracted variables from original 2D
             meteorological Diagnostics dataset and suface flux Diagnostics datasets
         """
 
         def varList(self, date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chunk_size, time, lat, lon, dir_data):
             """ build the variables list for output netcdf file in the format
-            of (T2M,U2M, V2M, U10M,V10M,QV2M, lat, lon, time)"""            
-            
+            of (T2M,U2M, V2M, U10M,V10M,QV2M, lat, lon, time)"""
+
             date_ind, time_ind1, time_ind2, time_ind3 = MERRAgeneric().getTime(date)
-            
-            #Setup size of saving file
+
+            # Setup size of saving file
             date_size = len(date_ind)
             hour_size = len(time[0])
             int_size = date_size // chunk_size
             res_type = (date_size * hour_size) % (chunk_size * hour_size)
-                        
+
             if (res_type > 0):
                 size_type = [chunk_size * hour_size] * int_size + [res_type]
-            
+
             else:
-                size_type = [chunk_size * hour_size] * int_size           
+                size_type = [chunk_size * hour_size] * int_size
 
-            #Get the variables and set up the list for saving in netCDF file
+            # Get the variables and set up the list for saving in netCDF file
             t2m_total = []
             u2m_total = []
             v2m_total = []
             u10m_total = []
             v10m_total = []
             sh2m_total = []
-            
-            var_out = {'T2M': ['2-metre_air_temperature',   
-                               'temperature_at_2m_above_the_displacement_height',     
+
+            var_out = {'T2M': ['2-metre_air_temperature',
+                               'temperature_at_2m_above_the_displacement_height',
                                'K',     t2m_total],
-                       'U2M': ['2-metre_eastward_wind',     
-                               'eastward_wind_at _2m_above_the_displacement_height',  
+                       'U2M': ['2-metre_eastward_wind',
+                               'eastward_wind_at _2m_above_the_displacement_height',
                                'm/s',   u2m_total],
-                       'V2M': ['2-metre_northward_wind',    
-                               'northward_wind_at_2m_above_the_displacement_height',  
+                       'V2M': ['2-metre_northward_wind',
+                               'northward_wind_at_2m_above_the_displacement_height',
                                'm/s',   v2m_total],
-                       'U10M':['10-metre_eastward_wind',    
-                               'eastward_wind_at_10m_above_displacement_height',      
+                       'U10M':['10-metre_eastward_wind',
+                               'eastward_wind_at_10m_above_displacement_height',
                                'm/s',   u10m_total],
-                       'V10M':['10-metre_northward_wind',   
-                               'northward_wind_at_10m_above_the_displacement_height', 
+                       'V10M':['10-metre_northward_wind',
+                               'northward_wind_at_10m_above_the_displacement_height',
                                'm/s',   v10m_total],
-                       'QV2M':['2-metre_specific_humidity', 
-                               'specific_humidity_at_2m',                             
+                       'QV2M':['2-metre_specific_humidity',
+                               'specific_humidity_at_2m',
                                'kg/kg', sh2m_total]}
-                        
+
             var_list = []
             for i in range(0, len(get_variables_2dm[0: -3])):
                 for x in var_out.keys():
                     if x == get_variables_2dm[i]:
                         print("------Get Subset of Variable at Surface Level------", get_variables_2dm[i])
                         # the position of T2M, U2M, V2M, U10M, V10M in out_variable_2ds is the position in the get_variables
-                        var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dm)   
-                        
-                        # restructure the shape 
+                        var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dm)
+
+                        # restructure the shape
                         var_total = MERRAgeneric().restruDatastuff(var)
                         del var
                         var_out[x][3] = var_total
@@ -1157,109 +1060,110 @@ def varList(self, date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chu
                         var_list.append([get_variables_2dm[i], var_out[x][0], var_out[x][1], var_out[x][2], var_out[x][3]])
 
             return var_list, time_ind3, date_ind, size_type
-          
+
         def saveData(self, date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chunk_size, time, lat, lon, dir_data):
-            """build a NetCDF file for saving output variables 
+            """build a NetCDF file for saving output variables
             (T2M,U2M, V2M, U10M,V10M, PRECTOT, PRECTOTCORR,T2MDEW,QV2M,
             lat, lon, time)"""
-            
+
             # get the varable list and time indices
-            var_list, time_ind3, date_ind, size_type = self.varList(date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chunk_size, time, lat, lon, dir_data)                                    
+            var_list, time_ind3, date_ind, size_type = self.varList(date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chunk_size, time, lat, lon, dir_data)
 
-            #save nc file
+            # save nc file
             var_low = 0
             var_up = 0
-            
+
             for i in range(0, 1):
             # for i in range(0, len(size_type)):
                 var = size_type[i]
                 var_low = var_up
                 var_up = var_low + var
-    
-                #set up file path and names 
+
+                # set up file path and names
                 file_ncdf  = path.join(dir_data,("merra_sa" + "_" + (date_ind[var_low // len(time[0])]) + "_" + "to" + "_" +(date_ind[var_up // len(time[0]) - 1]) + ".nc"))
                 rootgrp = Dataset(file_ncdf, 'w', format='NETCDF4_CLASSIC')
-                
+
                 print("Saved File Type:", rootgrp.file_format)
-                
+
                 rootgrp.source      = 'Merra, abstrated meteorological variables from metadata at surface level'
                 rootgrp.featureType = "2_Dimension"
-            
-                #Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup 
+
+                # Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup
                 LAT = lat[0]
                 LON = lon[0]
-                
-                #dimensions
+
+                # dimensions
                 time  = rootgrp.createDimension('time', None)
                 lats  = rootgrp.createDimension('lats', len(LAT))
                 lons  = rootgrp.createDimension('lons', len(LON))
-                
-                #Output the results of extracted variables
+
+                # Output the results of extracted variables
                 for x in range(0, len(var_list)):
-                    out_var = rootgrp.createVariable(var_list[x][0], 'f4', ('time','lats','lons'), fill_value=9.9999999E14)       
+                    out_var = rootgrp.createVariable(var_list[x][0], 'f4', ('time','lats','lons'), fill_value=9.9999999E14)
                     out_var.standard_name = var_list[x][1]
                     out_var.long_name = var_list[x][2]
-                    out_var.units         = var_list[x][3] 
+                    out_var.units         = var_list[x][3]
                     out_var.missing_value = 9.9999999E14
-                    #out_var.fmissing_value = (9.9999999E14, 'f')
-                    #out_var.vmax = (9.9999999E14, 'f')
-                    #out_var.vmin = (-9.9999999E14, 'f')   
-                    out_var[:,:,:] = var_list[x][4][var_low:var_up,:,:]     #data generic name with data stored in it
-        
+                    # out_var.fmissing_value = (9.9999999E14, 'f')
+                    # out_var.vmax = (9.9999999E14, 'f')
+                    # out_var.vmin = (-9.9999999E14, 'f')
+                    out_var[:,:,:] = var_list[x][4][var_low:var_up,:,:]     # data generic name with data stored in it
+
                 Time  = rootgrp.createVariable('time', 'i4', ('time'))
                 Time.standard_name = "time"
                 # Time.units         = "hour since " + str(datetime.strptime(beg, '%Y/%m/%d'))
-                Time.units  = "hours since 1980-1-1 00:00:00" 
+                Time.units  = "hours since 1980-1-1 00:00:00"
                 Time.calendar      = "gregorian"
                 # pass the values
                 netCDFTime = []
                 for x in range(0, len(time_ind3)):
                     netCDFTime.append(nc.date2num(time_ind3[x], units = Time.units, calendar = Time.calendar))
-                Time[:] = netCDFTime[var_low:var_up]                                                                                                        
-    
+                Time[:] = netCDFTime[var_low:var_up]
+
                 Latitudes               = rootgrp.createVariable('latitude', 'f4',('lats'))
                 Latitudes.standard_name = "latitude"
                 Latitudes.units         = "degrees_north"
                 Latitudes.axis          = "Y"
                 Latitudes[:]  = [L for L in lat[0]]    # pass the values of latitude
-    
+
                 Longitudes               = rootgrp.createVariable('longitude', 'f4',('lons'))
                 Longitudes.standard_name = "longitude"
                 Longitudes.units         = "degrees_east"
                 Longitudes.axis          = "X"
                 Longitudes[:] = [L for L in lon[0]]   # pass the values of longitudes
-            
-            
+
+
                 #close the root group
-                rootgrp.close() 
-                                          
-class SaveNCDF_sf():                               
-    """ write output netCDF file for abstracted variables from original 2D 
+                rootgrp.close()
+
+
+class SaveNCDF_sf():
+    """ write output netCDF file for abstracted variables from original 2D
         radiation Diagnostics datasets  datasets
     """
     def varList(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv,
-               id_lat, id_lon, out_variable_2dr,out_variable_2ds, out_variable_2dv, 
+               id_lat, id_lon, out_variable_2dr,out_variable_2ds, out_variable_2dv,
                chunk_size, time, lat, lon, dir_data):
-        
-        """build the variables list for output netcdf file 
+
+        """build the variables list for output netcdf file
         (PRECTOT, PRECTOTCORR,T2MDEW,SWGDN, SWGDNCLR, LWRNT, LWGEM, LWGNTCLR, lat,lon,time)"""
         date_ind, time_ind1, time_ind2, time_ind3 = MERRAgeneric().getTime(date)
 
-        #Set up time_ind3 with the begin at year-mm-dd 00:30:00 
+        # Set up time_ind3 with the begin at year-mm-dd 00:30:00
         time_ind3 = time_ind3 + timedelta(minutes=30)
-        
-        #Setup size of saving file
+
+        # Setup size of saving file
         date_size = len(date_ind)
         hour_size = len(time[0])
         int_size = date_size // chunk_size
         res_type = (date_size * hour_size) % (chunk_size*hour_size)
-        
+
         if (res_type > 0):
             size_type = [chunk_size * hour_size] * int_size + [res_type]
         else:
-            size_type = [chunk_size * hour_size] * int_size           
+            size_type = [chunk_size * hour_size] * int_size
 
-        #Get the variables and Set up the list of output variables
+        # Get the variables and Set up the list of output variables
 
         prectot_total = []
         prectotcorr_total = []
@@ -1270,41 +1174,41 @@ def varList(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv,
         lwgem_total = []
         lwgntclr_total = []
 
-        var_out = {'PRECTOT':    ['total_precipitation',             
-                                  'total_surface_precipitation_flux',           
+        var_out = {'PRECTOT':    ['total_precipitation',
+                                  'total_surface_precipitation_flux',
                                   'kg/m2/s', prectot_total],
-                   'PRECTOTCORR':['total_precipitation_corrected',   
-                                  'total_surface_precipitation_flux_corrected', 
+                   'PRECTOTCORR':['total_precipitation_corrected',
+                                  'total_surface_precipitation_flux_corrected',
                                   'kg/m2/s', prectotcorr_total],
-                   'T2MDEW':     ['2-metre_dew_point_temperature',   
-                                  'dew_point_temperature_at_2m',                
+                   'T2MDEW':     ['2-metre_dew_point_temperature',
+                                  'dew_point_temperature_at_2m',
                                   'K',       t2mdew_total],
-                   'SWGDN':      ['surface_incoming_shortwave_flux', 
-                                  'surface_incoming_shortwave_flux',            
+                   'SWGDN':      ['surface_incoming_shortwave_flux',
+                                  'surface_incoming_shortwave_flux',
                                   'W/m2',    swgdn_total],
-                   'SWGDNCLR':   ['surface_incoming_shortwave_flux_assuming_clear_sky', 
-                                  'surface_incoming_shortwave_flux_assuming_clear_sky', 
+                   'SWGDNCLR':   ['surface_incoming_shortwave_flux_assuming_clear_sky',
+                                  'surface_incoming_shortwave_flux_assuming_clear_sky',
                                   'W/m2', swgdnclr_total],
-                   'LWGNT':      ['surface_net_downward_longwave_flux', 
-                                  'surface_net_downward_longwave_flux',      
+                   'LWGNT':      ['surface_net_downward_longwave_flux',
+                                  'surface_net_downward_longwave_flux',
                                   'W/m2', lwgnt_total],
-                   'LWGEM':      ['longwave_flux_emitted_from_surface', 
-                                  'longwave_flux_emitted_from_surface',      
+                   'LWGEM':      ['longwave_flux_emitted_from_surface',
+                                  'longwave_flux_emitted_from_surface',
                                   'W/m2', lwgem_total],
                    'LWGNTCLR':   ['surface_net_downward_longwave_flux_assuming_clear_sky',
-                                  'surface_net_downward_longwave_flux_assuming_clear_sky', 
+                                  'surface_net_downward_longwave_flux_assuming_clear_sky',
                                   'W/m2', lwgntclr_total]}
-                   
+
         var_list = []
         for i in range(0, len(get_variables_2ds[0:-3])):
             for x in var_out.keys():
                 if x == get_variables_2ds[i]:
                     print("------Get Subset of Variable at Surface Level------", get_variables_2ds[i])
-                    
+
                     # the position of PRECTOT in out_variable_2ds is 0
-                    var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2ds) 
-                    
-                    # restructure the shape 
+                    var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2ds)
+
+                    # restructure the shape
                     var_total = MERRAgeneric().restruDatastuff(var)
                     del var
                     var_out[x][3] = var_total
@@ -1316,60 +1220,64 @@ def varList(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv,
                 if x == get_variables_2dv[i]:
                     print("------Get Subset of Variable at Surface Level------", get_variables_2dv[i])
                     # the position of PRECTOT in out_variable_2ds is 0
-                    var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dv) 
-                    # restructing the shape 
+                    var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dv)
+                    # restructing the shape
                     var_total = MERRAgeneric().restruDatastuff(var)
                     del var
                     var_out[x][3] = var_total
                     del var_total
                     var_list.append([get_variables_2dv[i], var_out[x][0], var_out[x][1], var_out[x][2], var_out[x][3]])
-     
+
         for i in range(0, len(get_variables_2dr[0:-3])):
             for x in var_out.keys():
                 if x == get_variables_2dr[i]:
                     print("------Get Subset of Variable at Surface Level------", get_variables_2dr[i])
                     var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dr)
-                    
-                    # restructure the shape 
+
+                    # restructure the shape
                     var_total = MERRAgeneric().restruDatastuff(var)
                     del var
                     var_out[x][3] = var_total
-                    
+
                     if x == 'LWGNT':
                         lwgnt_total = var_total
                     elif x == 'LWGNTCLR':
                         lwgntclr_total = var_total
                     elif x == 'LWGEM':
                         lwgem_total = var_total
-                    
+
                     del var_total
-                    var_list.append([get_variables_2dr[i],var_out[x][0],var_out[x][1],var_out[x][2],var_out[x][3]])            
-      
+                    var_list.append([get_variables_2dr[i],
+                                     var_out[x][0],
+                                     var_out[x][1],
+                                     var_out[x][2],
+                                     var_out[x][3]])
+
         # Getting downwelling longwave radiation flux conversed by the function below :
         # - downwelling longwave flux in air =  Upwelling longwave flux from surface + surface net downward longwave flux
         # - downwelling longwave flux in air assuming clear sky =  Upwelling longwave flux from surface + surface net downward longwave flux assuming clear sky
-        
+
         lwgdn_total = lwgnt_total + lwgem_total
         lwgdnclr_total = lwgntclr_total + lwgem_total
-                    
-        #append LWGDN, LWGDNCLR     
+
+        #append LWGDN, LWGDNCLR
         var_list.append(['LWGDN', 'downwelling_longwave_flux_in_air','downwelling_longwave_flux_in_air','W/m2', lwgdn_total])
         var_list.append(['LWGDNCLR','downwelling_longwave_flux_in_air_assuming_clear_sky','downwelling_longwave_flux_in_air_assuming_clear_sky','W/m2', lwgdnclr_total])
-                    
+
         return var_list, time_ind3, date_ind, size_type
 
-    def saveData(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv, 
+    def saveData(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv,
                  id_lat, id_lon, out_variable_2dr, out_variable_2ds, out_variable_2dv,
                  chunk_size, time, lat, lon, dir_data):
         """Build a NetCDF file for saving output variables (SWGDN, SWGDNCLR,
            LWRNT, LWGEM, LWGNTCLR, LWGAB, LWGABCLR, lat, lon, time)
            """
-                    
-        # get variable list and time indices 
+
+        # get variable list and time indices
         var_list, time_ind3, date_ind, size_type = self.varList(
-                date, get_variables_2dr, get_variables_2ds, 
-                get_variables_2dv, id_lat, id_lon, out_variable_2dr, 
-                out_variable_2ds, out_variable_2dv, chunk_size, time, lat, 
+                date, get_variables_2dr, get_variables_2ds,
+                get_variables_2dv, id_lat, id_lon, out_variable_2dr,
+                out_variable_2ds, out_variable_2dv, chunk_size, time, lat,
                 lon, dir_data)
 
         var_low = 0
@@ -1386,13 +1294,13 @@ def saveData(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv
             fname = "merra_sf" + "_" + beg + "_" + "to" + "_" + end + ".nc"
             file_ncdf  = path.join(dir_data, fname)
             rootgrp = Dataset(file_ncdf, 'w', format='NETCDF4_CLASSIC')
-            
+
             print("Saved File Type:", rootgrp.file_format)
-            
+
             rootgrp.source      = 'Merra, abstrated radiation variables from metadata at surface level'
             rootgrp.featureType = "2_Dimension"
-        
-            #Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup 
+
+            #Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup
             LAT = lat[0]
             LON = lon[0]
 
@@ -1400,28 +1308,28 @@ def saveData(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv
             time  = rootgrp.createDimension('time', None)
             lats  = rootgrp.createDimension('lats', len(LAT))
             lons  = rootgrp.createDimension('lons', len(LON))
-        
+
             #Output the results of extracted variables
             for x in range(0, len(var_list)):
-                out_var = rootgrp.createVariable(var_list[x][0], 'f4', ('time', 'lats', 'lons'), fill_value=9.9999999E14)    
+                out_var = rootgrp.createVariable(var_list[x][0], 'f4', ('time', 'lats', 'lons'), fill_value=9.9999999E14)
                 out_var.standard_name = var_list[x][1]
                 out_var.long_name = var_list[x][2]
-                out_var.units         = var_list[x][3] 
+                out_var.units         = var_list[x][3]
                 out_var.missing_value = 9.9999999E14
-                #out_var.fmissing_value = (9.9999999E14, 'f')
-                #out_var.vmax = (9.9999999E14, 'f')
-                #out_var.vmin = (-9.9999999E14, 'f')   
-                out_var[:,:,:] = var_list[x][4][var_low:var_up,:,:]         
-                                
+                # out_var.fmissing_value = (9.9999999E14, 'f')
+                # out_var.vmax = (9.9999999E14, 'f')
+                # out_var.vmin = (-9.9999999E14, 'f')
+                out_var[:,:,:] = var_list[x][4][var_low:var_up,:,:]
+
             Time               = rootgrp.createVariable('time', 'i4', ('time'))
             Time.standard_name = "time"
-            Time.units  = "hours since 1980-1-1 00:30:00" 
+            Time.units  = "hours since 1980-1-1 00:30:00"
             Time.calendar      = "gregorian"
             # pass the values
             netCDFTime = []
             for x in range(0, len(time_ind3)):
                 netCDFTime.append(nc.date2num(time_ind3[x], units = Time.units, calendar = Time.calendar))
-            Time[:] = netCDFTime[var_low:var_up]                                                                                                        
+            Time[:] = netCDFTime[var_low:var_up]
 
             Latitudes               = rootgrp.createVariable('latitude', 'f4', ('lats'))
             Latitudes.standard_name = "latitude"
@@ -1434,237 +1342,203 @@ def saveData(self, date, get_variables_2dr, get_variables_2ds, get_variables_2dv
             Longitudes.units         = "degrees_east"
             Longitudes.axis          = 'X'
             Longitudes[:] = [L for L in lon[0]]                    # pass the values of longitudes
-        
+
             #close the root group
-            rootgrp.close()          
+            rootgrp.close()
+
 
-class SaveNCDF_sc():                                  
-        """ write output netCDF file for abstracted variables from original 2D 
+class SaveNCDF_sc():
+        """ write output netCDF file for abstracted variables from original 2D
             Constant Model Parameters
         """
-        def varList(self, get_variables_2dc, id_lat, id_lon, out_variable_2dc, 
+        def varList(self, get_variables_2dc, id_lat, id_lon, out_variable_2dc,
                     chunk_size, time, lat, lon, dir_data):
-            """Create a NetCDF file for saving output variables 
+            """Create a NetCDF file for saving output variables
             (Dataset object, also the root group)"""
-            
-            #Get the variables and set up the list for saving in netCDF file
+
+            # Get the variables and set up the list for saving in netCDF file
             phis_total = []
             frlake_total = []
             frland_total = []
             frlandice_total = []
             frocean_total = []
             sgh_total = []
-            
-            var_out = {'PHIS':['surface_geopotential_height', 
+
+            var_out = {'PHIS':['surface_geopotential_height',
                                'surface_geopotential_height','m2/s2', phis_total],
                        'FRLAKE':['fraction_of_lake',
                                  'fraction_of_lake','1',frlake_total],
                        'FRLAND':['fraction_of_land',
                                  'fraction_of_land', '1', frland_total],
-                       'FRLANDICE':['fraction_of_land_ice', 
+                       'FRLANDICE':['fraction_of_land_ice',
                                     'fraction_of_land_ice', '1', frlandice_total],
-                       'FROCEAN':['fraction_of_ocean', 
+                       'FROCEAN':['fraction_of_ocean',
                                   'fraction_of_ocean', '1',frocean_total],
-                       'SGH':['isotropic_stdv_of_GWD_topography', 
-                              'isotropic_stdv_of_GWD_topography', 'm', sgh_total]}            
-            
+                       'SGH':['isotropic_stdv_of_GWD_topography',
+                              'isotropic_stdv_of_GWD_topography', 'm', sgh_total]}
+
             var_list = []
             for i in range(0, len(get_variables_2dc[0:-3])):
                 for x in var_out.keys():
                     if x == get_variables_2dc[i]:
                         print("------Get Subset of Constant Model Parameters------", get_variables_2dc[i])
-                        
+
                         # the position of T2M, U2M, V2M, U10M, V10M in out_variable_2ds is the position in the get_variables
-                        var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dc)   
-                        
-                        # restructure the shape 
+                        var = MERRAgeneric().dataStuff_2d(i, id_lat, id_lon, out_variable_2dc)
+
+                        # restructure the shape
                         var_total = MERRAgeneric().restruDatastuff(var)
                         del var
                         var_out[x][3] = var_total
                         del var_total
                         var_list.append([get_variables_2dc[i], var_out[x][0], var_out[x][1], var_out[x][2], var_out[x][3]])
-            
-            return var_list 
-        
+
+            return var_list
+
         def saveData(self, get_variables_2dc, id_lat, id_lon, out_variable_2dc, chunk_size, time, lat, lon, dir_data):
             """create a NetCDF file for saving output variables (Dataset object, also the root group)"""
-            
+
             # get var_list
             var_list = self.varList(get_variables_2dc, id_lat, id_lon, out_variable_2dc, chunk_size, time, lat, lon, dir_data)
-              
-            #set up file path and names 
+
+            #set up file path and names
             file_ncdf  = path.join(dir_data,("merra_sc" + ".nc"))
             rootgrp = Dataset(file_ncdf, 'w', format='NETCDF4_CLASSIC')
             print("Saved File Type:", rootgrp.file_format)
             rootgrp.source      = 'Merra, abstrated constant model parameters'
             rootgrp.featureType = "2_Dimension"
 
-            #Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup 
-            
-            #dimensions
+            # Arrange the format of dimensions for time, levels, latitude and longitude for dimension setup
+
+            # dimensions
             time  = rootgrp.createDimension('time', None)
             lats  = rootgrp.createDimension('lats', len(lat[0]))
             lons  = rootgrp.createDimension('lons', len(lon[0]))
-            
-            #Output the results of extracted variables
+
+            # Output the results of extracted variables
             for x in range(0, len(var_list)):
-                out_var = rootgrp.createVariable(var_list[x][0], 'f4', 
-                                                 ('time','lats','lons'), 
-                                                 fill_value=9.9999999E14)       
+                out_var = rootgrp.createVariable(var_list[x][0], 'f4',
+                                                 ('time','lats','lons'),
+                                                 fill_value=9.9999999E14)
                 out_var.standard_name = var_list[x][1]
                 out_var.long_name = var_list[x][2]
-                out_var.units         = var_list[x][3] 
+                out_var.units         = var_list[x][3]
                 out_var.missing_value = 9.9999999E14
-                #out_var.fmissing_value = (9.9999999E14, 'f')
-                #out_var.vmax = (9.9999999E14, 'f')
-                #out_var.vmin = (-9.9999999E14, 'f')   
+                # out_var.fmissing_value = (9.9999999E14, 'f')
+                # out_var.vmax = (9.9999999E14, 'f')
+                # out_var.vmin = (-9.9999999E14, 'f')
                 out_var[:,:,:] = var_list[x][4][:,:,:] # data generic name with data stored in it
-    
+
             Time  = rootgrp.createVariable('time', 'i4', ('time'))
             Time.standard_name = "time"
-            Time.units  = "hours since 1992-01-02 03:00:00" 
+            Time.units  = "hours since 1992-01-02 03:00:00"
             Time.calendar      = "gregorian"
 
-            #Set up the value of time (one single value)
+            # Set up the value of time (one single value)
             time_ind4 = datetime.combine(
-                    datetime.strptime("1992-01-02", "%Y-%m-%d"), 
+                    datetime.strptime("1992-01-02", "%Y-%m-%d"),
                     datetime.strptime("0300","%H%M").time())
             time_ind4 = (pandas.date_range(time_ind4, time_ind4, freq = '1H'))
 
             # pass the values
             netCDFTime = []
             for x in range(0, len(time_ind4)):
-                 netCDFTime.append(nc.date2num(time_ind4[x], 
-                                               units = Time.units, 
+                 netCDFTime.append(nc.date2num(time_ind4[x],
+                                               units = Time.units,
                                                calendar = Time.calendar))
-            Time[:] = netCDFTime[:]                                                                                                        
-                                                                                                  
-            Latitudes               = rootgrp.createVariable('latitude', 
+            Time[:] = netCDFTime[:]
+
+            Latitudes               = rootgrp.createVariable('latitude',
                                                              'f4', ('lats'))
             Latitudes.standard_name = "latitude"
             Latitudes.units         = "degrees_north"
             Latitudes.axis          = "Y"
             Latitudes[:]  = [L for L in lat[0]]  # pass the values of latitude
 
-            Longitudes               = rootgrp.createVariable('longitude', 
+            Longitudes               = rootgrp.createVariable('longitude',
                                                               'f4', ('lons'))
             Longitudes.standard_name = "longitude"
             Longitudes.units         = "degrees_east"
             Longitudes.axis          = "X"
             Longitudes[:] = [L for L in lon[0]] # pass the values of longitudes
-               
-            #close the root group
-            rootgrp.close()          
-    
+
+            # close the root group
+            rootgrp.close()
+
 
 """
-Referenced from era_interim.py (Dr.Stephan Gruber): Class ERAdownload() 
+Referenced from era_interim.py (Dr.Stephan Gruber): Class ERAdownload()
 
-Class for accessing the parameter file for downloading Merra-2 specified 
-variables,latitude and longitude coordinates, start, end date, minimum and 
+Class for accessing the parameter file for downloading Merra-2 specified
+variables,latitude and longitude coordinates, start, end date, minimum and
 maximum elevations.
 
 Args:
     pfile: Full path to a Globsim Download Parameter file.
-    
 
-"""   
-class MERRAdownload(object):
+
+"""
+# TODO: update functions of checking available files
+class MERRAdownload(GenericDownload):
     """
-    Class for MERRA-2 data that has methods for querying NASA GES DISC server, 
+    Class for MERRA-2 data that has methods for querying NASA GES DISC server,
     and returning all variables usually wanted.
     """
     def __init__(self, pfile):
-        # read parameter file
-        self.pfile = pfile
-        par = ParameterIO(self.pfile)
-        
-        # assign bounding box
-        self.area  = {'north':  par.bbN,
-                      'south':  par.bbS,
-                      'west' :  par.bbW,
-                      'east' :  par.bbE}
-        
-        # sanity check to make sure area is good
-        if (par.bbN < par.bbS) or (par.bbE < par.bbW):
-            raise Exception("Bounding box is invalid: {}".format(self.area))
-            
-        if (np.abs(par.bbN-par.bbS) < 1.5) or (np.abs(par.bbE-par.bbW) < 1.5):
-            raise Exception("Download area is too small to conduct interpolation.")
-                  
+        super().__init__(pfile)
+        par = self.par
+
+        self._set_data_directory("merra2")
+
+
         # time bounds
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end}
-
-        # elevation
-        self.elevation = {'min' : par.ele_min, 
-                          'max' : par.ele_max}
-        
-        # data directory for MERRA-2  
-        self.directory = path.join(par.project_directory, "merra2")  
-        if path.isdir(self.directory) == False:
-            makedirs(self.directory)   
-        
-        # credential 
-        self.credential = path.join(par.credentials_directory, ".merrarc")
+        self.date  = {'beg': datetime.strptime(par['beg'], '%Y/%m/%d'),#get_begin_date(par, 'merra2', ["merra_pl*", "merra_sa*", "merra_sf*"]),
+                      'end': datetime.strptime(par['end'], '%Y/%m/%d')}
+
+        # credential
+        self.credential = path.join(par['credentials_directory'], ".merrarc")
         self.account = open(self.credential, "r")
         self.inf = self.account.readlines()
         # pass the first line to username  (covert list to str)
-        self.username = ''.join(self.inf[0].split())                                      
+        self.username = ''.join(self.inf[0].split())
          # pass the second line to passworrd (covert list to str)
-        self.password = ''.join(self.inf[1].split())                                    
-        
-        # variables
-        self.variables = par.variables
-            
-        # chunk size for downloading and storing data [days]        
-        self.chunk_size = par.chunk_size
-        
-        # the diretory for storing downloaded data
-        self.dir_data = self.directory
-
-        #build full dictionary between variable names from input parameter 
-        #file and original merra2 data products
+        self.password = ''.join(self.inf[1].split())
+
+        # chunk size for downloading and storing data [days]
+        self.chunk_size = par['chunk_size']
+
+        # build full dictionary between variable names from input parameter
+        # file and original merra2 data products
         self.full_variables_dic = {
-                'air_temperature': [
-                        'air_temperature', 
-                        '2-meter_air_temperature' ],
-                'relative_humidity' : [
-                        'relative_humidity',
-                        '2-metre_dewpoint_temperature',
-                        '2-metre_specific_humidity' ],
-                'precipitation_amount': [
-                        'total_precipitation',
-                        'total_precipitation_corrected' ],
-                'wind_from_direction': [
-                        'eastward_wind',
-                        'northward_wind',
-                        '2-meter_eastward_wind',
-                        '2-meter_northward_wind', 
-                        '10-meter_eastward_wind', 
-                        '10-meter_northward_wind' ],
-                'wind_speed':[
-                        'eastward_wind',
-                        'northward_wind',
-                        '2-meter_eastward_wind',
-                        '2-meter_northward_wind', 
-                        '10-meter_eastward_wind', 
-                        '10-meter_northward_wind' ],
-                'downwelling_shortwave_flux_in_air': [
-                        'surface_incoming_shortwave_flux' ],
-                'downwelling_shortwave_flux_in_air_assuming_clear_sky': [
-                        'surface_incoming_shortwave_flux_assuming_clear_sky' ],
-                'downwelling_longwave_flux_in_air': [
-                        'surface_net_downward_longwave_flux',
-                        'longwave_flux_emitted_from_surface' ],
-                'downwelling_longwave_flux_in_air_assuming_clear_sky':[
-                        'surface_net_downward_longwave_flux_assuming_clear_sky',
-                        'longwave_flux_emitted_from_surface' ]
-                }
-        
-        # build variables Standards Names and referenced Names for downloading 
+                'air_temperature': ['air_temperature',
+                                    '2-meter_air_temperature'],
+                'relative_humidity' : ['relative_humidity',
+                                       '2-metre_dewpoint_temperature',
+                                       '2-metre_specific_humidity'],
+                'precipitation_amount': ['total_precipitation',
+                                         'total_precipitation_corrected'],
+                'wind_from_direction': ['eastward_wind',
+                                        'northward_wind',
+                                        '2-meter_eastward_wind',
+                                        '2-meter_northward_wind',
+                                        '10-meter_eastward_wind',
+                                        '10-meter_northward_wind'],
+                'wind_speed':['eastward_wind',
+                              'northward_wind',
+                              '2-meter_eastward_wind',
+                              '2-meter_northward_wind',
+                              '10-meter_eastward_wind',
+                              '10-meter_northward_wind'],
+                'downwelling_shortwave_flux_in_air': ['surface_incoming_shortwave_flux'],
+                'downwelling_shortwave_flux_in_air_assuming_clear_sky': ['surface_incoming_shortwave_flux_assuming_clear_sky'],
+                'downwelling_longwave_flux_in_air': ['surface_net_downward_longwave_flux',
+                                                     'longwave_flux_emitted_from_surface'],
+                'downwelling_longwave_flux_in_air_assuming_clear_sky':['surface_net_downward_longwave_flux_assuming_clear_sky',
+                                                                       'longwave_flux_emitted_from_surface']}
+
+        # build variables Standards Names and referenced Names for downloading
         # from orginal MERRA-2 datasets
-        # 3D Analyzed Meteorological fields data 
+        # 3D Analyzed Meteorological fields data
         self.full_variables_pl_ana = {'geopotential_height':'H',
                                       'air_temperature':'T',
                                       'eastward_wind':'U',
@@ -1674,7 +1548,7 @@ def __init__(self, pfile):
         # 2D Single-Level Diagnostics data (instantaneous)
         self.full_variables_sm = {'2-meter_air_temperature': 'T2M',
                                   '2-meter_eastward_wind': 'U2M',
-                                  '2-meter_northward_wind':'V2M', 
+                                  '2-meter_northward_wind':'V2M',
                                   '10-meter_eastward_wind':'U10M',
                                   '10-meter_northward_wind':'V10M',
                                   '2-metre_specific_humidity':'QV2M'}
@@ -1683,38 +1557,40 @@ def __init__(self, pfile):
                                   'total_precipitation_corrected': 'PRECTOTCORR'}
         # 2D single-level diagnostics (time-averageed)
         self.full_variables_sv = {'2-metre_dewpoint_temperature': 'T2MDEW'}
-        # 2D radiation diagnostics data                
+        # 2D radiation diagnostics data
         self.full_variables_sr = {'surface_incoming_shortwave_flux': 'SWGDN',
                                   'surface_incoming_shortwave_flux_assuming_clear_sky': 'SWGDNCLR',
                                   'surface_net_downward_longwave_flux':'LWGNT',
                                   'longwave_flux_emitted_from_surface': 'LWGEM',
                                   'surface_net_downward_longwave_flux_assuming_clear_sky': 'LWGNTCLR'}
 
+
+
     def getVariables(self, full_variables_dic, full_variables_type):
         """
-        build the major variable list for retrieving between lists from 
+        build the major variable list for retrieving between lists from
         download parameter file and the product
-        """       
+        """
         get_variables = []
 
         for i in range(0, len(self.variables)):
             for var in full_variables_dic.keys():
                 if  var == self.variables[i]:
                     var_names = full_variables_dic[var]
-                    #  Set up the variables list for accassing original type 
+                    #  Set up the variables list for accassing original type
                     # of MERRA-2 datasets (3D and 2D)
                     for j in range(0, len(var_names)):
                         for var1 in full_variables_type.keys():
                             if var1 == var_names[j]:
                                 get_variables.append(full_variables_type[var1])
         # set the list
-        get_variables = list(set(get_variables))                                                                   
+        get_variables = list(set(get_variables))
 
         #add the basic variables into the list
         if 'relative_humidity' in list(full_variables_type.keys()) :
             get_variables.extend(['lat','lon','lev','time'])
         elif 'air_temperature' in list(full_variables_type.keys()):
-            # !ADD Geopotential Height in the first element of downloading 
+            # !ADD Geopotential Height in the first element of downloading
             # list. Must be the first one
             get_variables.insert(0,'H')
             # add the variables names of latitude, longitude, levels and time
@@ -1722,440 +1598,300 @@ def getVariables(self, full_variables_dic, full_variables_type):
         else:
             # add the variables names of latitude, longitude and time
             get_variables.extend(['lat','lon','time'])
-        
+
         return get_variables
-        
+
     def retrieve(self):
         """
-        Retrive all required MERRA-2 data from NASA Goddard Earth Sciences Data 
+        Retrive all required MERRA-2 data from NASA Goddard Earth Sciences Data
         and Information Services Center
-        """                   
-        
-        # Get merra-2 3d meteorological analysis variables at pressure levels 
+        """
+
+        # Get merra-2 3d meteorological analysis variables at pressure levels
         t_start = tc.time()
-                
+
         #Chunk size for spliting files and download [days], Format:Integer
         chunk_size = int(self.chunk_size)
-                
+
         # download all variables by looping over the date with chunk_size
         startDay = self.date['beg']
         endDay   = self.date['end']
-        
+
         # Get merra-2 2d Constant Model Parameters (outside of time & date looping!)
         print('''========== Get Variables From MERRA2 2d Time-Invariant ==========''')
         print('''========== Single-level, Constant Model Parameters ==========''')
 
         self.download_merra_to()
-        
+
         print("----------- Result NO.0: Completed -----------")
-        
+
         # Begin downloading time-dependent variables
         x = 0
         for dt in rrule(DAILY, dtstart = startDay, until = endDay):
             currentDay = (str(dt.strftime("%Y")) + "/" + str(dt.strftime("%m")) + "/" + str(dt.strftime("%d")))
             x += 1
-            if (x == 1):                                     
-                
+            if (x == 1):
+
                 self.date['beg'] = currentDay
-                
+
                 print('DOWNLOADING BEGINS ON:', self.date['beg'])
-                
-                #convert date['beg'] from string back to datetime object
-                self.date['beg'] = datetime.strptime(self.date['beg'],'%Y/%m/%d')  
-        
-            if (x == chunk_size or dt == endDay):   
-                
+
+                # convert date['beg'] from string back to datetime object
+                self.date['beg'] = datetime.strptime(self.date['beg'],'%Y/%m/%d')
+
+            if (x == chunk_size or dt == endDay):
+
                 x = 0
                 self.date['end'] = currentDay
-                
+
                 print('DOWNLOADING ENDS ON: {}'.format(self.date['end']))
-                   
-                #convert date['beg'] from string back to datetime object
+
+                # convert date['beg'] from string back to datetime object
                 self.date['end'] = datetime.strptime(self.date['end'],'%Y/%m/%d')
 
                 # get the urls for all types of data products
                 urls_3dmana, urls_3dmasm, urls_2dm, urls_2ds, urls_2dr, url_2dc, urls_2dv = MERRAgeneric().getURLs(self.date)
-          
+
                 # Get merra-2 3d meteorological assimilated variables at pressure levels
                 print('''========== Get Variables From Merra-2 3d ==========
                          ========== 3-hourly, Pressure-Level, Assimilated Meteorological Fields ==========''')
-                
+
                 # get the shared variables dictionaries and pass the information to the build-in dictionaries
                 get_variables_3dmasm = self.getVariables(self.full_variables_dic,self.full_variables_pl_asm)
-                
+
                 print(get_variables_3dmasm)
-                                    
+
                 ds_asm = MERRAgeneric().download(self.username, self.password, urls_3dmasm)
-                
+
                 id_lat, id_lon =  MERRAgeneric().getArea(self.area, ds_asm)
-                  
+
                 id_lev = MERRAgeneric().getPressureLevels(self.elevation)
 
                 out_variable_3dmasm = MERRAgeneric().Variables(get_variables_3dmasm, ds_asm)
-                                                                                      
-                #get merra-2 meteorological varaibles at pressure levels
+
+                # get merra-2 meteorological varaibles at pressure levels
                 print('''========== Get Variables From Merra-2 3d ==========
                          ========== 6-hourly, Pressure-Level, Analyzed Meteorological Fields ==========''')
-                
+
                 # get the shared variables dictionaries and pass the information to the build-in dictionaries
                 get_variables_3dmana = self.getVariables(self.full_variables_dic, self.full_variables_pl_ana)
                 print(get_variables_3dmana)
-                
+
                 ds_ana = MERRAgeneric().download(self.username, self.password, urls_3dmana)
-                 
+
                 id_lat, id_lon =  MERRAgeneric().getArea(self.area, ds_ana)
-                                      
+
                 out_variable_3dmana = MERRAgeneric().Variables(get_variables_3dmana, ds_ana)
-                
+
                 # print(out_variable_3dmana)
                 lat, lon, lev, time = MERRAgeneric().latLon_3d(out_variable_3dmana, id_lat, id_lon, id_lev)
-                  
+
                 # Output merra-2 meteorological analysis variable at pressure levels
-                #For T, V, U, H
-                SaveNCDF_pl_3dm().saveData(self.date, get_variables_3dmasm, get_variables_3dmana, id_lat, id_lon, id_lev, 
-                                           out_variable_3dmasm, out_variable_3dmana, chunk_size, time, lev, lat, lon, self.dir_data, self.elevation)
-                                        
+                # For T, V, U, H
+                SaveNCDF_pl_3dm().saveData(self.date, get_variables_3dmasm, get_variables_3dmana, id_lat, id_lon, id_lev,
+                                           out_variable_3dmasm, out_variable_3dmana, chunk_size, time, lev, lat, lon, self.directory, self.elevation)
+
                 print("----------- Result NO.1: Completed -----------")
-    
+
                 # Get merra-2 2d meteorological Diagnostics variables at surface level
                 print('''========== Get Variables From Merra-2 ==========
                       ========== 2d, 1-hourly, Single-level, Meteorological Diagnostics ==========''')
-                
+
                 # get the shared variables dictionaries and pass the information to the build-in dictionaries
                 get_variables_2dm = self.getVariables(self.full_variables_dic, self.full_variables_sm)
 
                 print(get_variables_2dm)
-                                    
+
                 ds_2dm = MERRAgeneric().download(self.username, self.password, urls_2dm)
-                
+
                 out_variable_2dm = MERRAgeneric().Variables(get_variables_2dm, ds_2dm)
-                
+
                 lat, lon, time = MERRAgeneric().latLon_2d(out_variable_2dm, id_lat, id_lon)
-                                                                        
-                # Output merra-2 variable at surface level 
-                SaveNCDF_sa().saveData(self.date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chunk_size, time, lat, lon, self.dir_data)
-                
+
+                # Output merra-2 variable at surface level
+                SaveNCDF_sa().saveData(self.date, get_variables_2dm, id_lat, id_lon, out_variable_2dm, chunk_size, time, lat, lon, self.directory)
+
                 print("----------- Result NO.2: Completed -----------")
-    
+
                 # Get merra-2 2d surface flux Diagnostics variables at surface level
                 print('''========== Get Variables From Merra-2 2d ==========
                       ========== 1-hourly, Single-level, Surface Flux Diagnostics ==========''')
-                
+
                 # get the shared variables dictionaries and pass the information to the build-in dictionaries
                 get_variables_2ds = self.getVariables(self.full_variables_dic, self.full_variables_sf)
 
                 print(get_variables_2ds)
-                                    
+
                 ds_2ds = MERRAgeneric().download(self.username, self.password, urls_2ds)
-                
-                out_variable_2ds = MERRAgeneric().Variables(get_variables_2ds, ds_2ds)          
-                
+
+                out_variable_2ds = MERRAgeneric().Variables(get_variables_2ds, ds_2ds)
+
                 print('''========== Get Variables From Merra-2 2d ==========
                       ========== 1-hourly, Single-Level, Assimilation,Single-Level Diagnostics ==========''')
-                
+
                 # get the shared variables dictionaries and pass the information to the build-in dictionaries
                 get_variables_2dv = self.getVariables(self.full_variables_dic, self.full_variables_sv)
-                
+
                 print(get_variables_2dv)
-                                    
+
                 ds_2dv = MERRAgeneric().download(self.username, self.password, urls_2dv)
-                
+
                 out_variable_2dv = MERRAgeneric().Variables(get_variables_2dv, ds_2dv)
 
                 # Get merra-2 2d radiation variables
-                print('''========== Get Variables From Merra-2 2d ========== 
+                print('''========== Get Variables From Merra-2 2d ==========
                       ========== 1-Hourly, Single-Level, Radiation Diagnostics ==========''')
-                
+
                 # get the shared variables dictionaries and pass the information to the build-in dictionaries
                 get_variables_2dr = self.getVariables(self.full_variables_dic, self.full_variables_sr)
-                
+
                 print(get_variables_2dr)
-                
+
                 ds_2dr = MERRAgeneric().download(self.username, self.password, urls_2dr)
-                
+
                 out_variable_2dr = MERRAgeneric().Variables(get_variables_2dr, ds_2dr)
-                
+
                 lat, lon, time = MERRAgeneric().latLon_2d(out_variable_2dr, id_lat, id_lon)
 
-                #Output merra-2 radiation variables 
-                SaveNCDF_sf().saveData(self.date, get_variables_2dr, 
+                # Output merra-2 radiation variables
+                SaveNCDF_sf().saveData(self.date, get_variables_2dr,
                            get_variables_2ds, get_variables_2dv,
-                           id_lat, id_lon, out_variable_2dr, 
-                           out_variable_2ds, out_variable_2dv, 
-                           chunk_size, time, lat, lon, self.dir_data)
-                
+                           id_lat, id_lon, out_variable_2dr,
+                           out_variable_2ds, out_variable_2dv,
+                           chunk_size, time, lat, lon, self.directory)
+
                 print("----------- Result NO.3: Completed -----------")
-       
-   
+
+
         t_end = tc.time()
         t_total = int((t_end - t_start) // 60)
         print("Total Time (Minutes):", t_total)
-        
+
     def download_merra_to(self):
             # check if it already exists
             if path.isfile(path.join(self.directory, ("merra_to" + ".nc"))):
                 print("WARNING:  file 'merra_to.nc' already exists and is being skipped")
                 return()
-    
-            urls_3dmana, urls_3dmasm, urls_2dm, urls_2ds, urls_2dr, url_2dc, urls_2dv = MERRAgeneric().getURLs(self.date)    
-            
+
+            urls_3dmana, urls_3dmasm, urls_2dm, urls_2ds, urls_2dr, url_2dc, urls_2dv = MERRAgeneric().getURLs(self.date)
+
             # get the shared variables dictionaries and pass the information to the build-in dictionaries
             get_variables_2dc = ['PHIS','FRLAKE','FRLAND','FRLANDICE',
                                  'FROCEAN','SGH','lat','lon','time']
-            
+
             print(get_variables_2dc)
-                    
-            ds_2dc = MERRAgeneric().download(self.username, self.password, 
+
+            ds_2dc = MERRAgeneric().download(self.username, self.password,
                                  url_2dc)
-            
-            out_variable_2dc = MERRAgeneric().Variables(get_variables_2dc, 
+
+            out_variable_2dc = MERRAgeneric().Variables(get_variables_2dc,
                                            ds_2dc)
-            
+
             id_lat, id_lon =  MERRAgeneric().getArea(self.area, ds_2dc)
-            
-            lat, lon, time = MERRAgeneric().latLon_2d(out_variable_2dc, 
+
+            lat, lon, time = MERRAgeneric().latLon_2d(out_variable_2dc,
                                          id_lat, id_lon)
-                         
-            # Output merra-2 variable at surface level 
-            SaveNCDF_sc().saveData(get_variables_2dc, id_lat, id_lon, 
-                       out_variable_2dc, int(self.chunk_size), 
-                       time, lat, lon, self.dir_data)
-                      
-class MERRAinterpolate(object):
+
+            # Output merra-2 variable at surface level
+            SaveNCDF_sc().saveData(get_variables_2dc, id_lat, id_lon,
+                       out_variable_2dc, int(self.chunk_size),
+                       time, lat, lon, self.directory)
+
+
+class MERRAinterpolate(GenericInterpolate):
     """
-    Algorithms to interpolate MERRA-2 netCDF files to station coordinates. 
-    All variables retains their original units and time-steps. 
-    
-    Referenced from era_interim.py (Dr.Stephan Gruber): Class ERAinterpolate()     
+    Algorithms to interpolate MERRA-2 netCDF files to station coordinates.
+    All variables retains their original units and time-steps.
+
+    Referenced from era_interim.py (Dr.Stephan Gruber): Class ERAinterpolate()
     """
 
     def __init__(self, ifile):
-        #read parameter file
-        self.ifile = ifile
-        par = ParameterIO(self.ifile)
-        self.dir_inp = path.join(par.project_directory,'merra2')
-        self.dir_out = self.makeOutDir(par)
-        self.variables = par.variables
-        self.list_name = par.station_list.split(path.extsep)[0]
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-        
-        # time bounds, add one day to par.end to include entire last day
-        self.date  = {'beg' : par.beg,
-                      'end' : par.end + timedelta(days=1)}
-        
-        # chunk size: how many time steps to interpolate at the same time?
-        # A small chunk size keeps memory usage down but is slow.
-        self.cs  = int(par.chunk_size)
-    
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirIntp = path.join(par.project_directory, 'interpolated')
-        
-        if not (path.isdir(dirIntp)):
-            makedirs(dirIntp)
-            
-        return dirIntp
-                                    
-                                    
-    def MERRA2interp2D(self, ncfile_in, ncf_in, points, tmask_chunk,
-                       variables=None, date=None):    
-        """
-        Biliner interpolation from fields on regular grid (latitude, longitude) 
-        to individual point stations (latitude, longitude). This works for
-        surface and for pressure level files (all MERRA-2 files).
-          
-        Args:
-            ncfile_in: Full path to an MERRA-2 derived netCDF file. This can
-                        contain wildcards to point to multiple files if temporal
-                        chunking was used.
-              
-            ncf_in: A netCDF4.MFDataset derived from reading in MERRA-2 multiple 
-                    files (def MERRA2station_append())
-            
-            points: A dictionary of locations. See method StationListRead in
-                    generic.py for more details.
-        
-            variables:  List of variable(s) to interpolate such as 
-                        ['T','RH','U','V',' T2M', 'U2M', 'V2M', 'U10M', 'V10M', 
-                        'PRECTOT', 'SWGDN','SWGDNCLR','LWGDN', 'LWGDNCLR'].
-                        Defaults to using all variables available.
-        
-            date: Directory to specify begin and end time for the derived time 
-                  series. Defaluts to using all times available in ncfile_in.
-              
-        Example:
-            from datetime import datetime
-            date  = {'beg' : datetime(2008, 1, 1),
-                      'end' : datetime(2008,12,31)}
-            variables  = ['T','U', 'V']       
-            stations = StationListRead("points.csv")      
-            MERRA2station('merra_sa.nc', 'merra_sa_inter.nc', stations, 
-                        variables=variables, date=date)        
-        """   
+        super().__init__(ifile)
+        par = self.par
+
+        self.dir_raw = path.join(par['project_directory'],'merra2')
 
-        # is it a file with pressure levels?
-        pl = 'level' in ncf_in.dimensions.keys()
 
-        # get spatial dimensions
-        lat  = ncf_in.variables['latitude'][:]
-        lon  = ncf_in.variables['longitude'][:]
-        if pl: # only for pressure level files
-            lev  = ncf_in.variables['level'][:]
-            nlev = len(lev)
-              
-        # test if time steps to interpolate remain
-        nt = sum(tmask_chunk)
-        if nt == 0:
-            raise ValueError('No time steps from netCDF file selected.')
-    
-        # get variables
-        varlist = [str_encode(x)for x in ncf_in.variables.keys()]
+    # TODO: CHECK
+    @staticmethod
+    def remove_select_variables(varlist, pl, ens = False):
+        # OVERRIDING parent method for MERRA2 extra variables
         varlist.remove('time')
         varlist.remove('latitude')
         varlist.remove('longitude')
         if pl: #only for pressure level files
             varlist.remove('level')
-            
-        # remove extra variables from merra2 
-        varlist = MERRAgeneric().MERRA_skip(varlist)  
-    
-        #list variables that should be interpolated
-        if variables is None:
-            variables = varlist
-        #test is variables given are available in file
-        if (set(variables) < set(varlist) == 0):
-            raise ValueError('One or more variables not in netCDF file.')
-       
-        # Create source grid from a SCRIP formatted file. As ESMF needs one
-        # file rather than an MFDataset, give first file in directory.
-        flist = np.sort(filter(listdir(self.dir_inp), 
-                               path.basename(ncfile_in)))
-        ncsingle = path.join(self.dir_inp, flist[0])
-        sgrid = ESMF.Grid(filename=ncsingle, filetype=ESMF.FileFormat.GRIDSPEC)
-
-        # create source field on source grid
-        if pl: #only for pressure level files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt, nlev])
-        else: # 2D files
-            sfield = ESMF.Field(sgrid, name='sgrid',
-                                staggerloc=ESMF.StaggerLoc.CENTER,
-                                ndbounds=[len(variables), nt])
-
-        # assign data from ncdf: (variable, time, latitude, longitude) 
-        for n, var in enumerate(variables):
-            if pl: # only for pressure level files
-                if ESMFnew:
-                    sfield.data[:,:,n,:,:] = ncf_in.variables[var][tmask_chunk,:,:,:].transpose((3,2,0,1)) 
-                else:
-                    sfield.data[n,:,:,:,:] = ncf_in.variables[var][tmask_chunk,:,:,:].transpose((0,1,3,2)) 
-            else:
-                if ESMFnew:
-                    sfield.data[:,:,n,:] = ncf_in.variables[var][tmask_chunk,:,:].transpose((2,1,0))
-                else:
-                    sfield.data[n,:,:,:] = ncf_in.variables[var][tmask_chunk,:,:].transpose((0,2,1))
-
-        # create locstream, CANNOT have third dimension!!!
-        locstream = ESMF.LocStream(len(self.stations), coord_sys=ESMF.CoordSys.SPH_DEG)
-        locstream["ESMF:Lon"] = list(self.stations['longitude_dd'])
-        locstream["ESMF:Lat"] = list(self.stations['latitude_dd'])
 
-        # create destination field
-        if pl: # only for pressure level files
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt, nlev])
-        else:
-            dfield = ESMF.Field(locstream, name='dfield', 
-                                ndbounds=[len(variables), nt])    
-
-        # regridding function, consider ESMF.UnmappedAction.ERROR
-        regrid2D = ESMF.Regrid(sfield, dfield,
-                                regrid_method=ESMF.RegridMethod.BILINEAR,
-                                unmapped_action=ESMF.UnmappedAction.IGNORE,
-                                dst_mask_values=None)
-                  
-        # regrid operation, create destination field (variables, times, points)
-        dfield = regrid2D(sfield, dfield)        
-        sfield.destroy() #free memory                  
-        
-        return dfield, variables
+        # remove extra variables from merra2
+        MERRAgeneric().MERRA_skip(varlist)
 
     def MERRA2station(self, ncfile_in, ncfile_out, points,
-                             variables = None, date = None):
+                      variables = None, date = None):
         """
         Given the type of variables to interpoalted from MERRA2 downloaded diretory
-        Create the empty of netCDF file to hold the interpolated results, by calling 
+        Create the empty of netCDF file to hold the interpolated results, by calling
         MERRAgeneric().netCDF_empty
         Get the interpolated results from MERRA2station
         Append all variables into the empty netCDF file
         Close all files
-        
+
         Args:
         ncfile_in: Full path to an MERRA-2 derived netCDF file. This can
                     contain wildcards to point to multiple files if temporal
                     chunking was used.
-            
-        ncfile_out: Full path to the output netCDF file to write.     
-        
+
+        ncfile_out: Full path to the output netCDF file to write.
+
         points: A dictionary of locations. See method StationListRead in
-                generic.py for more details.
-    
-        variables:  List of variable(s) to interpolate such as 
-                    ['T','RH','U','V',' T2M', 'U2M', 'V2M', 'U10M', 'V10M', 
+                common_utils.py for more details.
+
+        variables:  List of variable(s) to interpolate such as
+                    ['T','RH','U','V',' T2M', 'U2M', 'V2M', 'U10M', 'V10M',
                     'PRECTOT', 'SWGDN','SWGDNCLR','LWGDN', 'LWGDNCLR'].
                     Defaults to using all variables available.
-    
-        date: Directory to specify begin and end time for the derived time 
+
+        date: Directory to specify begin and end time for the derived time
                 series. Defaluts to using all times available in ncfile_in.
-  
+
         """
-        
+
         # read in one type of mutiple netcdf files
         ncf_in = nc.MFDataset(ncfile_in, 'r', aggdim ='time')
-        
+
         # is it a file with pressure levels?
         pl = 'level' in ncf_in.dimensions.keys()
 
         # build the output of empty netCDF file
-        MERRAgeneric().netCDF_empty(ncfile_out, self.stations, ncf_in) 
-                                     
+        MERRAgeneric().netCDF_empty(ncfile_out, self.stations, ncf_in)
+
         # open the output netCDF file, set it to be appendable ('a')
         ncf_out = nc.Dataset(ncfile_out, 'a')
 
         # get time and convert to datetime object
         nctime = ncf_in.variables['time'][:]
         #"hours since 1980-01-01 00:00:00"
-        t_unit = "hours since 1980-01-01 00:00:00"#ncf_in.variables['time'].units 
+        t_unit = "hours since 1980-01-01 00:00:00"#ncf_in.variables['time'].units
         try :
             t_cal = ncf_in.variables['time'].calendar
         except AttributeError : # Attribute doesn't exist
             t_cal = u"gregorian" # or standard
         time = [nc.num2date(timei, units = t_unit, calendar = t_cal) for timei in nctime]
         time = np.asarray(time)
-                                                                                    
+
         # detect invariant files (topography etc.)
-        if len(time) ==1:
-            invariant=True
-        else:
-            invariant=False                                                                         
-        
+        invariant = True if len(time) == 1 else False
+
         # restrict to date/time range if given
         if date is None:
             tmask = time < datetime(3000, 1, 1)
         else:
             tmask = (time < date['end']) * (time >= date['beg'])
-        
+
         if not any(tmask):
-            sys.exit('''\n ERROR: No downloaded data exist within date range specified by interpolation control file. 
+            sys.exit('''\n ERROR: No downloaded data exist within date range specified by interpolation control file.
                      Download new data or change 'beg' / 'end' in interpolation control file''')
-            
+
         # get time indices
         time_in = nctime[tmask]
 
@@ -2170,8 +1906,8 @@ def MERRA2station(self, ncfile_in, ncfile_out, points,
             beg = n * self.cs
             #restrict last chunk to lenght of tmask plus one (to get last time)
             end = min(n*self.cs + self.cs, len(time_in))-1
-            
-            #time to make tmask for chunk 
+
+            #time to make tmask for chunk
             beg_time = nc.num2date(time_in[beg], units = t_unit, calendar = t_cal)
             if invariant:
                 # allow topography to work in same code, len(nctime) = 1
@@ -2179,62 +1915,53 @@ def MERRA2station(self, ncfile_in, ncfile_out, points,
                 #end = 1
             else:
                 end_time = nc.num2date(time_in[end], units=t_unit, calendar=t_cal)
-            
-            # !! CAN'T HAVE '<= end_time', would damage appeding 
+
+            # !! CAN'T HAVE '<= end_time', would damage appeding
             tmask_chunk = (time <= end_time) * (time >= beg_time)
             if invariant:
                 # allow topography to work in same code
                 tmask_chunk = [True]
-           
+
             # get the interpolated variables
-            dfield, variables = self.MERRA2interp2D(ncfile_in, ncf_in, 
-                                                    self.stations, tmask_chunk,
-                                                    variables=None, date=None) 
+            dfield, variables = self.interp2D(ncfile_in, ncf_in,
+                                              self.stations, tmask_chunk,
+                                              variables=None, date=None)
 
             # append time
-            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:], 
+            ncf_out.variables['time'][:] = np.append(ncf_out.variables['time'][:],
                                                      time_in[beg:end+1])
             #append variables
             for i, var in enumerate(variables):
                 if variables_skip(var):
                     continue
-                                          
+
                 # extra treatment for pressure level files
                 if pl:
                     lev = ncf_in.variables['level'][:]
-                    
-                    if ESMFnew:
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:]
-                    else:
-                        # dimension: time, level, latitude, longitude
-                        ncf_out.variables[var][beg:end+1,:,:] = dfield.data[i,:,:,:]      
+                    ncf_out.variables[var][beg:end+1,:,:] = dfield.data[:,i,:,:]
                 else:
-                    if ESMFnew:
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:]
-                    else:
-                        # time, latitude, longitude
-                        ncf_out.variables[var][beg:end+1,:] = dfield.data[i,:,:]
-                                     
+                    ncf_out.variables[var][beg:end+1,:] = dfield.data[:,i,:]
+
         #close the file
         ncf_in.close()
-        ncf_out.close()         
-        #close read-in and read-out files====================================                  
-        
-    def levels2elevation(self, ncfile_in, ncfile_out):    
+        ncf_out.close()
+        #close read-in and read-out files====================================
+
+    def levels2elevation(self, ncfile_in, ncfile_out):
         """
         Linear 1D interpolation of pressure level data available for individual
-        stations to station elevation. Where and when stations are below the 
-        lowest pressure level, they are assigned the value of the lowest 
+        stations to station elevation. Where and when stations are below the
+        lowest pressure level, they are assigned the value of the lowest
         pressure level.
-        
+
         """
-        # open file 
-        
+        # open file
+
         ncf = nc.MFDataset(ncfile_in, 'r', aggdim='time')
         height = ncf.variables['height'][:]
         nt = len(ncf.variables['time'][:])
         nl = len(ncf.variables['level'][:])
-        
+
         # list variables
         varlist = [str_encode(x) for x in ncf.variables.keys()]
         for V in ['time', 'station', 'latitude', 'longitude', 'level', 'height', 'H']:
@@ -2246,83 +1973,61 @@ def levels2elevation(self, ncfile_in, ncfile_out):
         #            others: ...(time, station)
         # stations are integer numbers
         # create a file (Dataset object, also the root group).
-        rootgrp = nc.Dataset(ncfile_out, 'w', format='NETCDF4')
-        rootgrp.Conventions = 'CF-1.6'
-        rootgrp.source      = 'MERRA-2, interpolated (bi)linearly to stations'
-        rootgrp.featureType = "timeSeries"
-
-        # dimensions
-        station = rootgrp.createDimension('station', len(height))
-        time    = rootgrp.createDimension('time', nt)
-
-        # base variables
-        time           = rootgrp.createVariable('time',     'i4',('time'))
-        time.long_name = 'time'
-        time.units     = 'hours since 1980-01-01 00:00:00'
-        time.calendar  = 'gregorian'
-        station             = rootgrp.createVariable('station',  'i4',('station'))
-        station.long_name   = 'station for time series data'
-        station.units       = '1'
-        latitude            = rootgrp.createVariable('latitude', 'f4',('station'))
-        latitude.long_name  = 'latitude'
-        latitude.units      = 'degrees_north'    
-        longitude           = rootgrp.createVariable('longitude','f4',('station'))
-        longitude.long_name = 'longitude'
-        longitude.units     = 'degrees_east'  
-        height           = rootgrp.createVariable('height','f4',('station'))
-        height.long_name = 'height_above_reference_ellipsoid'
-        height.units     = 'm'  
-        
+        rootgrp = netcdf_base(ncf, ncfile_out, len(height), nt,
+                              'hours since 1980-01-01 00:00:00')
+        rootgrp.source  = 'MERRA-2, interpolated (bi)linearly to stations'
+
+        time = rootgrp["time"]
+        station = rootgrp["station"]
+        latitude = rootgrp["latitude"]
+        longitude = rootgrp["longitude"]
+        height = rootgrp["height"]
+
         # assign base variables
-        time[:] = ncf.variables['time'][:]
+        time[:]      = ncf.variables['time'][:]
         station[:]   = ncf.variables['station'][:]
         latitude[:]  = ncf.variables['latitude'][:]
         longitude[:] = ncf.variables['longitude'][:]
         height[:]    = ncf.variables['height'][:]
-        
+
         # create and assign variables from input file
         for var in varlist:
-            tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
+            tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))
             tmp.long_name = str_encode(ncf.variables[var].long_name)
             tmp.units     = str_encode(ncf.variables[var].units)
-        
+
         # add air pressure as new variable
         var = 'air_pressure'
         varlist.append(var)
-        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))    
+        tmp   = rootgrp.createVariable(var,'f4',('time', 'station'))
         tmp.long_name = str_encode(var)
-        tmp.units     = str_encode('hPa')            
+        tmp.units     = str_encode('hPa')
         # end file prepation ==================================================
-                                                                                             
+
         # loop over stations
-        for n, h in enumerate(height): 
+        for n, h in enumerate(height):
             # geopotential unit: height [m]
             # shape: (time, level)
-            ele = ncf.variables['H'][:,:,n]
-            # TODO: check if height of stations in data range (+50m at top, 
+            elevation = ncf.variables['H'][:,:,n]
+            # TODO: check if height of stations in data range (+50m at top,
             # lapse r.)
-            
-            # difference in elevation. 
+
+            # difference in elevation.
             # level directly above will be >= 0
-            dele = -(ele - h)
-            # vector of level indices that fall directly above station. 
+            elev_diff = -(elevation - h)
+            # vector of level indices that fall directly above station.
             # Apply after ravel() of data.
-            va = np.argmin(dele + (dele < 0) * 100000, axis=1) 
+            va = np.argmin(elev_diff + (elev_diff < 0) * 100000, axis=1)
             # mask for situations where station is below lowest level
             mask = va < (nl-1)
-            va += np.arange(ele.shape[0]) * ele.shape[1]
-            
+            va += np.arange(elevation.shape[0]) * elevation.shape[1]
+
             # Vector level indices that fall directly below station.
             # Apply after ravel() of data.
             vb = va + mask # +1 when OK, +0 when below lowest level
-            
-            # weights
-            wa = np.absolute(dele.ravel()[vb]) 
-            wb = np.absolute(dele.ravel()[va])
-            wt = wa + wb
-            wa /= wt # Apply after ravel() of data.
-            wb /= wt # Apply after ravel() of data.
-            
+
+            wa, wb = self.calculate_weights(elev_diff, va, vb)
+
             #loop over variables and apply interpolation weights
             for v, var in enumerate(varlist):
                 if var == 'air_pressure':
@@ -2338,135 +2043,114 @@ def levels2elevation(self, ncfile_in, ncfile_out):
                         if value == level_highest:
                             ipol[j] = level_lowest
                     #---------------------------------------------------------
-                else:    
+                else:
                     #read data from netCDF
                     data = ncf.variables[var][:,:,n].ravel()
-                    ipol = data[va]*wa + data[vb]*wb   # interpolated value                    
-                rootgrp.variables[var][:,n] = ipol # assign to file   
-    
+                    ipol = data[va]*wa + data[vb]*wb   # interpolated value
+                rootgrp.variables[var][:,n] = ipol # assign to file
+
         rootgrp.close()
         ncf.close()
-        # closed file ==========================================================    
-
-    def TranslateCF2short(self, dpar):
-        """
-        Map CF Standard Names into short codes used in MERRA2 netCDF files.
-        """
-        varlist = [] 
-        for var in self.variables:
-            varlist.append(dpar.get(var))
-        # drop none
-        varlist = [item for item in varlist if item is not None]      
-        # flatten
-        varlist = [item for sublist in varlist for item in sublist]         
-        return(varlist) 
+        # closed file ==========================================================
 
     def process(self):
         """
-        Interpolate point time series from downloaded data. Provides access to 
+        Interpolate point time series from downloaded data. Provides access to
         the more generically MERRA-like interpolation functions.
-        """                       
+        """
 
-        # 2D Interpolation for Constant Model Parameters    
+        # 2D Interpolation for Constant Model Parameters
         # dictionary to translate CF Standard Names into MERRA
-        # pressure level variable keys.            
+        # pressure level variable keys.
         dummy_date  = {'beg' : datetime(1992, 1, 2, 3, 0),
                        'end' : datetime(1992, 1, 2, 4, 0)}
-        
-        if not path.isdir(self.dir_out):
-            makedirs(self.dir_out) 
 
-        # === 2D Interpolation for Surface Analysis Data ===    
+        if not path.isdir(self.dir_inp):
+            makedirs(self.dir_inp)
+
+        # === 2D Interpolation for Surface Analysis Data ===
         # dictionary to translate CF Standard Names into MERRA2
-        # pressure level variable keys. 
-        dpar = {'air_temperature'   : ['T2M'],  # [K] 2m values                                                            
-                'wind_speed' : ['U2M', 'V2M', 'U10M','V10M'],   # [m s-1] 2m & 10m values 
+        # pressure level variable keys.
+        dpar = {'air_temperature'   : ['T2M'],  # [K] 2m values
+                'wind_speed' : ['U2M', 'V2M', 'U10M','V10M'],   # [m s-1] 2m & 10m values
                 'relative_humidity' : ['QV2M']} # 2m value
-        varlist = self.TranslateCF2short(dpar)                      
-        self.MERRA2station(path.join(self.dir_inp,'merra_sa_*.nc'), 
-                           path.join(self.dir_out,'merra2_sa_' + 
+        varlist = self.TranslateCF2short(dpar)
+        self.MERRA2station(path.join(self.dir_raw,'merra_sa_*.nc'),
+                           path.join(self.dir_inp,'merra2_sa_' +
                                      self.list_name + '.nc'), self.stations,
-                                     varlist, date = self.date)          
-        
-        # 2D Interpolation for Single-level Radiation Diagnostics Data 'SWGDN', 
-        # 'LWGDN', 'SWGDNCLR'. 'LWGDNCLR' 
+                                     varlist, date = self.date)
+
+        # 2D Interpolation for Single-level Radiation Diagnostics Data 'SWGDN',
+        # 'LWGDN', 'SWGDNCLR'. 'LWGDNCLR'
         # dictionary to translate CF Standard Names into MERRA2
-        # pressure level variable keys.       
+        # pressure level variable keys.
         dpar = {'air_temperature'   : ['T2MDEW'],  # [K] 2m values
-                'precipitation_amount' : ['PRECTOT','PRECTOTCORR'], # [kg/m2/s] total precipitation 
+                'precipitation_amount' : ['PRECTOT','PRECTOTCORR'], # [kg/m2/s] total precipitation
                 'downwelling_shortwave_flux_in_air' : ['SWGDN'], # [W/m2] short-wave downward
                 'downwelling_longwave_flux_in_air'  : ['LWGDN'], # [W/m2] long-wave downward
                 'downwelling_shortwave_flux_in_air_assuming_clear_sky': ['SWGDNCLR'], # [W/m2] short-wave downward assuming clear sky
                 'downwelling_longwave_flux_in_air_assuming_clear_sky': ['LWGDNCLR']} # [W/m2] long-wave downward assuming clear sky
-        varlist = self.TranslateCF2short(dpar)                           
-        self.MERRA2station(path.join(self.dir_inp,'merra_sf_*.nc'), 
-                           path.join(self.dir_out,'merra2_sf_' + 
+        varlist = self.TranslateCF2short(dpar)
+        self.MERRA2station(path.join(self.dir_raw,'merra_sf_*.nc'),
+                           path.join(self.dir_inp,'merra2_sf_' +
                                     self.list_name + '.nc'), self.stations,
-                                    varlist, date = self.date)          
-                        
+                                    varlist, date = self.date)
+
         # NEED ADD 'H' in it!
         # === 2D Interpolation for Pressure-Level, Analyzed Meteorological DATA ===
         # dictionary to translate CF Standard Names into MERRA2
-        # pressure level variable keys. 
+        # pressure level variable keys.
         dpar = {'air_temperature'   : ['T'],           # [K]
                 'wind_speed'        : ['U', 'V'],      # [m s-1]
                 'relative_humidity' : ['RH']}          # [1]
         varlist = self.TranslateCF2short(dpar).append('H')
-        self.MERRA2station(path.join(self.dir_inp,'merra_pl_*.nc'), 
-                           path.join(self.dir_out,'merra2_pl_' + 
+        self.MERRA2station(path.join(self.dir_raw,'merra_pl_*.nc'),
+                           path.join(self.dir_inp,'merra2_pl_' +
                                     self.list_name + '.nc'), self.stations,
-                                    varlist, date = self.date)  
-                                                                                                                                                                       
-        # 1D Interpolation for Pressure Level Analyzed Meteorological Data 
-        self.levels2elevation(path.join(self.dir_out,'merra2_pl_' + 
-                                        self.list_name + '.nc'), 
-                              path.join(self.dir_out,'merra2_pl_' + 
+                                    varlist, date = self.date)
+
+        # 1D Interpolation for Pressure Level Analyzed Meteorological Data
+        self.levels2elevation(path.join(self.dir_inp,'merra2_pl_' +
+                                        self.list_name + '.nc'),
+                              path.join(self.dir_inp,'merra2_pl_' +
                                         self.list_name + '_surface.nc'))
 
-      
-class MERRAscale(object):
+
+class MERRAscale(GenericScale):
     """
     Class for MERRA data that has methods for scaling station data to
     better resemble near-surface fluxes.
-    
+
     Processing kernels have names in UPPER CASE.
-       
+
     Args:
-        sfile: Full path to a Globsim Scaling Parameter file. 
-              
-    Example:          
-        MERRAd = MERRAscale(sfile) 
+        sfile: Full path to a Globsim Scaling Parameter file.
+
+    Example:
+        MERRAd = MERRAscale(sfile)
         MERRAd.process()
     """
-        
+    NAME = "MERRA-2"
+
     def __init__(self, sfile):
-        # read parameter file
-        self.sfile = sfile
-        par = ParameterIO(self.sfile)
-        self.intpdir = path.join(par.project_directory, 'interpolated')
-        self.scdir = self.makeOutDir(par)
-        self.list_name = par.station_list.split(path.extsep)[0]
-        
-        # read kernels
-        self.kernels = par.kernels
-        if not isinstance(self.kernels, list):
-            self.kernels = [self.kernels]
-            
+        super().__init__(sfile)
+        par = self.par
+
         # input file names
         self.nc_pl = nc.Dataset(path.join(self.intpdir,
-                                          'merra2_pl_' + 
+                                          'merra2_pl_' +
                                 self.list_name + '_surface.nc'), 'r')
         self.nc_sa = nc.Dataset(path.join(self.intpdir,
-                                          'merra2_sa_' + 
+                                          'merra2_sa_' +
                                 self.list_name + '.nc'), 'r')
         self.nc_sf = nc.Dataset(path.join(self.intpdir,
-                                          'merra2_sf_' + 
+                                          'merra2_sf_' +
                                 self.list_name + '.nc'), 'r')
         #self.nc_sc = nc.Dataset(path.join(self.intpdir,
-        #                                  'merra2_to_' + 
+        #                                  'merra2_to_' +
         #                        self.list_name + '.nc'), 'r')
         self.nstation = len(self.nc_pl.variables['station'][:])
-                              
+
         # check if output file exists and remove if overwrite parameter is set
         self.output_file = self.getOutNCF(par, 'merra2')
 
@@ -2476,340 +2160,313 @@ def __init__(self, sfile):
         self.t_unit = self.nc_pl.variables['time'].units #"hours since 1980-01-01 00:00:00"
         self.t_cal  = self.nc_pl.variables['time'].calendar
         time = nc.num2date(nctime, units = self.t_unit, calendar = self.t_cal)
-        
+
         # interpolation scale factor
-        self.time_step = par.time_step * 3600    # [s] scaled file
+        self.time_step = par['time_step'] * 3600    # [s] scaled file
         interval_in = (time[1]-time[0]).seconds #interval in seconds
         self.interpN = floor(interval_in/self.time_step)
-        
+
         #number of time steps for output
-        self.nt = int(floor((max(time) - min(time)).total_seconds() 
-                      / 3600 / par.time_step))+1 # +1 : include last value
-        self.time_step = par.time_step * 3600    # [s] scaled file
-        
+        self.nt = int(floor((max(time) - min(time)).total_seconds()
+                      / 3600 / par['time_step']))+1 # +1 : include last value
+        self.time_step = par['time_step'] * 3600    # [s] scaled file
+
         # vector of output time steps as datetime object
         # 'seconds since 1980-01-01 00:00:00'
         mt = min(time)
-        self.times_out = [mt + timedelta(seconds = (x*self.time_step)) 
-                          for x in range(0, self.nt)]                                                                   
+        self.times_out = [mt + timedelta(seconds = (x*self.time_step))
+                          for x in range(0, self.nt)]
 
         # vector of output time steps as written in ncdf file
         self.scaled_t_units = 'seconds since 1980-01-01 00:00:00'
-        self.times_out_nc = nc.date2num(self.times_out, 
-                                        units = self.scaled_t_units, 
-                                        calendar = self.t_cal) 
-
-        # get the station file
-        self.stations_csv = path.join(par.project_directory,
-                                      'par', par.station_list)
-        #read station points 
-        self.stations = StationListRead(self.stations_csv)  
-                                                                                
-        
+        self.times_out_nc = nc.date2num(self.times_out,
+                                        units = self.scaled_t_units,
+                                        calendar = self.t_cal)
+
     def process(self):
         """
-        Run all relevant processes and save data. Each kernel processes one 
+        Run all relevant processes and save data. Each kernel processes one
         variable and adds it to the netCDF file.
         """
-        
+
         if not path.isdir(path.dirname(self.output_file)):
             makedirs(path.dirname(self.outfile))
-        
-        self.rg = ScaledFileOpen(self.output_file, 
-                                 self.nc_pl, 
-                                 self.times_out_nc, 
+
+        self.rg = new_scaled_netcdf(self.output_file,
+                                 self.nc_pl,
+                                 self.times_out_nc,
                                  t_unit = self.scaled_t_units)
-        
+
         # add station names to netcdf
         # first convert to character array
         names_out = nc.stringtochar(np.array(self.stations['station_name'], 'S32'))
-        
+
         # create space in the netcdf
-        nchar        = self.rg.createDimension('name_strlen', 32) 
-        st           = self.rg.createVariable('station_name', "S1", 
+        nchar        = self.rg.createDimension('name_strlen', 32)
+        st           = self.rg.createVariable('station_name', "S1",
                                               ('station', 'name_strlen'))
         st.standard_name = 'platform_name'
         st.units     = ''
-        
+
         # add data
         st[:] = names_out
-        
+
         # iterate through kernels and start process
         for kernel_name in self.kernels:
             if hasattr(self, kernel_name):
                 print(kernel_name)
                 getattr(self, kernel_name)()
-            
-        # self.conv_geotop()    
-            
-        # close netCDF files   
+
+        # self.conv_geotop()
+
+        # close netCDF files
         self.rg.close()
         self.nc_pl.close()
         self.nc_sf.close()
         self.nc_sa.close()
         #self.nc_sc.close()
-        
-    def getOutNCF(self, par, src, scaleDir = 'scale'):
-        '''make out file name'''
-        
-        timestep = str(par.time_step) + 'h'
-        src = '_'.join(['scaled', src, timestep])
-        src = src + '.nc'
-        fname = path.join(self.scdir, src)
-        
-        return fname
-
-    
-    def makeOutDir(self, par):
-        '''make directory to hold outputs'''
-        
-        dirSC = path.join(par.project_directory, 'scaled')
-        
-        if not (path.isdir(dirSC)):
-            makedirs(dirSC)
-            
-        return dirSC
-    
+
     def PRESS_Pa_pl(self):
         """
         Surface air pressure from pressure levels.
-        """        
+        """
         # add variable to ncdf file
-        vn = 'PRESS_MERRA2_Pa_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
+        vn = 'PRESS_pl' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
         var.long_name = 'air_pressure MERRA-2 pressure levels only'
         var.units     = 'Pa'
         var.standard_name = 'surface_air_pressure'
-        
+
         # interpolate station by station
-        time_in = self.nc_pl.variables['time'][:].astype(np.int64)  
-        values  = self.nc_pl.variables['air_pressure'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            #scale from hPa to Pa 
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                        time_in*3600, values[:, n]) * 100          
+        time_in = self.nc_pl.variables['time'][:].astype(np.int64)
+        values  = self.nc_pl.variables['air_pressure'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            #scale from hPa to Pa
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                        time_in*3600, values[:, n]) * 100
 
     def AIRT_C_pl(self):
         """
         Air temperature derived from pressure levels, exclusively.
-        """        
-        vn = 'AIRT_MERRA2_C_pl' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time','station'))    
-        var.long_name = 'air_temperature MERRA2 pressure levels only'
+        """
+        vn = 'AIRT_pl' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time','station'))
+        var.long_name = 'air_temperature {} pressure levels only'.format(self.NAME)
         var.units     = 'degrees_C'
         var.standard_name = 'air_temperature'
-        
+
         # interpolate station by station
         time_in = self.nc_pl.variables['time'][:].astype(np.int64)
-        values  = self.nc_pl.variables['T'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                            time_in*3600, values[:, n]-273.15)            
-
+        values  = self.nc_pl.variables['T'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                            time_in*3600, values[:, n]-273.15)
 
     def AIRT_C_sur(self):
         """
         Air temperature derived from surface data, exclusively.
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'AIRT_MERRA2_C_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '2_metre_temperature MERRA2 surface only'
+        vn = 'AIRT_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '2_metre_temperature {} surface only'.format(self.NAME)
         var.units     = 'degrees_C'
         var.standard_name = 'air_temperature'
-        
+
         # interpolate station by station
         time_in = self.nc_sa.variables['time'][:].astype(np.int64)
-        values  = self.nc_sa.variables['T2M'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                                    time_in*3600, 
-                                                    values[:, n]-273.15)            
+        values  = self.nc_sa.variables['T2M'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                                    time_in*3600,
+                                                    values[:, n]-273.15)
 
     def RH_per_sur(self):
         """
         Relative Humdity derived from surface data, exclusively.Clipped to
         range [0.1,99.9]. Kernel AIRT_MERRA_C_sur must be run before.
-        """   
-        
+        """
+
         # temporary variable,  interpolate station by station
         time_in = self.nc_pl.variables['time'][:].astype(np.int64)
         values = self.nc_pl.variables['RH'][:]
-                                                             
+
         # add variable to ncdf file
-        vn = 'RH_MERRA2_per_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Relative humidity MERRA2 surface only'
+        vn = 'RH_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Relative humidity {} surface only'.format(self.NAME)
         var.units     = 'percent'
         var.standard_name = 'relative_humidity'
-        
+
         # simple: https://en.wikipedia.org/wiki/Dew_point
-        #RH = 100-5 * (self.rg.variables['AIRT_MERRA2_C_sur'][:, :]-dewp[:, :])
+        #RH = 100-5 * (self.rg.variables['AIRT_sur'][:, :]-dewp[:, :])
         for n, s in enumerate(self.rg.variables['station'][:].tolist()):
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
                                             time_in*3600, values[:, n])
-                                                    
+
     def WIND_sur(self):
         """
-        Wind speed and direction at 10 metre derived from surface data, 
+        Wind speed and direction at 10 metre derived from surface data,
         exclusively.
-        """   
-        
+        """
+
         # temporary variable, interpolate station by station
-        U = np.zeros((self.nt, self.nstation), dtype=np.float32)        
+        U = np.zeros((self.nt, self.nstation), dtype=np.float32)
         time_in = self.nc_sa.variables['time'][:].astype(np.int64)
-        values  = self.nc_sa.variables['U10M'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            U[:, n] = series_interpolate(self.times_out_nc, 
-                                         time_in*3600, values[:, n]) 
+        values  = self.nc_sa.variables['U10M'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            U[:, n] = series_interpolate(self.times_out_nc,
+                                         time_in*3600, values[:, n])
 
         # temporary variable, interpolate station by station
-        V = np.zeros((self.nt, self.nstation), dtype=np.float32)        
+        V = np.zeros((self.nt, self.nstation), dtype=np.float32)
         time_in = self.nc_sa.variables['time'][:].astype(np.int64)
-        values  = self.nc_sa.variables['V10M'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            V[:, n] = series_interpolate(self.times_out_nc, 
+        values  = self.nc_sa.variables['V10M'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            V[:, n] = series_interpolate(self.times_out_nc,
                                          time_in*3600, values[:, n])
-                                          
+
         # add variable to ncdf file
-        vn = 'WSPD_MERRA2_ms_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 metre wind speed MERRA-2 surface only'
-        var.units     = 'm s-1' 
+        vn = 'WSPD_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '10 metre wind speed {} surface only'.format(self.NAME)
+        var.units     = 'm s-1'
         var.standard_name = 'wind_speed'
- 
+
         # add variable to ncdf file
-        vn = 'WDIR_MERRA2_deg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = '10 metre wind direction MERRA-2 surface only'
-        var.units     = 'degree' 
+        vn = 'WDIR_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = '10 metre wind direction {} surface only'.format(self.NAME)
+        var.units     = 'degree'
         var.standard_name = 'wind_from_direction'
-        
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
+
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
             WS = np.sqrt(np.power(V,2) + np.power(U,2))
-            WD = [atan2(V[i, n], U[i, n])*(180/pi) + 
+            WD = [atan2(V[i, n], U[i, n])*(180/pi) +
                   180 for i in np.arange(V.shape[0])]
-            self.rg.variables['WSPD_MERRA2_ms_sur'][:, n] = WS
-            self.rg.variables['WDIR_MERRA2_deg_sur'][:,n] = WD
-        
+
+            self.rg.variables['WSPD_sur'][:, n] = WS
+            self.rg.variables['WDIR_sur'][:,n] = WD
+
 
     def SW_Wm2_sur(self):
         """
         solar radiation downwards derived from surface data, exclusively.
-        """   
-    
+        """
+
         # add variable to ncdf file
-        vn = 'SW_MERRA2_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface solar radiation downwards MERRA-2 surface only'
+        vn = 'SW_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface solar radiation downwards {} surface only'.format(self.NAME)
         var.units     = 'W m-2'
         var.standard_name = 'surface_downwelling_shortwave_flux'
 
         # interpolate station by station
-        time_in = self.nc_sf.variables['time'][:].astype(np.int64)  
-        values  = self.nc_sf.variables['SWGDN'][:]                                
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                          time_in*3600, values[:, n]) 
+        time_in = self.nc_sf.variables['time'][:].astype(np.int64)
+        values  = self.nc_sf.variables['SWGDN'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                          time_in*3600, values[:, n])
 
     def LW_Wm2_sur(self):
         """
         Long-wave radiation downwards derived from surface data, exclusively.
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'LW_MERRA2_Wm2_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Surface thermal radiation downwards MERRA-2 surface only'
+        vn = 'LW_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Surface thermal radiation downwards {} surface only'.format(self.NAME)
         var.units     = 'W m-2'
         var.standard_name = 'surface_downwelling_longwave_flux'
 
         # interpolate station by station
         time_in = self.nc_sf.variables['time'][:].astype(np.int64)
-        values  = self.nc_sf.variables['LWGDN'][:]                                
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                          time_in*3600, values[:, n]) 
+        values  = self.nc_sf.variables['LWGDN'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                          time_in*3600, values[:, n])
 
     def PREC_mm_sur(self):
         """
         Precipitation derived from surface data, exclusively.
         Convert units: kg/m2/s to mm/time_step (hours)
         1 kg/m2 = 1mm
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'PREC_MERRA2_mm_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Total precipitation MERRA2 surface only'
-        var.units     = str_encode('mm')  
-        
+        vn = 'PREC_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Total precipitation {} surface only'.format(self.NAME)
+        var.units     = str_encode('mm')
+        var.standard_name = 'precipitation_amount'
+
         # interpolate station by station
         time_in = self.nc_sf.variables['time'][:].astype(np.int64)
         values  = self.nc_sf.variables['PRECTOT'][:] # mm in 1 second
         for n, s in enumerate(self.rg.variables['station'][:].tolist()):
             f = interp1d(time_in*3600, values[:, n], kind='linear')
-            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step        
+            self.rg.variables[vn][:, n] = f(self.times_out_nc)*self.time_step
 
     def PRECCORR_mm_sur(self):
         """
         Corrected Precipitation derived from surface data, exclusively.
         Convert units: kg/m2/s to mm/time_step (hours)
         1 kg/m2 = 1mm
-        """   
-        
+        """
+
         # add variable to ncdf file
-        vn = 'PRECCORR_MERRA2_mm_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Corrected Total precipitation MERRA2 surface only'
-        var.units     = str_encode('mm')  
-        
+        vn = 'PRECCORR_sur'  # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Corrected Total precipitation {} surface only'.format(self.NAME)
+        var.units     = str_encode('mm')
+        var.standard_name = 'precipitation_amount'
+
         # interpolate station by station
         time_in = self.nc_sf.variables['time'][:].astype(np.int64)
         values  = self.nc_sf.variables['PRECTOTCORR'][:]
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()): 
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                          time_in*3600, values[:, n]) * self.time_step            
-
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                          time_in*3600, values[:, n]) * self.time_step
 
     def SH_kgkg_sur(self):
         '''
         Specific humidity [kg/kg] derived from surface data, exclusively.
         '''
-                
+
         # add variable to ncdf file
-        vn = 'SH_MERRA2_kgkg_sur' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Specific humidity MERRA-2 surface only'
+        vn = 'SH_sur' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Specific humidity {} surface only'.format(self.NAME)
         var.units     = '1'
         var.standard_name = 'specific_humidity'
 
         # interpolate station by station
         time_in = self.nc_sa.variables['time'][:].astype(np.int64)
-        values  = self.nc_sa.variables['QV2M'][:]                   
-        for n, s in enumerate(self.rg.variables['station'][:].tolist()):  
-            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc, 
-                                          time_in*3600, values[:, n])            
-  
+        values  = self.nc_sa.variables['QV2M'][:]
+        for n, s in enumerate(self.rg.variables['station'][:].tolist()):
+            self.rg.variables[vn][:, n] = series_interpolate(self.times_out_nc,
+                                          time_in*3600, values[:, n])
+
     def LW_Wm2_topo(self):
         """
         Long-wave radiation downwards [W/m2]
         https://www.geosci-model-dev.net/7/387/2014/gmd-7-387-2014.pdf
-        """             
+        """
         # get sky view, and interpolate in time
         N = np.asarray(list(self.stations['sky_view'][:]))
 
         # add variable to ncdf file
-        vn = 'LW_MERRA2_Wm2_topo' # variable name
-        var           = self.rg.createVariable(vn,'f4',('time', 'station'))    
-        var.long_name = 'Incoming long-wave radiation MERRA-2 surface only'
+        vn = 'LW_topo' # variable name
+        var           = self.rg.createVariable(vn,'f4',('time', 'station'))
+        var.long_name = 'Incoming long-wave radiation {} surface only'.format(self.NAME)
         var.units     = 'W m-2'
         var.standard_name = 'surface_downwelling_longwave_flux'
 
-        # compute                            
-        for i in range(0, len(self.rg.variables['RH_MERRA2_per_sur'][:])):
+        # compute
+        for i in range(0, len(self.rg.variables['RH_sur'][:])):
             for n, s in enumerate(self.rg.variables['station'][:].tolist()):
-                LW = LW_downward(self.rg.variables['RH_MERRA2_per_sur'][i, n],
-                     self.rg.variables['AIRT_MERRA2_C_sur'][i, n]+273.15, N[n])
+                LW = LW_downward(self.rg.variables['RH_sur'][i, n],
+                     self.rg.variables['AIRT_sur'][i, n]+273.15, N[n])
                 self.rg.variables[vn][i, n] = LW
diff --git a/globsim/meteorology.py b/globsim/meteorology.py
new file mode 100644
index 0000000..272754e
--- /dev/null
+++ b/globsim/meteorology.py
@@ -0,0 +1,102 @@
+import numpy as np
+
+
+def satvapp_kPa_fT(T):
+    '''
+    Saturation water vapour pressure [kPa] following the Tetens formula, Eq 4.2
+    in Stull, Practical Meteorology.
+
+    T: Temperature [C]
+    '''
+    e0 = 0.6113  # [kPa]
+    b = 17.2694  # fitting constant
+    T1 = 273.15  # [K]
+    T2 = 35.86   # [K]
+    T += T1
+    return e0 * np.exp((b * (T - T1)) / (T - T2))
+
+
+def vapp_kPa_fTd(Td):
+    '''
+    Water vapour pressure [hPa] derived from dewpoint temperature [C]. Taken
+    from www.eol.ucar.edu/projects/ceop/dm/documents/refdata_report/eqns.html
+    where it is attributed to (Bolton 1980)
+    https://doi.org/10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2
+
+    Td: Dew point temperature [C]
+
+    '''
+    # (Bolton 1980)
+    # https://doi.org/10.1175/1520-0493(1980)108<1046:TCOEPT>2.0.CO;2
+    return 6.112 * np.exp((17.67 * Td) / (Td + 243.5))
+
+    # https://www.weather.gov/media/epz/wxcalc/vaporPressure.pdf
+    # return 6.112 * np.power(10,(7.5 * Td)/(237.3 + Td))
+
+
+def spec_hum_kgkg(Td, Pr):
+    '''
+    Specific humidity [Kg/Kg]. Eq 4.7 in Stull, Practical Meteorology.
+    Td: Dewpoint temperature [C]
+    Pr:  Air pressure [Pa]
+    '''
+    E = 0.622  # density of vater vapour / density of dry air
+    e = vapp_kPa_fTd(Td) / 10
+    P = Pr / 1000.  # from Pa to kPa
+    spec_hum = E * e / (P - e * (1 - E))
+    return spec_hum
+
+
+def water_vap_pressure(RH,T):
+    '''
+    water vapour pressure [unit:1], Eq C9,C10 in Fiddes and Gruber (2014)
+    RH: relative humidity (%)
+    Tair: air temperature (kelvin)
+    '''
+    es0 = 6.11  # reference saturation vapour pressure at 0ºC
+    T0 = 273.15  # Kelvin
+    lv = 2.5 * 1000000  # latent heat of vaporization of water
+    Rv = 461.5  # gas constant for water vapour
+    es = es0 * np.exp((lv) / Rv * (1 / T0 - 1 / T))
+    pv = (RH * es) / 100
+    return pv
+
+
+def emissivity_clear_sky(RH,T):
+    '''
+    clear sky emissivity, Eq(1) in Fiddes and Gruber (2014)
+    pv: water vapour pressure (1)
+    T: air temperature (kelvin)
+    '''
+    pv = water_vap_pressure(RH, T)
+    x1 = 0.43
+    x2 = 5.7
+    e_clear = 0.23 + x1 * (pv / T)**(1 / x2)
+    return e_clear
+
+
+def pressure_from_elevation(elevation):
+    """Convert elevation into air pressure using barometric formula"""
+    g = 9.80665    # Gravitational acceleration [m/s2]
+    R = 8.31432    # Universal gas constant for air [N·m /(mol·K)]
+    M = 0.0289644  # Molar mass of Earth's air [kg/mol]
+    P0 = 101325    # Pressure at sea level [Pa]
+    T0 = 288.15    # Temperature at sea level [K]
+    # http://en.wikipedia.org/wiki/Barometric_formula
+    return P0 * np.exp((-g * M * elevation) / (R * T0)) / 100  # [hPa] or [bar]
+
+
+def LW_downward(RH,T,N):
+    '''
+    incoming longware radiation [W/m2], Eq(14) in Fiddes and Gruber (2014)
+    e_clear: clear sky emissivity
+    N: cloud cover
+    T: air temperature
+    '''
+    e_clear = emissivity_clear_sky(RH,T)
+    p1 = 6
+    p2 = 4
+    e_as = 0.979
+    con = 5.67 * 10**(-8)  # J/s/m/K4 Stefan-Boltzmann constant
+    lw = e_clear * (1 - N**p1) + (e_as * (N**p2)) * con * T**4
+    return lw
diff --git a/globsim/nc_elements.py b/globsim/nc_elements.py
new file mode 100755
index 0000000..a0ed6d6
--- /dev/null
+++ b/globsim/nc_elements.py
@@ -0,0 +1,232 @@
+"""
+functions for creating netcdf files
+"""
+import netCDF4 as nc
+from globsim.common_utils import variables_skip, str_encode
+from os import path
+import numpy as np
+
+
+def nc_new_file(ncfile_out, featureType="timeSeries", fmt='NETCDF4_CLASSIC'):
+    rootgrp = nc.Dataset(ncfile_out, 'w', format=fmt)
+    rootgrp.Conventions = 'CF-1.6'
+    rootgrp.featureType = featureType
+
+    return rootgrp
+
+
+def ncvar_add_latitude(rootgrp, dimensions=('station')):
+    latitude  = rootgrp.createVariable('latitude', 'f8', dimensions)
+    latitude.long_name  = 'latitude'
+    latitude.units  = 'degrees_north'
+    latitude.standard_name = 'latitude'
+    latitude.axis  = 'Y'
+
+    return latitude
+
+
+def ncvar_add_longitude(rootgrp, dimensions=('station')):
+    longitude           = rootgrp.createVariable('longitude', 'f8', dimensions)
+    longitude.long_name = 'longitude'
+    longitude.units     = 'degrees_east'
+    longitude.standard_name = 'longitude'
+    longitude.axis  = 'X'
+
+    return longitude
+
+
+def ncvar_add_time(rootgrp, units, calendar, dimensions=('time'), dtype='i4'):
+    time           = rootgrp.createVariable('time', dtype, dimensions)
+    time.long_name = 'time'
+    time.units     = units
+    time.calendar  = calendar
+    time.standard_name = 'time'
+    time.axis = 'T'
+
+    return time
+
+
+def ncvar_add_station(rootgrp, dimensions=('station')):
+    station            = rootgrp.createVariable('station', 'i4', dimensions)
+    station.long_name  = 'station for time series data'
+    station.units      = '1'
+
+    return station
+
+
+def ncvar_add_number(rootgrp, dimensions=('number')):
+    number             = rootgrp.createVariable('number', 'i4', dimensions)
+    number.long_name   = 'ensemble_member'
+    number.units       = '1'
+
+    return number
+
+
+def ncvar_add_ellipsoid_height(rootgrp, dimensions=('station')):
+    height           = rootgrp.createVariable('height', 'f4', dimensions)
+    height.long_name = 'Elevation relative to ellipsoid'
+    height.units     = 'm'
+    height.axis      = 'Z'
+    height.standard_name = 'height_above_reference_ellipsoid'
+    height.positive  = 'up'
+
+    return height
+
+
+def new_scaled_netcdf(ncfile_out, nc_interpol, times_out,
+                      t_unit, station_names=None):
+    """
+    Create netCDF file for scaled results (same for all reanalyses)
+    Returns the file object so that kernel functions can
+    successively write variables to it.
+
+    """
+
+    if path.isfile(ncfile_out):  # raise exception if file exists
+        raise FileExistsError("File already exists: {}".format(ncfile_out))
+
+    # make netCDF outfile, variables are written in kernels
+    rootgrp = nc_new_file(ncfile_out, fmt='NETCDF4')
+    rootgrp.source      = 'Reanalysis data interpolated and scaled to stations'
+
+    # dimensions
+    n_station = len(nc_interpol.variables['station'][:])
+    station = rootgrp.createDimension('station', n_station)
+    time    = rootgrp.createDimension('time', len(times_out))
+
+    # base variables
+    time           = ncvar_add_time(rootgrp, units=t_unit,
+                                    calendar='gregorian', dimensions=('time'),
+                                    dtype='i8')
+    station        = ncvar_add_station(rootgrp)
+    latitude       = ncvar_add_latitude(rootgrp)
+    longitude      = ncvar_add_longitude(rootgrp)
+    height         = ncvar_add_ellipsoid_height(rootgrp)
+
+    crs           = rootgrp.createVariable('crs','i4')
+    crs.long_name = 'coordinate system'
+    crs.grid_mapping_name = 'latitude_longitude'
+    crs.longitude_of_prime_meridian = 0.0
+    crs.semi_major_axis = 6378137
+    crs.inverse_flattening = 298.2572236
+    crs.wkt = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]'
+
+    # assign base variables
+    time[:]      = times_out
+    station[:]   = nc_interpol.variables['station'][:]
+    latitude[:]  = nc_interpol.variables['latitude'][:]
+    longitude[:] = nc_interpol.variables['longitude'][:]
+    height[:]    = nc_interpol.variables['height'][:]
+
+    # add station names to netcdf
+    if station_names is not None:
+        # first convert to character array
+        names_out = nc.stringtochar(np.array(station_names, 'S32'))
+
+        # create space in the netcdf
+        _            = rootgrp.createDimension('name_strlen', 32)
+        st           = rootgrp.createVariable('station_name', "S1", ('station', 'name_strlen'))
+        st.standard_name = 'platform_name'
+        st.units     = ''
+
+        # add data
+        st[:] = names_out
+
+    return rootgrp
+
+
+def new_interpolated_netcdf(ncfile_out, stations, nc_in, time_units):
+    """
+    Creates an empty station file to hold interpolated reults. The number of
+    stations is defined by the variable stations, variables are determined by
+    the variable list passed from the gridded original netCDF.
+
+    ncfile_out: full name of the file to be created
+    stations:   station list read with common_utils.StationListRead()
+    variables:  variables read from netCDF handle
+    lev:        list of pressure levels, empty is [] (default)
+    """
+    rootgrp = netcdf_base(nc_in, ncfile_out, len(stations), None, time_units)
+
+    station = rootgrp['station']
+    latitude = rootgrp['latitude']
+    longitude = rootgrp['longitude']
+    height = rootgrp['height']
+
+    # assign station characteristics
+    station[:]   = list(stations['station_number'])
+    latitude[:]  = list(stations['latitude_dd'])
+    longitude[:] = list(stations['longitude_dd'])
+    height[:]    = list(stations['elevation_m'])
+
+    # extra treatment for pressure level files
+    try:
+        lev = nc_in.variables['level'][:]
+        print("== 3D: file has pressure levels")
+        level           = rootgrp.createDimension('level', len(lev))
+        level           = rootgrp.createVariable('level', 'i4', ('level'))
+        level.long_name = 'pressure_level'
+        level.units     = 'hPa'
+        level[:] = lev
+    except Exception:
+        print("== 2D: file without pressure levels")
+        lev = []
+
+    try:
+        num = rootgrp['number'][:]
+    except Exception:
+        num = []
+
+    # create and assign variables based on input file
+    for n, var in enumerate(nc_in.variables):
+        if variables_skip(var):
+            continue
+        print("VAR: ", str_encode(var))
+        # extra treatment for pressure level files
+        if len(num):
+            if len(lev):
+                tmp = rootgrp.createVariable(var,'f4',('time', 'number',
+                                                       'level', 'station'))
+            else:
+                tmp = rootgrp.createVariable(var,'f4',('time','number',
+                                                       'station'))
+        else:
+            if len(lev):
+                tmp = rootgrp.createVariable(var,'f4', ('time','level',
+                                                        'station'))
+            else:
+                tmp = rootgrp.createVariable(var,'f4', ('time','station'))
+
+        tmp.long_name = nc_in.variables[var].long_name.encode('UTF8')
+        tmp.units     = nc_in.variables[var].units.encode('UTF8')
+
+    return rootgrp
+
+
+def netcdf_base(nc_in, ncfile_out, n_stations, n_time, time_units):
+    # Build the netCDF file
+    rootgrp = nc_new_file(ncfile_out)
+
+    # dimensions
+    _ = rootgrp.createDimension('station', n_stations)
+    _ = rootgrp.createDimension('time', None)
+
+    # base variables
+    _ = ncvar_add_time(rootgrp, units=time_units,
+                       calendar='gregorian', dimensions=('time'))
+    _ = ncvar_add_station(rootgrp)
+    _ = ncvar_add_latitude(rootgrp)
+    _ = ncvar_add_longitude(rootgrp)
+    _ = ncvar_add_ellipsoid_height(rootgrp)
+
+    # for ERA5 enmsemble member only
+    try:
+        num    = nc_in.variables['number'][:]
+        number = rootgrp.createDimension('number', len(num))
+        number = ncvar_add_number(rootgrp)
+        number = rootgrp['number']
+        number[:] = num
+    except Exception:
+        pass
+
+    return rootgrp
diff --git a/lib/esmf-python.tar.gz b/lib/esmf-python.tar.gz
new file mode 100644
index 0000000..df49911
Binary files /dev/null and b/lib/esmf-python.tar.gz differ
diff --git a/setup.py b/setup.py
index d0784c2..26495b6 100644
--- a/setup.py
+++ b/setup.py
@@ -1,19 +1,25 @@
+import re
 import setuptools
 
 with open("README.md", "r") as fh:
     long_description = fh.read()
 
+version = re.search(
+    '^__version__\\s*=\\s*"(.*)"',
+    open('globsim/globsim_cli.py').read(),
+    re.M
+    ).group(1)
 
 setuptools.setup(
     name="globsim",
-    version="0.0.1",
+    version=version,
     author="Stephan Gruber",
     author_email="stephan.gruber@carleton.ca",
     description="Using global reanalysis data for local permafrost simulation",
     long_description=long_description,
     long_description_content_type="text/markdown",
     url="https://github.com/geocryology/globsim",
-    packages=setuptools.find_packages(),
+    packages=['globsim'],
     classifiers=[
         "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
@@ -21,6 +27,19 @@
         "Operating System :: POSIX :: Linux",
         "Topic :: Scientific/Engineering :: GIS",
         "Topic :: Scientific/Engineering :: Atmospheric Science"
-        
-    ],
+        ],
+    entry_points={
+        'console_scripts': [
+            'globsim = globsim.globsim_cli:main',
+        ]},
+    install_requires=[
+                    'pandas',
+                    'netCDF4',
+                    'scipy',
+                    'pydap', 
+                    'numpy',
+                    'tomlkit',
+                    'ecmwf-api-client',
+                    'cdsapi'
+    ]
 )
\ No newline at end of file


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..23df1be
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,45 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Tests
+
+on:
+  push:
+    branches: [ master ]
+    paths:
+    - 'globsim/**'
+  pull_request:
+    branches: [ master ]
+    paths:
+    - 'globsim/**'
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8]
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install flake8
+        python setup.py install
+    - name: Lint with flake8
+      run: |
+        # stop the build if there are Python syntax errors or undefined names
+        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+    - name: Test with pytest
+      run: |
+        cd globsim
+        cd test
+        python -m unittest
diff --git a/globsim/test/test_globsim.py b/globsim/test/test_globsim.py
new file mode 100644
index 0000000..818ab36
--- /dev/null
+++ b/globsim/test/test_globsim.py
@@ -0,0 +1,10 @@
+import unittest
+
+
+class MyTestCase(unittest.TestCase):
+    def test_something(self):
+        self.assertEqual(True, True)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/globsim/test/test_meteo.py b/globsim/test/test_meteo.py
new file mode 100644
index 0000000..50c1df3
--- /dev/null
+++ b/globsim/test/test_meteo.py
@@ -0,0 +1,33 @@
+import unittest
+from globsim import meteorology as met
+
+
+class SatVap(unittest.TestCase):
+
+    def test_zero(self):
+        self.assertAlmostEqual(met.satvapp_kPa_fT(0), 0.6113)
+
+
+class PressureFromElevation(unittest.TestCase):
+    
+    def test_zero(self):
+        self.assertAlmostEqual(met.pressure_from_elevation(0), 1013.25)
+
+
+class WaterVaporPressure(unittest.TestCase):
+
+    def test_zero(self):
+        self.assertAlmostEqual(met.vapp_kPa_fTd(0), 6.112)
+        
+    def test_increasing(self):
+        self.assertGreater(met.vapp_kPa_fTd(10), met.vapp_kPa_fTd(0))
+
+
+class SpecificHumidity(unittest.TestCase):
+
+    def test_increase_pressure(self):
+        self.assertGreater(met.spec_hum_kgkg(10, 101325), met.spec_hum_kgkg(10, 105325))
+
+
+if __name__ == '__main__':
+    unittest.main()