Repository: pysat/pysatMissions (language: python)

Issue:
MAINT: use OMMBV
# Description

Addresses #19

Find and replace `pysatMagVect` with `OMMBV`.  Note that a manual install is required in Github Actions.  This is documented in https://github.com/rstoneback/OMMBV/issues/50

## Type of change

- New feature (non-breaking change which adds functionality)

# How Has This Been Tested?

Covered by unit tests

# Checklist:

- [x] Make sure you are merging into the ``develop`` (not ``main``) branch
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
- [x] Add a note to ``CHANGELOG.md``, summarizing the changes



Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index c2fe4fae..00000000
--- a/.flake8
+++ /dev/null
@@ -1,3 +0,0 @@
-[flake8]
-max-line-length = 90
-exclude = .git,__pycache__,.eggs/,doc/,docs/,build/,dist/,archive/
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 00000000..9772e71a
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,38 @@
+# 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: Documentation Check
+
+on: [push, pull_request]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [3.9]
+
+    name: Documentation tests
+    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 -r requirements.txt
+        pip install -r test_requirements.txt
+
+    - name: Check documentation build
+      run: sphinx-build -E -b html docs dist/docs
+
+    - name: Check documentation links
+      run: sphinx-build -b linkcheck docs dist/docs
+
+    - name: Load .zenodo.json to check for errors
+      run: python -c "import json; json.loads(open('.zenodo.json').read())"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..3f1f5f0b
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,51 @@
+# 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: Pytest with Flake8
+
+on: [push, pull_request]
+
+jobs:
+  build:
+
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [3.7, 3.8, 3.9]
+        os: [ubuntu-latest]
+
+    name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+    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 -r test_requirements.txt
+        pip install -r requirements.txt
+        # Manual install of OMMBV
+        pip install --no-binary :OMMBV: OMMBV
+
+    - name: Set up pysat
+      run: |
+        mkdir pysatData
+        python -c "import pysat; pysat.params['data_dirs'] = 'pysatData'"
+
+    - name: Test PEP8 compliance
+      run: flake8 . --count --select=E,F,W --show-source --statistics
+
+    - name: Evaluate complexity
+      run: flake8 . --count --exit-zero --max-complexity=10 --statistics
+
+    - name: Test with pytest
+      run: |
+        pytest --cov=pysatMissions/
+
+    - name: Publish results to coveralls
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      run: coveralls --rcfile=setup.cfg --service=github
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8a7df39a..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,86 +0,0 @@
-os: linux
-language: python
-dist: xenial
-jobs:
-  include:
-    - name: Minimum NEP 029 versions
-      python: 3.6
-      env: NUMPY_VER=1.15
-    # Versions with latest numpy
-    - python: 3.6
-    - python: 3.7
-    - python: 3.8
-
-services: xvfb
-addons:
-  apt:
-    packages:
-    - gfortran
-
-install:
-  - sudo apt-get update
-  # We do this conditionally because it saves us some downloading if the
-  # version is the same.
-  - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
-  - bash miniconda.sh -b -p $HOME/miniconda
-  - source "$HOME/miniconda/etc/profile.d/conda.sh"
-  - hash -r
-  - conda config --set always_yes True --set changeps1 False
-  - conda update -q conda
-  # Useful for debugging any issues with conda
-  - conda info -a
-  # Create conda test environment
-  - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy pandas xarray matplotlib requests beautifulsoup4 lxml netCDF4 h5py nose pytest-cov pytest-ordering coveralls future
-  - conda activate test-environment
-  # Check for custom numpy version install
-  - if [ -z ${NUMPY_VER} ]; then
-      echo 'Using latest numpy';
-    else
-      conda install numpy==$NUMPY_VER;
-    fi
-  # Dependencies not available through conda, install through pip
-  - pip install apexpy
-  - pip install aacgmv2
-  - pip install madrigalWeb
-  - pip install pyEphem
-  - pip install sgp4
-  - pip install pytest-flake8
-  - pip install pysatCDF >/dev/null
-
-  # Prepare modified pysat install
-  - cd ..
-  - echo 'cloning pysat'
-  - git clone https://github.com/pysat/pysat.git >/dev/null
-  - echo 'installing pysat'
-  - cd ./pysat
-  # set up data directory
-  - mkdir /home/travis/build/pysatData
-  # install pysat
-  - git checkout develop-3
-  - python setup.py install >/dev/null
-
-  # install pyglow, space science models
-  - cd ..
-  - echo 'cloning pyglow'
-  - git clone https://github.com/timduly4/pyglow.git >/dev/null
-  - echo 'installing pyglow'
-  - cd ./pyglow
-  - make -C src/pyglow/models source >/dev/null
-  - python setup.py install >/dev/null
-
-  # Install pysatMagVect after pysat
-  - pip install pysatMagVect >/dev/null
-
-  # install pysatMissions
-  - cd ../pysatMissions
-  - python setup.py install
-
-before_script:
-  # set up display screen
-  - export DISPLAY=:99.0
-
-script:
-  - pytest -vs --cov=pysatMissions/
-
-after_success:
-  - coveralls
diff --git a/.zenodo.json b/.zenodo.json
new file mode 100644
index 00000000..baee59a6
--- /dev/null
+++ b/.zenodo.json
@@ -0,0 +1,27 @@
+{
+    "creators": [
+      {
+        "affiliation": "Goddard Space Flight Center",
+        "name": "Klenzing, Jeffrey",
+        "orcid": "0000-0001-8321-6074"
+      },
+      {
+        "affiliation": "The University of Texas at Dallas",
+        "name": "Stoneback, Russell",
+        "orcid": "0000-0001-7216-4336"
+      },
+      {
+        "affiliation": "U.S. Naval Research Laboratory",
+        "name": "Burrell, Angeline G.",
+        "orcid": "0000-0001-8875-9326"
+      },
+      {
+        "affiliation": "The University of Texas at Dallas",
+        "name": "Depew, Matthew"
+      },
+      {
+        "name": "Spence, Carey",
+        "orcid": "0000-0001-8340-5625"
+      }
+    ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5aeff967..fbf804c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,31 +1,40 @@
 # Change Log
 All notable changes to this project will be documented in this file.
-This project adheres to [Semantic Versioning](http://semver.org/).
+This project adheres to [Semantic Versioning](https://semver.org/).
+
+## [0.2.2] - 2021-06-18
+* Migrate pyglow interface to pysatIncubator
+* Style updates for consistency with pysat 3.0
+  * Use `inst_id` instead of `sat_id`
+  * Use `cadence` instead of `freq`
+  * Use 'missions' as the platform name ('pysat' now reserved for core code)
+* Migrate CI testing to Github Actions
+* Use OMMBV instead of pysatMagVect
 
 ## [0.2.1] - 2020-07-29
-- Use conda to manage Travis CI environment
-- Updated style to be compliant with pandas 2.0 and pysat 3.0
-  - Import datetime from datetime
-  - import DataFrame and Series from pandas rather than pysat
-- Rename default branch as `main`
+* Use conda to manage Travis CI environment
+* Updated style to be compliant with pandas 2.0 and pysat 3.0
+  * Import datetime from datetime
+  * import DataFrame and Series from pandas rather than pysat
+* Rename default branch as `main`
 
 ## [0.2.0] - 2020-03-07
-- Renamed as pysatMissions
-- Added method files to access aacgmv2, apexpy, pyglow for any pysat instrument
-- Added method for spacecraft to handle attitude and coordinates
-- Added method for plotting simulated data
-- Added support for readthedocs
-- Updates to testing environment
-- Split pysat_sgp4 into pysat_sgp4 and pysat_ephem to allow different propagators
-- Removed basemap
-- Added numeric strings as options for sat_id
-- Added `_get_times` to streamline time steps for simulated instruments
-- Bugs
-  - Fixed wrong metadata name for mlt in apexpy
+* Renamed as pysatMissions
+* Added method files to access aacgmv2, apexpy, pyglow for any pysat instrument
+* Added method for spacecraft to handle attitude and coordinates
+* Added method for plotting simulated data
+* Added support for readthedocs
+* Updates to testing environment
+* Split pysat_sgp4 into pysat_sgp4 and pysat_ephem to allow different propagators
+* Removed basemap
+* Added numeric strings as options for sat_id
+* Added `_get_times` to streamline time steps for simulated instruments
+* Bugs
+  * Fixed wrong metadata name for mlt in apexpy
 
 ## [0.1.1] - 2019-10-22
-- pypi compatibility
-- Add DOI badge
+* pypi compatibility
+* Add DOI badge
 
 ## [0.1.0] - 2019-10-07
-- Initial release
+* Initial release
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6cdbd174..317bf714 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -48,9 +48,9 @@ To set up `pysatMissions` for local development:
     git checkout -b name-of-your-bugfix-or-feature
 
    Now you can make your changes locally. Tests for new instruments are
-   performed automatically.  Tests for custom functions should be added to the
-   appropriately named file in ``pysatMissions/tests``.  For example, the averaging routines in avg.py are tested in ``pysatMissions/tests/test_avg.py``.  If no
-   test file exists, then you should create one.  This testing uses pytest, which
+   performed automatically.  Tests for custom functions should be added to
+   the appropriately named file in ``pysatMissions/tests``.  If no test
+   file exists, then you should create one.  This testing uses pytest, which
    will run tests on any python file in the test directory that starts with
    ``test_``.
 
@@ -80,11 +80,46 @@ For merging, you should:
 
 1. Include an example for use
 2. Add a note to ``CHANGELOG.md`` about the changes
-3. Ensure that all checks passed (current checks include Travis-CI
+3. Ensure that all checks passed (current checks include GitHub Actions
    and Coveralls) [1]_
 
 .. [1] If you don't have all the necessary Python versions available locally or
        have trouble building all the testing environments, you can rely on
-       Travis to run the tests for each change you add in the pull request.
-       Because testing here will delay tests by other developers, please ensure
-       that the code passes all tests on your local system first.
+       GitHub Actions to run the tests for each change you add in the pull
+       request. Because testing here will delay tests by other developers,
+       please ensure that the code passes all tests on your local system first.
+
+ Project Style Guidelines
+ ^^^^^^^^^^^^^^^^^^^^^^^^
+
+ In general, pysat follows PEP8 and numpydoc guidelines.  Pytest runs the unit
+ and integration tests, flake8 checks for style, and sphinx-build performs
+ documentation tests.  However, there are certain additional style elements that
+ have been settled on to ensure the project maintains a consistent coding style.
+ These include:
+
+ * Line breaks should occur before a binary operator (ignoring flake8 W503)
+ * Combine long strings using `join`
+ * Preferably break long lines on open parentheses rather than using `\`
+ * Use no more than 80 characters per line
+ * Avoid using Instrument class key attribute names as unrelated variable names:
+   `platform`, `name`, `tag`, and `inst_id`
+ * The pysat logger is imported into each sub-module and provides status updates
+   at the info and warning levels (as appropriate)
+ * Several dependent packages have common nicknames, including:
+   * `import datetime as dt`
+   * `import numpy as np`
+   * `import pandas as pds`
+   * `import xarray as xr`
+ * All classes should have `__repr__` and `__str__` functions
+ * Docstrings use `Note` instead of `Notes`
+ * Try to avoid creating a try/except statement where except passes
+ * Use setup and teardown in test classes
+ * Use pytest parametrize in test classes when appropriate
+ * Provide testing class methods with informative failure statements and
+   descriptive, one-line docstrings
+ * Block and inline comments should use proper English grammar and punctuation
+   with the exception of single sentences in a block, which may then omit the
+   final period
+ * When casting is necessary, use `np.int64` and `np.float64` to ensure operating
+   system agnosticism
diff --git a/README.md b/README.md
index 0189ce69..394390f8 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,46 @@
 <div align="left">
         <img height="0" width="0px">
-        <img width="20%" src="/poweredbypysat.png" alt="pysat" title="pysat"</img>
+        <img width="20%" src="https://raw.githubusercontent.com/pysat/pysatMissions/docs/figures/missions-draft-logo.jpeg" alt="pysat Missions logo - the python snakes dreaming of a spaceship" title="pysatMissions"</img>
 </div>
 
 # pysatMissions
 [![Documentation Status](https://readthedocs.org/projects/pysatmissions/badge/?version=latest)](https://pysatmissions.readthedocs.io/en/latest/?badge=latest)
 [![DOI](https://zenodo.org/badge/209358908.svg)](https://zenodo.org/badge/latestdoi/209358908)
 
-[![Build Status](https://travis-ci.org/pysat/pysatMissions.svg?branch=main)](https://travis-ci.org/pysat/pysatMissions)
+[![Build Status](https://github.com/github/docs/actions/workflows/main.yml/badge.svg)](https://github.com/github/docs/actions/workflows/main.yml/badge.svg)
 [![Coverage Status](https://coveralls.io/repos/github/pysat/pysatMissions/badge.svg?branch=main)](https://coveralls.io/github/pysat/pysatMissions?branch=main)
 [![Maintainability](https://api.codeclimate.com/v1/badges/83011911691b9d2076e9/maintainability)](https://codeclimate.com/github/pysat/pysatMissions/maintainability)
 
-pysatMissions allows users to run build simulated satellites for Two-Line Elements (TLE) and add empirical data.  It includes the pysat_ephem and pysat_sgp4 instrument modules which can be imported into pysat.
+pysatMissions allows users to run build simulated satellites for Two-Line Elements (TLE) and add empirical data.  It includes the missions_ephem and mission_sgp4 instrument modules which can be imported into pysat.
 
 Main Features
 -------------
 - Simulate satellite orbits from TLEs and add data from empirical models
 - Import ionosphere and thermosphere model values through pyglow
 - Import magnetic coordinates through apexpy and aacgmv2
-- Import geomagnetic basis vectors through pysatMagVect
+- Import geomagnetic basis vectors through OMMBV
 
 Documentation
 ---------------------
-[Full Documentation for main package](http://pysat.readthedocs.io/en/latest/)
+[Full Documentation for main package](https://pysat.readthedocs.io/en/latest/)
 
 
 # Installation
 
+### Prerequisites
+
+pysatMissions uses common Python modules, as well as modules developed by
+and for the Space Physics community.  This module officially supports
+Python 3.7+.  
+
+| Common modules | Community modules |
+| -------------- | ----------------- |
+| numpy          | aacgmv2           |
+| pandas         | apexpy            |
+| pyEphem        | OMMBV             |
+| sgp4           | pysat>=3.0        |
+
+
 One way to install is through pip.  Just type
 
 ```
@@ -49,19 +63,8 @@ python setup.py install
 ```
 
 Note: pre-1.0.0 version
-------------------
-pysatMissions is currently in an initial development phase.  Much of the API is being built off of the upcoming pysat 3.0.0 software in order to streamline the usage and test coverage.  This version of pysat is planned for release later this year.  Currently, you can access the develop version of this through github:
-```
-git clone https://github.com/pysat/pysat.git
-cd pysat
-git checkout develop-3
-python setup.py install
-```
-It should be noted that this is a working branch and is subject to change.
-
-A note on empirical models
---------------------------
-pysatMissions allows users to interact with a number of upper atmospheric empirical models through the [pyglow](https://github.com/timduly4/pyglow) package.  However, pyglow currently requires manual install through git.  While pysatMissions can be installed and used without pyglow, it should be installed by the user to access the pyglow methods.  Please follow the install instructions at https://github.com/timduly4/pyglow.
+-----------------------
+pysatMissions is currently in an initial development phase and requires pysat 3.0.0.  
 
 # Using with pysat
 
@@ -69,20 +72,16 @@ The instrument modules are portable and designed to be run like any pysat instru
 
 ```
 import pysat
-from pysatMissions.instruments import pysat_ephem
+from pysatMissions.instruments import missions_ephem
 
-simInst = pysat.Instrument(inst_module=pysat_ephem)
+simInst = pysat.Instrument(inst_module=missions_ephem)
 ```
-
-The methods that run empirical models can also be exported to any pysat instrument. For instance, to add thermal plasma predictions from the IRI model to the C/NOFS IVM instrument, one can invoke
+Another way to use the instruments in an external repository is to register the instruments.  This only needs to be done the first time you load an instrument.  Afterward, pysat will identify them using the `platform` and `name` keywords.
 
 ```
 import pysat
-from pysatMissions.methods import empirical
+import pysatMissions
 
-ivm = pysat.Instrument(platform='cnofs', name='ivm')
-ivm.custom.attach(empirical.add_iri_thermal_plasma, 'modify',
-               glat_label='glat',
-               glong_label='glon', alt_label='altitude')
+pysat.utils.registry.register_by_module(pysatMissions.instruments)
+simInst = pysat.Instrument('missions', 'ephem')
 ```
-Once the custom function is added, the model will automatically be run when the dataset is loaded.
diff --git a/description.txt b/description.txt
deleted file mode 100644
index 5fd11c1f..00000000
--- a/description.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-pysatMissions allows users to run build simulated satellites for TLE info and add empirical data. It includes the pysat_sgp4 instrument module which can be imported into pysat.
-
-Main Features
--------------
-- Simulate satellite orbits from TLEs and add data from empirical models
-- Import ionosphere and thermosphere values through pyglow
-- Import coordinates through apexpy
-
-Documentation
----------------------
-`Full Documentation for main package <http://pysat.readthedocs.io/en/latest/>`_
diff --git a/docs/Makefile b/docs/Makefile
index dc6d3aac..401e05b0 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -9,7 +9,7 @@ BUILDDIR      = _build
 
 # User-friendly check for sphinx-build
 ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/)
 endif
 
 # Internal variables.
diff --git a/docs/citing.rst b/docs/citing.rst
index ae0e51e3..bc96850d 100644
--- a/docs/citing.rst
+++ b/docs/citing.rst
@@ -1,46 +1,31 @@
-Citations in the pysat ecosystem
-================================
-
-When referring to this software package, please cite the original paper by Stoneback et al [2018] https://doi.org/10.1029/2018JA025297 as well as the package https://doi.org/10.5281/zenodo.3475498. Note that this doi will always point to the latest version of the code.  A list of dois for all versions can be found at the [zenodo page](https://zenodo.org/record/3475499).
-
-Example for citation in BibTex for a generalized version:
-
-.. code::
-
-  @misc{pysatMissions,
-    author       = {Stoneback, R.A. and
-                    Klenzing, J.H. and
-                    Burrell, A.G. and
-                    Depew, M. and
-                    Spence, C.},
-    title        = {Pysat Mission Planning Toolkit (pysatMissions) vX.Y.Z},
-    month        = oct,
-    year         = 2019,
-    doi          = {10.5281/zenodo.3475499},
-    url          = {https://doi.org/10.5281/zenodo.3475499}
+Citation Guidelines
+===================
+
+When publishing work that uses pysatMissions, please cite the package and
+any package it depends on that plays an important role in your analysis.
+Specifying which version of pysatMissions used will also improve the
+reproducibility of your presented results.
+
+pysatMissions
+-------------
+
+* Jeff Klenzing, Russell Stoneback, Angeline Burrell, Matthew Depew,
+  & Carey Spence. (2020, July 29). pysat/pysatMissions: Version 0.2.1
+  (Version v0.2.1). Zenodo. https://doi.org/10.5281/zenodo.3966508
+
+.. code-block:: latex
+
+  @software{pysatMissions,
+  author       = {Klenzing, Jeff and
+                  Stoneback, Russell and
+                  Burrell, Angeline and
+                  Depew, Matthew and
+                  Spence, Carey},
+  title        = {pysat/pysatMissions: Version 0.2.1},
+  month        = jul,
+  year         = 2020,
+  publisher    = {Zenodo},
+  version      = {v0.2.1},
+  doi          = {10.5281/zenodo.3966508},
+  url          = {https://doi.org/10.5281/zenodo.3966508}
   }
-
-Citing the publication:
-
-.. code::
-
-  @article{Stoneback2018,
-    author    = {Stoneback, R. A. and
-                 Burrell, A. G. and
-                 Klenzing, J. and
-                 Depew, M. D.},
-    doi       = {10.1029/2018JA025297},
-    issn      = {21699402},
-    journal   = {Journal of Geophysical Research: Space Physics},
-    number    = {6},
-    pages     = {5271--5283},
-    title     = {{PYSAT: Python Satellite Data Analysis Toolkit}},
-    volume    = {123},
-    year      = {2018}
-  }
-
-To aid in scientific reproducibility, please include the version number in publications that use this code.  This can be found by invoking
-
-.. code::
-
-  pysatMissions.__version__
diff --git a/docs/conf.py b/docs/conf.py
index 2944c0de..ff9c01d7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -4,7 +4,7 @@
 #
 # This file does only contain a selection of the most common options. For a
 # full list see the documentation:
-# http://www.sphinx-doc.org/en/master/config
+# https://www.sphinx-doc.org/en/master/config
 
 # -- Path setup --------------------------------------------------------------
 
@@ -12,21 +12,28 @@
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #
+import json
 import os
 import sys
 sys.path.insert(0, os.path.abspath('..'))
-from pysatMissions import __version__
 
 # -- Project information -----------------------------------------------------
 
 project = 'pysatMissions'
-copyright = '2020, Russell Stoneback'
-author = 'Jeff Klenzing \\and Russell Stoneback \\and Angeline Burrell'
+title = '{:s} Documentation'.format(project)
+zenodo = json.loads(open('../.zenodo.json').read())
+author = ', '.join([x['name'] for x in zenodo['creators']])
+copyright = ', '.join(['2021', author])
+description = 'Tools for generating simulated instruments in pysat.'
 
 # The short X.Y version
-version = __version__[::-1].partition('.')[2][::-1]
-# The full version, including alpha/beta/rc tags
-release = __version__
+module_dir = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0]
+version_file = os.path.join(module_dir, project, 'version.txt')
+with open(version_file, 'r') as fin:
+    version = fin.read().strip()
+
+# The full version, including alpha/beta/rc tags.
+release = '{:s}-alpha'.format(version)
 
 
 # -- General configuration ---------------------------------------------------
@@ -38,17 +45,16 @@
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
-extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.doctest',
-    'sphinx.ext.todo',
-    'sphinx.ext.imgmath',
-    'sphinx.ext.autosummary',
-    'sphinx.ext.napoleon',
-    'recommonmark',
-    'numpydoc',
-    'IPython.sphinxext.ipython_console_highlighting'
-]
+extensions = ['sphinx.ext.autodoc',
+              'sphinx.ext.intersphinx',
+              'sphinx.ext.doctest',
+              'sphinx.ext.todo',
+              'sphinx.ext.imgmath',
+              'sphinx.ext.autosummary',
+              'sphinx.ext.napoleon',
+              'numpydoc',
+              'IPython.sphinxext.ipython_console_highlighting',
+              'm2r2']
 
 numpydoc_show_class_members = False
 
@@ -85,7 +91,7 @@
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
-html_theme = 'alabaster'
+html_theme = 'sphinx_rtd_theme'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
@@ -117,41 +123,35 @@
 
 # -- Options for LaTeX output ------------------------------------------------
 
-latex_elements = {
-    # The paper size ('letterpaper' or 'a4paper').
-    #
-    # 'papersize': 'letterpaper',
+# The paper size ('letterpaper' or 'a4paper').
+#
+# 'papersize': 'letterpaper',
 
-    # The font size ('10pt', '11pt' or '12pt').
-    #
-    # 'pointsize': '10pt',
+# The font size ('10pt', '11pt' or '12pt').
+#
+# 'pointsize': '10pt',
 
-    # Additional stuff for the LaTeX preamble.
-    #
-    # 'preamble': '',
+# Additional stuff for the LaTeX preamble.
+#
+# 'preamble': '',
 
-    # Latex figure (float) alignment
-    #
-    # 'figure_align': 'htbp',
-}
+# Latex figure (float) alignment
+#
+# 'figure_align': 'htbp',
+latex_elements = {}
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, 'pysatMissions.tex', 'pysatMissions Documentation',
-     'Jeff Klenzing', 'manual'),
-]
+latex_documents = [(master_doc, '{:s}.tex'.format(project), title,
+                    author, 'manual')]
 
 
 # -- Options for manual page output ------------------------------------------
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'pysatMissions', 'pysatMissions Documentation',
-     [author], 1)
-]
+man_pages = [(master_doc, project, title, [author], 1)]
 
 
 # -- Options for Texinfo output ----------------------------------------------
@@ -159,11 +159,8 @@
 # Grouping the document tree into Texinfo files. List of tuples
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
-texinfo_documents = [
-    (master_doc, 'pysatMissions', 'pysatMissions Documentation',
-     author, 'pysatMissions', 'One line description of project.',
-     'Miscellaneous'),
-]
+texinfo_documents = [(master_doc, project, title, author, project, description,
+                      'Space Physics')]
 
 
 # -- Options for Epub output -------------------------------------------------
diff --git a/docs/contributing.rst b/docs/contributing.rst
deleted file mode 100644
index 58977a88..00000000
--- a/docs/contributing.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-.. include:: ../CONTRIBUTING.md
diff --git a/docs/develop_guide.rst b/docs/develop_guide.rst
new file mode 100644
index 00000000..a0cc339d
--- /dev/null
+++ b/docs/develop_guide.rst
@@ -0,0 +1,6 @@
+Guide for Developers
+====================
+
+.. toctree::
+   develop_guide/code_of_conduct.rst
+   develop_guide/contributing.rst
diff --git a/docs/develop_guide/code_of_conduct.rst b/docs/develop_guide/code_of_conduct.rst
new file mode 100644
index 00000000..f4ab40ba
--- /dev/null
+++ b/docs/develop_guide/code_of_conduct.rst
@@ -0,0 +1,1 @@
+.. mdinclude:: ../../CODE_OF_CONDUCT.md
diff --git a/docs/develop_guide/contributing.rst b/docs/develop_guide/contributing.rst
new file mode 100644
index 00000000..f2c987c7
--- /dev/null
+++ b/docs/develop_guide/contributing.rst
@@ -0,0 +1,1 @@
+.. mdinclude:: ../../CONTRIBUTING.md
diff --git a/docs/figures/missions-draft-logo.jpeg b/docs/figures/missions-draft-logo.jpeg
new file mode 100644
index 00000000..a0b940b6
Binary files /dev/null and b/docs/figures/missions-draft-logo.jpeg differ
diff --git a/docs/figures/poweredbypysat.png b/docs/figures/poweredbypysat.png
new file mode 100644
index 00000000..ff169c67
Binary files /dev/null and b/docs/figures/poweredbypysat.png differ
diff --git a/docs/history.rst b/docs/history.rst
new file mode 100644
index 00000000..6336d766
--- /dev/null
+++ b/docs/history.rst
@@ -0,0 +1,1 @@
+.. mdinclude:: ../CHANGELOG.md
diff --git a/docs/index.rst b/docs/index.rst
index 6a7a5b17..7481ba08 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,14 +1,20 @@
 .. pysatMissions documentation main file
 
-Welcome to pysatMissions's documentation!
-=========================================
+Welcome to the pysatMissions documentation!
+===========================================
+
+This documentation describes the pysatMissions module, which contains
+routines to generate simualted instruments using Two-Line Elements (TLEs),
+as well as apply geomagnetic field models to existing pysat instruments.
 
 .. toctree::
-   :maxdepth: 2
+   :maxdepth: -1
 
    introduction.rst
    installation.rst
-   tutorial.rst
-   contributing.rst
    citing.rst
+   supported_instruments.rst
+   tutorial.rst
    references.rst
+   develop_guide.rst
+   history.rst
diff --git a/docs/installation.rst b/docs/installation.rst
index 4eee5419..ff497dc3 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -2,45 +2,56 @@
 Installation
 ============
 
-**Starting from scratch**
+The following instructions will allow you to install pysatMissions.
 
-----
+Prerequisites
+-------------
 
-Python and associated packages for science are freely available. Convenient science python package setups are available from https://www.python.org/ and `Anaconda <https://www.anaconda.com/distribution/>`_. Anaconda also includes a developer environment. Core science packages such as numpy, scipy, matplotlib, pandas and many others may also be installed directly via pip or your favorite package manager.
+.. image:: figures/poweredbypysat.png
+    :width: 150px
+    :align: right
+    :alt: powered by pysat Logo, blue planet with orbiting python and the logo superimposed
 
-For educational users, developer environments from `Jet Brains <https://www.jetbrains.com/student/>`_ are available for free.
 
+pysatMissions uses common Python modules, as well as modules developed by
+and for the Space Physics community.  This module officially supports
+Python 3.7+ and pysat 3.0.0+.
 
-**pysatMissions**
+ ================ ==================
+ Common  modules   Community modules
+ ================ ==================
+  numpy            aacgmv2
+  pandas           apexpy
+  pyEphem          OMMBV
+  sgp4             pysat>=3.0
+ ================ ==================
 
-----
 
-pysatMissions itself may be installed from a terminal command line via:
+ Installation Options
+ --------------------
 
-.. code-block:: console
+ 1. Clone the git repository
+ ::
 
-   pip install pysatMissions
 
-Note that pysatMissions requires pysat to interact with the instruments and models here.  [Full Documentation for main package](http://pysat.readthedocs.io/en/latest/)
+    git clone https://github.com/pysat/pysatMissions.git
 
 
-**Note: pre-1.0.0 version**
+ 2. Install pysatMissions:
+    Change directories into the repository folder and run the setup.py file.
+    There are a few ways you can do this:
 
-----
+    A. Install on the system (root privileges required)::
 
-pysatMissions is currently in an initial development phase.  Much of the API is being built off of the upcoming pysat 3.0.0 software in order to streamline the usage and test coverage.  This version of pysat is planned for release later this year.  Currently, you can access the develop version of this through github:
 
-.. code-block:: console
+         sudo python setup.py install
 
-   git clone https://github.com/pysat/pysat.git
-   cd pysat
-   git checkout develop-3
-   python setup.py install
+    B. Install at the user level::
 
-It should be noted that this is a working branch and is subject to change.
 
-**A note on empirical models**
+         python setup.py install --user
 
-----
+    C. Install with the intent to develop locally::
 
-pysatMissions allows users to interact with a number of upper atmospheric empirical models through the pyglow package. However, pyglow currently requires manual install through git. While pysatMissions can be installed and used without pyglow, it should be installed by the user to access the pyglow methods. Please follow the install instructions at https://github.com/timduly4/pyglow.
+
+         python setup.py develop --user
diff --git a/docs/introduction.rst b/docs/introduction.rst
index 84662f93..d2e55a4b 100644
--- a/docs/introduction.rst
+++ b/docs/introduction.rst
@@ -2,13 +2,15 @@
 Introduction
 ============
 
-pysatMissions allows users to run build simulated satellites for Two-Line Elements (TLE) and add empirical data.  It includes the pysat_ephem and pysat_sgp4 instrument modules which can be imported into pysat.
+pysatMissions allows users to run build simulated satellites for Two-Line
+Elements (TLE) and add empirical data.  It includes the missions_ephem and
+missions_sgp4 instrument modules which can be imported into pysat.
 
 Main Features
 -------------
 - Simulate satellite orbits from TLEs and add data from empirical models
-- Import ionosphere and thermosphere model values through pyglow
 - Import magnetic coordinates through apexpy and aacgmv2
-- Import geomagnetic basis vectors through pysatMagVect
+- Import geomagnetic basis vectors through OMMBV
 
-This document covers installation, a tutorial on pysatMissions including demonstration code, and an API reference.
+This document covers installation, a tutorial on pysatMissions including
+demonstration code, and an API reference.
diff --git a/docs/references.rst b/docs/references.rst
index 2be13c14..b7bd48e1 100644
--- a/docs/references.rst
+++ b/docs/references.rst
@@ -1,53 +1,42 @@
 References
 ==========
 
-**aacgmv2**
+aacgmv2
+-------
 
-----
+.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1212694.svg
+   :target: https://doi.org/10.5281/zenodo.1212694
 
-`aacgmv2 <https://github.com/aburrell/aacgmv2>`_ is a python library for accessing the Altitude-Adjusted Corrected Geomagnetic (AACGM) coordinates.
+`aacgmv2 <https://github.com/aburrell/aacgmv2>`_ is a python library for
+accessing the Altitude-Adjusted Corrected Geomagnetic (AACGM) coordinates.
 
-* Baker, K. B., & Wing, S. (1989). A new magnetic coordinate system for conjugate studies at high latitudes. Journal ofGeophysical Research, 94, 9139–9143.
-* Shepherd, S. G. (2014). Altitude−adjusted corrected geomagnetic coordinates: Definition and functional approximations. Journal of Geophysical Research: Space Physics, 119, 7501–7521. https://doi.org/10.1002/2014JA020264
+* Baker, K. B., & Wing, S. (1989). A new magnetic coordinate system for
+  conjugate studies at high latitudes. Journal of Geophysical Research, 94,
+  9139–9143.
+* Shepherd, S. G. (2014). Altitude−adjusted corrected geomagnetic coordinates:
+  Definition and functional approximations. Journal of Geophysical Research:
+  Space Physics, 119, 7501–7521. https://doi.org/10.1002/2014JA020264
 
-**apexpy**
+apexpy
+------
 
-----
+.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1214206.svg
+   :target: https://doi.org/10.5281/zenodo.1214206
 
-`apexpy <https://github.com/aburrell/apexpy>`_ is a python library for calculating magnetic apex coordinates.
+`apexpy <https://github.com/aburrell/apexpy>`_ is a python library for
+calculating magnetic apex coordinates.
 
-* Richmond, A. D. (1995). Ionospheric electrodynamics using magnetic apex coordinates. Journal ofGeomagnetism and Geoelectricity, 47(2), 191–212.
-* Emmert, J. T., Richmond, A. D., & Drob, D. P. (2010). A computationally compact representation of magnetic-apex and quasi-dipole coordinates with smooth base vectors. Journal ofGeophysical Research, 115, A08322. https://doi.org/10.1029/2010JA015326
-* Laundal, K. M., & Richmond, A. D. (2017). Magnetic coordinate systems. Space Science Reviews, 206, 27–59
+* Richmond, A. D. (1995). Ionospheric electrodynamics using magnetic apex
+  coordinates. Journal of Geomagnetism and Geoelectricity, 47(2), 191–212.
+* Emmert, J. T., Richmond, A. D., & Drob, D. P. (2010). A computationally
+  compact representation of magnetic-apex and quasi-dipole coordinates with
+  smooth base vectors. Journal of Geophysical Research, 115, A08322.
+  https://doi.org/10.1029/2010JA015326
+* Laundal, K. M., & Richmond, A. D. (2017). Magnetic coordinate systems. Space
+  Science Reviews, 206, 27–59
 
-**pyglow**
+OMMBV
+------------
 
-----
-
-`pyglow <https://github.com/timduly4/pyglow>`_ is a python package wrapping multiple empirical models:
-
-Horizontal Wind Model (HWM)
-
-* Hedin, A. E., Schmidlin, F. J., Fleming, E. L., Avery, S. K., Manson, A. H., & Franke, S. J. (1993). Empirical wind model for the middle and lower atmosphere—Part 1: Local time average. https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19930015971.pdf
-* Hedin, A. E., Fleming, E. L., Manson, A. H., Schmidlin, F. J., Avery, S. K., Clark, R. R., et al. (1993). Empirical wind model for the middle and lower atmosphere—Part 2: Local time variations (NASA Technical Memorandum 104592) Greenbelt, MD: Goddard Space Flight Center. https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19940017389.pdf
-* Drob, D. P., Emmert, J. T., Crowley, G., Picone, J. M., Shepherd, G. G., Skinner, W., et al. (2008). An empirical model of the Earth’s horizontal wind fields: HWM07. Journal ofGeophysical Research, 113, A12304. https://doi.org/10.1029/2008JA013668
-* Drob, D. P., Emmert, J. T., Meriwether, J. W., Makela, J. J., Doornbos, E., Conde, M., et al. (2015). An update to the Horizontal Wind Model (HWM): The quiet time thermosphere. Earth and Space Science, 2, 301–319. https://doi.org/10.1002/2014EA000089
-
-International Geomagnetic Reference Field (IGRF)
-
-* Thébault, E., Finlay, C. C., & Toh, H. (2015). Special issue “international geomagnetic reference field—The twelfth generation”. Earth, Planets and Space, 67(1), 158. https://doi.org/10.1186/s40623-015-0313-0
-
-International Reference Ionosphere (IRI)
-
-* Bilitza, D., Altadill, D., Truhlik, V., Shubin, V., Galkin, I., Reinisch, B., & Huang, X. (2017). International Reference Ionosphere 2016: From ionospheric climate to real-time weather predictions. Space Weather, 15, 418–429. https://doi.org/10.1002/2016SW001593
-* Bilitza, D., Altadill, D., Zhang, Y., Mertens, C., Truhlik, V., Richards, P., et al. (2014). The International Reference Ionosphere 2012—A model of international collaboration. Journal ofSpace Weather and Space Climate, 4, A07.
-
-Naval Research Laboratory Mass Spectrometer Incoherent Scatter radar Exobase (NRLMSISE)
-
-* Picone, J. M. (2002). NRLMSISE-00 empirical model of the atmosphere: Statistical comparisons and scientific issues. Journal ofGeophysical Research, 107(A12), 1468–SIA 15–16.
-
-**pysatMagVect**
-
-----
-
-`pysatMagVect <https://github.com/rstoneback/OMMBV>`_ calculates Orthogonal Multipole Magnetic Basis Vectors for the earth's magnetic field.  pysatMagVect is being renamed OMMBV.
+`OMMBV <https://github.com/rstoneback/OMMBV>`_ calculates
+Orthogonal Multipole Magnetic Basis Vectors for the earth's magnetic field.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 1ff8ec63..b0dcb5e1 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,3 @@
-numpydoc
 ipython
+m2r2
+numpydoc
diff --git a/docs/supported_instruments.rst b/docs/supported_instruments.rst
new file mode 100644
index 00000000..1eda1ea3
--- /dev/null
+++ b/docs/supported_instruments.rst
@@ -0,0 +1,18 @@
+Supported Instruments
+=====================
+
+.. _missions_ephem:
+
+Missions ephem
+--------------
+
+.. automodule:: pysatMissions.instruments.missions_ephem
+   :members:
+
+.. _missions_sgp4:
+
+Missions SGP4
+-------------
+
+.. automodule:: pysatMissions.instruments.missions_sgp4
+   :members:
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 02744b77..90a9b709 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -2,42 +2,80 @@
 Tutorial
 ========
 
-**Set up sgp4 with pysat**
+Set up sgp4 with pysat
+----------------------
 
-The sgp4 instrument within pysatMissions is designed to be run like any pysat instrument.  To access, use
+The :ref:`missions_sgp4` instrument within pysatMissions is designed to be run
+like any pysat instrument.  To access, use
 
 .. code:: python
 
   import pysat
-  from pysatMissions.instruments import pysat_sgp4
+  from pysatMissions.instruments import missions_sgp4
 
-  sgp4 = pysat.Instrument(inst_module=pysat_sgp4)
+  sgp4 = pysat.Instrument(inst_module=missions_sgp4)
 
-For pysat 3.0.0 or greater, this can be permanently added via the instrument registry.
+This can be permanently added via the instrument registry.
 
 .. code:: python
 
   import pysat
   import pysatMissions
-  pysat.utils.registry.register('pysatMissions.instruments.pysat_sgp4')
+  pysat.utils.registry.register('pysatMissions.instruments.missions_sgp4')
+
+or, to register all modules in pysat
+
+.. code:: python
+
+  import pysat
+  import pysatMissions
+  pysat.utils.registry.register_by_module(pysatMissions.instruments)
+
+
+For other instruments, simply replace the module name (in this case, missions_sgp4)
+with the name of the desired instrument.
+
+Orbital Propagators
+-------------------
+
+Currently, two orbital propagators are included with pysatMissions. The
+:ref:`missions_sgp4` instrument uses the wgs72 gravity model to provide satellite
+position and velocity in ECI coordinates.  The :ref:`missions_ephem` instrument
+uses the ephem pysat package to calculate an orbit in lat/lon/alt and ECEF
+coordinates.  As an example, it also loads a series of empirical models to
+provide simulated magnetic data as an aid for mission planning.
+
+The orbital propagators are activated by the load command, similar to any
+pysat instrument.  To generate a simulated hour of orbital information with a
+one-second cadence, run
+
+.. code:: python
+
+  sgp4 = pysat.Instrument(inst_module=missions_sgp4, num_samples=3600)
+  sgp4.load(2019, 1)
 
-**Orbital Propagators**
 
-Currently, two orbital propagators are included with pysatMissions. The pysat_sgp4 instrument uses the wgs72 gravity model to provide satellite position and velocity in ECI co-ordinates.  The pysat_ephem instrument uses the ephem pysat package to calculate an orbit in lat/lon/alt and ECEF co-ordinates.  As an example, it also loads a series of empirical models to provide simulated ionospheric, thermospheric, and magnetic data as an aid for mission planning.
 
-**Empirical Models**
+Empirical Models
+----------------
 
-A number of methods are included to invoke several python wrappers for empirical models.  This includes the aacgmv2, apexpy, and pyglow models.  These methods can be added to any pysat instrument in order to compare.  These can be added using the `custom` functions in pysat.
+A number of methods are included to invoke several python wrappers for empirical
+models.  This includes the aacgmv2, apexpy, and OMMBV models.  These
+methods can be added to any pysat instrument using the `custom` functions in
+pysat.  The example below adds the aacgmv2 coordinates to one of the test
+instruments in the core pysat package.
 
 .. code:: python
 
   import pysat
-  from pysatMissions.methods import empirical
+  from pysatMissions.methods import magcoord
 
-  ivm = pysat.Instrument(platform='cnofs', name='ivm')
-  ivm.custom.attach(empirical.add_iri_thermal_plasma,
-                    kwargs={'glat_label': 'glat',
-                            'glong_label': 'glon',
-                            'alt_label': 'altitude'})
+  inst = pysat.Instrument(platform='pysat', name='testing')
+  inst.custom_attach(magcoord.add_aacgm_coordinates,
+                     kwargs={'glat_label': 'latitude',
+                             'glong_label': 'longitude',
+                             'alt_label': 'altitude'})
 
-Note that in this case, the latitude, longitude, and altitude variable names of the instrument must be specified since they are not identical to the default names in the function.
+Note that the latitude, longitude, and altitude variable names of the
+instrument should be specified since the dataset may use different variable
+names from those in the custom function.
diff --git a/pysatMissions/__init__.py b/pysatMissions/__init__.py
index 0020d454..bf54ece0 100644
--- a/pysatMissions/__init__.py
+++ b/pysatMissions/__init__.py
@@ -6,11 +6,10 @@
 try:
     from pysatMissions import instruments
     from pysatMissions import methods
-    from pysatMissions import plot
 except ImportError as errstr:
     logging.exception('problem importing pysatMissions: ' + str(errstr))
 
-__all__ = ['instruments', 'methods', 'plot']
+__all__ = ['instruments', 'methods']
 
 # set version
 here = os.path.abspath(os.path.dirname(__file__))
diff --git a/pysatMissions/instruments/__init__.py b/pysatMissions/instruments/__init__.py
index d42e4b60..f8b8e3e5 100644
--- a/pysatMissions/instruments/__init__.py
+++ b/pysatMissions/instruments/__init__.py
@@ -3,6 +3,6 @@
 the instrument modules to be used with pysat
 """
 
-from pysatMissions.instruments import pysat_ephem, pysat_sgp4
+from pysatMissions.instruments import missions_ephem, missions_sgp4
 
-__all__ = ['pysat_ephem', 'pysat_sgp4']
+__all__ = ['missions_ephem', 'missions_sgp4']
diff --git a/pysatMissions/instruments/_core.py b/pysatMissions/instruments/_core.py
index 004c23ed..6545defb 100644
--- a/pysatMissions/instruments/_core.py
+++ b/pysatMissions/instruments/_core.py
@@ -2,43 +2,9 @@
 Handles the default pysat functions for simulated instruments
 """
 
-import datetime as dt
-import os
-import pandas as pds
 
+def _clean(self):
+    """Cleaning function.  Simple pass since data is generated locally.
+    """
 
-def _list_files(tag=None, sat_id=None, data_path=None, format_str=None):
-    """Produce a fake list of files spanning a year"""
-
-    index = pds.date_range(dt.datetime(2017, 12, 1),
-                           dt.datetime(2018, 12, 1))
-    # file list is effectively just the date in string format - '%D' works
-    # only in Mac. '%x' workins in both Windows and Mac
-    names = [data_path + date.strftime('%Y-%m-%d') + '.nofile'
-             for date in index]
-    return pds.Series(names, index=index)
-
-
-def _download(date_array, tag, sat_id, data_path=None):
-    """ Data is simulated so no download routine is possible. Simple pass
-    function"""
-    pass
-
-
-def _get_times(fnames, sat_id):
-    """Construct list of times for simulated instruments"""
-
-    # grab date from filename
-    parts = os.path.split(fnames[0])[-1].split('-')
-    yr = int(parts[0])
-    month = int(parts[1])
-    day = int(parts[2][0:2])
-    date = dt.datetime(yr, month, day)
-
-    # create timing at 1 Hz (defaults to 1 day)
-    # Allow numeric string to set number of time steps
-    num = 86399 if sat_id == '' else int(sat_id)
-    times = pds.date_range(start=date, end=date+pds.DateOffset(seconds=num),
-                           freq='1S')
-
-    return times
+    return
diff --git a/pysatMissions/instruments/missions_ephem.py b/pysatMissions/instruments/missions_ephem.py
new file mode 100644
index 00000000..11e3f466
--- /dev/null
+++ b/pysatMissions/instruments/missions_ephem.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+"""
+Produces satellite orbit data. Orbit is simulated using
+Two Line Elements (TLEs) and ephem. Satellite position is coupled
+to several space science models to simulate the atmosphere the
+satellite is in.
+
+Properties
+----------
+platform
+    'missions'
+name
+    'ephem'
+tag
+    None supported
+inst_id
+    None supported
+
+"""
+
+import datetime as dt
+import functools
+import numpy as np
+
+import ephem
+import OMMBV
+import pandas as pds
+import pysat
+
+from pysat.instruments.methods import testing as ps_meth
+from pysatMissions.instruments import _core as mcore
+from pysatMissions.methods import magcoord as mm_magcoord
+from pysatMissions.methods import spacecraft as mm_sc
+
+logger = pysat.logger
+
+# -------------------------------
+# Required Instrument attributes
+platform = 'missions'
+name = 'ephem'
+tags = {'': 'Satellite simulation data set'}
+inst_ids = {'': ['']}
+
+# ------------------
+# Testing attributes
+_test_dates = {'': {'': dt.datetime(2018, 1, 1)}}
+
+
+def init(self):
+    """
+    Adds custom calculations to orbit simulation.
+    This routine is run once, and only once, upon instantiation.
+
+    Adds custom routines for quasi-dipole coordinates, velocity calculation in
+    ECEF coords, and attitude vectors of spacecraft (assuming x is ram pointing
+    and z is generally nadir).
+
+    """
+
+    self.acknowledgements = ' '.join((
+        'The project uses the pyephem library available at',
+        'https://github.com/brandon-rhodes/pyephem'))
+    self.references = 'Please contact the pyephem project for references'
+    logger.info(self.acknowledgements)
+
+    return
+
+
+def preprocess(self):
+    """
+    Add modeled magnetic field values and attitude vectors to spacecraft
+
+    Runs after load is invoked.
+
+    """
+
+    mm_magcoord.add_quasi_dipole_coordinates(self)
+    mm_magcoord.add_aacgm_coordinates(self)
+    mm_sc.calculate_ecef_velocity(self)
+    mm_sc.add_ram_pointing_sc_attitude_vectors(self)
+
+    return
+
+
+# Clean method
+clean = mcore._clean
+
+
+def load(fnames, tag=None, inst_id=None, obs_long=0., obs_lat=0., obs_alt=0.,
+         TLE1=None, TLE2=None, num_samples=None, cadence='1S'):
+    """
+    Returns data and metadata in the format required by pysat. Generates
+    position of satellite in both geographic and ECEF co-ordinates.
+
+    Routine is directly called by pysat and not the user.
+
+    Parameters
+    ----------
+    fnames : list
+        List of filenames
+    tag : str or NoneType
+        Identifies a particular subset of satellite data (accepts '')
+        (default=None)
+    inst_id : str or NoneType
+        Instrument satellite ID (accepts '')
+        (default=None)
+    obs_long: float
+        Longitude of the observer on the Earth's surface
+        (default=0.)
+    obs_lat: float
+        Latitude of the observer on the Earth's surface
+        (default=0.)
+    obs_alt: float
+        Altitude of the observer on the Earth's surface
+        (default=0.)
+    TLE1 : string or NoneType
+        First string for Two Line Element. Must be in TLE format (default=None)
+    TLE2 : string or NoneType
+        Second string for Two Line Element. Must be in TLE format (default=None)
+    num_samples : int or NoneType
+        Number of samples per day (default=None)
+    cadence : str
+        Uses pandas.frequency string formatting ('1S', etc)
+        (default='1S')
+
+    Returns
+    -------
+    data : pandas.DataFrame
+        Object containing satellite data
+    meta : pysat.Meta
+        Object containing metadata such as column names and units
+
+    Example
+    -------
+    ::
+
+          TLE1='1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998'
+          TLE2='2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452'
+          inst = pysat.Instrument('pysat', 'ephem', TLE1=TLE1, TLE2=TLE2)
+          inst.load(2018, 1)
+
+    """
+
+    # TLEs (Two Line Elements for ISS)
+    # format of TLEs is fixed and available from wikipedia...
+    # lines encode list of orbital elements of an Earth-orbiting object
+    # for a given point in time
+    line1 = ''.join(('1 25544U 98067A   18135.61844383  .00002728  00000-0  ',
+                     '48567-4 0  9998'))
+    line2 = ''.join(('2 25544  51.6402 181.0633 0004018  88.8954  22.2246 ',
+                     '15.54059185113452'))
+
+    # Use ISS defaults if not provided by user
+    if TLE1 is not None:
+        line1 = TLE1
+    if TLE2 is not None:
+        line2 = TLE2
+
+    if num_samples is None:
+        num_samples = 100
+
+    # Extract list of times from filenames and inst_id
+    times, index, dates = ps_meth.generate_times(fnames, num_samples,
+                                                 freq=cadence)
+
+    # The observer's (ground station) position on the Earth surface
+    site = ephem.Observer()
+    site.lon = str(obs_long)
+    site.lat = str(obs_lat)
+    site.elevation = obs_alt
+
+    # The first parameter in readtle() is the satellite name
+    sat = ephem.readtle('pysat', line1, line2)
+    output_params = []
+    for timestep in index:
+        lp = {}
+        site.date = timestep
+        sat.compute(site)
+
+        # Parameters relative to the ground station
+        lp['obs_sat_az_angle'] = ephem.degrees(sat.az)
+        lp['obs_sat_el_angle'] = ephem.degrees(sat.alt)
+
+        # Total distance between transmitter and receiver
+        lp['obs_sat_slant_range'] = sat.range
+
+        # Satellite location (sub-latitude and sub-longitude)
+        lp['glat'] = np.degrees(sat.sublat)
+        lp['glong'] = np.degrees(sat.sublong)
+
+        # Elevation of satellite in m, converted to km
+        lp['alt'] = sat.elevation / 1000.0
+
+        # Get ECEF position of satellite
+        lp['x'], lp['y'], lp['z'] = OMMBV.geodetic_to_ecef(lp['glat'],
+                                                           lp['glong'],
+                                                           lp['alt'])
+        output_params.append(lp)
+
+    output = pds.DataFrame(output_params, index=index)
+    # Modify input object to include calculated parameters
+    # Put data into DataFrame
+    data = pds.DataFrame({'glong': output['glong'],
+                          'glat': output['glat'],
+                          'alt': output['alt'],
+                          'position_ecef_x': output['x'],
+                          'position_ecef_y': output['y'],
+                          'position_ecef_z': output['z'],
+                          'obs_sat_az_angle': output['obs_sat_az_angle'],
+                          'obs_sat_el_angle': output['obs_sat_el_angle'],
+                          'obs_sat_slant_range':
+                          output['obs_sat_slant_range']},
+                         index=index)
+    data.index.name = 'Epoch'
+
+    return data, meta.copy()
+
+
+list_files = functools.partial(ps_meth.list_files, test_dates=_test_dates)
+download = functools.partial(ps_meth.download)
+
+# Create metadata corresponding to variables in load routine just above.
+# Defined here here rather than above to avoid regeneration at every load call.
+meta = pysat.Meta()
+meta['Epoch'] = {
+    meta.labels.units: 'Milliseconds since 1970-1-1',
+    meta.labels.notes: 'UTC time at middle of geophysical measurement.',
+    meta.labels.desc: 'UTC seconds',
+    meta.labels.name: 'Time index in milliseconds'}
+meta['glong'] = {meta.labels.units: 'degrees',
+                 meta.labels.desc: 'WGS84 geodetic longitude'}
+meta['glat'] = {meta.labels.units: 'degrees',
+                meta.labels.desc: 'WGS84 geodetic latitude'}
+meta['alt'] = {meta.labels.units: 'km',
+               meta.labels.desc: "WGS84 height above Earth's surface"}
+meta['position_ecef_x'] = {meta.labels.units: 'km',
+                           meta.labels.desc: 'ECEF x co-ordinate of satellite'}
+meta['position_ecef_y'] = {meta.labels.units: 'km',
+                           meta.labels.desc: 'ECEF y co-ordinate of satellite'}
+meta['position_ecef_z'] = {meta.labels.units: 'km',
+                           meta.labels.desc: 'ECEF z co-ordinate of satellite'}
+meta['obs_sat_az_angle'] = {
+    meta.labels.units: 'degrees',
+    meta.labels.desc: 'Azimuth of satellite from ground station'}
+meta['obs_sat_el_angle'] = {
+    meta.labels.units: 'degrees',
+    meta.labels.desc: 'Elevation of satellite from ground station'}
+meta['obs_sat_slant_range'] = {
+    meta.labels.units: 'km',
+    meta.labels.desc: 'Distance of satellite from ground station'}
diff --git a/pysatMissions/instruments/missions_sgp4.py b/pysatMissions/instruments/missions_sgp4.py
new file mode 100644
index 00000000..ad3b84ae
--- /dev/null
+++ b/pysatMissions/instruments/missions_sgp4.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+"""
+Produces satellite orbit data. Orbit is simulated using
+Two Line Elements (TLEs) and SGP4.
+
+Properties
+----------
+platform
+    'missions'
+name
+    'sgp4'
+tag
+    None supported
+inst_id
+    None supported
+
+"""
+
+import datetime as dt
+import functools
+import pandas as pds
+
+import pysat
+from pysat.instruments.methods import testing as ps_meth
+from pysatMissions.instruments import _core as mcore
+
+logger = pysat.logger
+
+# -------------------------------
+# Required Instrument attributes
+platform = 'missions'
+name = 'sgp4'
+tags = {'': 'Satellite simulation data set'}
+inst_ids = {'': ['']}
+
+# ------------------
+# Testing attributes
+_test_dates = {'': {'': dt.datetime(2018, 1, 1)}}
+
+
+def init(self):
+    """
+    Initializes the Instrument object with required values.
+
+    Runs once upon instantiation.
+
+    """
+
+    self.acknowledgements = ' '.join((
+        'The project uses the sgp4 library available at',
+        'https://github.com/brandon-rhodes/python-sgp4'))
+    self.references = ' '.join((
+        'Vallado, David A., Paul Crawford, Richard, Hujsak, and T.S. Kelso,',
+        '"Revisiting Spacetrack Report #3," presented at the AIAA/AAS',
+        'Astrodynamics Specialist Conference, Keystone, CO, 2006',
+        'August 21–24.'))
+    logger.info(self.acknowledgements)
+
+    return
+
+
+# Clean method
+clean = mcore._clean
+
+
+def load(fnames, tag=None, inst_id=None, obs_long=0., obs_lat=0., obs_alt=0.,
+         TLE1=None, TLE2=None, num_samples=None, cadence='1S'):
+    """
+    Returns data and metadata in the format required by pysat. Generates
+    position of satellite in ECI co-ordinates.
+
+    Routine is directly called by pysat and not the user.
+
+    Parameters
+    ----------
+    fnames : list-like collection
+        File name that contains date in its name.
+    tag : string
+        Identifies a particular subset of satellite data
+    inst_id : string
+        Instrument satellite ID (accepts '' or a number (i.e., '10'), which
+        specifies the number of seconds to simulate the satellite)
+        (default='')
+    obs_long: float
+        Longitude of the observer on the Earth's surface
+        (default=0.)
+    obs_lat: float
+        Latitude of the observer on the Earth's surface
+        (default=0.)
+    obs_alt: float
+        Altitude of the observer on the Earth's surface
+        (default=0.)
+    TLE1 : string
+        First string for Two Line Element. Must be in TLE format
+    TLE2 : string
+        Second string for Two Line Element. Must be in TLE format
+    num_samples : int
+        Number of samples per day
+    cadence : str
+        Uses pandas.frequency string formatting ('1S', etc)
+        (default='1S')
+
+    Returns
+    -------
+    data : pandas.DataFrame
+        Object containing satellite data
+    meta : pysat.Meta
+        Object containing metadata such as column names and units
+
+    Example
+    -------
+    ::
+
+          TLE1='1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998'
+          TLE2='2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452'
+          inst = pysat.Instrument('pysat', 'sgp4', TLE1=TLE1, TLE2=TLE2)
+          inst.load(2018, 1)
+
+    """
+
+    # wgs72 is the most commonly used gravity model in satellite tracking
+    # community
+    from sgp4.earth_gravity import wgs72
+    from sgp4.io import twoline2rv
+
+    # TLEs (Two Line Elements for ISS)
+    # format of TLEs is fixed and available from wikipedia...
+    # lines encode list of orbital elements of an Earth-orbiting object
+    # for a given point in time
+    line1 = ('1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998')
+    line2 = ('2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452')
+    # use ISS defaults if not provided by user
+    if TLE1 is not None:
+        line1 = TLE1
+    if TLE2 is not None:
+        line2 = TLE2
+
+    if num_samples is None:
+        num_samples = 100
+
+    # Create satellite from TLEs and assuming a gravity model;
+    # according to module webpage, wgs72 is common
+    satellite = twoline2rv(line1, line2, wgs72)
+
+    # Extract list of times from filenames and inst_id
+    times, index, dates = ps_meth.generate_times(fnames, num_samples,
+                                                 freq=cadence)
+
+    # Create list to hold satellite position, velocity
+    position = []
+    velocity = []
+    for timestep in index:
+        # orbit propagator - computes x,y,z position and velocity
+        pos, vel = satellite.propagate(timestep.year, timestep.month,
+                                       timestep.day, timestep.hour,
+                                       timestep.minute, timestep.second)
+        position.extend(pos)
+        velocity.extend(vel)
+
+    # Put data into DataFrame
+    data = pds.DataFrame({'position_eci_x': position[::3],
+                          'position_eci_y': position[1::3],
+                          'position_eci_z': position[2::3],
+                          'velocity_eci_x': velocity[::3],
+                          'velocity_eci_y': velocity[1::3],
+                          'velocity_eci_z': velocity[2::3]},
+                         index=index)
+    data.index.name = 'Epoch'
+
+    # TODO: add call for GEI/ECEF translation here => #56
+
+    return data, meta.copy()
+
+
+list_files = functools.partial(ps_meth.list_files, test_dates=_test_dates)
+download = functools.partial(ps_meth.download)
+clean = functools.partial(mcore._clean)
+
+# Create metadata corresponding to variables in load routine just above
+# made once here rather than regenerate every load call
+meta = pysat.Meta()
+meta['Epoch'] = {
+    meta.labels.units: 'Milliseconds since 1970-1-1',
+    meta.labels.notes: 'UTC time at middle of geophysical measurement.',
+    meta.labels.desc: 'UTC seconds',
+    meta.labels.name: 'Time index in milliseconds'}
+meta['position_eci_x'] = {
+    meta.labels.units: 'km',
+    meta.labels.name: 'ECI x-position',
+    meta.labels.desc: 'Earth Centered Inertial x-position of satellite.'}
+meta['position_eci_y'] = {
+    meta.labels.units: 'km',
+    meta.labels.name: 'ECI y-position',
+    meta.labels.desc: 'Earth Centered Inertial y-position of satellite.'}
+meta['position_eci_z'] = {
+    meta.labels.units: 'km',
+    meta.labels.name: 'ECI z-position',
+    meta.labels.desc: 'Earth Centered Inertial z-position of satellite.'}
+meta['velocity_eci_x'] = {
+    meta.labels.units: 'km/s',
+    meta.labels.desc: 'Satellite velocity along ECI-x',
+    meta.labels.name: 'Satellite velocity ECI-x'}
+meta['velocity_eci_y'] = {
+    meta.labels.units: 'km/s',
+    meta.labels.desc: 'Satellite velocity along ECI-y',
+    meta.labels.name: 'Satellite velocity ECI-y'}
+meta['velocity_eci_z'] = {
+    meta.labels.units: 'km/s',
+    meta.labels.desc: 'Satellite velocity along ECI-z',
+    meta.labels.name: 'Satellite velocity ECI-z'}
diff --git a/pysatMissions/instruments/pysat_ephem.py b/pysatMissions/instruments/pysat_ephem.py
deleted file mode 100644
index 8039c3c4..00000000
--- a/pysatMissions/instruments/pysat_ephem.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Produces satellite orbit data. Orbit is simulated using
-Two Line Elements (TLEs) and ephem. Satellite position is coupled
-to several space science models to simulate the atmosphere the
-satellite is in.
-
-"""
-
-from __future__ import print_function
-from __future__ import absolute_import
-import datetime as dt
-import functools
-import numpy as np
-
-import ephem
-import pandas as pds
-import pysat
-import pysatMagVect
-
-from pysatMissions.instruments import _core as mcore
-from pysatMissions.methods import magcoord as mm_magcoord
-from pysatMissions.methods import empirical as mm_emp
-from pysatMissions.methods import spacecraft as mm_sc
-
-# pysat required parameters
-platform = 'pysat'
-name = 'ephem'
-# dictionary of data 'tags' and corresponding description
-tags = {'': 'Satellite simulation data set'}
-# dictionary of satellite IDs, list of corresponding tags
-sat_ids = {'': ['']}
-_test_dates = {'': {'': dt.datetime(2018, 1, 1)}}
-
-
-def init(self):
-    """
-    Adds custom calculations to orbit simulation.
-    This routine is run once, and only once, upon instantiation.
-
-    Adds quasi-dipole coordiantes, velocity calculation in ECEF coords,
-    adds the attitude vectors of spacecraft assuming x is ram pointing and
-    z is generally nadir, adds ionospheric parameters from the Interational
-    Reference Ionosphere (IRI), as well as simulated winds from the
-    Horiontal Wind Model (HWM).
-
-    """
-
-    self.custom.attach(mm_magcoord.add_quasi_dipole_coordinates)
-    self.custom.attach(mm_magcoord.add_aacgm_coordinates)
-    self.custom.attach(mm_sc.calculate_ecef_velocity)
-    self.custom.attach(mm_sc.add_ram_pointing_sc_attitude_vectors)
-    # project simulated vectors onto s/c basis
-    # IGRF
-    self.custom.attach(mm_emp.add_igrf)
-    # create metadata to be added along with vector projection
-    in_meta = {'desc': 'IGRF geomagnetic field expressed in the s/c basis.',
-               'units': 'nT'}
-    # project IGRF
-    self.custom.attach(mm_sc.project_ecef_vector_onto_sc,
-                       args=['B_ecef_x', 'B_ecef_y', 'B_ecef_z', 'B_sc_x',
-                             'B_sc_y', 'B_sc_z'],
-                       kwargs={'meta': [in_meta.copy(), in_meta.copy(),
-                                        in_meta.copy()]})
-    # Thermal Ion Parameters
-    self.custom.attach(mm_emp.add_iri_thermal_plasma)
-    # Thermal Neutral parameters
-    self.custom.attach(mm_emp.add_msis)
-    self.custom.attach(mm_emp.add_hwm_winds_and_ecef_vectors)
-    # project total wind vector
-    self.custom.attach(mm_emp.project_hwm_onto_sc)
-
-
-def load(fnames, tag=None, sat_id=None, obs_long=0., obs_lat=0., obs_alt=0.,
-         TLE1=None, TLE2=None):
-    """
-    Returns data and metadata in the format required by pysat. Generates
-    position of satellite in both geographic and ECEF co-ordinates.
-
-    Routine is directly called by pysat and not the user.
-
-    Parameters
-    ----------
-    fnames : list-like collection
-        File name that contains date in its name.
-    tag : string
-        Identifies a particular subset of satellite data
-    sat_id : string
-        Instrument satellite ID (accepts '' or a number (i.e., '10'), which
-        specifies the number of seconds to simulate the satellite)
-        (default = '')
-    obs_long: float
-        Longitude of the observer on the Earth's surface
-        (default = 0.)
-    obs_lat: float
-        Latitude of the observer on the Earth's surface
-        (default = 0.)
-    obs_alt: float
-        Altitude of the observer on the Earth's surface
-        (default = 0.)
-    TLE1 : string
-        First string for Two Line Element. Must be in TLE format
-    TLE2 : string
-        Second string for Two Line Element. Must be in TLE format
-
-    Returns
-    -------
-    data : (pandas.DataFrame)
-        Object containing satellite data
-    meta : (pysat.Meta)
-        Object containing metadata such as column names and units
-
-    Example
-    -------
-      inst = pysat.Instrument('pysat', 'sgp4',
-          TLE1='1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998',
-          TLE2='2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452')
-      inst.load(2018, 1)
-
-    """
-
-    # TLEs (Two Line Elements for ISS)
-    # format of TLEs is fixed and available from wikipedia...
-    # lines encode list of orbital elements of an Earth-orbiting object
-    # for a given point in time
-    line1 = ''.join(('1 25544U 98067A   18135.61844383  .00002728  00000-0  ',
-                     '48567-4 0  9998'))
-    line2 = ''.join(('2 25544  51.6402 181.0633 0004018  88.8954  22.2246 ',
-                     '15.54059185113452'))
-    # use ISS defaults if not provided by user
-    if TLE1 is not None:
-        line1 = TLE1
-    if TLE2 is not None:
-        line2 = TLE2
-
-    # Extract list of times from filenames and sat_id
-    times = mcore._get_times(fnames, sat_id)
-
-    # the observer's (ground station) position on the Earth surface
-    site = ephem.Observer()
-    site.lon = str(obs_long)
-    site.lat = str(obs_lat)
-    site.elevation = obs_alt
-
-    # The first parameter in readtle() is the satellite name
-    sat = ephem.readtle('pysat', line1, line2)
-    output_params = []
-    for timestep in times:
-        lp = {}
-        site.date = timestep
-        sat.compute(site)
-        # parameters relative to the ground station
-        lp['obs_sat_az_angle'] = ephem.degrees(sat.az)
-        lp['obs_sat_el_angle'] = ephem.degrees(sat.alt)
-        # total distance away
-        lp['obs_sat_slant_range'] = sat.range
-        # satellite location
-        # sub latitude point
-        lp['glat'] = np.degrees(sat.sublat)
-        # sublongitude point
-        lp['glong'] = np.degrees(sat.sublong)
-        # elevation of sat in m, stored as km
-        lp['alt'] = sat.elevation/1000.
-        # get ECEF position of satellite
-        lp['x'], lp['y'], lp['z'] = pysatMagVect.geodetic_to_ecef(lp['glat'],
-                                                                  lp['glong'],
-                                                                  lp['alt'])
-        output_params.append(lp)
-
-    output = pds.DataFrame(output_params, index=times)
-    # modify input object to include calculated parameters
-    # put data into DataFrame
-    data = pds.DataFrame({'glong': output['glong'],
-                          'glat': output['glat'],
-                          'alt': output['alt'],
-                          'position_ecef_x': output['x'],
-                          'position_ecef_y': output['y'],
-                          'position_ecef_z': output['z'],
-                          'obs_sat_az_angle': output['obs_sat_az_angle'],
-                          'obs_sat_el_angle': output['obs_sat_el_angle'],
-                          'obs_sat_slant_range':
-                          output['obs_sat_slant_range']},
-                         index=times)
-    data.index.name = 'Epoch'
-
-    return data, meta.copy()
-
-
-list_files = functools.partial(mcore._list_files)
-download = functools.partial(mcore._download)
-
-# create metadata corresponding to variables in load routine just above
-# made once here rather than regenerate every load call
-meta = pysat.Meta()
-meta['Epoch'] = {'units': 'Milliseconds since 1970-1-1',
-                 'Bin_Location': 0.5,
-                 'notes': 'UTC time at middle of geophysical measurement.',
-                 'desc': 'UTC seconds',
-                 'long_name': 'Time index in milliseconds'}
-meta['glong'] = {'units': 'degrees',
-                 'long_name': 'Geodetic longitude',
-                 'desc': 'WGS84 geodetic longitude'}
-meta['glat'] = {'units': 'degrees',
-                'long_name': 'Geodetic latitude',
-                'desc': 'WGS84 geodetic latitude'}
-meta['alt'] = {'units': 'km',
-               'long_name': 'Geodetic height',
-               'desc': "WGS84 height above Earth's surface"}
-meta['position_ecef_x'] = {'units': 'km',
-                           'desc': 'ECEF x co-ordinate of satellite'}
-meta['position_ecef_y'] = {'units': 'km',
-                           'desc': 'ECEF y co-ordinate of satellite'}
-meta['position_ecef_z'] = {'units': 'km',
-                           'desc': 'ECEF z co-ordinate of satellite'}
-meta['obs_sat_az_angle'] = {'units': 'degrees',
-                            'desc': 'Azimuth of satellite from ground station'}
-meta['obs_sat_el_angle'] = {'units': 'degrees',
-                            'desc': 'Elevation of satellite from ground ' +
-                            'station'}
-meta['obs_sat_slant_range'] = {'units': 'km',
-                               'desc': 'Distance of satellite from ground ' +
-                               'station'}
diff --git a/pysatMissions/instruments/pysat_sgp4.py b/pysatMissions/instruments/pysat_sgp4.py
deleted file mode 100644
index 52b6938b..00000000
--- a/pysatMissions/instruments/pysat_sgp4.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Produces satellite orbit data. Orbit is simulated using
-Two Line Elements (TLEs) and SGP4.
-
-"""
-
-from __future__ import print_function
-from __future__ import absolute_import
-import datetime as dt
-import functools
-import pandas as pds
-
-import pysat
-
-from pysatMissions.instruments import _core as mcore
-
-# pysat required parameters
-platform = 'pysat'
-name = 'sgp4'
-# dictionary of data 'tags' and corresponding description
-tags = {'': 'Satellite simulation data set'}
-# dictionary of satellite IDs, list of corresponding tags
-sat_ids = {'': ['']}
-_test_dates = {'': {'': dt.datetime(2018, 1, 1)}}
-
-
-def init(self):
-    """
-    Adds custom calculations to orbit simulation.
-    This routine is run once, and only once, upon instantiation.
-
-    """
-
-    pass
-
-
-def load(fnames, tag=None, sat_id=None, obs_long=0., obs_lat=0., obs_alt=0.,
-         TLE1=None, TLE2=None):
-    """
-    Returns data and metadata in the format required by pysat. Generates
-    position of satellite in ECI co-ordinates.
-
-    Routine is directly called by pysat and not the user.
-
-    Parameters
-    ----------
-    fnames : list-like collection
-        File name that contains date in its name.
-    tag : string
-        Identifies a particular subset of satellite data
-    sat_id : string
-        Instrument satellite ID (accepts '' or a number (i.e., '10'), which
-        specifies the number of seconds to simulate the satellite)
-        (default = '')
-    obs_long: float
-        Longitude of the observer on the Earth's surface
-        (default = 0.)
-    obs_lat: float
-        Latitude of the observer on the Earth's surface
-        (default = 0.)
-    obs_alt: float
-        Altitude of the observer on the Earth's surface
-        (default = 0.)
-    TLE1 : string
-        First string for Two Line Element. Must be in TLE format
-    TLE2 : string
-        Second string for Two Line Element. Must be in TLE format
-
-    Returns
-    -------
-    data : (pandas.DataFrame)
-        Object containing satellite data
-    meta : (pysat.Meta)
-        Object containing metadata such as column names and units
-
-    Example
-    -------
-      inst = pysat.Instrument('pysat', 'sgp4',
-          TLE1='1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998',
-          TLE2='2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452')
-      inst.load(2018, 1)
-
-    """
-
-    # wgs72 is the most commonly used gravity model in satellite tracking
-    # community
-    from sgp4.earth_gravity import wgs72
-    from sgp4.io import twoline2rv
-
-    # TLEs (Two Line Elements for ISS)
-    # format of TLEs is fixed and available from wikipedia...
-    # lines encode list of orbital elements of an Earth-orbiting object
-    # for a given point in time
-    line1 = ('1 25544U 98067A   18135.61844383  .00002728  00000-0  48567-4 0  9998')
-    line2 = ('2 25544  51.6402 181.0633 0004018  88.8954  22.2246 15.54059185113452')
-    # use ISS defaults if not provided by user
-    if TLE1 is not None:
-        line1 = TLE1
-    if TLE2 is not None:
-        line2 = TLE2
-
-    # create satellite from TLEs and assuming a gravity model
-    # according to module webpage, wgs72 is common
-    satellite = twoline2rv(line1, line2, wgs72)
-
-    # Extract list of times from filenames and sat_id
-    times = mcore._get_times(fnames, sat_id)
-
-    # create list to hold satellite position, velocity
-    position = []
-    velocity = []
-    for timestep in times:
-        # orbit propagator - computes x,y,z position and velocity
-        pos, vel = satellite.propagate(timestep.year, timestep.month,
-                                       timestep.day, timestep.hour,
-                                       timestep.minute, timestep.second)
-        position.extend(pos)
-        velocity.extend(vel)
-
-    # put data into DataFrame
-    data = pds.DataFrame({'position_eci_x': position[::3],
-                          'position_eci_y': position[1::3],
-                          'position_eci_z': position[2::3],
-                          'velocity_eci_x': velocity[::3],
-                          'velocity_eci_y': velocity[1::3],
-                          'velocity_eci_z': velocity[2::3]},
-                         index=times)
-    data.index.name = 'Epoch'
-
-    # TODO: add call for GEI/ECEF translation here
-
-    return data, meta.copy()
-
-
-list_files = functools.partial(mcore._list_files)
-download = functools.partial(mcore._download)
-
-# create metadata corresponding to variables in load routine just above
-# made once here rather than regenerate every load call
-meta = pysat.Meta()
-meta['Epoch'] = {'units': 'Milliseconds since 1970-1-1',
-                 'Bin_Location': 0.5,
-                 'notes': 'UTC time at middle of geophysical measurement.',
-                 'desc': 'UTC seconds',
-                 'long_name': 'Time index in milliseconds'}
-meta['position_eci_x'] = {'units': 'km',
-                          'long_name': 'ECI x-position',
-                          'desc': 'Earth Centered Inertial x-position of ' +
-                          'satellite.',
-                          'label': 'ECI-X'}
-meta['position_eci_y'] = {'units': 'km',
-                          'long_name': 'ECI y-position',
-                          'desc': 'Earth Centered Inertial y-position of ' +
-                          'satellite.',
-                          'label': 'ECI-Y'}
-meta['position_eci_z'] = {'units': 'km',
-                          'long_name': 'ECI z-position',
-                          'desc': 'Earth Centered Inertial z-position of ' +
-                          'satellite.',
-                          'label': 'ECI-Z'}
-meta['velocity_eci_x'] = {'units': 'km/s',
-                          'desc': 'Satellite velocity along ECI-x',
-                          'long_name': 'Satellite velocity ECI-x'}
-meta['velocity_eci_y'] = {'units': 'km/s',
-                          'desc': 'Satellite velocity along ECI-y',
-                          'long_name': 'Satellite velocity ECI-y'}
-meta['velocity_eci_z'] = {'units': 'km/s',
-                          'desc': 'Satellite velocity along ECI-z',
-                          'long_name': 'Satellite velocity ECI-z'}
diff --git a/pysatMissions/methods/__init__.py b/pysatMissions/methods/__init__.py
index bc3e97a7..7375ae87 100644
--- a/pysatMissions/methods/__init__.py
+++ b/pysatMissions/methods/__init__.py
@@ -3,8 +3,7 @@
 the methods to interface with numerous empirical model packages
 """
 
-from pysatMissions.methods import empirical
 from pysatMissions.methods import magcoord
 from pysatMissions.methods import spacecraft
 
-__all__ = ['empirical', 'magcoord', 'spacecraft']
+__all__ = ['magcoord', 'spacecraft']
diff --git a/pysatMissions/methods/empirical.py b/pysatMissions/methods/empirical.py
deleted file mode 100644
index 8c1678d6..00000000
--- a/pysatMissions/methods/empirical.py
+++ /dev/null
@@ -1,476 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Provides default routines for projecting pyglow model values onto locations
-from pysat instruments.
-
-"""
-
-import pandas as pds
-import numpy as np
-import warnings
-
-try:
-    from pyglow.pyglow import Point
-except ImportError:
-    pass
-import pysatMagVect
-
-from pysatMissions.methods import spacecraft as mm_sc
-
-
-# TODO add checks for ECEF and import rest of changes here
-pyglow_warning = ' '.join(['pyglow must be installed to use this',
-                           'function.  See instructions at',
-                           'https://github.com/pysat/pysatMissions'])
-
-
-def add_iri_thermal_plasma(inst, glat_label='glat', glong_label='glong',
-                           alt_label='alt'):
-    """
-    Uses IRI (International Reference Ionosphere) model to simulate an
-    ionosphere.
-
-    Uses pyglow module to run IRI. Configured to use actual solar parameters
-    to run model.
-
-    Parameters
-    ----------
-    inst : pysat.Instrument
-        instrument object including lat, lon, and alt as timeseries
-    glat_label : string
-        label used in inst to identify WGS84 geodetic latitude (degrees)
-    glong_label : string
-        label used in inst to identify WGS84 geodetic longitude (degrees)
-    alt_label : string
-        label used in inst to identify WGS84 geodetic altitude (km, height
-        above surface)
-
-    Returns
-    -------
-    inst
-        Input pysat.Instrument object modified to include thermal plasma
-        parameters.
-        'ion_temp' for ion temperature in Kelvin
-        'e_temp' for electron temperature in Kelvin
-        'ion_dens' for the total ion density (O+ and H+)
-        'frac_dens_o' for the fraction of total density that is O+
-        'frac_dens_h' for the fraction of total density that is H+
-
-    Example
-    -------
-        # function added velow modifies the inst object upon every inst.load
-        call inst.custom.attach(add_iri_thermal_plasma,
-        kwargs={'glat_label': 'custom_label'})
-
-    """
-
-    iri_params = []
-    try:
-        for time, lat, lon, alt in zip(inst.data.index, inst[glat_label],
-                                       inst[glong_label], inst[alt_label]):
-            # Point class is instantiated. Its parameters are a function of
-            # time and spatial location
-            pt = Point(time, lat, lon, alt)
-            # IRI-2016 currently experiencing bugs in pyglow
-            pt.run_iri(version=2012)
-            iri = {}
-            # After the model is run, its members like Ti, ni[O+], etc. can be
-            # accessed
-            iri['ion_temp'] = pt.Ti
-            iri['e_temp'] = pt.Te
-            iri['ion_dens'] = pt.ni['O+'] + pt.ni['H+'] + pt.ni['HE+']
-            # pt.ne - pt.ni['NO+'] - pt.ni['O2+'] - pt.ni['HE+']
-            iri['frac_dens_o'] = pt.ni['O+']/iri['ion_dens']
-            iri['frac_dens_h'] = pt.ni['H+']/iri['ion_dens']
-            iri['frac_dens_he'] = pt.ni['HE+']/iri['ion_dens']
-            iri_params.append(iri)
-        iri = pds.DataFrame(iri_params)
-        iri.index = inst.data.index
-        inst[iri.keys()] = iri
-    except NameError:
-        # Triggered if pyglow not installed
-        warnings.warn(pyglow_warning, stacklevel=2)
-
-    inst.meta['ion_temp'] = {'units': 'Kelvin', 'long_name': 'Ion Temperature',
-                             'desc': ' '.join(['Ion temperature from IRI',
-                                               'model run.'])}
-    inst.meta['ion_dens'] = {'units': 'N/cc', 'long_name': 'Ion Density',
-                             'desc': ' '.join(['Total ion density including O+'
-                                               'and H+ from IRI model run.'])}
-    inst.meta['frac_dens_o'] = {'units': '',
-                                'long_name': 'Fractional O+ Density',
-                                'desc': ' '.join(['Fraction of O+ generated'
-                                                  'from IRI model run.'])}
-    inst.meta['frac_dens_h'] = {'units': '',
-                                'long_name': 'Fractional H+ Density',
-                                'desc': ' '.join(['Fraction of O+ generated'
-                                                  'from IRI model run.'])}
-
-
-def add_igrf(inst, glat_label='glat', glong_label='glong', alt_label='alt'):
-    """
-    Uses International Geomagnetic Reference Field (IGRF) model to obtain
-    geomagnetic field values.
-
-    Uses pyglow module to run IGRF. Configured to use actual solar parameters
-    to run model.
-
-    Parameters
-    ----------
-    inst : pysat.Instrument
-        instrument object including lat, lon, and alt as timeseries
-    glat_label : string
-        label used in inst to identify WGS84 geodetic latitude (degrees)
-    glong_label : string
-        label used in inst to identify WGS84 geodetic longitude (degrees)
-    alt_label : string
-        label used in inst to identify WGS84 geodetic altitude (km, height
-        above surface)
-
-    Returns
-    -------
-    inst
-        Input pysat.Instrument object modified to include HWM winds.
-        'B' total geomagnetic field
-        'B_east' Geomagnetic field component along east/west directions
-                (+ east)
-        'B_north' Geomagnetic field component along north/south directions
-                (+ north)
-        'B_up' Geomagnetic field component along up/down directions (+ up)
-        'B_ecef_x' Geomagnetic field component along ECEF x
-        'B_ecef_y' Geomagnetic field component along ECEF y
-        'B_ecef_z' Geomagnetic field component along ECEF z
-
-    Example
-    -------
-        # function added velow modifies the inst object upon every inst.load
-        call inst.custom.attach(add_igrf, kwargs={'glat_label': 'custom_label'})
-
-    """
-
-    igrf_params = []
-    try:
-        for time, lat, lon, alt in zip(inst.data.index, inst[glat_label],
-                                       inst[glong_label], inst[alt_label]):
-            pt = Point(time, lat, lon, alt)
-            pt.run_igrf()
-            igrf = {}
-            igrf['B'] = pt.B
-            igrf['B_east'] = pt.Bx
-            igrf['B_north'] = pt.By
-            igrf['B_up'] = pt.Bz
-            igrf_params.append(igrf)
-        igrf = pds.DataFrame(igrf_params)
-        igrf.index = inst.data.index
-        inst[igrf.keys()] = igrf
-
-        # convert magnetic field in East/north/up to ECEF basis
-        x, y, z = pysatMagVect.enu_to_ecef_vector(inst['B_east'],
-                                                  inst['B_north'],
-                                                  inst['B_up'],
-                                                  inst[glat_label],
-                                                  inst[glong_label])
-        inst['B_ecef_x'] = x
-        inst['B_ecef_y'] = y
-        inst['B_ecef_z'] = z
-    except NameError:
-        # Triggered if pyglow not installed
-        warnings.warn(pyglow_warning, stacklevel=2)
-
-    # metadata
-    inst.meta['B'] = {'units': 'nT',
-                      'desc': 'Total geomagnetic field from IGRF.'}
-    inst.meta['B_east'] = {'units': 'nT',
-                           'desc': 'Geomagnetic field from IGRF expressed ' +
-                           'using the East/North/Up (ENU) basis.'}
-    inst.meta['B_north'] = {'units': 'nT',
-                            'desc': 'Geomagnetic field from IGRF expressed ' +
-                            'using the East/North/Up (ENU) basis.'}
-    inst.meta['B_up'] = {'units': 'nT',
-                         'desc': 'Geomagnetic field from IGRF expressed ' +
-                         'using the East/North/Up (ENU) basis.'}
-
-    inst.meta['B_ecef_x'] = {'units': 'nT',
-                             'desc': 'Geomagnetic field from IGRF expressed ' +
-                             'using the Earth Centered Earth Fixed (ECEF) ' +
-                             'basis.'}
-    inst.meta['B_ecef_y'] = {'units': 'nT',
-                             'desc': 'Geomagnetic field from IGRF expressed ' +
-                             'using the Earth Centered Earth Fixed (ECEF) ' +
-                             'basis.'}
-    inst.meta['B_ecef_z'] = {'units': 'nT',
-                             'desc': 'Geomagnetic field from IGRF expressed ' +
-                             'using the Earth Centered Earth Fixed (ECEF) ' +
-                             'basis.'}
-    return
-
-
-def add_msis(inst, glat_label='glat', glong_label='glong', alt_label='alt'):
-    """
-    Uses MSIS model to obtain thermospheric values.
-
-    Uses pyglow module to run MSIS. Configured to use actual solar parameters
-    to run model.
-
-    Parameters
-    ----------
-    inst : pysat.Instrument
-        instrument object including lat, lon, and alt as timeseries
-    glat_label : string
-        label used in inst to identify WGS84 geodetic latitude (degrees)
-    glong_label : string
-        label used in inst to identify WGS84 geodetic longitude (degrees)
-    alt_label : string
-        label used in inst to identify WGS84 geodetic altitude (km, height
-        above surface)
-
-    Returns
-    -------
-    inst
-        Input pysat.Instrument object modified to include MSIS values winds.
-        'Nn' total neutral density particles/cm^3
-        'Nn_N' Nitrogen number density (particles/cm^3)
-        'Nn_N2' N2 number density (particles/cm^3)
-        'Nn_O' Oxygen number density (particles/cm^3)
-        'Nn_O2' O2 number density (particles/cm^3)
-        'Tn_msis' Temperature from MSIS (Kelvin)
-
-    Example
-    -------
-        # function added velow modifies the inst object upon every inst.load
-        call inst.custom.attach(add_msis, kwargs={'glat_label': 'custom_label'})
-
-    """
-
-    msis_params = []
-    try:
-        for time, lat, lon, alt in zip(inst.data.index, inst[glat_label],
-                                       inst[glong_label], inst[alt_label]):
-            pt = Point(time, lat, lon, alt)
-            pt.run_msis()
-            msis = {}
-            total = 0
-            for key in pt.nn.keys():
-                total += pt.nn[key]
-            msis['Nn'] = total
-            msis['Nn_H'] = pt.nn['H']
-            msis['Nn_He'] = pt.nn['HE']
-            msis['Nn_N'] = pt.nn['N']
-            msis['Nn_N2'] = pt.nn['N2']
-            msis['Nn_O'] = pt.nn['O']
-            msis['Nn_O2'] = pt.nn['O2']
-            msis['Nn_Ar'] = pt.nn['AR']
-            msis['Tn_msis'] = pt.Tn_msis
-            msis_params.append(msis)
-        msis = pds.DataFrame(msis_params)
-        msis.index = inst.data.index
-        inst[msis.keys()] = msis
-    except NameError:
-        # Triggered if pyglow not installed
-        warnings.warn(pyglow_warning, stacklevel=2)
-
-    # metadata
-    inst.meta['Nn'] = {'units': 'cm^-3',
-                       'desc': 'Total neutral number particle density ' +
-                       'from MSIS.'}
-    inst.meta['Nn_H'] = {'units': 'cm^-3',
-                         'desc': 'Total hydrogen number particle density ' +
-                         'from MSIS.'}
-    inst.meta['Nn_He'] = {'units': 'cm^-3',
-                          'desc': 'Total helium number particle density ' +
-                          'from MSIS.'}
-    inst.meta['Nn_N'] = {'units': 'cm^-3',
-                         'desc': 'Total nitrogen number particle density ' +
-                         'from MSIS.'}
-    inst.meta['Nn_N2'] = {'units': 'cm^-3',
-                          'desc': 'Total N2 number particle density ' +
-                          'from MSIS.'}
-    inst.meta['Nn_O'] = {'units': 'cm^-3',
-                         'desc': 'Total oxygen number particle density ' +
-                         'from MSIS.'}
-    inst.meta['Nn_O2'] = {'units': 'cm^-3',
-                          'desc': 'Total O2 number particle density ' +
-                          'from MSIS.'}
-    inst.meta['Nn_Ar'] = {'units': 'cm^-3',
-                          'desc': 'Total argon number particle density ' +
-                          'from MSIS.'}
-    inst.meta['Tn_msis'] = {'units': 'K',
-                            'desc': 'Neutral temperature from MSIS.'}
-
-    return
-
-
-def add_hwm_winds_and_ecef_vectors(inst, glat_label='glat',
-                                   glong_label='glong', alt_label='alt'):
-    """
-    Uses HWM (Horizontal Wind Model) model to obtain neutral wind details.
-
-    Uses pyglow module to run HWM. Configured to use actual solar parameters
-    to run model.
-
-    Parameters
-    ----------
-    inst : pysat.Instrument
-        instrument object including lat, lon, and alt as timeseries
-    glat_label : string
-        label used in inst to identify WGS84 geodetic latitude (degrees)
-    glong_label : string
-        label used in inst to identify WGS84 geodetic longitude (degrees)
-    alt_label : string
-        label used in inst to identify WGS84 geodetic altitude (km, height
-        above surface)
-
-    Returns
-    -------
-    inst
-        Input pysat.Instrument object modified to include HWM winds.
-        'zonal_wind' for the east/west winds (u in model) in m/s
-        'meiridional_wind' for the north/south winds (v in model) in m/s
-        'unit_zonal_wind_ecef_*' (*=x,y,z) is the zonal vector expressed in
-                the ECEF basis
-        'unit_mer_wind_ecef_*' (*=x,y,z) is the meridional vector expressed
-                in the ECEF basis
-        'sim_inst_wind_*' (*=x,y,z) is the projection of the total wind
-                vector onto s/c basis
-
-    Example
-    -------
-        # function added velow modifies the inst object upon every inst.load
-        call inst.custom.attach(add_hwm_winds_and_ecef_vectors,
-        kwargs={'glat_label': 'custom_label'})
-
-    """
-
-    hwm_params = []
-    try:
-        for time, lat, lon, alt in zip(inst.data.index, inst[glat_label],
-                                       inst[glong_label], inst[alt_label]):
-            # Point class is instantiated.
-            # Its parameters are a function of time and spatial location
-            pt = Point(time, lat, lon, alt)
-            pt.run_hwm()
-            hwm = {}
-            hwm['zonal_wind'] = pt.u
-            hwm['meridional_wind'] = pt.v
-            hwm_params.append(hwm)
-        hwm = pds.DataFrame(hwm_params)
-        hwm.index = inst.data.index
-        inst[['zonal_wind', 'meridional_wind']] = hwm[['zonal_wind',
-                                                       'meridional_wind']]
-    except NameError:
-        # Triggered if pyglow not installed
-        warnings.warn(pyglow_warning, stacklevel=2)
-
-    # calculate zonal unit vector in ECEF
-    # zonal wind: east - west; positive east
-    # EW direction is tangent to XY location of S/C in ECEF coordinates
-    mag = np.sqrt(inst['position_ecef_x']**2 + inst['position_ecef_y']**2)
-    inst['unit_zonal_wind_ecef_x'] = -inst['position_ecef_y']/mag
-    inst['unit_zonal_wind_ecef_y'] = inst['position_ecef_x']/mag
-    inst['unit_zonal_wind_ecef_z'] = 0 * inst['position_ecef_x']
-
-    # calculate meridional unit vector in ECEF
-    # meridional wind: north - south; positive north
-    # mer direction completes RHS of position and zonal vector
-    unit_pos_x, unit_pos_y, unit_pos_z = \
-        pysatMagVect.normalize_vector(-inst['position_ecef_x'],
-                                      -inst['position_ecef_y'],
-                                      -inst['position_ecef_z'])
-
-    # mer = r x zonal
-    merx, mery, merz = \
-        pysatMagVect.cross_product(unit_pos_x, unit_pos_y, unit_pos_z,
-                                   inst['unit_zonal_wind_ecef_x'],
-                                   inst['unit_zonal_wind_ecef_y'],
-                                   inst['unit_zonal_wind_ecef_z'])
-    inst['unit_mer_wind_ecef_x'] = merx
-    inst['unit_mer_wind_ecef_y'] = mery
-    inst['unit_mer_wind_ecef_z'] = merz
-
-    # Adding metadata information
-    def get_ecef_wind_meta(coord='x', geo='mer'):
-        """Generates consistent metadat for ecef winds"""
-        if geo == 'mer':
-            name = 'Meridional'
-        else:
-            name = 'Zonal'
-        dict = {'units': '',
-                'long_name': ' '.join(['{name:s} Wind Unit ECEF',
-                                       '{coord:s}-vector']
-                                      ).format(name=name, coord=coord),
-                'desc': ' '.join(['{coord:s}-value of {name:s} wind unit',
-                                  'vector in ECEF coordinates']
-                                 ).format(name=name.lower(), coord=coord)}
-        return dict
-
-    inst.meta['zonal_wind'] = {'units': 'm/s',
-                               'long_name': 'Zonal Wind',
-                               'desc': 'HWM model zonal wind'}
-    inst.meta['meridional_wind'] = {'units': 'm/s',
-                                    'long_name': 'Meridional Wind',
-                                    'desc': 'HWM model meridional wind'}
-    inst.meta['unit_zonal_wind_ecef_x'] = get_ecef_wind_meta(coord='x',
-                                                             geo='zon')
-    inst.meta['unit_zonal_wind_ecef_y'] = get_ecef_wind_meta(coord='y',
-                                                             geo='zon')
-    inst.meta['unit_zonal_wind_ecef_z'] = get_ecef_wind_meta(coord='z',
-                                                             geo='zon')
-    inst.meta['unit_mer_wind_ecef_x'] = get_ecef_wind_meta(coord='x',
-                                                           geo='mer')
-    inst.meta['unit_mer_wind_ecef_y'] = get_ecef_wind_meta(coord='y',
-                                                           geo='mer')
-    inst.meta['unit_mer_wind_ecef_z'] = get_ecef_wind_meta(coord='z',
-                                                           geo='mer')
-
-    return
-
-
-def project_hwm_onto_sc(inst):
-    """
-    Projects the modeled wind onto the spacecraft coordinates.
-
-    Parameters
-    ----------
-    inst : pysat.Instrument
-        instrument object including unit vectors in ecef coords
-
-    Returns
-    -------
-    inst
-        Input pysat.Instrument object modified to include neutral wind
-        parameters.
-
-    Example
-    -------
-        # function added velow modifies the inst object upon every inst.load
-        call inst.custom.attach(project_hwm_onto_sc)
-
-    """
-
-    def get_wind_comp(inst, direction='x'):
-        unit_zon = 'unit_zonal_wind_ecef_' + direction
-        unit_mer = 'unit_mer_wind_ecef_' + direction
-
-        return (inst['zonal_wind']*inst[unit_zon] +
-                inst['meridional_wind']*inst[unit_mer])
-
-    def get_wind_meta(coord='x'):
-        dict = {'units': 'm/s',
-                'long_name': ' '.join(['Simulated {:s}-vector instrument',
-                                       'wind']).format(coord),
-                'desc': ' '.join(['Wind from model as measured by instrument',
-                                  'in its {:s}-direction']).format(coord)}
-        return dict
-
-    inst['total_wind_x'] = get_wind_comp(inst, direction='x')
-    inst['total_wind_y'] = get_wind_comp(inst, direction='y')
-    inst['total_wind_z'] = get_wind_comp(inst, direction='z')
-
-    mm_sc.project_ecef_vector_onto_sc(inst, 'total_wind_x', 'total_wind_y',
-                                      'total_wind_z', 'sim_wind_sc_x',
-                                      'sim_wind_sc_y', 'sim_wind_sc_z')
-
-    inst.meta['sim_wind_sc_x'] = get_wind_meta('x')
-    inst.meta['sim_wind_sc_y'] = get_wind_meta('y')
-    inst.meta['sim_wind_sc_z'] = get_wind_meta('z')
-
-    return
diff --git a/pysatMissions/methods/magcoord.py b/pysatMissions/methods/magcoord.py
index ff68f3dc..a571b425 100644
--- a/pysatMissions/methods/magcoord.py
+++ b/pysatMissions/methods/magcoord.py
@@ -1,5 +1,5 @@
-"""Provides default routines for projecting aacgmv2 and apexpy model values onto locations
-from pysat instruments.
+"""Provides default routines for projecting aacgmv2 and apexpy model values onto
+locations from pysat instruments.
 
 """
 
@@ -113,7 +113,7 @@ def add_quasi_dipole_coordinates(inst, glat_label='glat', glong_label='glong',
     mlt = []
     for lat, lon, alt, time in zip(inst[glat_label], inst[glong_label],
                                    inst[alt_label], inst.data.index):
-        # quasi-dipole latitude and longitude from geodetic coords
+        # Quasi-dipole latitude and longitude from geodetic coords
         tlat, tlon = ap.geo2qd(lat, lon, alt)
         qd_lat.append(tlat)
         qd_lon.append(tlon)
diff --git a/pysatMissions/methods/spacecraft.py b/pysatMissions/methods/spacecraft.py
index 3b1774ed..6600a6fb 100644
--- a/pysatMissions/methods/spacecraft.py
+++ b/pysatMissions/methods/spacecraft.py
@@ -4,7 +4,7 @@
 """
 
 import numpy as np
-import pysatMagVect
+import OMMBV
 
 
 def add_ram_pointing_sc_attitude_vectors(inst):
@@ -40,96 +40,96 @@ def add_ram_pointing_sc_attitude_vectors(inst):
 
     """
 
-    # ram pointing is along velocity vector
+    # Ram pointing is along velocity vector
     inst['sc_xhat_ecef_x'], inst['sc_xhat_ecef_y'], inst['sc_xhat_ecef_z'] = \
-        pysatMagVect.normalize_vector(inst['velocity_ecef_x'],
-                                      inst['velocity_ecef_y'],
-                                      inst['velocity_ecef_z'])
+        OMMBV.normalize_vector(inst['velocity_ecef_x'],
+                               inst['velocity_ecef_y'],
+                               inst['velocity_ecef_z'])
 
-    # begin with z along Nadir (towards Earth)
+    # Begin with z along Nadir (towards Earth)
     # if orbit isn't perfectly circular, then the s/c z vector won't
     # point exactly along nadir. However, nadir pointing is close enough
     # to the true z (in the orbital plane) that we can use it to get y,
     # and use x and y to get the real z
     inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z'] = \
-        pysatMagVect.normalize_vector(-inst['position_ecef_x'],
-                                      -inst['position_ecef_y'],
-                                      -inst['position_ecef_z'])
+        OMMBV.normalize_vector(-inst['position_ecef_x'],
+                               -inst['position_ecef_y'],
+                               -inst['position_ecef_z'])
 
     # get y vector assuming right hand rule
     # Z x X = Y
     inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z'] = \
-        pysatMagVect.cross_product(inst['sc_zhat_ecef_x'],
-                                   inst['sc_zhat_ecef_y'],
-                                   inst['sc_zhat_ecef_z'],
-                                   inst['sc_xhat_ecef_x'],
-                                   inst['sc_xhat_ecef_y'],
-                                   inst['sc_xhat_ecef_z'])
-    # normalize since Xhat and Zhat from above may not be orthogonal
+        OMMBV.cross_product(inst['sc_zhat_ecef_x'],
+                            inst['sc_zhat_ecef_y'],
+                            inst['sc_zhat_ecef_z'],
+                            inst['sc_xhat_ecef_x'],
+                            inst['sc_xhat_ecef_y'],
+                            inst['sc_xhat_ecef_z'])
+    # Normalize since Xhat and Zhat from above may not be orthogonal
     inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z'] = \
-        pysatMagVect.normalize_vector(inst['sc_yhat_ecef_x'],
-                                      inst['sc_yhat_ecef_y'],
-                                      inst['sc_yhat_ecef_z'])
+        OMMBV.normalize_vector(inst['sc_yhat_ecef_x'],
+                               inst['sc_yhat_ecef_y'],
+                               inst['sc_yhat_ecef_z'])
 
-    # strictly, need to recalculate Zhat so that it is consistent with RHS
+    # Strictly, need to recalculate Zhat so that it is consistent with RHS
     # just created
     # Z = X x Y
     inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z'] = \
-        pysatMagVect.cross_product(inst['sc_xhat_ecef_x'],
-                                   inst['sc_xhat_ecef_y'],
-                                   inst['sc_xhat_ecef_z'],
-                                   inst['sc_yhat_ecef_x'],
-                                   inst['sc_yhat_ecef_y'],
-                                   inst['sc_yhat_ecef_z'])
+        OMMBV.cross_product(inst['sc_xhat_ecef_x'],
+                            inst['sc_xhat_ecef_y'],
+                            inst['sc_xhat_ecef_z'],
+                            inst['sc_yhat_ecef_x'],
+                            inst['sc_yhat_ecef_y'],
+                            inst['sc_yhat_ecef_z'])
 
     # Adding metadata
-    inst.meta['sc_xhat_ecef_x'] = {'units': '',
-                                   'desc': 'S/C attitude (x-direction, ram) ' +
-                                   'unit vector, expressed in ECEF basis, ' +
-                                   'x-component'}
-    inst.meta['sc_xhat_ecef_y'] = {'units': '',
-                                   'desc': 'S/C attitude (x-direction, ram) ' +
-                                   'unit vector, expressed in ECEF basis, ' +
-                                   'y-component'}
-    inst.meta['sc_xhat_ecef_z'] = {'units': '',
-                                   'desc': 'S/C attitude (x-direction, ram) ' +
-                                   'unit vector, expressed in ECEF basis, ' +
-                                   'z-component'}
-
-    inst.meta['sc_zhat_ecef_x'] = {'units': '',
-                                   'desc': 'S/C attitude (z-direction, ' +
-                                   'generally nadir) unit vector, expressed ' +
-                                   'in ECEF basis, x-component'}
-    inst.meta['sc_zhat_ecef_y'] = {'units': '',
-                                   'desc': 'S/C attitude (z-direction, ' +
-                                   'generally nadir) unit vector, expressed ' +
-                                   'in ECEF basis, y-component'}
-    inst.meta['sc_zhat_ecef_z'] = {'units': '',
-                                   'desc': 'S/C attitude (z-direction, ' +
-                                   'generally nadir) unit vector, expressed ' +
-                                   'in ECEF basis, z-component'}
-
-    inst.meta['sc_yhat_ecef_x'] = {'units': '',
-                                   'desc': 'S/C attitude (y-direction, ' +
-                                   'generally south) unit vector, expressed ' +
-                                   'in ECEF basis, x-component'}
-    inst.meta['sc_yhat_ecef_y'] = {'units': '',
-                                   'desc': 'S/C attitude (y-direction, ' +
-                                   'generally south) unit vector, expressed ' +
-                                   'in ECEF basis, y-component'}
-    inst.meta['sc_yhat_ecef_z'] = {'units': '',
-                                   'desc': 'S/C attitude (y-direction, ' +
-                                   'generally south) unit vector, expressed ' +
-                                   'in ECEF basis, z-component'}
+    inst.meta['sc_xhat_ecef_x'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (x-direction, ram) unit vector,',
+                          'expressed in ECEF basis, x-component'))}
+    inst.meta['sc_xhat_ecef_y'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (x-direction, ram) unit vector,',
+                          'expressed in ECEF basis, y-component'))}
+    inst.meta['sc_xhat_ecef_z'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (x-direction, ram) unit vector,',
+                          'expressed in ECEF basis, z-component'))}
+
+    inst.meta['sc_zhat_ecef_x'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (z-direction, generally nadir) unit',
+                          'vector, expressed in ECEF basis, x-component'))}
+    inst.meta['sc_zhat_ecef_y'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (z-direction, generally nadir) unit',
+                          'vector, expressed in ECEF basis, y-component'))}
+    inst.meta['sc_zhat_ecef_z'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (z-direction, generally nadir) unit',
+                          'vector, expressed in ECEF basis, z-component'))}
+
+    inst.meta['sc_yhat_ecef_x'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (y-direction, generally south) unit',
+                          'vector, expressed in ECEF basis, x-component'))}
+    inst.meta['sc_yhat_ecef_y'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (y-direction, generally south) unit',
+                          'vector, expressed in ECEF basis, y-component'))}
+    inst.meta['sc_yhat_ecef_z'] = {
+        'units': '',
+        'desc': ' '.join(('S/C attitude (y-direction, generally south) unit',
+                          'vector, expressed in ECEF basis, z-component'))}
 
     # check what magnitudes we get
-    mag = np.sqrt(inst['sc_zhat_ecef_x']**2 + inst['sc_zhat_ecef_y']**2 +
-                  inst['sc_zhat_ecef_z']**2)
+    mag = np.sqrt(inst['sc_zhat_ecef_x']**2 + inst['sc_zhat_ecef_y']**2
+                  + inst['sc_zhat_ecef_z']**2)
     idx, = np.where((mag < .999999999) | (mag > 1.000000001))
     if len(idx) > 0:
         print(mag[idx])
-        raise RuntimeError('Unit vector generation failure. Not sufficently ' +
-                           'orthogonal.')
+        raise RuntimeError(' '.join(('Unit vector generation failure. Not',
+                                     'sufficently orthogonal.')))
 
     return
 
@@ -158,7 +158,7 @@ def calculate_ecef_velocity(inst):
     """
 
     def get_vel_from_pos(x):
-        vel = (x.values[2:] - x.values[0:-2])/2.
+        vel = (x.values[2:] - x.values[0:-2]) / 2.
         return vel
 
     vel_x = get_vel_from_pos(inst['position_ecef_x'])
@@ -170,14 +170,17 @@ def get_vel_from_pos(x):
     inst[1:-1, 'velocity_ecef_z'] = vel_z
 
     inst.meta['velocity_ecef_x'] = {'units': 'km/s',
-                                    'desc': 'Velocity of satellite ' +
-                                    'calculated with respect to ECEF frame.'}
+                                    'desc': ' '.join(('Velocity of satellite',
+                                                      'calculated with respect',
+                                                      'to ECEF frame.'))}
     inst.meta['velocity_ecef_y'] = {'units': 'km/s',
-                                    'desc': 'Velocity of satellite ' +
-                                    'calculated with respect to ECEF frame.'}
+                                    'desc': ' '.join(('Velocity of satellite',
+                                                      'calculated with respect',
+                                                      'to ECEF frame.'))}
     inst.meta['velocity_ecef_z'] = {'units': 'km/s',
-                                    'desc': 'Velocity of satellite ' +
-                                    'calculated with respect to ECEF frame.'}
+                                    'desc': ' '.join(('Velocity of satellite',
+                                                      'calculated with respect',
+                                                      'to ECEF frame.'))}
     return
 
 
@@ -210,19 +213,11 @@ def project_ecef_vector_onto_sc(inst, x_label, y_label, z_label,
 
     # TODO: add checks for existence of ecef labels in inst
 
-    x, y, z = \
-        pysatMagVect.project_ecef_vector_onto_basis(inst[x_label],
-                                                    inst[y_label],
-                                                    inst[z_label],
-                                                    inst['sc_xhat_ecef_x'],
-                                                    inst['sc_xhat_ecef_y'],
-                                                    inst['sc_xhat_ecef_z'],
-                                                    inst['sc_yhat_ecef_x'],
-                                                    inst['sc_yhat_ecef_y'],
-                                                    inst['sc_yhat_ecef_z'],
-                                                    inst['sc_zhat_ecef_x'],
-                                                    inst['sc_zhat_ecef_y'],
-                                                    inst['sc_zhat_ecef_z'])
+    x, y, z = OMMBV.project_ecef_vector_onto_basis(
+        inst[x_label], inst[y_label], inst[z_label],
+        inst['sc_xhat_ecef_x'], inst['sc_xhat_ecef_y'], inst['sc_xhat_ecef_z'],
+        inst['sc_yhat_ecef_x'], inst['sc_yhat_ecef_y'], inst['sc_yhat_ecef_z'],
+        inst['sc_zhat_ecef_x'], inst['sc_zhat_ecef_y'], inst['sc_zhat_ecef_z'])
     inst[new_x_label] = x
     inst[new_y_label] = y
     inst[new_z_label] = z
diff --git a/pysatMissions/plot.py b/pysatMissions/plot.py
deleted file mode 100644
index 172e5558..00000000
--- a/pysatMissions/plot.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Provides default plotting routines for simulated pysat instruments.
-
-"""
-
-import numpy as np
-
-
-def plot_simulated_data(inst, filename=None):
-
-    import matplotlib
-    import matplotlib.pyplot as plt
-    import matplotlib.gridspec as gridspec
-
-    if filename is None:
-        out_fname = './summary_orbit_simulated_data.png'
-    else:
-        out_fname = filename
-
-    # make monotonically increasing longitude signal
-    diff = inst['glong'].diff()
-
-    idx, = np.where(diff < 0.)
-    for item in idx:
-        inst[item:, 'glong'] += 360.
-
-    fig = plt.figure(figsize=(8.5, 7))
-
-    time1 = inst.data.index[0].strftime('%Y-%h-%d %H:%M:%S')
-    if inst.data.index[0].date() == inst.data.index[-1].date():
-        time2 = inst.data.index[-1].strftime('%H:%M:%S')
-    else:
-        time2 = inst.data.index[-1].strftime('%Y-%h-%d %H:%M:%S')
-    # Overall Plot Title
-    plt.suptitle(''.join(('Simulated inst ', time1, ' -- ', time2)),
-                 fontsize=18)
-
-    # create grid for plots
-    gs = gridspec.GridSpec(5, 2, width_ratios=[12, 1])
-
-    ax = fig.add_subplot(gs[0, 0])
-    plt.plot(np.log10(inst['ion_dens']), 'k', label='total')
-    plt.plot(np.log10(inst['ion_dens']*inst['frac_dens_o']), 'r', label='O+')
-    plt.plot(np.log10(inst['ion_dens']*inst['frac_dens_h']), 'b', label='H+')
-    plt.legend(loc=(01.01, 0.15))
-    ax.set_title('Log Ion Density')
-    ax.set_ylabel('Log Density (N/cc)')
-    ax.set_ylim([1., 6.])
-    ax.axes.get_xaxis().set_visible(False)
-
-    ax2 = fig.add_subplot(gs[1, 0], sharex=ax)
-    plt.plot(inst['ion_temp'])
-    plt.legend(loc=(1.01, 0.15))
-    ax2.set_title('Ion Temperature')
-    ax2.set_ylabel('Temp (K)')
-    ax2.set_ylim([500., 1500.])
-    ax2.axes.get_xaxis().set_visible(False)
-
-    ax3 = fig.add_subplot(gs[2, 0], sharex=ax)
-    plt.plot(inst['sim_wind_sc_x'], color='b', linestyle='--')
-    plt.plot(inst['sim_wind_sc_y'], color='r', linestyle='--')
-    plt.plot(inst['sim_wind_sc_z'], color='g', linestyle='--')
-    ax3.set_title('Neutral Winds in S/C X, Y, and Z')
-    ax3.set_ylabel('Velocity (m/s)')
-    ax3.set_ylim([-200., 200.])
-    ax3.axes.get_xaxis().set_visible(False)
-    plt.legend(loc=(1.01, 0.15))
-    ax3.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
-
-    ax4 = fig.add_subplot(gs[3, 0], sharex=ax)
-    plt.plot(inst['B_sc_x']*1e5, color='b', linestyle='--')
-    plt.plot(inst['B_sc_y']*1e5, color='r', linestyle='--')
-    plt.plot(inst['B_sc_z']*1e5, color='g', linestyle='--')
-    ax4.set_title('Magnetic Field in S/C X, Y, and Z')
-    ax4.set_ylabel('Gauss')
-    ax4.set_ylim([-3.5, 3.5])
-    plt.legend(loc=(1.01, 0.15))
-    ax4.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
-    plt.setp(ax4.xaxis.get_majorticklabels(), rotation=20, ha='right')
-
-    fig.add_subplot(gs[4, 0])
-    # do long time plot
-    inst['glat'].plot(label='glat')
-    inst['mlt'].plot(label='mlt')
-    plt.title('Satellite Position')
-    plt.legend(['mlat', 'mlt'], loc=(1.01, 0.15))
-
-    fig.tight_layout()
-    # buffer for overall title
-    fig.subplots_adjust(bottom=0.06, top=0.91, right=.91)
-    plt.subplots_adjust(hspace=0.44)
-
-    plt.savefig(out_fname)
-
-    return
diff --git a/pysatMissions/version.txt b/pysatMissions/version.txt
index 0c62199f..ee1372d3 100644
--- a/pysatMissions/version.txt
+++ b/pysatMissions/version.txt
@@ -1,1 +1,1 @@
-0.2.1
+0.2.2
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..1b1faea6
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+aacgmv2
+apexpy
+numpy
+pandas
+pysat>=3.0
+pyEphem
+sgp4
diff --git a/setup.cfg b/setup.cfg
index 22d9e742..a258b972 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,59 @@
+[metadata]
+name = pysatMissions
+version = file: pysatMissions/version.txt
+url = https://github.com/pysat/pysatMissions
+author = Jeff Klenzing, Russell Stoneback
+author_email = pysat.developers@gmail.com
+description = 'Mission Planning toolkit for pysat'
+keywords =
+  pysat
+  ionosphere
+  cubesat
+  satellite
+  ephemeris
+  orbit
+  mission-planning
+classifiers =
+  Development Status :: 3 - Alpha
+  Topic :: Scientific/Engineering :: Physics
+  Topic :: Scientific/Engineering :: Atmospheric Science
+  Intended Audience :: Science/Research
+  License :: OSI Approved :: BSD License
+  Natural Language :: English
+  Programming Language :: Python :: 3.7
+  Programming Language :: Python :: 3.8
+  Programming Language :: Python :: 3.9
+  Operating System :: MacOS :: MacOS X
+  Operating System :: POSIX :: Linux
+license_file = LICENSE
+long_description = file: README.md
+long_description_content_type = text/markdown
+
+[options]
+python_requires = >= 3.5
+setup_requires =
+  setuptools >= 38.6
+  pip >= 10
+include_package_data = True
+zip_safe = False
+packages = find:
+install_requires =
+  aacgmv2
+  apexpy
+  numpy
+  OMMBV
+  pandas
+  pysat>=3.0
+  pyEphem
+  sgp4
+
+
+[coverage:report]
+
+[flake8]
+max-line-length = 90
+ignore = W503
+
 [tool:pytest]
 markers =
     all_inst: tests all instruments
@@ -5,6 +61,3 @@ markers =
     no_download: tests for instruments without download support
     first: first tests to run
     second: second tests to run
-flake8-ignore =
-  *.py W503
-  docs/conf.py ALL
diff --git a/setup.py b/setup.py
index cd31ecd2..0a47a6bc 100644
--- a/setup.py
+++ b/setup.py
@@ -1,53 +1,11 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# Copyright (C) 2019, RS
+# Copyright (C) 2021, Authors
 # Full license can be found in License.md
 # -----------------------------------------------------------------------------
 
-import os
-import codecs
-from setuptools import setup, find_packages
+from setuptools import setup
 
+# package metadata stored in setup.cfg
 
-here = os.path.abspath(os.path.dirname(__file__))
-with codecs.open(os.path.join(here, 'description.txt'), encoding='utf-8') as f:
-    long_description = f.read()
-version_filename = os.path.join('pysatMissions', 'version.txt')
-with codecs.open(os.path.join(here, version_filename)) as version_file:
-    version = version_file.read().strip()
-
-# change setup.py for readthedocs - commented for now
-# on_rtd = os.environ.get('READTHEpysatMissionsDOCS') == 'True'
-
-install_requires = ['pysat', 'numpy', 'pandas', 'sgp4', 'pyEphem',
-                    'matplotlib', 'apexpy', 'aacgmv2', 'pysatMagVect']
-
-
-# Run setup
-
-setup(name='pysatMissions',
-      version=version,
-      url='https://github.com/pysat/pysatMissions',
-      author='Russell Stoneback, Jeff Klenzing',
-      author_email='rstoneba@utdallas.edu',
-      description='Mission Planning toolkit for pysat',
-      long_description=long_description,
-      long_description_content_type='text/markdown',
-      packages=find_packages(),
-      classifiers=[
-          "Development Status :: 4 - Beta",
-          "Topic :: Scientific/Engineering :: Astronomy",
-          "Topic :: Scientific/Engineering :: Physics",
-          "Topic :: Scientific/Engineering :: Atmospheric Science",
-          "Intended Audience :: Science/Research",
-          "License :: OSI Approved :: BSD License",
-          "Natural Language :: English",
-          "Programming Language :: Python :: 3.6",
-          "Programming Language :: Python :: 3.7",
-          "Programming Language :: Python :: 3.8",
-          "Operating System :: MacOS :: MacOS X",
-      ],
-      include_package_data=True,
-      zip_safe=False,
-      install_requires=install_requires,
-      )
+setup()


Test patch:
diff --git a/pysatMissions/tests/test_core.py b/pysatMissions/tests/test_core.py
deleted file mode 100644
index 23cbc206..00000000
--- a/pysatMissions/tests/test_core.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import pysatMissions
-import pysat
-
-
-class TestBasic():
-    """Test basic functionality
-    """
-
-    def setup(self):
-        self.module = pysatMissions.instruments.pysat_sgp4
-
-    def teardown(self):
-        del self.module
-
-    def test_module_loading(self):
-        """Check if instrument module can be loaded as a pysat instrument"""
-
-        testInst = pysat.Instrument(inst_module=self.module)
-
-        assert isinstance(testInst, pysat._instrument.Instrument)
diff --git a/pysatMissions/tests/test_instruments.py b/pysatMissions/tests/test_instruments.py
index 2d81a553..75499c01 100644
--- a/pysatMissions/tests/test_instruments.py
+++ b/pysatMissions/tests/test_instruments.py
@@ -1,78 +1,88 @@
-# -*- coding: utf-8 -*-
-"""
-Basic test of the instrument objects in pysatMissions
-
-To be replaced once pysat testing techniques are finalized
-"""
-
 import datetime as dt
 import numpy as np
+import tempfile
+
+import pytest
+
 import pysat
+# Make sure to import your instrument package here
+# e.g.,
+import pysatMissions
+
+# Import the test classes from pysat
+from pysat.utils import generate_instrument_list
+from pysat.tests.instrument_test_class import InstTestClass
+
+
+saved_path = pysat.params['data_dirs']
+
+# Developers for instrument libraries should update the following line to
+# point to their own subpackage location
+# e.g.,
+# instruments = generate_instrument_list(inst_loc=mypackage.inst)
+instruments = generate_instrument_list(inst_loc=pysatMissions.instruments)
+
+# The following lines apply the custom instrument lists to each type of test
+method_list = [func for func in dir(InstTestClass)
+               if callable(getattr(InstTestClass, func))]
+# Search tests for iteration via pytestmark, update instrument list
+for method in method_list:
+    if hasattr(getattr(InstTestClass, method), 'pytestmark'):
+        # Get list of names of pytestmarks
+        n_args = len(getattr(InstTestClass, method).pytestmark)
+        names = [getattr(InstTestClass, method).pytestmark[j].name
+                 for j in range(0, n_args)]
+        # Add instruments from your library
+        if 'all_inst' in names:
+            mark = pytest.mark.parametrize("inst_name", instruments['names'])
+            getattr(InstTestClass, method).pytestmark.append(mark)
+        elif 'download' in names:
+            mark = pytest.mark.parametrize("inst_dict", instruments['download'])
+            getattr(InstTestClass, method).pytestmark.append(mark)
+        elif 'no_download' in names:
+            mark = pytest.mark.parametrize("inst_dict",
+                                           instruments['no_download'])
+            getattr(InstTestClass, method).pytestmark.append(mark)
 
 
-class TestSGP4():
-    def setup(self):
-        """Runs before every method to create a clean testing setup."""
-        from pysatMissions.instruments import pysat_sgp4
-        self.testInst = pysat.Instrument(inst_module=pysat_sgp4, sat_id='100')
-        self.targets1 = ['position_eci_x', 'position_eci_y', 'position_eci_z',
-                         'velocity_eci_x', 'velocity_eci_y', 'velocity_eci_z']
-        self.targets2 = []
+class TestInstruments(InstTestClass):
+    """Uses class level setup and teardown so that all tests use the same
+    temporary directory. We do not want to geneate a new tempdir for each test,
+    as the load tests need to be the same as the download tests.
+    """
 
-    def teardown(self):
-        """Clean up test environment after tests"""
-        del self
+    def setup_class(self):
+        """Runs once before the tests to initialize the testing setup."""
+        # Make sure to use a temporary directory so that the user's setup is not
+        # altered
+        self.tempdir = tempfile.TemporaryDirectory()
+        self.saved_path = pysat.params['data_dirs']
+        pysat.params.data['data_dirs'] = [self.tempdir.name]
+        # Developers for instrument libraries should update the following line
+        # to point to their own subpackage location, e.g.,
+        # self.inst_loc = mypackage.instruments
+        self.inst_loc = pysatMissions.instruments
 
-    def test_basic_instrument_load(self):
-        """Checks if instrument loads proper data and metadata"""
-        # Check if instrument is instrument
-        assert isinstance(self.testInst, pysat._instrument.Instrument)
+    def teardown_class(self):
+        """Runs once to clean up testing from this class."""
+        pysat.params.data['data_dirs'] = self.saved_path
+        self.tempdir.cleanup()
+        del self.inst_loc, self.saved_path, self.tempdir
 
-        self.testInst.load(date=dt.datetime(2018, 1, 1))
-        # Check for completeness of first set of targets
-        for target in self.targets1:
-            # Check if data is added
-            assert target in self.testInst.data.keys()
-            assert not np.isnan(self.testInst[target]).any()
-            # Check if metadata is added
-            assert target in self.testInst.meta.data.index
-        # Check for completeness of second set of targets
-        for target in self.targets2:
-            # Check if data is added
-            assert target in self.testInst.data.keys()
-            # First and last values are NaN if related to velocity
-            assert not np.isnan(self.testInst[target][1:-1]).any()
-            assert np.isnan(self.testInst[target][0])
-            assert np.isnan(self.testInst[target][-1])
-            # Check if metadata is added
-            assert target in self.testInst.meta.data.index
+    # Custom package unit tests can be added here
 
+    @pytest.mark.parametrize("inst_dict", [x for x in instruments['download']])
+    @pytest.mark.parametrize("kwarg,output", [(None, 1), ('10s', 10)])
+    def test_inst_cadence(self, inst_dict, kwarg, output):
+        """Test operation of cadence keyword, including default behavior"""
 
-class TestEphem(TestSGP4):
-    def setup(self):
-        """Runs before every method to create a clean testing setup."""
-        # NOTE: aacgm not checked here because undefined near the equator
-        # TODO: add check for aacgm values
-        from pysatMissions.instruments import pysat_ephem
-        self.testInst = pysat.Instrument(inst_module=pysat_ephem, tag='all',
-                                         sat_id='100')
-        self.targets1 = ['glong', 'glat', 'alt', 'obs_sat_slant_range',
-                         'obs_sat_az_angle', 'obs_sat_el_angle',
-                         'position_ecef_x', 'position_ecef_y', 'position_ecef_z',
-                         'qd_lat', 'qd_long', 'mlt',
-                         'e_temp', 'frac_dens_h', 'frac_dens_he', 'frac_dens_o',
-                         'ion_dens', 'ion_temp',
-                         'B', 'B_east', 'B_north', 'B_up', 'B_ecef_x',
-                         'B_ecef_y', 'B_ecef_z',
-                         'Nn', 'Nn_H', 'Nn_He', 'Nn_N', 'Nn_N2', 'Nn_O', 'Nn_O2',
-                         'Nn_Ar', 'Tn_msis',
-                         'total_wind_x', 'total_wind_y', 'total_wind_z']
-        self.targets2 = ['velocity_ecef_x', 'velocity_ecef_y', 'velocity_ecef_z',
-                         'sc_xhat_ecef_x', 'sc_xhat_ecef_y', 'sc_xhat_ecef_z',
-                         'sc_yhat_ecef_x', 'sc_yhat_ecef_y', 'sc_yhat_ecef_z',
-                         'sc_zhat_ecef_x', 'sc_zhat_ecef_y', 'sc_zhat_ecef_z',
-                         'sim_wind_sc_x', 'sim_wind_sc_y', 'sim_wind_sc_z']
+        if kwarg:
+            self.test_inst = pysat.Instrument(
+                inst_module=inst_dict['inst_module'], cadence=kwarg)
+        else:
+            self.test_inst = pysat.Instrument(
+                inst_module=inst_dict['inst_module'])
 
-    def teardown(self):
-        """Clean up test environment after tests"""
-        del self
+        self.test_inst.load(2019, 1)
+        cadence = np.diff(self.test_inst.data.index.to_pydatetime())
+        assert np.all(cadence == dt.timedelta(seconds=output))
diff --git a/pysatMissions/tests/test_methods_empirical.py b/pysatMissions/tests/test_methods_empirical.py
deleted file mode 100644
index fed50cd2..00000000
--- a/pysatMissions/tests/test_methods_empirical.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-# Test some of the pyglow method functions
-
-import datetime as dt
-import numpy as np
-import pysat
-from pysatMissions.methods import empirical as mm_emp
-
-
-class TestBasics():
-    def setup(self):
-        """Runs before every method to create a clean testing setup."""
-        self.testInst = pysat.Instrument(platform='pysat', name='testing',
-                                         sat_id='100', clean_level='clean')
-
-    def teardown(self):
-        """Clean up test environment after tests"""
-        del self
-
-    def test_add_iri_thermal_plasma(self):
-        """Test adding thermal plasma data to test inst"""
-        self.testInst.custom.attach(mm_emp.add_iri_thermal_plasma,
-                                    kwargs={'glat_label': 'latitude',
-                                            'glong_label': 'longitude',
-                                            'alt_label': 'altitude'})
-        self.testInst.load(date=dt.datetime(2009, 1, 1))
-        targets = ['e_temp', 'frac_dens_h', 'frac_dens_he', 'frac_dens_o',
-                   'ion_dens', 'ion_temp']
-        for target in targets:
-            # Check if data is added
-            assert target in self.testInst.data.keys()
-            assert not np.isnan(self.testInst[target]).any()
-            # Check if metadata is added
-            assert target in self.testInst.meta.data.index
-
-    def test_add_igrf(self):
-        """Test adding igrf model to test inst"""
-        self.testInst.custom.attach(mm_emp.add_igrf,
-                                    kwargs={'glat_label': 'latitude',
-                                            'glong_label': 'longitude',
-                                            'alt_label': 'altitude'})
-        self.testInst.load(date=dt.datetime(2009, 1, 1))
-        targets = ['B', 'B_east', 'B_north', 'B_up', 'B_ecef_x', 'B_ecef_y',
-                   'B_ecef_z']
-        for target in targets:
-            # Check if data is added
-            assert target in self.testInst.data.keys()
-            assert not np.isnan(self.testInst[target]).any()
-            # Check if metadata is added
-            assert target in self.testInst.meta.data.index
-
-    def test_add_msis(self):
-        """Test adding msis model to test inst"""
-        self.testInst.custom.attach(mm_emp.add_msis,
-                                    kwargs={'glat_label': 'latitude',
-                                            'glong_label': 'longitude',
-                                            'alt_label': 'altitude'})
-        self.testInst.load(date=dt.datetime(2009, 1, 1))
-        targets = ['Nn', 'Nn_H', 'Nn_He', 'Nn_N', 'Nn_N2', 'Nn_O', 'Nn_O2',
-                   'Nn_Ar', 'Tn_msis']
-        for target in targets:
-            # Check if data is added
-            assert target in self.testInst.data.keys()
-            assert not np.isnan(self.testInst[target]).any()
-            # Check if metadata is added
-            assert target in self.testInst.meta.data.index
-
-    # TODO: Add hwm tests once routine is generalized
diff --git a/pysatMissions/tests/test_methods_magcoord.py b/pysatMissions/tests/test_methods_magcoord.py
index db78e528..1ed0e007 100644
--- a/pysatMissions/tests/test_methods_magcoord.py
+++ b/pysatMissions/tests/test_methods_magcoord.py
@@ -11,7 +11,7 @@ class TestBasics():
     def setup(self):
         """Runs before every method to create a clean testing setup."""
         self.testInst = pysat.Instrument(platform='pysat', name='testing',
-                                         sat_id='100', clean_level='clean')
+                                         num_samples=100, clean_level='clean')
 
     def teardown(self):
         """Clean up test environment after tests"""
@@ -19,7 +19,7 @@ def teardown(self):
 
     def test_add_aacgm_coordinates(self):
         """Test adding thermal plasma data to test inst"""
-        self.testInst.custom.attach(mm_magcoord.add_aacgm_coordinates,
+        self.testInst.custom_attach(mm_magcoord.add_aacgm_coordinates,
                                     kwargs={'glat_label': 'latitude',
                                             'glong_label': 'longitude',
                                             'alt_label': 'altitude'})
@@ -34,7 +34,7 @@ def test_add_aacgm_coordinates(self):
 
     def test_add_quasi_dipole_coordinates(self):
         """Test adding thermal plasma data to test inst"""
-        self.testInst.custom.attach(mm_magcoord.add_quasi_dipole_coordinates,
+        self.testInst.custom_attach(mm_magcoord.add_quasi_dipole_coordinates,
                                     kwargs={'glat_label': 'latitude',
                                             'glong_label': 'longitude',
                                             'alt_label': 'altitude'})
diff --git a/pysatMissions/tests/test_methods_spacecraft.py b/pysatMissions/tests/test_methods_spacecraft.py
index 600465f7..b169acca 100644
--- a/pysatMissions/tests/test_methods_spacecraft.py
+++ b/pysatMissions/tests/test_methods_spacecraft.py
@@ -31,15 +31,15 @@ class TestBasics():
     def setup(self):
         """Runs before every method to create a clean testing setup."""
         self.testInst = pysat.Instrument(platform='pysat', name='testing',
-                                         sat_id='9', clean_level='clean')
-        self.testInst.custom.attach(add_eci)
+                                         num_samples=9, clean_level='clean')
+        self.testInst.custom_attach(add_eci)
 
     def teardown(self):
         """Clean up test environment after tests"""
         del self
 
     def test_calculate_ecef_velocity(self):
-        self.testInst.custom.attach(mm_sc.calculate_ecef_velocity)
+        self.testInst.custom_attach(mm_sc.calculate_ecef_velocity)
         self.testInst.load(date=dt.datetime(2009, 1, 1))
         targets = ['velocity_ecef_x', 'velocity_ecef_y', 'velocity_ecef_z']
         for target in targets:
@@ -54,8 +54,8 @@ def test_calculate_ecef_velocity(self):
 
     def test_add_ram_pointing_sc_attitude_vectors(self):
         # TODO: check if calculations are correct
-        self.testInst.custom.attach(mm_sc.calculate_ecef_velocity)
-        self.testInst.custom.attach(mm_sc.add_ram_pointing_sc_attitude_vectors)
+        self.testInst.custom_attach(mm_sc.calculate_ecef_velocity)
+        self.testInst.custom_attach(mm_sc.add_ram_pointing_sc_attitude_vectors)
         self.testInst.load(date=dt.datetime(2009, 1, 1))
         targets = ['sc_xhat_ecef_x', 'sc_xhat_ecef_y', 'sc_xhat_ecef_z',
                    'sc_yhat_ecef_x', 'sc_yhat_ecef_y', 'sc_yhat_ecef_z',
@@ -72,10 +72,10 @@ def test_add_ram_pointing_sc_attitude_vectors(self):
 
     def test_project_ecef_vector_onto_sc(self):
         # TODO: check if calculations are correct
-        self.testInst.custom.attach(mm_sc.calculate_ecef_velocity)
-        self.testInst.custom.attach(mm_sc.add_ram_pointing_sc_attitude_vectors)
-        self.testInst.custom.attach(add_fake_data)
-        self.testInst.custom.attach(mm_sc.project_ecef_vector_onto_sc,
+        self.testInst.custom_attach(mm_sc.calculate_ecef_velocity)
+        self.testInst.custom_attach(mm_sc.add_ram_pointing_sc_attitude_vectors)
+        self.testInst.custom_attach(add_fake_data)
+        self.testInst.custom_attach(mm_sc.project_ecef_vector_onto_sc,
                                     args=['ax', 'ay', 'az', 'bx', 'by', 'bz'])
         self.testInst.load(date=dt.datetime(2009, 1, 1))
         targets = ['bx', 'by', 'bz']
diff --git a/pysatMissions/tests/test_plot.py b/pysatMissions/tests/test_plot.py
deleted file mode 100644
index c1324bb4..00000000
--- a/pysatMissions/tests/test_plot.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import datetime as dt
-
-import pysat
-from pysatMissions import plot as mplt
-
-
-class TestPlot():
-
-    def setup(self):
-        """Runs before every method to create a clean testing setup."""
-
-        from pysatMissions.instruments import pysat_ephem
-        self.testInst = pysat.Instrument(inst_module=pysat_ephem, tag='all',
-                                         sat_id='100')
-        self.testInst.load(date=dt.datetime(2018, 1, 1))
-
-    def teardown(self):
-        """Clean up test environment after tests"""
-
-        del self.testInst
-
-    def test_plot_simulated_data(self):
-        """Try running the plot to see if warning or errors are generated"""
-
-        mplt.plot_simulated_data(self.testInst)
-
-        assert True
diff --git a/test_requirements.txt b/test_requirements.txt
new file mode 100644
index 00000000..8091dd08
--- /dev/null
+++ b/test_requirements.txt
@@ -0,0 +1,9 @@
+coveralls
+ipython
+m2r2
+numpydoc
+pytest-cov
+pytest-flake8
+pytest-ordering
+sphinx
+sphinx_rtd_theme